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