##// END OF EJS Templates
Merged r15820 (#23758)....
Jean-Philippe Lang -
r15440:8b60a4ba0607
parent child
Show More
@@ -1,514 +1,514
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class ScmFetchError < Exception; end
18 class ScmFetchError < Exception; end
19
19
20 class Repository < ActiveRecord::Base
20 class Repository < ActiveRecord::Base
21 include Redmine::Ciphering
21 include Redmine::Ciphering
22 include Redmine::SafeAttributes
22 include Redmine::SafeAttributes
23
23
24 # Maximum length for repository identifiers
24 # Maximum length for repository identifiers
25 IDENTIFIER_MAX_LENGTH = 255
25 IDENTIFIER_MAX_LENGTH = 255
26
26
27 belongs_to :project
27 belongs_to :project
28 has_many :changesets, lambda{order("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC")}
28 has_many :changesets, lambda{order("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC")}
29 has_many :filechanges, :class_name => 'Change', :through => :changesets
29 has_many :filechanges, :class_name => 'Change', :through => :changesets
30
30
31 serialize :extra_info
31 serialize :extra_info
32
32
33 before_validation :normalize_identifier
33 before_validation :normalize_identifier
34 before_save :check_default
34 before_save :check_default
35
35
36 # Raw SQL to delete changesets and changes in the database
36 # Raw SQL to delete changesets and changes in the database
37 # has_many :changesets, :dependent => :destroy is too slow for big repositories
37 # has_many :changesets, :dependent => :destroy is too slow for big repositories
38 before_destroy :clear_changesets
38 before_destroy :clear_changesets
39
39
40 validates_length_of :password, :maximum => 255, :allow_nil => true
40 validates_length_of :password, :maximum => 255, :allow_nil => true
41 validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH, :allow_blank => true
41 validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH, :allow_blank => true
42 validates_uniqueness_of :identifier, :scope => :project_id
42 validates_uniqueness_of :identifier, :scope => :project_id
43 validates_exclusion_of :identifier, :in => %w(browse show entry raw changes annotate diff statistics graph revisions revision)
43 validates_exclusion_of :identifier, :in => %w(browse show entry raw changes annotate diff statistics graph revisions revision)
44 # donwcase letters, digits, dashes, underscores but not digits only
44 # donwcase letters, digits, dashes, underscores but not digits only
45 validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :allow_blank => true
45 validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :allow_blank => true
46 # Checks if the SCM is enabled when creating a repository
46 # Checks if the SCM is enabled when creating a repository
47 validate :repo_create_validation, :on => :create
47 validate :repo_create_validation, :on => :create
48 validate :validate_repository_path
48 validate :validate_repository_path
49 attr_protected :id
49 attr_protected :id
50
50
51 safe_attributes 'identifier',
51 safe_attributes 'identifier',
52 'login',
52 'login',
53 'password',
53 'password',
54 'path_encoding',
54 'path_encoding',
55 'log_encoding',
55 'log_encoding',
56 'is_default'
56 'is_default'
57
57
58 safe_attributes 'url',
58 safe_attributes 'url',
59 :if => lambda {|repository, user| repository.new_record?}
59 :if => lambda {|repository, user| repository.new_record?}
60
60
61 def repo_create_validation
61 def repo_create_validation
62 unless Setting.enabled_scm.include?(self.class.name.demodulize)
62 unless Setting.enabled_scm.include?(self.class.name.demodulize)
63 errors.add(:type, :invalid)
63 errors.add(:type, :invalid)
64 end
64 end
65 end
65 end
66
66
67 def self.human_attribute_name(attribute_key_name, *args)
67 def self.human_attribute_name(attribute_key_name, *args)
68 attr_name = attribute_key_name.to_s
68 attr_name = attribute_key_name.to_s
69 if attr_name == "log_encoding"
69 if attr_name == "log_encoding"
70 attr_name = "commit_logs_encoding"
70 attr_name = "commit_logs_encoding"
71 end
71 end
72 super(attr_name, *args)
72 super(attr_name, *args)
73 end
73 end
74
74
75 # Removes leading and trailing whitespace
75 # Removes leading and trailing whitespace
76 def url=(arg)
76 def url=(arg)
77 write_attribute(:url, arg ? arg.to_s.strip : nil)
77 write_attribute(:url, arg ? arg.to_s.strip : nil)
78 end
78 end
79
79
80 # Removes leading and trailing whitespace
80 # Removes leading and trailing whitespace
81 def root_url=(arg)
81 def root_url=(arg)
82 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
82 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
83 end
83 end
84
84
85 def password
85 def password
86 read_ciphered_attribute(:password)
86 read_ciphered_attribute(:password)
87 end
87 end
88
88
89 def password=(arg)
89 def password=(arg)
90 write_ciphered_attribute(:password, arg)
90 write_ciphered_attribute(:password, arg)
91 end
91 end
92
92
93 def scm_adapter
93 def scm_adapter
94 self.class.scm_adapter_class
94 self.class.scm_adapter_class
95 end
95 end
96
96
97 def scm
97 def scm
98 unless @scm
98 unless @scm
99 @scm = self.scm_adapter.new(url, root_url,
99 @scm = self.scm_adapter.new(url, root_url,
100 login, password, path_encoding)
100 login, password, path_encoding)
101 if root_url.blank? && @scm.root_url.present?
101 if root_url.blank? && @scm.root_url.present?
102 update_attribute(:root_url, @scm.root_url)
102 update_attribute(:root_url, @scm.root_url)
103 end
103 end
104 end
104 end
105 @scm
105 @scm
106 end
106 end
107
107
108 def scm_name
108 def scm_name
109 self.class.scm_name
109 self.class.scm_name
110 end
110 end
111
111
112 def name
112 def name
113 if identifier.present?
113 if identifier.present?
114 identifier
114 identifier
115 elsif is_default?
115 elsif is_default?
116 l(:field_repository_is_default)
116 l(:field_repository_is_default)
117 else
117 else
118 scm_name
118 scm_name
119 end
119 end
120 end
120 end
121
121
122 def identifier=(identifier)
122 def identifier=(identifier)
123 super unless identifier_frozen?
123 super unless identifier_frozen?
124 end
124 end
125
125
126 def identifier_frozen?
126 def identifier_frozen?
127 errors[:identifier].blank? && !(new_record? || identifier.blank?)
127 errors[:identifier].blank? && !(new_record? || identifier.blank?)
128 end
128 end
129
129
130 def identifier_param
130 def identifier_param
131 if is_default?
131 if is_default?
132 nil
132 nil
133 elsif identifier.present?
133 elsif identifier.present?
134 identifier
134 identifier
135 else
135 else
136 id.to_s
136 id.to_s
137 end
137 end
138 end
138 end
139
139
140 def <=>(repository)
140 def <=>(repository)
141 if is_default?
141 if is_default?
142 -1
142 -1
143 elsif repository.is_default?
143 elsif repository.is_default?
144 1
144 1
145 else
145 else
146 identifier.to_s <=> repository.identifier.to_s
146 identifier.to_s <=> repository.identifier.to_s
147 end
147 end
148 end
148 end
149
149
150 def self.find_by_identifier_param(param)
150 def self.find_by_identifier_param(param)
151 if param.to_s =~ /^\d+$/
151 if param.to_s =~ /^\d+$/
152 find_by_id(param)
152 find_by_id(param)
153 else
153 else
154 find_by_identifier(param)
154 find_by_identifier(param)
155 end
155 end
156 end
156 end
157
157
158 # TODO: should return an empty hash instead of nil to avoid many ||{}
158 # TODO: should return an empty hash instead of nil to avoid many ||{}
159 def extra_info
159 def extra_info
160 h = read_attribute(:extra_info)
160 h = read_attribute(:extra_info)
161 h.is_a?(Hash) ? h : nil
161 h.is_a?(Hash) ? h : nil
162 end
162 end
163
163
164 def merge_extra_info(arg)
164 def merge_extra_info(arg)
165 h = extra_info || {}
165 h = extra_info || {}
166 return h if arg.nil?
166 return h if arg.nil?
167 h.merge!(arg)
167 h.merge!(arg)
168 write_attribute(:extra_info, h)
168 write_attribute(:extra_info, h)
169 end
169 end
170
170
171 def report_last_commit
171 def report_last_commit
172 true
172 true
173 end
173 end
174
174
175 def supports_cat?
175 def supports_cat?
176 scm.supports_cat?
176 scm.supports_cat?
177 end
177 end
178
178
179 def supports_annotate?
179 def supports_annotate?
180 scm.supports_annotate?
180 scm.supports_annotate?
181 end
181 end
182
182
183 def supports_all_revisions?
183 def supports_all_revisions?
184 true
184 true
185 end
185 end
186
186
187 def supports_directory_revisions?
187 def supports_directory_revisions?
188 false
188 false
189 end
189 end
190
190
191 def supports_revision_graph?
191 def supports_revision_graph?
192 false
192 false
193 end
193 end
194
194
195 def entry(path=nil, identifier=nil)
195 def entry(path=nil, identifier=nil)
196 scm.entry(path, identifier)
196 scm.entry(path, identifier)
197 end
197 end
198
198
199 def scm_entries(path=nil, identifier=nil)
199 def scm_entries(path=nil, identifier=nil)
200 scm.entries(path, identifier)
200 scm.entries(path, identifier)
201 end
201 end
202 protected :scm_entries
202 protected :scm_entries
203
203
204 def entries(path=nil, identifier=nil)
204 def entries(path=nil, identifier=nil)
205 entries = scm_entries(path, identifier)
205 entries = scm_entries(path, identifier)
206 load_entries_changesets(entries)
206 load_entries_changesets(entries)
207 entries
207 entries
208 end
208 end
209
209
210 def branches
210 def branches
211 scm.branches
211 scm.branches
212 end
212 end
213
213
214 def tags
214 def tags
215 scm.tags
215 scm.tags
216 end
216 end
217
217
218 def default_branch
218 def default_branch
219 nil
219 nil
220 end
220 end
221
221
222 def properties(path, identifier=nil)
222 def properties(path, identifier=nil)
223 scm.properties(path, identifier)
223 scm.properties(path, identifier)
224 end
224 end
225
225
226 def cat(path, identifier=nil)
226 def cat(path, identifier=nil)
227 scm.cat(path, identifier)
227 scm.cat(path, identifier)
228 end
228 end
229
229
230 def diff(path, rev, rev_to)
230 def diff(path, rev, rev_to)
231 scm.diff(path, rev, rev_to)
231 scm.diff(path, rev, rev_to)
232 end
232 end
233
233
234 def diff_format_revisions(cs, cs_to, sep=':')
234 def diff_format_revisions(cs, cs_to, sep=':')
235 text = ""
235 text = ""
236 text << cs_to.format_identifier + sep if cs_to
236 text << cs_to.format_identifier + sep if cs_to
237 text << cs.format_identifier if cs
237 text << cs.format_identifier if cs
238 text
238 text
239 end
239 end
240
240
241 # Returns a path relative to the url of the repository
241 # Returns a path relative to the url of the repository
242 def relative_path(path)
242 def relative_path(path)
243 path
243 path
244 end
244 end
245
245
246 # Finds and returns a revision with a number or the beginning of a hash
246 # Finds and returns a revision with a number or the beginning of a hash
247 def find_changeset_by_name(name)
247 def find_changeset_by_name(name)
248 return nil if name.blank?
248 return nil if name.blank?
249 s = name.to_s
249 s = name.to_s
250 if s.match(/^\d*$/)
250 if s.match(/^\d*$/)
251 changesets.where("revision = ?", s).first
251 changesets.where("revision = ?", s).first
252 else
252 else
253 changesets.where("revision LIKE ?", s + '%').first
253 changesets.where("revision LIKE ?", s + '%').first
254 end
254 end
255 end
255 end
256
256
257 def latest_changeset
257 def latest_changeset
258 @latest_changeset ||= changesets.first
258 @latest_changeset ||= changesets.first
259 end
259 end
260
260
261 # Returns the latest changesets for +path+
261 # Returns the latest changesets for +path+
262 # Default behaviour is to search in cached changesets
262 # Default behaviour is to search in cached changesets
263 def latest_changesets(path, rev, limit=10)
263 def latest_changesets(path, rev, limit=10)
264 if path.blank?
264 if path.blank?
265 changesets.
265 changesets.
266 reorder("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC").
266 reorder("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC").
267 limit(limit).
267 limit(limit).
268 preload(:user).
268 preload(:user).
269 to_a
269 to_a
270 else
270 else
271 filechanges.
271 filechanges.
272 where("path = ?", path.with_leading_slash).
272 where("path = ?", path.with_leading_slash).
273 reorder("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC").
273 reorder("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC").
274 limit(limit).
274 limit(limit).
275 preload(:changeset => :user).
275 preload(:changeset => :user).
276 collect(&:changeset)
276 collect(&:changeset)
277 end
277 end
278 end
278 end
279
279
280 def scan_changesets_for_issue_ids
280 def scan_changesets_for_issue_ids
281 self.changesets.each(&:scan_comment_for_issue_ids)
281 self.changesets.each(&:scan_comment_for_issue_ids)
282 end
282 end
283
283
284 # Returns an array of committers usernames and associated user_id
284 # Returns an array of committers usernames and associated user_id
285 def committers
285 def committers
286 @committers ||= Changeset.where(:repository_id => id).uniq.pluck(:committer, :user_id)
286 @committers ||= Changeset.where(:repository_id => id).uniq.pluck(:committer, :user_id)
287 end
287 end
288
288
289 # Maps committers username to a user ids
289 # Maps committers username to a user ids
290 def committer_ids=(h)
290 def committer_ids=(h)
291 if h.is_a?(Hash)
291 if h.is_a?(Hash)
292 committers.each do |committer, user_id|
292 committers.each do |committer, user_id|
293 new_user_id = h[committer]
293 new_user_id = h[committer]
294 if new_user_id && (new_user_id.to_i != user_id.to_i)
294 if new_user_id && (new_user_id.to_i != user_id.to_i)
295 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
295 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
296 Changeset.where(["repository_id = ? AND committer = ?", id, committer]).
296 Changeset.where(["repository_id = ? AND committer = ?", id, committer]).
297 update_all("user_id = #{new_user_id.nil? ? 'NULL' : new_user_id}")
297 update_all("user_id = #{new_user_id.nil? ? 'NULL' : new_user_id}")
298 end
298 end
299 end
299 end
300 @committers = nil
300 @committers = nil
301 @found_committer_users = nil
301 @found_committer_users = nil
302 true
302 true
303 else
303 else
304 false
304 false
305 end
305 end
306 end
306 end
307
307
308 # Returns the Redmine User corresponding to the given +committer+
308 # Returns the Redmine User corresponding to the given +committer+
309 # It will return nil if the committer is not yet mapped and if no User
309 # It will return nil if the committer is not yet mapped and if no User
310 # with the same username or email was found
310 # with the same username or email was found
311 def find_committer_user(committer)
311 def find_committer_user(committer)
312 unless committer.blank?
312 unless committer.blank?
313 @found_committer_users ||= {}
313 @found_committer_users ||= {}
314 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
314 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
315
315
316 user = nil
316 user = nil
317 c = changesets.where(:committer => committer).
317 c = changesets.where(:committer => committer).
318 includes(:user).references(:user).first
318 includes(:user).references(:user).first
319 if c && c.user
319 if c && c.user
320 user = c.user
320 user = c.user
321 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
321 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
322 username, email = $1.strip, $3
322 username, email = $1.strip, $3
323 u = User.find_by_login(username)
323 u = User.find_by_login(username)
324 u ||= User.find_by_mail(email) unless email.blank?
324 u ||= User.find_by_mail(email) unless email.blank?
325 user = u
325 user = u
326 end
326 end
327 @found_committer_users[committer] = user
327 @found_committer_users[committer] = user
328 user
328 user
329 end
329 end
330 end
330 end
331
331
332 def repo_log_encoding
332 def repo_log_encoding
333 encoding = log_encoding.to_s.strip
333 encoding = log_encoding.to_s.strip
334 encoding.blank? ? 'UTF-8' : encoding
334 encoding.blank? ? 'UTF-8' : encoding
335 end
335 end
336
336
337 # Fetches new changesets for all repositories of active projects
337 # Fetches new changesets for all repositories of active projects
338 # Can be called periodically by an external script
338 # Can be called periodically by an external script
339 # eg. ruby script/runner "Repository.fetch_changesets"
339 # eg. ruby script/runner "Repository.fetch_changesets"
340 def self.fetch_changesets
340 def self.fetch_changesets
341 Project.active.has_module(:repository).all.each do |project|
341 Project.active.has_module(:repository).all.each do |project|
342 project.repositories.each do |repository|
342 project.repositories.each do |repository|
343 begin
343 begin
344 repository.fetch_changesets
344 repository.fetch_changesets
345 rescue Redmine::Scm::Adapters::CommandFailed => e
345 rescue Redmine::Scm::Adapters::CommandFailed => e
346 logger.error "scm: error during fetching changesets: #{e.message}"
346 logger.error "scm: error during fetching changesets: #{e.message}"
347 end
347 end
348 end
348 end
349 end
349 end
350 end
350 end
351
351
352 # scan changeset comments to find related and fixed issues for all repositories
352 # scan changeset comments to find related and fixed issues for all repositories
353 def self.scan_changesets_for_issue_ids
353 def self.scan_changesets_for_issue_ids
354 all.each(&:scan_changesets_for_issue_ids)
354 all.each(&:scan_changesets_for_issue_ids)
355 end
355 end
356
356
357 def self.scm_name
357 def self.scm_name
358 'Abstract'
358 'Abstract'
359 end
359 end
360
360
361 def self.available_scm
361 def self.available_scm
362 subclasses.collect {|klass| [klass.scm_name, klass.name]}
362 subclasses.collect {|klass| [klass.scm_name, klass.name]}
363 end
363 end
364
364
365 def self.factory(klass_name, *args)
365 def self.factory(klass_name, *args)
366 repository_class(klass_name).new(*args) rescue nil
366 repository_class(klass_name).new(*args) rescue nil
367 end
367 end
368
368
369 def self.repository_class(class_name)
369 def self.repository_class(class_name)
370 class_name = class_name.to_s.classify
370 class_name = class_name.to_s.camelize
371 if Redmine::Scm::Base.all.include?(class_name)
371 if Redmine::Scm::Base.all.include?(class_name)
372 "Repository::#{class_name}".constantize
372 "Repository::#{class_name}".constantize
373 end
373 end
374 end
374 end
375
375
376 def self.scm_adapter_class
376 def self.scm_adapter_class
377 nil
377 nil
378 end
378 end
379
379
380 def self.scm_command
380 def self.scm_command
381 ret = ""
381 ret = ""
382 begin
382 begin
383 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
383 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
384 rescue Exception => e
384 rescue Exception => e
385 logger.error "scm: error during get command: #{e.message}"
385 logger.error "scm: error during get command: #{e.message}"
386 end
386 end
387 ret
387 ret
388 end
388 end
389
389
390 def self.scm_version_string
390 def self.scm_version_string
391 ret = ""
391 ret = ""
392 begin
392 begin
393 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
393 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
394 rescue Exception => e
394 rescue Exception => e
395 logger.error "scm: error during get version string: #{e.message}"
395 logger.error "scm: error during get version string: #{e.message}"
396 end
396 end
397 ret
397 ret
398 end
398 end
399
399
400 def self.scm_available
400 def self.scm_available
401 ret = false
401 ret = false
402 begin
402 begin
403 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
403 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
404 rescue Exception => e
404 rescue Exception => e
405 logger.error "scm: error during get scm available: #{e.message}"
405 logger.error "scm: error during get scm available: #{e.message}"
406 end
406 end
407 ret
407 ret
408 end
408 end
409
409
410 def set_as_default?
410 def set_as_default?
411 new_record? && project && Repository.where(:project_id => project.id).empty?
411 new_record? && project && Repository.where(:project_id => project.id).empty?
412 end
412 end
413
413
414 # Returns a hash with statistics by author in the following form:
414 # Returns a hash with statistics by author in the following form:
415 # {
415 # {
416 # "John Smith" => { :commits => 45, :changes => 324 },
416 # "John Smith" => { :commits => 45, :changes => 324 },
417 # "Bob" => { ... }
417 # "Bob" => { ... }
418 # }
418 # }
419 #
419 #
420 # Notes:
420 # Notes:
421 # - this hash honnors the users mapping defined for the repository
421 # - this hash honnors the users mapping defined for the repository
422 def stats_by_author
422 def stats_by_author
423 commits = Changeset.where("repository_id = ?", id).select("committer, user_id, count(*) as count").group("committer, user_id")
423 commits = Changeset.where("repository_id = ?", id).select("committer, user_id, count(*) as count").group("committer, user_id")
424
424
425 #TODO: restore ordering ; this line probably never worked
425 #TODO: restore ordering ; this line probably never worked
426 #commits.to_a.sort! {|x, y| x.last <=> y.last}
426 #commits.to_a.sort! {|x, y| x.last <=> y.last}
427
427
428 changes = Change.joins(:changeset).where("#{Changeset.table_name}.repository_id = ?", id).select("committer, user_id, count(*) as count").group("committer, user_id")
428 changes = Change.joins(:changeset).where("#{Changeset.table_name}.repository_id = ?", id).select("committer, user_id, count(*) as count").group("committer, user_id")
429
429
430 user_ids = changesets.map(&:user_id).compact.uniq
430 user_ids = changesets.map(&:user_id).compact.uniq
431 authors_names = User.where(:id => user_ids).inject({}) do |memo, user|
431 authors_names = User.where(:id => user_ids).inject({}) do |memo, user|
432 memo[user.id] = user.to_s
432 memo[user.id] = user.to_s
433 memo
433 memo
434 end
434 end
435
435
436 (commits + changes).inject({}) do |hash, element|
436 (commits + changes).inject({}) do |hash, element|
437 mapped_name = element.committer
437 mapped_name = element.committer
438 if username = authors_names[element.user_id.to_i]
438 if username = authors_names[element.user_id.to_i]
439 mapped_name = username
439 mapped_name = username
440 end
440 end
441 hash[mapped_name] ||= { :commits_count => 0, :changes_count => 0 }
441 hash[mapped_name] ||= { :commits_count => 0, :changes_count => 0 }
442 if element.is_a?(Changeset)
442 if element.is_a?(Changeset)
443 hash[mapped_name][:commits_count] += element.count.to_i
443 hash[mapped_name][:commits_count] += element.count.to_i
444 else
444 else
445 hash[mapped_name][:changes_count] += element.count.to_i
445 hash[mapped_name][:changes_count] += element.count.to_i
446 end
446 end
447 hash
447 hash
448 end
448 end
449 end
449 end
450
450
451 # Returns a scope of changesets that come from the same commit as the given changeset
451 # Returns a scope of changesets that come from the same commit as the given changeset
452 # in different repositories that point to the same backend
452 # in different repositories that point to the same backend
453 def same_commits_in_scope(scope, changeset)
453 def same_commits_in_scope(scope, changeset)
454 scope = scope.joins(:repository).where(:repositories => {:url => url, :root_url => root_url, :type => type})
454 scope = scope.joins(:repository).where(:repositories => {:url => url, :root_url => root_url, :type => type})
455 if changeset.scmid.present?
455 if changeset.scmid.present?
456 scope = scope.where(:scmid => changeset.scmid)
456 scope = scope.where(:scmid => changeset.scmid)
457 else
457 else
458 scope = scope.where(:revision => changeset.revision)
458 scope = scope.where(:revision => changeset.revision)
459 end
459 end
460 scope
460 scope
461 end
461 end
462
462
463 protected
463 protected
464
464
465 # Validates repository url based against an optional regular expression
465 # Validates repository url based against an optional regular expression
466 # that can be set in the Redmine configuration file.
466 # that can be set in the Redmine configuration file.
467 def validate_repository_path(attribute=:url)
467 def validate_repository_path(attribute=:url)
468 regexp = Redmine::Configuration["scm_#{scm_name.to_s.downcase}_path_regexp"]
468 regexp = Redmine::Configuration["scm_#{scm_name.to_s.downcase}_path_regexp"]
469 if changes[attribute] && regexp.present?
469 if changes[attribute] && regexp.present?
470 regexp = regexp.to_s.strip.gsub('%project%') {Regexp.escape(project.try(:identifier).to_s)}
470 regexp = regexp.to_s.strip.gsub('%project%') {Regexp.escape(project.try(:identifier).to_s)}
471 unless send(attribute).to_s.match(Regexp.new("\\A#{regexp}\\z"))
471 unless send(attribute).to_s.match(Regexp.new("\\A#{regexp}\\z"))
472 errors.add(attribute, :invalid)
472 errors.add(attribute, :invalid)
473 end
473 end
474 end
474 end
475 end
475 end
476
476
477 def normalize_identifier
477 def normalize_identifier
478 self.identifier = identifier.to_s.strip
478 self.identifier = identifier.to_s.strip
479 end
479 end
480
480
481 def check_default
481 def check_default
482 if !is_default? && set_as_default?
482 if !is_default? && set_as_default?
483 self.is_default = true
483 self.is_default = true
484 end
484 end
485 if is_default? && is_default_changed?
485 if is_default? && is_default_changed?
486 Repository.where(["project_id = ?", project_id]).update_all(["is_default = ?", false])
486 Repository.where(["project_id = ?", project_id]).update_all(["is_default = ?", false])
487 end
487 end
488 end
488 end
489
489
490 def load_entries_changesets(entries)
490 def load_entries_changesets(entries)
491 if entries
491 if entries
492 entries.each do |entry|
492 entries.each do |entry|
493 if entry.lastrev && entry.lastrev.identifier
493 if entry.lastrev && entry.lastrev.identifier
494 entry.changeset = find_changeset_by_name(entry.lastrev.identifier)
494 entry.changeset = find_changeset_by_name(entry.lastrev.identifier)
495 end
495 end
496 end
496 end
497 end
497 end
498 end
498 end
499
499
500 private
500 private
501
501
502 # Deletes repository data
502 # Deletes repository data
503 def clear_changesets
503 def clear_changesets
504 cs = Changeset.table_name
504 cs = Changeset.table_name
505 ch = Change.table_name
505 ch = Change.table_name
506 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
506 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
507 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
507 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
508
508
509 self.class.connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
509 self.class.connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
510 self.class.connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
510 self.class.connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
511 self.class.connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
511 self.class.connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
512 self.class.connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
512 self.class.connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
513 end
513 end
514 end
514 end
General Comments 0
You need to be logged in to leave comments. Login now