##// END OF EJS Templates
Fixed undefined method `<=>' for nil:NilClass when sorting repositories with nil identifiers (#10827)....
Jean-Philippe Lang -
r9436:9f396a6dfbb9
parent child
Show More
@@ -1,397 +1,397
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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
22
23 belongs_to :project
23 belongs_to :project
24 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
24 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
25 has_many :changes, :through => :changesets
25 has_many :changes, :through => :changesets
26
26
27 serialize :extra_info
27 serialize :extra_info
28
28
29 before_save :check_default
29 before_save :check_default
30
30
31 # Raw SQL to delete changesets and changes in the database
31 # Raw SQL to delete changesets and changes in the database
32 # has_many :changesets, :dependent => :destroy is too slow for big repositories
32 # has_many :changesets, :dependent => :destroy is too slow for big repositories
33 before_destroy :clear_changesets
33 before_destroy :clear_changesets
34
34
35 validates_length_of :password, :maximum => 255, :allow_nil => true
35 validates_length_of :password, :maximum => 255, :allow_nil => true
36 validates_length_of :identifier, :maximum => 255, :allow_blank => true
36 validates_length_of :identifier, :maximum => 255, :allow_blank => true
37 validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? }
37 validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? }
38 validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true
38 validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true
39 validates_exclusion_of :identifier, :in => %w(show entry raw changes annotate diff show stats graph)
39 validates_exclusion_of :identifier, :in => %w(show entry raw changes annotate diff show stats graph)
40 # donwcase letters, digits, dashes but not digits only
40 # donwcase letters, digits, dashes but not digits only
41 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :allow_blank => true
41 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :allow_blank => true
42 # Checks if the SCM is enabled when creating a repository
42 # Checks if the SCM is enabled when creating a repository
43 validate :repo_create_validation, :on => :create
43 validate :repo_create_validation, :on => :create
44
44
45 def repo_create_validation
45 def repo_create_validation
46 unless Setting.enabled_scm.include?(self.class.name.demodulize)
46 unless Setting.enabled_scm.include?(self.class.name.demodulize)
47 errors.add(:type, :invalid)
47 errors.add(:type, :invalid)
48 end
48 end
49 end
49 end
50
50
51 def self.human_attribute_name(attribute_key_name, *args)
51 def self.human_attribute_name(attribute_key_name, *args)
52 attr_name = attribute_key_name.to_s
52 attr_name = attribute_key_name.to_s
53 if attr_name == "log_encoding"
53 if attr_name == "log_encoding"
54 attr_name = "commit_logs_encoding"
54 attr_name = "commit_logs_encoding"
55 end
55 end
56 super(attr_name, *args)
56 super(attr_name, *args)
57 end
57 end
58
58
59 # Removes leading and trailing whitespace
59 # Removes leading and trailing whitespace
60 def url=(arg)
60 def url=(arg)
61 write_attribute(:url, arg ? arg.to_s.strip : nil)
61 write_attribute(:url, arg ? arg.to_s.strip : nil)
62 end
62 end
63
63
64 # Removes leading and trailing whitespace
64 # Removes leading and trailing whitespace
65 def root_url=(arg)
65 def root_url=(arg)
66 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
66 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
67 end
67 end
68
68
69 def password
69 def password
70 read_ciphered_attribute(:password)
70 read_ciphered_attribute(:password)
71 end
71 end
72
72
73 def password=(arg)
73 def password=(arg)
74 write_ciphered_attribute(:password, arg)
74 write_ciphered_attribute(:password, arg)
75 end
75 end
76
76
77 def scm_adapter
77 def scm_adapter
78 self.class.scm_adapter_class
78 self.class.scm_adapter_class
79 end
79 end
80
80
81 def scm
81 def scm
82 unless @scm
82 unless @scm
83 @scm = self.scm_adapter.new(url, root_url,
83 @scm = self.scm_adapter.new(url, root_url,
84 login, password, path_encoding)
84 login, password, path_encoding)
85 if root_url.blank? && @scm.root_url.present?
85 if root_url.blank? && @scm.root_url.present?
86 update_attribute(:root_url, @scm.root_url)
86 update_attribute(:root_url, @scm.root_url)
87 end
87 end
88 end
88 end
89 @scm
89 @scm
90 end
90 end
91
91
92 def scm_name
92 def scm_name
93 self.class.scm_name
93 self.class.scm_name
94 end
94 end
95
95
96 def name
96 def name
97 if identifier.present?
97 if identifier.present?
98 identifier
98 identifier
99 elsif is_default?
99 elsif is_default?
100 l(:field_repository_is_default)
100 l(:field_repository_is_default)
101 else
101 else
102 scm_name
102 scm_name
103 end
103 end
104 end
104 end
105
105
106 def identifier_param
106 def identifier_param
107 if is_default?
107 if is_default?
108 nil
108 nil
109 elsif identifier.present?
109 elsif identifier.present?
110 identifier
110 identifier
111 else
111 else
112 id.to_s
112 id.to_s
113 end
113 end
114 end
114 end
115
115
116 def <=>(repository)
116 def <=>(repository)
117 if is_default?
117 if is_default?
118 -1
118 -1
119 elsif repository.is_default?
119 elsif repository.is_default?
120 1
120 1
121 else
121 else
122 identifier <=> repository.identifier
122 identifier.to_s <=> repository.identifier.to_s
123 end
123 end
124 end
124 end
125
125
126 def self.find_by_identifier_param(param)
126 def self.find_by_identifier_param(param)
127 if param.to_s =~ /^\d+$/
127 if param.to_s =~ /^\d+$/
128 find_by_id(param)
128 find_by_id(param)
129 else
129 else
130 find_by_identifier(param)
130 find_by_identifier(param)
131 end
131 end
132 end
132 end
133
133
134 def merge_extra_info(arg)
134 def merge_extra_info(arg)
135 h = extra_info || {}
135 h = extra_info || {}
136 return h if arg.nil?
136 return h if arg.nil?
137 h.merge!(arg)
137 h.merge!(arg)
138 write_attribute(:extra_info, h)
138 write_attribute(:extra_info, h)
139 end
139 end
140
140
141 def report_last_commit
141 def report_last_commit
142 true
142 true
143 end
143 end
144
144
145 def supports_cat?
145 def supports_cat?
146 scm.supports_cat?
146 scm.supports_cat?
147 end
147 end
148
148
149 def supports_annotate?
149 def supports_annotate?
150 scm.supports_annotate?
150 scm.supports_annotate?
151 end
151 end
152
152
153 def supports_all_revisions?
153 def supports_all_revisions?
154 true
154 true
155 end
155 end
156
156
157 def supports_directory_revisions?
157 def supports_directory_revisions?
158 false
158 false
159 end
159 end
160
160
161 def supports_revision_graph?
161 def supports_revision_graph?
162 false
162 false
163 end
163 end
164
164
165 def entry(path=nil, identifier=nil)
165 def entry(path=nil, identifier=nil)
166 scm.entry(path, identifier)
166 scm.entry(path, identifier)
167 end
167 end
168
168
169 def entries(path=nil, identifier=nil)
169 def entries(path=nil, identifier=nil)
170 scm.entries(path, identifier)
170 scm.entries(path, identifier)
171 end
171 end
172
172
173 def branches
173 def branches
174 scm.branches
174 scm.branches
175 end
175 end
176
176
177 def tags
177 def tags
178 scm.tags
178 scm.tags
179 end
179 end
180
180
181 def default_branch
181 def default_branch
182 nil
182 nil
183 end
183 end
184
184
185 def properties(path, identifier=nil)
185 def properties(path, identifier=nil)
186 scm.properties(path, identifier)
186 scm.properties(path, identifier)
187 end
187 end
188
188
189 def cat(path, identifier=nil)
189 def cat(path, identifier=nil)
190 scm.cat(path, identifier)
190 scm.cat(path, identifier)
191 end
191 end
192
192
193 def diff(path, rev, rev_to)
193 def diff(path, rev, rev_to)
194 scm.diff(path, rev, rev_to)
194 scm.diff(path, rev, rev_to)
195 end
195 end
196
196
197 def diff_format_revisions(cs, cs_to, sep=':')
197 def diff_format_revisions(cs, cs_to, sep=':')
198 text = ""
198 text = ""
199 text << cs_to.format_identifier + sep if cs_to
199 text << cs_to.format_identifier + sep if cs_to
200 text << cs.format_identifier if cs
200 text << cs.format_identifier if cs
201 text
201 text
202 end
202 end
203
203
204 # Returns a path relative to the url of the repository
204 # Returns a path relative to the url of the repository
205 def relative_path(path)
205 def relative_path(path)
206 path
206 path
207 end
207 end
208
208
209 # Finds and returns a revision with a number or the beginning of a hash
209 # Finds and returns a revision with a number or the beginning of a hash
210 def find_changeset_by_name(name)
210 def find_changeset_by_name(name)
211 return nil if name.blank?
211 return nil if name.blank?
212 s = name.to_s
212 s = name.to_s
213 changesets.find(:first, :conditions => (s.match(/^\d*$/) ?
213 changesets.find(:first, :conditions => (s.match(/^\d*$/) ?
214 ["revision = ?", s] : ["revision LIKE ?", s + '%']))
214 ["revision = ?", s] : ["revision LIKE ?", s + '%']))
215 end
215 end
216
216
217 def latest_changeset
217 def latest_changeset
218 @latest_changeset ||= changesets.find(:first)
218 @latest_changeset ||= changesets.find(:first)
219 end
219 end
220
220
221 # Returns the latest changesets for +path+
221 # Returns the latest changesets for +path+
222 # Default behaviour is to search in cached changesets
222 # Default behaviour is to search in cached changesets
223 def latest_changesets(path, rev, limit=10)
223 def latest_changesets(path, rev, limit=10)
224 if path.blank?
224 if path.blank?
225 changesets.find(
225 changesets.find(
226 :all,
226 :all,
227 :include => :user,
227 :include => :user,
228 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
228 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
229 :limit => limit)
229 :limit => limit)
230 else
230 else
231 changes.find(
231 changes.find(
232 :all,
232 :all,
233 :include => {:changeset => :user},
233 :include => {:changeset => :user},
234 :conditions => ["path = ?", path.with_leading_slash],
234 :conditions => ["path = ?", path.with_leading_slash],
235 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
235 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
236 :limit => limit
236 :limit => limit
237 ).collect(&:changeset)
237 ).collect(&:changeset)
238 end
238 end
239 end
239 end
240
240
241 def scan_changesets_for_issue_ids
241 def scan_changesets_for_issue_ids
242 self.changesets.each(&:scan_comment_for_issue_ids)
242 self.changesets.each(&:scan_comment_for_issue_ids)
243 end
243 end
244
244
245 # Returns an array of committers usernames and associated user_id
245 # Returns an array of committers usernames and associated user_id
246 def committers
246 def committers
247 @committers ||= Changeset.connection.select_rows(
247 @committers ||= Changeset.connection.select_rows(
248 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
248 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
249 end
249 end
250
250
251 # Maps committers username to a user ids
251 # Maps committers username to a user ids
252 def committer_ids=(h)
252 def committer_ids=(h)
253 if h.is_a?(Hash)
253 if h.is_a?(Hash)
254 committers.each do |committer, user_id|
254 committers.each do |committer, user_id|
255 new_user_id = h[committer]
255 new_user_id = h[committer]
256 if new_user_id && (new_user_id.to_i != user_id.to_i)
256 if new_user_id && (new_user_id.to_i != user_id.to_i)
257 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
257 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
258 Changeset.update_all(
258 Changeset.update_all(
259 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
259 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
260 ["repository_id = ? AND committer = ?", id, committer])
260 ["repository_id = ? AND committer = ?", id, committer])
261 end
261 end
262 end
262 end
263 @committers = nil
263 @committers = nil
264 @found_committer_users = nil
264 @found_committer_users = nil
265 true
265 true
266 else
266 else
267 false
267 false
268 end
268 end
269 end
269 end
270
270
271 # Returns the Redmine User corresponding to the given +committer+
271 # Returns the Redmine User corresponding to the given +committer+
272 # It will return nil if the committer is not yet mapped and if no User
272 # It will return nil if the committer is not yet mapped and if no User
273 # with the same username or email was found
273 # with the same username or email was found
274 def find_committer_user(committer)
274 def find_committer_user(committer)
275 unless committer.blank?
275 unless committer.blank?
276 @found_committer_users ||= {}
276 @found_committer_users ||= {}
277 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
277 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
278
278
279 user = nil
279 user = nil
280 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
280 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
281 if c && c.user
281 if c && c.user
282 user = c.user
282 user = c.user
283 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
283 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
284 username, email = $1.strip, $3
284 username, email = $1.strip, $3
285 u = User.find_by_login(username)
285 u = User.find_by_login(username)
286 u ||= User.find_by_mail(email) unless email.blank?
286 u ||= User.find_by_mail(email) unless email.blank?
287 user = u
287 user = u
288 end
288 end
289 @found_committer_users[committer] = user
289 @found_committer_users[committer] = user
290 user
290 user
291 end
291 end
292 end
292 end
293
293
294 def repo_log_encoding
294 def repo_log_encoding
295 encoding = log_encoding.to_s.strip
295 encoding = log_encoding.to_s.strip
296 encoding.blank? ? 'UTF-8' : encoding
296 encoding.blank? ? 'UTF-8' : encoding
297 end
297 end
298
298
299 # Fetches new changesets for all repositories of active projects
299 # Fetches new changesets for all repositories of active projects
300 # Can be called periodically by an external script
300 # Can be called periodically by an external script
301 # eg. ruby script/runner "Repository.fetch_changesets"
301 # eg. ruby script/runner "Repository.fetch_changesets"
302 def self.fetch_changesets
302 def self.fetch_changesets
303 Project.active.has_module(:repository).all.each do |project|
303 Project.active.has_module(:repository).all.each do |project|
304 project.repositories.each do |repository|
304 project.repositories.each do |repository|
305 begin
305 begin
306 repository.fetch_changesets
306 repository.fetch_changesets
307 rescue Redmine::Scm::Adapters::CommandFailed => e
307 rescue Redmine::Scm::Adapters::CommandFailed => e
308 logger.error "scm: error during fetching changesets: #{e.message}"
308 logger.error "scm: error during fetching changesets: #{e.message}"
309 end
309 end
310 end
310 end
311 end
311 end
312 end
312 end
313
313
314 # scan changeset comments to find related and fixed issues for all repositories
314 # scan changeset comments to find related and fixed issues for all repositories
315 def self.scan_changesets_for_issue_ids
315 def self.scan_changesets_for_issue_ids
316 find(:all).each(&:scan_changesets_for_issue_ids)
316 find(:all).each(&:scan_changesets_for_issue_ids)
317 end
317 end
318
318
319 def self.scm_name
319 def self.scm_name
320 'Abstract'
320 'Abstract'
321 end
321 end
322
322
323 def self.available_scm
323 def self.available_scm
324 subclasses.collect {|klass| [klass.scm_name, klass.name]}
324 subclasses.collect {|klass| [klass.scm_name, klass.name]}
325 end
325 end
326
326
327 def self.factory(klass_name, *args)
327 def self.factory(klass_name, *args)
328 klass = "Repository::#{klass_name}".constantize
328 klass = "Repository::#{klass_name}".constantize
329 klass.new(*args)
329 klass.new(*args)
330 rescue
330 rescue
331 nil
331 nil
332 end
332 end
333
333
334 def self.scm_adapter_class
334 def self.scm_adapter_class
335 nil
335 nil
336 end
336 end
337
337
338 def self.scm_command
338 def self.scm_command
339 ret = ""
339 ret = ""
340 begin
340 begin
341 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
341 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
342 rescue Exception => e
342 rescue Exception => e
343 logger.error "scm: error during get command: #{e.message}"
343 logger.error "scm: error during get command: #{e.message}"
344 end
344 end
345 ret
345 ret
346 end
346 end
347
347
348 def self.scm_version_string
348 def self.scm_version_string
349 ret = ""
349 ret = ""
350 begin
350 begin
351 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
351 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
352 rescue Exception => e
352 rescue Exception => e
353 logger.error "scm: error during get version string: #{e.message}"
353 logger.error "scm: error during get version string: #{e.message}"
354 end
354 end
355 ret
355 ret
356 end
356 end
357
357
358 def self.scm_available
358 def self.scm_available
359 ret = false
359 ret = false
360 begin
360 begin
361 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
361 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
362 rescue Exception => e
362 rescue Exception => e
363 logger.error "scm: error during get scm available: #{e.message}"
363 logger.error "scm: error during get scm available: #{e.message}"
364 end
364 end
365 ret
365 ret
366 end
366 end
367
367
368 def set_as_default?
368 def set_as_default?
369 new_record? && project && !Repository.first(:conditions => {:project_id => project.id})
369 new_record? && project && !Repository.first(:conditions => {:project_id => project.id})
370 end
370 end
371
371
372 protected
372 protected
373
373
374 def check_default
374 def check_default
375 if !is_default? && set_as_default?
375 if !is_default? && set_as_default?
376 self.is_default = true
376 self.is_default = true
377 end
377 end
378 if is_default? && is_default_changed?
378 if is_default? && is_default_changed?
379 Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
379 Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
380 end
380 end
381 end
381 end
382
382
383 private
383 private
384
384
385 # Deletes repository data
385 # Deletes repository data
386 def clear_changesets
386 def clear_changesets
387 cs = Changeset.table_name
387 cs = Changeset.table_name
388 ch = Change.table_name
388 ch = Change.table_name
389 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
389 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
390 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
390 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
391
391
392 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
392 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
393 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
393 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
394 connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
394 connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
395 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
395 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
396 end
396 end
397 end
397 end
@@ -1,321 +1,330
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 :members,
32 :members,
33 :member_roles,
33 :member_roles,
34 :roles,
34 :roles,
35 :enumerations
35 :enumerations
36
36
37 include Redmine::I18n
37 include Redmine::I18n
38
38
39 def setup
39 def setup
40 @repository = Project.find(1).repository
40 @repository = Project.find(1).repository
41 end
41 end
42
42
43 def test_blank_log_encoding_error_message
43 def test_blank_log_encoding_error_message
44 set_language_if_valid 'en'
44 set_language_if_valid 'en'
45 repo = Repository::Bazaar.new(
45 repo = Repository::Bazaar.new(
46 :project => Project.find(3),
46 :project => Project.find(3),
47 :url => "/test",
47 :url => "/test",
48 :log_encoding => ''
48 :log_encoding => ''
49 )
49 )
50 assert !repo.save
50 assert !repo.save
51 assert_include "Commit messages encoding can't be blank",
51 assert_include "Commit messages encoding can't be blank",
52 repo.errors.full_messages
52 repo.errors.full_messages
53 end
53 end
54
54
55 def test_blank_log_encoding_error_message_fr
55 def test_blank_log_encoding_error_message_fr
56 set_language_if_valid 'fr'
56 set_language_if_valid 'fr'
57 str = "Encodage des messages de commit doit \xc3\xaatre renseign\xc3\xa9(e)"
57 str = "Encodage des messages de commit doit \xc3\xaatre renseign\xc3\xa9(e)"
58 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
58 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
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_first_repository_should_be_set_as_default
79 def test_first_repository_should_be_set_as_default
80 repository1 = Repository::Subversion.new(
80 repository1 = Repository::Subversion.new(
81 :project => Project.find(3),
81 :project => Project.find(3),
82 :identifier => 'svn1',
82 :identifier => 'svn1',
83 :url => 'file:///svn1'
83 :url => 'file:///svn1'
84 )
84 )
85 assert repository1.save
85 assert repository1.save
86 assert repository1.is_default?
86 assert repository1.is_default?
87
87
88 repository2 = Repository::Subversion.new(
88 repository2 = Repository::Subversion.new(
89 :project => Project.find(3),
89 :project => Project.find(3),
90 :identifier => 'svn2',
90 :identifier => 'svn2',
91 :url => 'file:///svn2'
91 :url => 'file:///svn2'
92 )
92 )
93 assert repository2.save
93 assert repository2.save
94 assert !repository2.is_default?
94 assert !repository2.is_default?
95
95
96 assert_equal repository1, Project.find(3).repository
96 assert_equal repository1, Project.find(3).repository
97 assert_equal [repository1, repository2], Project.find(3).repositories.sort
97 assert_equal [repository1, repository2], Project.find(3).repositories.sort
98 end
98 end
99
99
100 def test_destroy
100 def test_destroy
101 changesets = Changeset.count(:all, :conditions => "repository_id = 10")
101 changesets = Changeset.count(:all, :conditions => "repository_id = 10")
102 changes = Change.count(:all, :conditions => "repository_id = 10",
102 changes = Change.count(:all, :conditions => "repository_id = 10",
103 :joins => :changeset)
103 :joins => :changeset)
104 assert_difference 'Changeset.count', -changesets do
104 assert_difference 'Changeset.count', -changesets do
105 assert_difference 'Change.count', -changes do
105 assert_difference 'Change.count', -changes do
106 Repository.find(10).destroy
106 Repository.find(10).destroy
107 end
107 end
108 end
108 end
109 end
109 end
110
110
111 def test_destroy_should_delete_parents_associations
111 def test_destroy_should_delete_parents_associations
112 changeset = Changeset.find(102)
112 changeset = Changeset.find(102)
113 changeset.parents = Changeset.find_all_by_id([100, 101])
113 changeset.parents = Changeset.find_all_by_id([100, 101])
114
114
115 assert_difference 'Changeset.connection.select_all("select * from changeset_parents").size', -2 do
115 assert_difference 'Changeset.connection.select_all("select * from changeset_parents").size', -2 do
116 Repository.find(10).destroy
116 Repository.find(10).destroy
117 end
117 end
118 end
118 end
119
119
120 def test_destroy_should_delete_issues_associations
120 def test_destroy_should_delete_issues_associations
121 changeset = Changeset.find(102)
121 changeset = Changeset.find(102)
122 changeset.issues = Issue.find_all_by_id([1, 2])
122 changeset.issues = Issue.find_all_by_id([1, 2])
123
123
124 assert_difference 'Changeset.connection.select_all("select * from changesets_issues").size', -2 do
124 assert_difference 'Changeset.connection.select_all("select * from changesets_issues").size', -2 do
125 Repository.find(10).destroy
125 Repository.find(10).destroy
126 end
126 end
127 end
127 end
128
128
129 def test_should_not_create_with_disabled_scm
129 def test_should_not_create_with_disabled_scm
130 # disable Subversion
130 # disable Subversion
131 with_settings :enabled_scm => ['Darcs', 'Git'] do
131 with_settings :enabled_scm => ['Darcs', 'Git'] do
132 repository = Repository::Subversion.new(
132 repository = Repository::Subversion.new(
133 :project => Project.find(3), :url => "svn://localhost")
133 :project => Project.find(3), :url => "svn://localhost")
134 assert !repository.save
134 assert !repository.save
135 assert_include I18n.translate('activerecord.errors.messages.invalid'),
135 assert_include I18n.translate('activerecord.errors.messages.invalid'),
136 repository.errors[:type]
136 repository.errors[:type]
137 end
137 end
138 end
138 end
139
139
140 def test_scan_changesets_for_issue_ids
140 def test_scan_changesets_for_issue_ids
141 Setting.default_language = 'en'
141 Setting.default_language = 'en'
142 Setting.notified_events = ['issue_added','issue_updated']
142 Setting.notified_events = ['issue_added','issue_updated']
143
143
144 # choosing a status to apply to fix issues
144 # choosing a status to apply to fix issues
145 Setting.commit_fix_status_id = IssueStatus.find(
145 Setting.commit_fix_status_id = IssueStatus.find(
146 :first,
146 :first,
147 :conditions => ["is_closed = ?", true]).id
147 :conditions => ["is_closed = ?", true]).id
148 Setting.commit_fix_done_ratio = "90"
148 Setting.commit_fix_done_ratio = "90"
149 Setting.commit_ref_keywords = 'refs , references, IssueID'
149 Setting.commit_ref_keywords = 'refs , references, IssueID'
150 Setting.commit_fix_keywords = 'fixes , closes'
150 Setting.commit_fix_keywords = 'fixes , closes'
151 Setting.default_language = 'en'
151 Setting.default_language = 'en'
152 ActionMailer::Base.deliveries.clear
152 ActionMailer::Base.deliveries.clear
153
153
154 # make sure issue 1 is not already closed
154 # make sure issue 1 is not already closed
155 fixed_issue = Issue.find(1)
155 fixed_issue = Issue.find(1)
156 assert !fixed_issue.status.is_closed?
156 assert !fixed_issue.status.is_closed?
157 old_status = fixed_issue.status
157 old_status = fixed_issue.status
158
158
159 Repository.scan_changesets_for_issue_ids
159 Repository.scan_changesets_for_issue_ids
160 assert_equal [101, 102], Issue.find(3).changeset_ids
160 assert_equal [101, 102], Issue.find(3).changeset_ids
161
161
162 # fixed issues
162 # fixed issues
163 fixed_issue.reload
163 fixed_issue.reload
164 assert fixed_issue.status.is_closed?
164 assert fixed_issue.status.is_closed?
165 assert_equal 90, fixed_issue.done_ratio
165 assert_equal 90, fixed_issue.done_ratio
166 assert_equal [101], fixed_issue.changeset_ids
166 assert_equal [101], fixed_issue.changeset_ids
167
167
168 # issue change
168 # issue change
169 journal = fixed_issue.journals.find(:first, :order => 'created_on desc')
169 journal = fixed_issue.journals.find(:first, :order => 'created_on desc')
170 assert_equal User.find_by_login('dlopper'), journal.user
170 assert_equal User.find_by_login('dlopper'), journal.user
171 assert_equal 'Applied in changeset r2.', journal.notes
171 assert_equal 'Applied in changeset r2.', journal.notes
172
172
173 # 2 email notifications
173 # 2 email notifications
174 assert_equal 2, ActionMailer::Base.deliveries.size
174 assert_equal 2, ActionMailer::Base.deliveries.size
175 mail = ActionMailer::Base.deliveries.first
175 mail = ActionMailer::Base.deliveries.first
176 assert_not_nil mail
176 assert_not_nil mail
177 assert mail.subject.starts_with?(
177 assert mail.subject.starts_with?(
178 "[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
178 "[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
179 assert_mail_body_match(
179 assert_mail_body_match(
180 "Status changed from #{old_status} to #{fixed_issue.status}", mail)
180 "Status changed from #{old_status} to #{fixed_issue.status}", mail)
181
181
182 # ignoring commits referencing an issue of another project
182 # ignoring commits referencing an issue of another project
183 assert_equal [], Issue.find(4).changesets
183 assert_equal [], Issue.find(4).changesets
184 end
184 end
185
185
186 def test_for_changeset_comments_strip
186 def test_for_changeset_comments_strip
187 repository = Repository::Mercurial.create(
187 repository = Repository::Mercurial.create(
188 :project => Project.find( 4 ),
188 :project => Project.find( 4 ),
189 :url => '/foo/bar/baz' )
189 :url => '/foo/bar/baz' )
190 comment = <<-COMMENT
190 comment = <<-COMMENT
191 This is a loooooooooooooooooooooooooooong comment
191 This is a loooooooooooooooooooooooooooong comment
192
192
193
193
194 COMMENT
194 COMMENT
195 changeset = Changeset.new(
195 changeset = Changeset.new(
196 :comments => comment, :commit_date => Time.now,
196 :comments => comment, :commit_date => Time.now,
197 :revision => 0, :scmid => 'f39b7922fb3c',
197 :revision => 0, :scmid => 'f39b7922fb3c',
198 :committer => 'foo <foo@example.com>',
198 :committer => 'foo <foo@example.com>',
199 :committed_on => Time.now, :repository => repository )
199 :committed_on => Time.now, :repository => repository )
200 assert( changeset.save )
200 assert( changeset.save )
201 assert_not_equal( comment, changeset.comments )
201 assert_not_equal( comment, changeset.comments )
202 assert_equal( 'This is a loooooooooooooooooooooooooooong comment',
202 assert_equal( 'This is a loooooooooooooooooooooooooooong comment',
203 changeset.comments )
203 changeset.comments )
204 end
204 end
205
205
206 def test_for_urls_strip_cvs
206 def test_for_urls_strip_cvs
207 repository = Repository::Cvs.create(
207 repository = Repository::Cvs.create(
208 :project => Project.find(4),
208 :project => Project.find(4),
209 :url => ' :pserver:login:password@host:/path/to/the/repository',
209 :url => ' :pserver:login:password@host:/path/to/the/repository',
210 :root_url => 'foo ',
210 :root_url => 'foo ',
211 :log_encoding => 'UTF-8')
211 :log_encoding => 'UTF-8')
212 assert repository.save
212 assert repository.save
213 repository.reload
213 repository.reload
214 assert_equal ':pserver:login:password@host:/path/to/the/repository',
214 assert_equal ':pserver:login:password@host:/path/to/the/repository',
215 repository.url
215 repository.url
216 assert_equal 'foo', repository.root_url
216 assert_equal 'foo', repository.root_url
217 end
217 end
218
218
219 def test_for_urls_strip_subversion
219 def test_for_urls_strip_subversion
220 repository = Repository::Subversion.create(
220 repository = Repository::Subversion.create(
221 :project => Project.find(4),
221 :project => Project.find(4),
222 :url => ' file:///dummy ')
222 :url => ' file:///dummy ')
223 assert repository.save
223 assert repository.save
224 repository.reload
224 repository.reload
225 assert_equal 'file:///dummy', repository.url
225 assert_equal 'file:///dummy', repository.url
226 end
226 end
227
227
228 def test_for_urls_strip_git
228 def test_for_urls_strip_git
229 repository = Repository::Git.create(
229 repository = Repository::Git.create(
230 :project => Project.find(4),
230 :project => Project.find(4),
231 :url => ' c:\dummy ')
231 :url => ' c:\dummy ')
232 assert repository.save
232 assert repository.save
233 repository.reload
233 repository.reload
234 assert_equal 'c:\dummy', repository.url
234 assert_equal 'c:\dummy', repository.url
235 end
235 end
236
236
237 def test_manual_user_mapping
237 def test_manual_user_mapping
238 assert_no_difference "Changeset.count(:conditions => 'user_id <> 2')" do
238 assert_no_difference "Changeset.count(:conditions => 'user_id <> 2')" do
239 c = Changeset.create!(
239 c = Changeset.create!(
240 :repository => @repository,
240 :repository => @repository,
241 :committer => 'foo',
241 :committer => 'foo',
242 :committed_on => Time.now,
242 :committed_on => Time.now,
243 :revision => 100,
243 :revision => 100,
244 :comments => 'Committed by foo.'
244 :comments => 'Committed by foo.'
245 )
245 )
246 assert_nil c.user
246 assert_nil c.user
247 @repository.committer_ids = {'foo' => '2'}
247 @repository.committer_ids = {'foo' => '2'}
248 assert_equal User.find(2), c.reload.user
248 assert_equal User.find(2), c.reload.user
249 # committer is now mapped
249 # committer is now mapped
250 c = Changeset.create!(
250 c = Changeset.create!(
251 :repository => @repository,
251 :repository => @repository,
252 :committer => 'foo',
252 :committer => 'foo',
253 :committed_on => Time.now,
253 :committed_on => Time.now,
254 :revision => 101,
254 :revision => 101,
255 :comments => 'Another commit by foo.'
255 :comments => 'Another commit by foo.'
256 )
256 )
257 assert_equal User.find(2), c.user
257 assert_equal User.find(2), c.user
258 end
258 end
259 end
259 end
260
260
261 def test_auto_user_mapping_by_username
261 def test_auto_user_mapping_by_username
262 c = Changeset.create!(
262 c = Changeset.create!(
263 :repository => @repository,
263 :repository => @repository,
264 :committer => 'jsmith',
264 :committer => 'jsmith',
265 :committed_on => Time.now,
265 :committed_on => Time.now,
266 :revision => 100,
266 :revision => 100,
267 :comments => 'Committed by john.'
267 :comments => 'Committed by john.'
268 )
268 )
269 assert_equal User.find(2), c.user
269 assert_equal User.find(2), c.user
270 end
270 end
271
271
272 def test_auto_user_mapping_by_email
272 def test_auto_user_mapping_by_email
273 c = Changeset.create!(
273 c = Changeset.create!(
274 :repository => @repository,
274 :repository => @repository,
275 :committer => 'john <jsmith@somenet.foo>',
275 :committer => 'john <jsmith@somenet.foo>',
276 :committed_on => Time.now,
276 :committed_on => Time.now,
277 :revision => 100,
277 :revision => 100,
278 :comments => 'Committed by john.'
278 :comments => 'Committed by john.'
279 )
279 )
280 assert_equal User.find(2), c.user
280 assert_equal User.find(2), c.user
281 end
281 end
282
282
283 def test_filesystem_avaialbe
283 def test_filesystem_avaialbe
284 klass = Repository::Filesystem
284 klass = Repository::Filesystem
285 assert klass.scm_adapter_class
285 assert klass.scm_adapter_class
286 assert_equal true, klass.scm_available
286 assert_equal true, klass.scm_available
287 end
287 end
288
288
289 def test_merge_extra_info
289 def test_merge_extra_info
290 repo = Repository::Subversion.new(:project => Project.find(3))
290 repo = Repository::Subversion.new(:project => Project.find(3))
291 assert !repo.save
291 assert !repo.save
292 repo.url = "svn://localhost"
292 repo.url = "svn://localhost"
293 assert repo.save
293 assert repo.save
294 repo.reload
294 repo.reload
295 project = Project.find(3)
295 project = Project.find(3)
296 assert_equal repo, project.repository
296 assert_equal repo, project.repository
297 assert_nil repo.extra_info
297 assert_nil repo.extra_info
298 h1 = {"test_1" => {"test_11" => "test_value_11"}}
298 h1 = {"test_1" => {"test_11" => "test_value_11"}}
299 repo.merge_extra_info(h1)
299 repo.merge_extra_info(h1)
300 assert_equal h1, repo.extra_info
300 assert_equal h1, repo.extra_info
301 h2 = {"test_2" => {
301 h2 = {"test_2" => {
302 "test_21" => "test_value_21",
302 "test_21" => "test_value_21",
303 "test_22" => "test_value_22",
303 "test_22" => "test_value_22",
304 }}
304 }}
305 repo.merge_extra_info(h2)
305 repo.merge_extra_info(h2)
306 assert_equal (h = {"test_11" => "test_value_11"}),
306 assert_equal (h = {"test_11" => "test_value_11"}),
307 repo.extra_info["test_1"]
307 repo.extra_info["test_1"]
308 assert_equal "test_value_21",
308 assert_equal "test_value_21",
309 repo.extra_info["test_2"]["test_21"]
309 repo.extra_info["test_2"]["test_21"]
310 h3 = {"test_2" => {
310 h3 = {"test_2" => {
311 "test_23" => "test_value_23",
311 "test_23" => "test_value_23",
312 "test_24" => "test_value_24",
312 "test_24" => "test_value_24",
313 }}
313 }}
314 repo.merge_extra_info(h3)
314 repo.merge_extra_info(h3)
315 assert_equal (h = {"test_11" => "test_value_11"}),
315 assert_equal (h = {"test_11" => "test_value_11"}),
316 repo.extra_info["test_1"]
316 repo.extra_info["test_1"]
317 assert_nil repo.extra_info["test_2"]["test_21"]
317 assert_nil repo.extra_info["test_2"]["test_21"]
318 assert_equal "test_value_23",
318 assert_equal "test_value_23",
319 repo.extra_info["test_2"]["test_23"]
319 repo.extra_info["test_2"]["test_23"]
320 end
320 end
321
322 def test_sort_should_not_raise_an_error_with_nil_identifiers
323 r1 = Repository.new
324 r2 = Repository.new
325
326 assert_nothing_raised do
327 [r1, r2].sort
328 end
329 end
321 end
330 end
General Comments 0
You need to be logged in to leave comments. Login now