##// END OF EJS Templates
Code cleanup....
Jean-Philippe Lang -
r13244:4e69edb5b82f
parent child
Show More
@@ -1,511 +1,510
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 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_save :check_default
33 before_save :check_default
34
34
35 # Raw SQL to delete changesets and changes in the database
35 # Raw SQL to delete changesets and changes in the database
36 # has_many :changesets, :dependent => :destroy is too slow for big repositories
36 # has_many :changesets, :dependent => :destroy is too slow for big repositories
37 before_destroy :clear_changesets
37 before_destroy :clear_changesets
38
38
39 validates_length_of :password, :maximum => 255, :allow_nil => true
39 validates_length_of :password, :maximum => 255, :allow_nil => true
40 validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH, :allow_blank => true
40 validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH, :allow_blank => true
41 validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? }
41 validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? }
42 validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true
42 validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true
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.connection.select_rows(
286 @committers ||= Changeset.where(:repository_id => id).uniq.pluck(:committer, :user_id)
287 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
288 end
287 end
289
288
290 # Maps committers username to a user ids
289 # Maps committers username to a user ids
291 def committer_ids=(h)
290 def committer_ids=(h)
292 if h.is_a?(Hash)
291 if h.is_a?(Hash)
293 committers.each do |committer, user_id|
292 committers.each do |committer, user_id|
294 new_user_id = h[committer]
293 new_user_id = h[committer]
295 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)
296 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)
297 Changeset.where(["repository_id = ? AND committer = ?", id, committer]).
296 Changeset.where(["repository_id = ? AND committer = ?", id, committer]).
298 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}")
299 end
298 end
300 end
299 end
301 @committers = nil
300 @committers = nil
302 @found_committer_users = nil
301 @found_committer_users = nil
303 true
302 true
304 else
303 else
305 false
304 false
306 end
305 end
307 end
306 end
308
307
309 # Returns the Redmine User corresponding to the given +committer+
308 # Returns the Redmine User corresponding to the given +committer+
310 # 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
311 # with the same username or email was found
310 # with the same username or email was found
312 def find_committer_user(committer)
311 def find_committer_user(committer)
313 unless committer.blank?
312 unless committer.blank?
314 @found_committer_users ||= {}
313 @found_committer_users ||= {}
315 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)
316
315
317 user = nil
316 user = nil
318 c = changesets.where(:committer => committer).
317 c = changesets.where(:committer => committer).
319 includes(:user).references(:user).first
318 includes(:user).references(:user).first
320 if c && c.user
319 if c && c.user
321 user = c.user
320 user = c.user
322 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
321 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
323 username, email = $1.strip, $3
322 username, email = $1.strip, $3
324 u = User.find_by_login(username)
323 u = User.find_by_login(username)
325 u ||= User.find_by_mail(email) unless email.blank?
324 u ||= User.find_by_mail(email) unless email.blank?
326 user = u
325 user = u
327 end
326 end
328 @found_committer_users[committer] = user
327 @found_committer_users[committer] = user
329 user
328 user
330 end
329 end
331 end
330 end
332
331
333 def repo_log_encoding
332 def repo_log_encoding
334 encoding = log_encoding.to_s.strip
333 encoding = log_encoding.to_s.strip
335 encoding.blank? ? 'UTF-8' : encoding
334 encoding.blank? ? 'UTF-8' : encoding
336 end
335 end
337
336
338 # Fetches new changesets for all repositories of active projects
337 # Fetches new changesets for all repositories of active projects
339 # Can be called periodically by an external script
338 # Can be called periodically by an external script
340 # eg. ruby script/runner "Repository.fetch_changesets"
339 # eg. ruby script/runner "Repository.fetch_changesets"
341 def self.fetch_changesets
340 def self.fetch_changesets
342 Project.active.has_module(:repository).all.each do |project|
341 Project.active.has_module(:repository).all.each do |project|
343 project.repositories.each do |repository|
342 project.repositories.each do |repository|
344 begin
343 begin
345 repository.fetch_changesets
344 repository.fetch_changesets
346 rescue Redmine::Scm::Adapters::CommandFailed => e
345 rescue Redmine::Scm::Adapters::CommandFailed => e
347 logger.error "scm: error during fetching changesets: #{e.message}"
346 logger.error "scm: error during fetching changesets: #{e.message}"
348 end
347 end
349 end
348 end
350 end
349 end
351 end
350 end
352
351
353 # 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
354 def self.scan_changesets_for_issue_ids
353 def self.scan_changesets_for_issue_ids
355 all.each(&:scan_changesets_for_issue_ids)
354 all.each(&:scan_changesets_for_issue_ids)
356 end
355 end
357
356
358 def self.scm_name
357 def self.scm_name
359 'Abstract'
358 'Abstract'
360 end
359 end
361
360
362 def self.available_scm
361 def self.available_scm
363 subclasses.collect {|klass| [klass.scm_name, klass.name]}
362 subclasses.collect {|klass| [klass.scm_name, klass.name]}
364 end
363 end
365
364
366 def self.factory(klass_name, *args)
365 def self.factory(klass_name, *args)
367 klass = "Repository::#{klass_name}".constantize
366 klass = "Repository::#{klass_name}".constantize
368 klass.new(*args)
367 klass.new(*args)
369 rescue
368 rescue
370 nil
369 nil
371 end
370 end
372
371
373 def self.scm_adapter_class
372 def self.scm_adapter_class
374 nil
373 nil
375 end
374 end
376
375
377 def self.scm_command
376 def self.scm_command
378 ret = ""
377 ret = ""
379 begin
378 begin
380 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
379 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
381 rescue Exception => e
380 rescue Exception => e
382 logger.error "scm: error during get command: #{e.message}"
381 logger.error "scm: error during get command: #{e.message}"
383 end
382 end
384 ret
383 ret
385 end
384 end
386
385
387 def self.scm_version_string
386 def self.scm_version_string
388 ret = ""
387 ret = ""
389 begin
388 begin
390 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
389 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
391 rescue Exception => e
390 rescue Exception => e
392 logger.error "scm: error during get version string: #{e.message}"
391 logger.error "scm: error during get version string: #{e.message}"
393 end
392 end
394 ret
393 ret
395 end
394 end
396
395
397 def self.scm_available
396 def self.scm_available
398 ret = false
397 ret = false
399 begin
398 begin
400 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
399 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
401 rescue Exception => e
400 rescue Exception => e
402 logger.error "scm: error during get scm available: #{e.message}"
401 logger.error "scm: error during get scm available: #{e.message}"
403 end
402 end
404 ret
403 ret
405 end
404 end
406
405
407 def set_as_default?
406 def set_as_default?
408 new_record? && project && Repository.where(:project_id => project.id).empty?
407 new_record? && project && Repository.where(:project_id => project.id).empty?
409 end
408 end
410
409
411 # Returns a hash with statistics by author in the following form:
410 # Returns a hash with statistics by author in the following form:
412 # {
411 # {
413 # "John Smith" => { :commits => 45, :changes => 324 },
412 # "John Smith" => { :commits => 45, :changes => 324 },
414 # "Bob" => { ... }
413 # "Bob" => { ... }
415 # }
414 # }
416 #
415 #
417 # Notes:
416 # Notes:
418 # - this hash honnors the users mapping defined for the repository
417 # - this hash honnors the users mapping defined for the repository
419 def stats_by_author
418 def stats_by_author
420 commits = Changeset.where("repository_id = ?", id).select("committer, user_id, count(*) as count").group("committer, user_id")
419 commits = Changeset.where("repository_id = ?", id).select("committer, user_id, count(*) as count").group("committer, user_id")
421
420
422 #TODO: restore ordering ; this line probably never worked
421 #TODO: restore ordering ; this line probably never worked
423 #commits.to_a.sort! {|x, y| x.last <=> y.last}
422 #commits.to_a.sort! {|x, y| x.last <=> y.last}
424
423
425 changes = Change.joins(:changeset).where("#{Changeset.table_name}.repository_id = ?", id).select("committer, user_id, count(*) as count").group("committer, user_id")
424 changes = Change.joins(:changeset).where("#{Changeset.table_name}.repository_id = ?", id).select("committer, user_id, count(*) as count").group("committer, user_id")
426
425
427 user_ids = changesets.map(&:user_id).compact.uniq
426 user_ids = changesets.map(&:user_id).compact.uniq
428 authors_names = User.where(:id => user_ids).inject({}) do |memo, user|
427 authors_names = User.where(:id => user_ids).inject({}) do |memo, user|
429 memo[user.id] = user.to_s
428 memo[user.id] = user.to_s
430 memo
429 memo
431 end
430 end
432
431
433 (commits + changes).inject({}) do |hash, element|
432 (commits + changes).inject({}) do |hash, element|
434 mapped_name = element.committer
433 mapped_name = element.committer
435 if username = authors_names[element.user_id.to_i]
434 if username = authors_names[element.user_id.to_i]
436 mapped_name = username
435 mapped_name = username
437 end
436 end
438 hash[mapped_name] ||= { :commits_count => 0, :changes_count => 0 }
437 hash[mapped_name] ||= { :commits_count => 0, :changes_count => 0 }
439 if element.is_a?(Changeset)
438 if element.is_a?(Changeset)
440 hash[mapped_name][:commits_count] += element.count.to_i
439 hash[mapped_name][:commits_count] += element.count.to_i
441 else
440 else
442 hash[mapped_name][:changes_count] += element.count.to_i
441 hash[mapped_name][:changes_count] += element.count.to_i
443 end
442 end
444 hash
443 hash
445 end
444 end
446 end
445 end
447
446
448 # Returns a scope of changesets that come from the same commit as the given changeset
447 # Returns a scope of changesets that come from the same commit as the given changeset
449 # in different repositories that point to the same backend
448 # in different repositories that point to the same backend
450 def same_commits_in_scope(scope, changeset)
449 def same_commits_in_scope(scope, changeset)
451 scope = scope.joins(:repository).where(:repositories => {:url => url, :root_url => root_url, :type => type})
450 scope = scope.joins(:repository).where(:repositories => {:url => url, :root_url => root_url, :type => type})
452 if changeset.scmid.present?
451 if changeset.scmid.present?
453 scope = scope.where(:scmid => changeset.scmid)
452 scope = scope.where(:scmid => changeset.scmid)
454 else
453 else
455 scope = scope.where(:revision => changeset.revision)
454 scope = scope.where(:revision => changeset.revision)
456 end
455 end
457 scope
456 scope
458 end
457 end
459
458
460 protected
459 protected
461
460
462 # Validates repository url based against an optional regular expression
461 # Validates repository url based against an optional regular expression
463 # that can be set in the Redmine configuration file.
462 # that can be set in the Redmine configuration file.
464 def validate_repository_path(attribute=:url)
463 def validate_repository_path(attribute=:url)
465 regexp = Redmine::Configuration["scm_#{scm_name.to_s.downcase}_path_regexp"]
464 regexp = Redmine::Configuration["scm_#{scm_name.to_s.downcase}_path_regexp"]
466 if changes[attribute] && regexp.present?
465 if changes[attribute] && regexp.present?
467 regexp = regexp.to_s.strip.gsub('%project%') {Regexp.escape(project.try(:identifier).to_s)}
466 regexp = regexp.to_s.strip.gsub('%project%') {Regexp.escape(project.try(:identifier).to_s)}
468 unless send(attribute).to_s.match(Regexp.new("\\A#{regexp}\\z"))
467 unless send(attribute).to_s.match(Regexp.new("\\A#{regexp}\\z"))
469 errors.add(attribute, :invalid)
468 errors.add(attribute, :invalid)
470 end
469 end
471 end
470 end
472 end
471 end
473
472
474 def check_default
473 def check_default
475 if !is_default? && set_as_default?
474 if !is_default? && set_as_default?
476 self.is_default = true
475 self.is_default = true
477 end
476 end
478 if is_default? && is_default_changed?
477 if is_default? && is_default_changed?
479 Repository.where(["project_id = ?", project_id]).update_all(["is_default = ?", false])
478 Repository.where(["project_id = ?", project_id]).update_all(["is_default = ?", false])
480 end
479 end
481 end
480 end
482
481
483 def load_entries_changesets(entries)
482 def load_entries_changesets(entries)
484 if entries
483 if entries
485 entries.each do |entry|
484 entries.each do |entry|
486 if entry.lastrev && entry.lastrev.identifier
485 if entry.lastrev && entry.lastrev.identifier
487 entry.changeset = find_changeset_by_name(entry.lastrev.identifier)
486 entry.changeset = find_changeset_by_name(entry.lastrev.identifier)
488 end
487 end
489 end
488 end
490 end
489 end
491 end
490 end
492
491
493 private
492 private
494
493
495 # Deletes repository data
494 # Deletes repository data
496 def clear_changesets
495 def clear_changesets
497 cs = Changeset.table_name
496 cs = Changeset.table_name
498 ch = Change.table_name
497 ch = Change.table_name
499 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
498 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
500 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
499 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
501
500
502 self.class.connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
501 self.class.connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
503 self.class.connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
502 self.class.connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
504 self.class.connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
503 self.class.connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
505 self.class.connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
504 self.class.connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
506 clear_extra_info_of_changesets
505 clear_extra_info_of_changesets
507 end
506 end
508
507
509 def clear_extra_info_of_changesets
508 def clear_extra_info_of_changesets
510 end
509 end
511 end
510 end
General Comments 0
You need to be logged in to leave comments. Login now