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