##// END OF EJS Templates
Be more conservative when fetching constants in Repository.factory (#23758)....
Jean-Philippe Lang -
r15434:45a0daa16a14
parent child
Show More
@@ -1,510 +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).distinct.pluck(:committer, :user_id)
286 @committers ||= Changeset.where(:repository_id => id).distinct.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 klass = "Repository::#{klass_name}".constantize
366 repository_class(klass_name).new(*args) rescue nil
367 klass.new(*args)
367 end
368 rescue
368
369 nil
369 def self.repository_class(class_name)
370 class_name = class_name.to_s.classify
371 if Redmine::Scm::Base.all.include?(class_name)
372 "Repository::#{class_name}".constantize
373 end
370 end
374 end
371
375
372 def self.scm_adapter_class
376 def self.scm_adapter_class
373 nil
377 nil
374 end
378 end
375
379
376 def self.scm_command
380 def self.scm_command
377 ret = ""
381 ret = ""
378 begin
382 begin
379 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
380 rescue Exception => e
384 rescue Exception => e
381 logger.error "scm: error during get command: #{e.message}"
385 logger.error "scm: error during get command: #{e.message}"
382 end
386 end
383 ret
387 ret
384 end
388 end
385
389
386 def self.scm_version_string
390 def self.scm_version_string
387 ret = ""
391 ret = ""
388 begin
392 begin
389 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
390 rescue Exception => e
394 rescue Exception => e
391 logger.error "scm: error during get version string: #{e.message}"
395 logger.error "scm: error during get version string: #{e.message}"
392 end
396 end
393 ret
397 ret
394 end
398 end
395
399
396 def self.scm_available
400 def self.scm_available
397 ret = false
401 ret = false
398 begin
402 begin
399 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
400 rescue Exception => e
404 rescue Exception => e
401 logger.error "scm: error during get scm available: #{e.message}"
405 logger.error "scm: error during get scm available: #{e.message}"
402 end
406 end
403 ret
407 ret
404 end
408 end
405
409
406 def set_as_default?
410 def set_as_default?
407 new_record? && project && Repository.where(:project_id => project.id).empty?
411 new_record? && project && Repository.where(:project_id => project.id).empty?
408 end
412 end
409
413
410 # Returns a hash with statistics by author in the following form:
414 # Returns a hash with statistics by author in the following form:
411 # {
415 # {
412 # "John Smith" => { :commits => 45, :changes => 324 },
416 # "John Smith" => { :commits => 45, :changes => 324 },
413 # "Bob" => { ... }
417 # "Bob" => { ... }
414 # }
418 # }
415 #
419 #
416 # Notes:
420 # Notes:
417 # - this hash honnors the users mapping defined for the repository
421 # - this hash honnors the users mapping defined for the repository
418 def stats_by_author
422 def stats_by_author
419 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")
420
424
421 #TODO: restore ordering ; this line probably never worked
425 #TODO: restore ordering ; this line probably never worked
422 #commits.to_a.sort! {|x, y| x.last <=> y.last}
426 #commits.to_a.sort! {|x, y| x.last <=> y.last}
423
427
424 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")
425
429
426 user_ids = changesets.map(&:user_id).compact.uniq
430 user_ids = changesets.map(&:user_id).compact.uniq
427 authors_names = User.where(:id => user_ids).inject({}) do |memo, user|
431 authors_names = User.where(:id => user_ids).inject({}) do |memo, user|
428 memo[user.id] = user.to_s
432 memo[user.id] = user.to_s
429 memo
433 memo
430 end
434 end
431
435
432 (commits + changes).inject({}) do |hash, element|
436 (commits + changes).inject({}) do |hash, element|
433 mapped_name = element.committer
437 mapped_name = element.committer
434 if username = authors_names[element.user_id.to_i]
438 if username = authors_names[element.user_id.to_i]
435 mapped_name = username
439 mapped_name = username
436 end
440 end
437 hash[mapped_name] ||= { :commits_count => 0, :changes_count => 0 }
441 hash[mapped_name] ||= { :commits_count => 0, :changes_count => 0 }
438 if element.is_a?(Changeset)
442 if element.is_a?(Changeset)
439 hash[mapped_name][:commits_count] += element.count.to_i
443 hash[mapped_name][:commits_count] += element.count.to_i
440 else
444 else
441 hash[mapped_name][:changes_count] += element.count.to_i
445 hash[mapped_name][:changes_count] += element.count.to_i
442 end
446 end
443 hash
447 hash
444 end
448 end
445 end
449 end
446
450
447 # 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
448 # in different repositories that point to the same backend
452 # in different repositories that point to the same backend
449 def same_commits_in_scope(scope, changeset)
453 def same_commits_in_scope(scope, changeset)
450 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})
451 if changeset.scmid.present?
455 if changeset.scmid.present?
452 scope = scope.where(:scmid => changeset.scmid)
456 scope = scope.where(:scmid => changeset.scmid)
453 else
457 else
454 scope = scope.where(:revision => changeset.revision)
458 scope = scope.where(:revision => changeset.revision)
455 end
459 end
456 scope
460 scope
457 end
461 end
458
462
459 protected
463 protected
460
464
461 # Validates repository url based against an optional regular expression
465 # Validates repository url based against an optional regular expression
462 # that can be set in the Redmine configuration file.
466 # that can be set in the Redmine configuration file.
463 def validate_repository_path(attribute=:url)
467 def validate_repository_path(attribute=:url)
464 regexp = Redmine::Configuration["scm_#{scm_name.to_s.downcase}_path_regexp"]
468 regexp = Redmine::Configuration["scm_#{scm_name.to_s.downcase}_path_regexp"]
465 if changes[attribute] && regexp.present?
469 if changes[attribute] && regexp.present?
466 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)}
467 unless send(attribute).to_s.match(Regexp.new("\\A#{regexp}\\z"))
471 unless send(attribute).to_s.match(Regexp.new("\\A#{regexp}\\z"))
468 errors.add(attribute, :invalid)
472 errors.add(attribute, :invalid)
469 end
473 end
470 end
474 end
471 end
475 end
472
476
473 def normalize_identifier
477 def normalize_identifier
474 self.identifier = identifier.to_s.strip
478 self.identifier = identifier.to_s.strip
475 end
479 end
476
480
477 def check_default
481 def check_default
478 if !is_default? && set_as_default?
482 if !is_default? && set_as_default?
479 self.is_default = true
483 self.is_default = true
480 end
484 end
481 if is_default? && is_default_changed?
485 if is_default? && is_default_changed?
482 Repository.where(["project_id = ?", project_id]).update_all(["is_default = ?", false])
486 Repository.where(["project_id = ?", project_id]).update_all(["is_default = ?", false])
483 end
487 end
484 end
488 end
485
489
486 def load_entries_changesets(entries)
490 def load_entries_changesets(entries)
487 if entries
491 if entries
488 entries.each do |entry|
492 entries.each do |entry|
489 if entry.lastrev && entry.lastrev.identifier
493 if entry.lastrev && entry.lastrev.identifier
490 entry.changeset = find_changeset_by_name(entry.lastrev.identifier)
494 entry.changeset = find_changeset_by_name(entry.lastrev.identifier)
491 end
495 end
492 end
496 end
493 end
497 end
494 end
498 end
495
499
496 private
500 private
497
501
498 # Deletes repository data
502 # Deletes repository data
499 def clear_changesets
503 def clear_changesets
500 cs = Changeset.table_name
504 cs = Changeset.table_name
501 ch = Change.table_name
505 ch = Change.table_name
502 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
506 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
503 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
507 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
504
508
505 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})")
506 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})")
507 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})")
508 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}")
509 end
513 end
510 end
514 end
@@ -1,499 +1,513
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 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class RepositoryTest < ActiveSupport::TestCase
20 class RepositoryTest < ActiveSupport::TestCase
21 fixtures :projects,
21 fixtures :projects,
22 :trackers,
22 :trackers,
23 :projects_trackers,
23 :projects_trackers,
24 :enabled_modules,
24 :enabled_modules,
25 :repositories,
25 :repositories,
26 :issues,
26 :issues,
27 :issue_statuses,
27 :issue_statuses,
28 :issue_categories,
28 :issue_categories,
29 :changesets,
29 :changesets,
30 :changes,
30 :changes,
31 :users,
31 :users,
32 :email_addresses,
32 :email_addresses,
33 :members,
33 :members,
34 :member_roles,
34 :member_roles,
35 :roles,
35 :roles,
36 :enumerations
36 :enumerations
37
37
38 include Redmine::I18n
38 include Redmine::I18n
39
39
40 def setup
40 def setup
41 @repository = Project.find(1).repository
41 @repository = Project.find(1).repository
42 end
42 end
43
43
44 def test_blank_log_encoding_error_message
44 def test_blank_log_encoding_error_message
45 set_language_if_valid 'en'
45 set_language_if_valid 'en'
46 repo = Repository::Bazaar.new(
46 repo = Repository::Bazaar.new(
47 :project => Project.find(3),
47 :project => Project.find(3),
48 :url => "/test",
48 :url => "/test",
49 :log_encoding => ''
49 :log_encoding => ''
50 )
50 )
51 assert !repo.save
51 assert !repo.save
52 assert_include "Commit messages encoding cannot be blank",
52 assert_include "Commit messages encoding cannot be blank",
53 repo.errors.full_messages
53 repo.errors.full_messages
54 end
54 end
55
55
56 def test_blank_log_encoding_error_message_fr
56 def test_blank_log_encoding_error_message_fr
57 set_language_if_valid 'fr'
57 set_language_if_valid 'fr'
58 str = "Encodage des messages de commit doit \xc3\xaatre renseign\xc3\xa9(e)".force_encoding('UTF-8')
58 str = "Encodage des messages de commit doit \xc3\xaatre renseign\xc3\xa9(e)".force_encoding('UTF-8')
59 repo = Repository::Bazaar.new(
59 repo = Repository::Bazaar.new(
60 :project => Project.find(3),
60 :project => Project.find(3),
61 :url => "/test"
61 :url => "/test"
62 )
62 )
63 assert !repo.save
63 assert !repo.save
64 assert_include str, repo.errors.full_messages
64 assert_include str, repo.errors.full_messages
65 end
65 end
66
66
67 def test_create
67 def test_create
68 repository = Repository::Subversion.new(:project => Project.find(3))
68 repository = Repository::Subversion.new(:project => Project.find(3))
69 assert !repository.save
69 assert !repository.save
70
70
71 repository.url = "svn://localhost"
71 repository.url = "svn://localhost"
72 assert repository.save
72 assert repository.save
73 repository.reload
73 repository.reload
74
74
75 project = Project.find(3)
75 project = Project.find(3)
76 assert_equal repository, project.repository
76 assert_equal repository, project.repository
77 end
77 end
78
78
79 def test_2_repositories_with_same_identifier_in_different_projects_should_be_valid
79 def test_2_repositories_with_same_identifier_in_different_projects_should_be_valid
80 Repository::Subversion.create!(:project_id => 2, :identifier => 'foo', :url => 'file:///foo')
80 Repository::Subversion.create!(:project_id => 2, :identifier => 'foo', :url => 'file:///foo')
81 r = Repository::Subversion.new(:project_id => 3, :identifier => 'foo', :url => 'file:///bar')
81 r = Repository::Subversion.new(:project_id => 3, :identifier => 'foo', :url => 'file:///bar')
82 assert r.save
82 assert r.save
83 end
83 end
84
84
85 def test_2_repositories_with_same_identifier_should_not_be_valid
85 def test_2_repositories_with_same_identifier_should_not_be_valid
86 Repository::Subversion.create!(:project_id => 3, :identifier => 'foo', :url => 'file:///foo')
86 Repository::Subversion.create!(:project_id => 3, :identifier => 'foo', :url => 'file:///foo')
87 r = Repository::Subversion.new(:project_id => 3, :identifier => 'foo', :url => 'file:///bar')
87 r = Repository::Subversion.new(:project_id => 3, :identifier => 'foo', :url => 'file:///bar')
88 assert !r.save
88 assert !r.save
89 end
89 end
90
90
91 def test_2_repositories_with_blank_identifier_should_not_be_valid
91 def test_2_repositories_with_blank_identifier_should_not_be_valid
92 Repository::Subversion.create!(:project_id => 3, :identifier => '', :url => 'file:///foo')
92 Repository::Subversion.create!(:project_id => 3, :identifier => '', :url => 'file:///foo')
93 r = Repository::Subversion.new(:project_id => 3, :identifier => '', :url => 'file:///bar')
93 r = Repository::Subversion.new(:project_id => 3, :identifier => '', :url => 'file:///bar')
94 assert !r.save
94 assert !r.save
95 end
95 end
96
96
97 def test_2_repositories_with_blank_identifier_and_one_as_default_should_not_be_valid
97 def test_2_repositories_with_blank_identifier_and_one_as_default_should_not_be_valid
98 Repository::Subversion.create!(:project_id => 3, :identifier => '', :url => 'file:///foo', :is_default => true)
98 Repository::Subversion.create!(:project_id => 3, :identifier => '', :url => 'file:///foo', :is_default => true)
99 r = Repository::Subversion.new(:project_id => 3, :identifier => '', :url => 'file:///bar')
99 r = Repository::Subversion.new(:project_id => 3, :identifier => '', :url => 'file:///bar')
100 assert !r.save
100 assert !r.save
101 end
101 end
102
102
103 def test_2_repositories_with_blank_and_nil_identifier_should_not_be_valid
103 def test_2_repositories_with_blank_and_nil_identifier_should_not_be_valid
104 Repository::Subversion.create!(:project_id => 3, :identifier => nil, :url => 'file:///foo')
104 Repository::Subversion.create!(:project_id => 3, :identifier => nil, :url => 'file:///foo')
105 r = Repository::Subversion.new(:project_id => 3, :identifier => '', :url => 'file:///bar')
105 r = Repository::Subversion.new(:project_id => 3, :identifier => '', :url => 'file:///bar')
106 assert !r.save
106 assert !r.save
107 end
107 end
108
108
109 def test_first_repository_should_be_set_as_default
109 def test_first_repository_should_be_set_as_default
110 repository1 = Repository::Subversion.new(
110 repository1 = Repository::Subversion.new(
111 :project => Project.find(3),
111 :project => Project.find(3),
112 :identifier => 'svn1',
112 :identifier => 'svn1',
113 :url => 'file:///svn1'
113 :url => 'file:///svn1'
114 )
114 )
115 assert repository1.save
115 assert repository1.save
116 assert repository1.is_default?
116 assert repository1.is_default?
117
117
118 repository2 = Repository::Subversion.new(
118 repository2 = Repository::Subversion.new(
119 :project => Project.find(3),
119 :project => Project.find(3),
120 :identifier => 'svn2',
120 :identifier => 'svn2',
121 :url => 'file:///svn2'
121 :url => 'file:///svn2'
122 )
122 )
123 assert repository2.save
123 assert repository2.save
124 assert !repository2.is_default?
124 assert !repository2.is_default?
125
125
126 assert_equal repository1, Project.find(3).repository
126 assert_equal repository1, Project.find(3).repository
127 assert_equal [repository1, repository2], Project.find(3).repositories.sort
127 assert_equal [repository1, repository2], Project.find(3).repositories.sort
128 end
128 end
129
129
130 def test_default_repository_should_be_one
130 def test_default_repository_should_be_one
131 assert_equal 0, Project.find(3).repositories.count
131 assert_equal 0, Project.find(3).repositories.count
132 repository1 = Repository::Subversion.new(
132 repository1 = Repository::Subversion.new(
133 :project => Project.find(3),
133 :project => Project.find(3),
134 :identifier => 'svn1',
134 :identifier => 'svn1',
135 :url => 'file:///svn1'
135 :url => 'file:///svn1'
136 )
136 )
137 assert repository1.save
137 assert repository1.save
138 assert repository1.is_default?
138 assert repository1.is_default?
139
139
140 repository2 = Repository::Subversion.new(
140 repository2 = Repository::Subversion.new(
141 :project => Project.find(3),
141 :project => Project.find(3),
142 :identifier => 'svn2',
142 :identifier => 'svn2',
143 :url => 'file:///svn2',
143 :url => 'file:///svn2',
144 :is_default => true
144 :is_default => true
145 )
145 )
146 assert repository2.save
146 assert repository2.save
147 assert repository2.is_default?
147 assert repository2.is_default?
148 repository1.reload
148 repository1.reload
149 assert !repository1.is_default?
149 assert !repository1.is_default?
150
150
151 assert_equal repository2, Project.find(3).repository
151 assert_equal repository2, Project.find(3).repository
152 assert_equal [repository2, repository1], Project.find(3).repositories.sort
152 assert_equal [repository2, repository1], Project.find(3).repositories.sort
153 end
153 end
154
154
155 def test_identifier_should_accept_letters_digits_dashes_and_underscores
155 def test_identifier_should_accept_letters_digits_dashes_and_underscores
156 r = Repository::Subversion.new(
156 r = Repository::Subversion.new(
157 :project_id => 3,
157 :project_id => 3,
158 :identifier => 'svn-123_45',
158 :identifier => 'svn-123_45',
159 :url => 'file:///svn'
159 :url => 'file:///svn'
160 )
160 )
161 assert r.save
161 assert r.save
162 end
162 end
163
163
164 def test_identifier_should_not_be_frozen_for_a_new_repository
164 def test_identifier_should_not_be_frozen_for_a_new_repository
165 assert_equal false, Repository.new.identifier_frozen?
165 assert_equal false, Repository.new.identifier_frozen?
166 end
166 end
167
167
168 def test_identifier_should_not_be_frozen_for_a_saved_repository_with_blank_identifier
168 def test_identifier_should_not_be_frozen_for_a_saved_repository_with_blank_identifier
169 Repository.where(:id => 10).update_all(["identifier = ''"])
169 Repository.where(:id => 10).update_all(["identifier = ''"])
170 assert_equal false, Repository.find(10).identifier_frozen?
170 assert_equal false, Repository.find(10).identifier_frozen?
171 end
171 end
172
172
173 def test_identifier_should_be_frozen_for_a_saved_repository_with_valid_identifier
173 def test_identifier_should_be_frozen_for_a_saved_repository_with_valid_identifier
174 Repository.where(:id => 10).update_all(["identifier = 'abc123'"])
174 Repository.where(:id => 10).update_all(["identifier = 'abc123'"])
175 assert_equal true, Repository.find(10).identifier_frozen?
175 assert_equal true, Repository.find(10).identifier_frozen?
176 end
176 end
177
177
178 def test_identifier_should_not_accept_change_if_frozen
178 def test_identifier_should_not_accept_change_if_frozen
179 r = Repository.new(:identifier => 'foo')
179 r = Repository.new(:identifier => 'foo')
180 r.stubs(:identifier_frozen?).returns(true)
180 r.stubs(:identifier_frozen?).returns(true)
181
181
182 r.identifier = 'bar'
182 r.identifier = 'bar'
183 assert_equal 'foo', r.identifier
183 assert_equal 'foo', r.identifier
184 end
184 end
185
185
186 def test_identifier_should_accept_change_if_not_frozen
186 def test_identifier_should_accept_change_if_not_frozen
187 r = Repository.new(:identifier => 'foo')
187 r = Repository.new(:identifier => 'foo')
188 r.stubs(:identifier_frozen?).returns(false)
188 r.stubs(:identifier_frozen?).returns(false)
189
189
190 r.identifier = 'bar'
190 r.identifier = 'bar'
191 assert_equal 'bar', r.identifier
191 assert_equal 'bar', r.identifier
192 end
192 end
193
193
194 def test_destroy
194 def test_destroy
195 repository = Repository.find(10)
195 repository = Repository.find(10)
196 changesets = repository.changesets.count
196 changesets = repository.changesets.count
197 changes = repository.filechanges.count
197 changes = repository.filechanges.count
198
198
199 assert_difference 'Changeset.count', -changesets do
199 assert_difference 'Changeset.count', -changesets do
200 assert_difference 'Change.count', -changes do
200 assert_difference 'Change.count', -changes do
201 Repository.find(10).destroy
201 Repository.find(10).destroy
202 end
202 end
203 end
203 end
204 end
204 end
205
205
206 def test_destroy_should_delete_parents_associations
206 def test_destroy_should_delete_parents_associations
207 changeset = Changeset.find(102)
207 changeset = Changeset.find(102)
208 changeset.parents = Changeset.where(:id => [100, 101]).to_a
208 changeset.parents = Changeset.where(:id => [100, 101]).to_a
209 assert_difference 'Changeset.connection.select_all("select * from changeset_parents").count', -2 do
209 assert_difference 'Changeset.connection.select_all("select * from changeset_parents").count', -2 do
210 Repository.find(10).destroy
210 Repository.find(10).destroy
211 end
211 end
212 end
212 end
213
213
214 def test_destroy_should_delete_issues_associations
214 def test_destroy_should_delete_issues_associations
215 changeset = Changeset.find(102)
215 changeset = Changeset.find(102)
216 changeset.issues = Issue.where(:id => [1, 2]).to_a
216 changeset.issues = Issue.where(:id => [1, 2]).to_a
217 assert_difference 'Changeset.connection.select_all("select * from changesets_issues").count', -2 do
217 assert_difference 'Changeset.connection.select_all("select * from changesets_issues").count', -2 do
218 Repository.find(10).destroy
218 Repository.find(10).destroy
219 end
219 end
220 end
220 end
221
221
222 def test_should_not_create_with_disabled_scm
222 def test_should_not_create_with_disabled_scm
223 # disable Subversion
223 # disable Subversion
224 with_settings :enabled_scm => ['Darcs', 'Git'] do
224 with_settings :enabled_scm => ['Darcs', 'Git'] do
225 repository = Repository::Subversion.new(
225 repository = Repository::Subversion.new(
226 :project => Project.find(3), :url => "svn://localhost")
226 :project => Project.find(3), :url => "svn://localhost")
227 assert !repository.save
227 assert !repository.save
228 assert_include I18n.translate('activerecord.errors.messages.invalid'),
228 assert_include I18n.translate('activerecord.errors.messages.invalid'),
229 repository.errors[:type]
229 repository.errors[:type]
230 end
230 end
231 end
231 end
232
232
233 def test_scan_changesets_for_issue_ids
233 def test_scan_changesets_for_issue_ids
234 Setting.default_language = 'en'
234 Setting.default_language = 'en'
235 Setting.commit_ref_keywords = 'refs , references, IssueID'
235 Setting.commit_ref_keywords = 'refs , references, IssueID'
236 Setting.commit_update_keywords = [
236 Setting.commit_update_keywords = [
237 {'keywords' => 'fixes , closes',
237 {'keywords' => 'fixes , closes',
238 'status_id' => IssueStatus.where(:is_closed => true).first.id,
238 'status_id' => IssueStatus.where(:is_closed => true).first.id,
239 'done_ratio' => '90'}
239 'done_ratio' => '90'}
240 ]
240 ]
241 Setting.default_language = 'en'
241 Setting.default_language = 'en'
242 ActionMailer::Base.deliveries.clear
242 ActionMailer::Base.deliveries.clear
243
243
244 # make sure issue 1 is not already closed
244 # make sure issue 1 is not already closed
245 fixed_issue = Issue.find(1)
245 fixed_issue = Issue.find(1)
246 assert !fixed_issue.closed?
246 assert !fixed_issue.closed?
247 old_status = fixed_issue.status
247 old_status = fixed_issue.status
248
248
249 with_settings :notified_events => %w(issue_added issue_updated) do
249 with_settings :notified_events => %w(issue_added issue_updated) do
250 Repository.scan_changesets_for_issue_ids
250 Repository.scan_changesets_for_issue_ids
251 end
251 end
252 assert_equal [101, 102], Issue.find(3).changeset_ids
252 assert_equal [101, 102], Issue.find(3).changeset_ids
253
253
254 # fixed issues
254 # fixed issues
255 fixed_issue.reload
255 fixed_issue.reload
256 assert fixed_issue.closed?
256 assert fixed_issue.closed?
257 assert_equal 90, fixed_issue.done_ratio
257 assert_equal 90, fixed_issue.done_ratio
258 assert_equal [101], fixed_issue.changeset_ids
258 assert_equal [101], fixed_issue.changeset_ids
259
259
260 # issue change
260 # issue change
261 journal = fixed_issue.journals.reorder('created_on desc').first
261 journal = fixed_issue.journals.reorder('created_on desc').first
262 assert_equal User.find_by_login('dlopper'), journal.user
262 assert_equal User.find_by_login('dlopper'), journal.user
263 assert_equal 'Applied in changeset r2.', journal.notes
263 assert_equal 'Applied in changeset r2.', journal.notes
264
264
265 # 2 email notifications
265 # 2 email notifications
266 assert_equal 2, ActionMailer::Base.deliveries.size
266 assert_equal 2, ActionMailer::Base.deliveries.size
267 mail = ActionMailer::Base.deliveries.first
267 mail = ActionMailer::Base.deliveries.first
268 assert_not_nil mail
268 assert_not_nil mail
269 assert mail.subject.starts_with?(
269 assert mail.subject.starts_with?(
270 "[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
270 "[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
271 assert_mail_body_match(
271 assert_mail_body_match(
272 "Status changed from #{old_status} to #{fixed_issue.status}", mail)
272 "Status changed from #{old_status} to #{fixed_issue.status}", mail)
273
273
274 # ignoring commits referencing an issue of another project
274 # ignoring commits referencing an issue of another project
275 assert_equal [], Issue.find(4).changesets
275 assert_equal [], Issue.find(4).changesets
276 end
276 end
277
277
278 def test_for_changeset_comments_strip
278 def test_for_changeset_comments_strip
279 repository = Repository::Mercurial.create(
279 repository = Repository::Mercurial.create(
280 :project => Project.find( 4 ),
280 :project => Project.find( 4 ),
281 :url => '/foo/bar/baz' )
281 :url => '/foo/bar/baz' )
282 comment = <<-COMMENT
282 comment = <<-COMMENT
283 This is a loooooooooooooooooooooooooooong comment
283 This is a loooooooooooooooooooooooooooong comment
284
284
285
285
286 COMMENT
286 COMMENT
287 changeset = Changeset.new(
287 changeset = Changeset.new(
288 :comments => comment, :commit_date => Time.now,
288 :comments => comment, :commit_date => Time.now,
289 :revision => 0, :scmid => 'f39b7922fb3c',
289 :revision => 0, :scmid => 'f39b7922fb3c',
290 :committer => 'foo <foo@example.com>',
290 :committer => 'foo <foo@example.com>',
291 :committed_on => Time.now, :repository => repository )
291 :committed_on => Time.now, :repository => repository )
292 assert( changeset.save )
292 assert( changeset.save )
293 assert_not_equal( comment, changeset.comments )
293 assert_not_equal( comment, changeset.comments )
294 assert_equal( 'This is a loooooooooooooooooooooooooooong comment',
294 assert_equal( 'This is a loooooooooooooooooooooooooooong comment',
295 changeset.comments )
295 changeset.comments )
296 end
296 end
297
297
298 def test_for_urls_strip_cvs
298 def test_for_urls_strip_cvs
299 repository = Repository::Cvs.create(
299 repository = Repository::Cvs.create(
300 :project => Project.find(4),
300 :project => Project.find(4),
301 :url => ' :pserver:login:password@host:/path/to/the/repository',
301 :url => ' :pserver:login:password@host:/path/to/the/repository',
302 :root_url => 'foo ',
302 :root_url => 'foo ',
303 :log_encoding => 'UTF-8')
303 :log_encoding => 'UTF-8')
304 assert repository.save
304 assert repository.save
305 repository.reload
305 repository.reload
306 assert_equal ':pserver:login:password@host:/path/to/the/repository',
306 assert_equal ':pserver:login:password@host:/path/to/the/repository',
307 repository.url
307 repository.url
308 assert_equal 'foo', repository.root_url
308 assert_equal 'foo', repository.root_url
309 end
309 end
310
310
311 def test_for_urls_strip_subversion
311 def test_for_urls_strip_subversion
312 repository = Repository::Subversion.create(
312 repository = Repository::Subversion.create(
313 :project => Project.find(4),
313 :project => Project.find(4),
314 :url => ' file:///dummy ')
314 :url => ' file:///dummy ')
315 assert repository.save
315 assert repository.save
316 repository.reload
316 repository.reload
317 assert_equal 'file:///dummy', repository.url
317 assert_equal 'file:///dummy', repository.url
318 end
318 end
319
319
320 def test_for_urls_strip_git
320 def test_for_urls_strip_git
321 repository = Repository::Git.create(
321 repository = Repository::Git.create(
322 :project => Project.find(4),
322 :project => Project.find(4),
323 :url => ' c:\dummy ')
323 :url => ' c:\dummy ')
324 assert repository.save
324 assert repository.save
325 repository.reload
325 repository.reload
326 assert_equal 'c:\dummy', repository.url
326 assert_equal 'c:\dummy', repository.url
327 end
327 end
328
328
329 def test_manual_user_mapping
329 def test_manual_user_mapping
330 assert_no_difference "Changeset.where('user_id <> 2').count" do
330 assert_no_difference "Changeset.where('user_id <> 2').count" do
331 c = Changeset.create!(
331 c = Changeset.create!(
332 :repository => @repository,
332 :repository => @repository,
333 :committer => 'foo',
333 :committer => 'foo',
334 :committed_on => Time.now,
334 :committed_on => Time.now,
335 :revision => 100,
335 :revision => 100,
336 :comments => 'Committed by foo.'
336 :comments => 'Committed by foo.'
337 )
337 )
338 assert_nil c.user
338 assert_nil c.user
339 @repository.committer_ids = {'foo' => '2'}
339 @repository.committer_ids = {'foo' => '2'}
340 assert_equal User.find(2), c.reload.user
340 assert_equal User.find(2), c.reload.user
341 # committer is now mapped
341 # committer is now mapped
342 c = Changeset.create!(
342 c = Changeset.create!(
343 :repository => @repository,
343 :repository => @repository,
344 :committer => 'foo',
344 :committer => 'foo',
345 :committed_on => Time.now,
345 :committed_on => Time.now,
346 :revision => 101,
346 :revision => 101,
347 :comments => 'Another commit by foo.'
347 :comments => 'Another commit by foo.'
348 )
348 )
349 assert_equal User.find(2), c.user
349 assert_equal User.find(2), c.user
350 end
350 end
351 end
351 end
352
352
353 def test_auto_user_mapping_by_username
353 def test_auto_user_mapping_by_username
354 c = Changeset.create!(
354 c = Changeset.create!(
355 :repository => @repository,
355 :repository => @repository,
356 :committer => 'jsmith',
356 :committer => 'jsmith',
357 :committed_on => Time.now,
357 :committed_on => Time.now,
358 :revision => 100,
358 :revision => 100,
359 :comments => 'Committed by john.'
359 :comments => 'Committed by john.'
360 )
360 )
361 assert_equal User.find(2), c.user
361 assert_equal User.find(2), c.user
362 end
362 end
363
363
364 def test_auto_user_mapping_by_email
364 def test_auto_user_mapping_by_email
365 c = Changeset.create!(
365 c = Changeset.create!(
366 :repository => @repository,
366 :repository => @repository,
367 :committer => 'john <jsmith@somenet.foo>',
367 :committer => 'john <jsmith@somenet.foo>',
368 :committed_on => Time.now,
368 :committed_on => Time.now,
369 :revision => 100,
369 :revision => 100,
370 :comments => 'Committed by john.'
370 :comments => 'Committed by john.'
371 )
371 )
372 assert_equal User.find(2), c.user
372 assert_equal User.find(2), c.user
373 end
373 end
374
374
375 def test_filesystem_avaialbe
375 def test_filesystem_avaialbe
376 klass = Repository::Filesystem
376 klass = Repository::Filesystem
377 assert klass.scm_adapter_class
377 assert klass.scm_adapter_class
378 assert_equal true, klass.scm_available
378 assert_equal true, klass.scm_available
379 end
379 end
380
380
381 def test_extra_info_should_not_return_non_hash_value
381 def test_extra_info_should_not_return_non_hash_value
382 repo = Repository.new
382 repo = Repository.new
383 repo.extra_info = "foo"
383 repo.extra_info = "foo"
384 assert_nil repo.extra_info
384 assert_nil repo.extra_info
385 end
385 end
386
386
387 def test_merge_extra_info
387 def test_merge_extra_info
388 repo = Repository::Subversion.new(:project => Project.find(3))
388 repo = Repository::Subversion.new(:project => Project.find(3))
389 assert !repo.save
389 assert !repo.save
390 repo.url = "svn://localhost"
390 repo.url = "svn://localhost"
391 assert repo.save
391 assert repo.save
392 repo.reload
392 repo.reload
393 project = Project.find(3)
393 project = Project.find(3)
394 assert_equal repo, project.repository
394 assert_equal repo, project.repository
395 assert_nil repo.extra_info
395 assert_nil repo.extra_info
396 h1 = {"test_1" => {"test_11" => "test_value_11"}}
396 h1 = {"test_1" => {"test_11" => "test_value_11"}}
397 repo.merge_extra_info(h1)
397 repo.merge_extra_info(h1)
398 assert_equal h1, repo.extra_info
398 assert_equal h1, repo.extra_info
399 h2 = {"test_2" => {
399 h2 = {"test_2" => {
400 "test_21" => "test_value_21",
400 "test_21" => "test_value_21",
401 "test_22" => "test_value_22",
401 "test_22" => "test_value_22",
402 }}
402 }}
403 repo.merge_extra_info(h2)
403 repo.merge_extra_info(h2)
404 assert_equal (h = {"test_11" => "test_value_11"}),
404 assert_equal (h = {"test_11" => "test_value_11"}),
405 repo.extra_info["test_1"]
405 repo.extra_info["test_1"]
406 assert_equal "test_value_21",
406 assert_equal "test_value_21",
407 repo.extra_info["test_2"]["test_21"]
407 repo.extra_info["test_2"]["test_21"]
408 h3 = {"test_2" => {
408 h3 = {"test_2" => {
409 "test_23" => "test_value_23",
409 "test_23" => "test_value_23",
410 "test_24" => "test_value_24",
410 "test_24" => "test_value_24",
411 }}
411 }}
412 repo.merge_extra_info(h3)
412 repo.merge_extra_info(h3)
413 assert_equal (h = {"test_11" => "test_value_11"}),
413 assert_equal (h = {"test_11" => "test_value_11"}),
414 repo.extra_info["test_1"]
414 repo.extra_info["test_1"]
415 assert_nil repo.extra_info["test_2"]["test_21"]
415 assert_nil repo.extra_info["test_2"]["test_21"]
416 assert_equal "test_value_23",
416 assert_equal "test_value_23",
417 repo.extra_info["test_2"]["test_23"]
417 repo.extra_info["test_2"]["test_23"]
418 end
418 end
419
419
420 def test_sort_should_not_raise_an_error_with_nil_identifiers
420 def test_sort_should_not_raise_an_error_with_nil_identifiers
421 r1 = Repository.new
421 r1 = Repository.new
422 r2 = Repository.new
422 r2 = Repository.new
423
423
424 assert_nothing_raised do
424 assert_nothing_raised do
425 [r1, r2].sort
425 [r1, r2].sort
426 end
426 end
427 end
427 end
428
428
429 def test_stats_by_author_reflect_changesets_and_changes
429 def test_stats_by_author_reflect_changesets_and_changes
430 repository = Repository.find(10)
430 repository = Repository.find(10)
431
431
432 expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}}
432 expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}}
433 assert_equal expected, repository.stats_by_author
433 assert_equal expected, repository.stats_by_author
434
434
435 set = Changeset.create!(
435 set = Changeset.create!(
436 :repository => repository,
436 :repository => repository,
437 :committer => 'dlopper',
437 :committer => 'dlopper',
438 :committed_on => Time.now,
438 :committed_on => Time.now,
439 :revision => 101,
439 :revision => 101,
440 :comments => 'Another commit by foo.'
440 :comments => 'Another commit by foo.'
441 )
441 )
442 Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file1')
442 Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file1')
443 Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file2')
443 Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file2')
444 expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>5}}
444 expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>5}}
445 assert_equal expected, repository.stats_by_author
445 assert_equal expected, repository.stats_by_author
446 end
446 end
447
447
448 def test_stats_by_author_honnor_committers
448 def test_stats_by_author_honnor_committers
449 # in fact it is really tested above, but let's have a dedicated test
449 # in fact it is really tested above, but let's have a dedicated test
450 # to ensure things are dynamically linked to Users
450 # to ensure things are dynamically linked to Users
451 User.find_by_login("dlopper").update_attribute(:firstname, "Dave's")
451 User.find_by_login("dlopper").update_attribute(:firstname, "Dave's")
452 repository = Repository.find(10)
452 repository = Repository.find(10)
453 expected = {"Dave's Lopper"=>{:commits_count=>10, :changes_count=>3}}
453 expected = {"Dave's Lopper"=>{:commits_count=>10, :changes_count=>3}}
454 assert_equal expected, repository.stats_by_author
454 assert_equal expected, repository.stats_by_author
455 end
455 end
456
456
457 def test_stats_by_author_doesnt_drop_unmapped_users
457 def test_stats_by_author_doesnt_drop_unmapped_users
458 repository = Repository.find(10)
458 repository = Repository.find(10)
459 Changeset.create!(
459 Changeset.create!(
460 :repository => repository,
460 :repository => repository,
461 :committer => 'unnamed <foo@bar.net>',
461 :committer => 'unnamed <foo@bar.net>',
462 :committed_on => Time.now,
462 :committed_on => Time.now,
463 :revision => 101,
463 :revision => 101,
464 :comments => 'Another commit by foo.'
464 :comments => 'Another commit by foo.'
465 )
465 )
466
466
467 assert repository.stats_by_author.has_key?("unnamed <foo@bar.net>")
467 assert repository.stats_by_author.has_key?("unnamed <foo@bar.net>")
468 end
468 end
469
469
470 def test_stats_by_author_merge_correctly
470 def test_stats_by_author_merge_correctly
471 # as we honnor users->committer map and it's not injective,
471 # as we honnor users->committer map and it's not injective,
472 # we must be sure merges happen correctly and stats are not
472 # we must be sure merges happen correctly and stats are not
473 # wiped out when two source counts map to the same user.
473 # wiped out when two source counts map to the same user.
474 #
474 #
475 # Here we have Changeset's with committer="dlopper" and others
475 # Here we have Changeset's with committer="dlopper" and others
476 # with committer="dlopper <dlopper@somefoo.net>"
476 # with committer="dlopper <dlopper@somefoo.net>"
477 repository = Repository.find(10)
477 repository = Repository.find(10)
478
478
479 expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}}
479 expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}}
480 assert_equal expected, repository.stats_by_author
480 assert_equal expected, repository.stats_by_author
481
481
482 set = Changeset.create!(
482 set = Changeset.create!(
483 :repository => repository,
483 :repository => repository,
484 :committer => 'dlopper <dlopper@somefoo.net>',
484 :committer => 'dlopper <dlopper@somefoo.net>',
485 :committed_on => Time.now,
485 :committed_on => Time.now,
486 :revision => 101,
486 :revision => 101,
487 :comments => 'Another commit by foo.'
487 :comments => 'Another commit by foo.'
488 )
488 )
489
489
490 expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>3}}
490 expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>3}}
491 assert_equal expected, repository.stats_by_author
491 assert_equal expected, repository.stats_by_author
492 end
492 end
493
493
494 def test_fetch_changesets
494 def test_fetch_changesets
495 # 2 repositories in fixtures
495 # 2 repositories in fixtures
496 Repository::Subversion.any_instance.expects(:fetch_changesets).twice.returns(true)
496 Repository::Subversion.any_instance.expects(:fetch_changesets).twice.returns(true)
497 Repository.fetch_changesets
497 Repository.fetch_changesets
498 end
498 end
499
500 def test_repository_class
501 assert_equal Repository::Subversion, Repository.repository_class('Subversion')
502 assert_equal Repository::Git, Repository.repository_class('Git')
503 assert_nil Repository.factory('Serializer')
504 assert_nil Repository.factory('Query')
505 end
506
507 def test_factory
508 assert_instance_of Repository::Subversion, Repository.factory('Subversion')
509 assert_instance_of Repository::Git, Repository.factory('Git')
510 assert_nil Repository.factory('Serializer')
511 assert_nil Repository.factory('Query')
512 end
499 end
513 end
General Comments 0
You need to be logged in to leave comments. Login now