##// END OF EJS Templates
Make repository identifier accept underscores (#11192)....
Jean-Philippe Lang -
r9692:3b854bee5921
parent child
Show More
@@ -1,409 +1,409
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 :filechanges, :class_name => 'Change', :through => :changesets
25 has_many :filechanges, :class_name => 'Change', :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, underscores 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.to_s <=> repository.identifier.to_s
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 entries = scm.entries(path, identifier)
170 entries = scm.entries(path, identifier)
171 load_entries_changesets(entries)
171 load_entries_changesets(entries)
172 entries
172 entries
173 end
173 end
174
174
175 def branches
175 def branches
176 scm.branches
176 scm.branches
177 end
177 end
178
178
179 def tags
179 def tags
180 scm.tags
180 scm.tags
181 end
181 end
182
182
183 def default_branch
183 def default_branch
184 nil
184 nil
185 end
185 end
186
186
187 def properties(path, identifier=nil)
187 def properties(path, identifier=nil)
188 scm.properties(path, identifier)
188 scm.properties(path, identifier)
189 end
189 end
190
190
191 def cat(path, identifier=nil)
191 def cat(path, identifier=nil)
192 scm.cat(path, identifier)
192 scm.cat(path, identifier)
193 end
193 end
194
194
195 def diff(path, rev, rev_to)
195 def diff(path, rev, rev_to)
196 scm.diff(path, rev, rev_to)
196 scm.diff(path, rev, rev_to)
197 end
197 end
198
198
199 def diff_format_revisions(cs, cs_to, sep=':')
199 def diff_format_revisions(cs, cs_to, sep=':')
200 text = ""
200 text = ""
201 text << cs_to.format_identifier + sep if cs_to
201 text << cs_to.format_identifier + sep if cs_to
202 text << cs.format_identifier if cs
202 text << cs.format_identifier if cs
203 text
203 text
204 end
204 end
205
205
206 # Returns a path relative to the url of the repository
206 # Returns a path relative to the url of the repository
207 def relative_path(path)
207 def relative_path(path)
208 path
208 path
209 end
209 end
210
210
211 # Finds and returns a revision with a number or the beginning of a hash
211 # Finds and returns a revision with a number or the beginning of a hash
212 def find_changeset_by_name(name)
212 def find_changeset_by_name(name)
213 return nil if name.blank?
213 return nil if name.blank?
214 s = name.to_s
214 s = name.to_s
215 changesets.find(:first, :conditions => (s.match(/^\d*$/) ?
215 changesets.find(:first, :conditions => (s.match(/^\d*$/) ?
216 ["revision = ?", s] : ["revision LIKE ?", s + '%']))
216 ["revision = ?", s] : ["revision LIKE ?", s + '%']))
217 end
217 end
218
218
219 def latest_changeset
219 def latest_changeset
220 @latest_changeset ||= changesets.find(:first)
220 @latest_changeset ||= changesets.find(:first)
221 end
221 end
222
222
223 # Returns the latest changesets for +path+
223 # Returns the latest changesets for +path+
224 # Default behaviour is to search in cached changesets
224 # Default behaviour is to search in cached changesets
225 def latest_changesets(path, rev, limit=10)
225 def latest_changesets(path, rev, limit=10)
226 if path.blank?
226 if path.blank?
227 changesets.find(
227 changesets.find(
228 :all,
228 :all,
229 :include => :user,
229 :include => :user,
230 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
230 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
231 :limit => limit)
231 :limit => limit)
232 else
232 else
233 filechanges.find(
233 filechanges.find(
234 :all,
234 :all,
235 :include => {:changeset => :user},
235 :include => {:changeset => :user},
236 :conditions => ["path = ?", path.with_leading_slash],
236 :conditions => ["path = ?", path.with_leading_slash],
237 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
237 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
238 :limit => limit
238 :limit => limit
239 ).collect(&:changeset)
239 ).collect(&:changeset)
240 end
240 end
241 end
241 end
242
242
243 def scan_changesets_for_issue_ids
243 def scan_changesets_for_issue_ids
244 self.changesets.each(&:scan_comment_for_issue_ids)
244 self.changesets.each(&:scan_comment_for_issue_ids)
245 end
245 end
246
246
247 # Returns an array of committers usernames and associated user_id
247 # Returns an array of committers usernames and associated user_id
248 def committers
248 def committers
249 @committers ||= Changeset.connection.select_rows(
249 @committers ||= Changeset.connection.select_rows(
250 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
250 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
251 end
251 end
252
252
253 # Maps committers username to a user ids
253 # Maps committers username to a user ids
254 def committer_ids=(h)
254 def committer_ids=(h)
255 if h.is_a?(Hash)
255 if h.is_a?(Hash)
256 committers.each do |committer, user_id|
256 committers.each do |committer, user_id|
257 new_user_id = h[committer]
257 new_user_id = h[committer]
258 if new_user_id && (new_user_id.to_i != user_id.to_i)
258 if new_user_id && (new_user_id.to_i != user_id.to_i)
259 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
259 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
260 Changeset.update_all(
260 Changeset.update_all(
261 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
261 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
262 ["repository_id = ? AND committer = ?", id, committer])
262 ["repository_id = ? AND committer = ?", id, committer])
263 end
263 end
264 end
264 end
265 @committers = nil
265 @committers = nil
266 @found_committer_users = nil
266 @found_committer_users = nil
267 true
267 true
268 else
268 else
269 false
269 false
270 end
270 end
271 end
271 end
272
272
273 # Returns the Redmine User corresponding to the given +committer+
273 # Returns the Redmine User corresponding to the given +committer+
274 # It will return nil if the committer is not yet mapped and if no User
274 # It will return nil if the committer is not yet mapped and if no User
275 # with the same username or email was found
275 # with the same username or email was found
276 def find_committer_user(committer)
276 def find_committer_user(committer)
277 unless committer.blank?
277 unless committer.blank?
278 @found_committer_users ||= {}
278 @found_committer_users ||= {}
279 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
279 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
280
280
281 user = nil
281 user = nil
282 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
282 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
283 if c && c.user
283 if c && c.user
284 user = c.user
284 user = c.user
285 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
285 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
286 username, email = $1.strip, $3
286 username, email = $1.strip, $3
287 u = User.find_by_login(username)
287 u = User.find_by_login(username)
288 u ||= User.find_by_mail(email) unless email.blank?
288 u ||= User.find_by_mail(email) unless email.blank?
289 user = u
289 user = u
290 end
290 end
291 @found_committer_users[committer] = user
291 @found_committer_users[committer] = user
292 user
292 user
293 end
293 end
294 end
294 end
295
295
296 def repo_log_encoding
296 def repo_log_encoding
297 encoding = log_encoding.to_s.strip
297 encoding = log_encoding.to_s.strip
298 encoding.blank? ? 'UTF-8' : encoding
298 encoding.blank? ? 'UTF-8' : encoding
299 end
299 end
300
300
301 # Fetches new changesets for all repositories of active projects
301 # Fetches new changesets for all repositories of active projects
302 # Can be called periodically by an external script
302 # Can be called periodically by an external script
303 # eg. ruby script/runner "Repository.fetch_changesets"
303 # eg. ruby script/runner "Repository.fetch_changesets"
304 def self.fetch_changesets
304 def self.fetch_changesets
305 Project.active.has_module(:repository).all.each do |project|
305 Project.active.has_module(:repository).all.each do |project|
306 project.repositories.each do |repository|
306 project.repositories.each do |repository|
307 begin
307 begin
308 repository.fetch_changesets
308 repository.fetch_changesets
309 rescue Redmine::Scm::Adapters::CommandFailed => e
309 rescue Redmine::Scm::Adapters::CommandFailed => e
310 logger.error "scm: error during fetching changesets: #{e.message}"
310 logger.error "scm: error during fetching changesets: #{e.message}"
311 end
311 end
312 end
312 end
313 end
313 end
314 end
314 end
315
315
316 # scan changeset comments to find related and fixed issues for all repositories
316 # scan changeset comments to find related and fixed issues for all repositories
317 def self.scan_changesets_for_issue_ids
317 def self.scan_changesets_for_issue_ids
318 find(:all).each(&:scan_changesets_for_issue_ids)
318 find(:all).each(&:scan_changesets_for_issue_ids)
319 end
319 end
320
320
321 def self.scm_name
321 def self.scm_name
322 'Abstract'
322 'Abstract'
323 end
323 end
324
324
325 def self.available_scm
325 def self.available_scm
326 subclasses.collect {|klass| [klass.scm_name, klass.name]}
326 subclasses.collect {|klass| [klass.scm_name, klass.name]}
327 end
327 end
328
328
329 def self.factory(klass_name, *args)
329 def self.factory(klass_name, *args)
330 klass = "Repository::#{klass_name}".constantize
330 klass = "Repository::#{klass_name}".constantize
331 klass.new(*args)
331 klass.new(*args)
332 rescue
332 rescue
333 nil
333 nil
334 end
334 end
335
335
336 def self.scm_adapter_class
336 def self.scm_adapter_class
337 nil
337 nil
338 end
338 end
339
339
340 def self.scm_command
340 def self.scm_command
341 ret = ""
341 ret = ""
342 begin
342 begin
343 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
343 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
344 rescue Exception => e
344 rescue Exception => e
345 logger.error "scm: error during get command: #{e.message}"
345 logger.error "scm: error during get command: #{e.message}"
346 end
346 end
347 ret
347 ret
348 end
348 end
349
349
350 def self.scm_version_string
350 def self.scm_version_string
351 ret = ""
351 ret = ""
352 begin
352 begin
353 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
353 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
354 rescue Exception => e
354 rescue Exception => e
355 logger.error "scm: error during get version string: #{e.message}"
355 logger.error "scm: error during get version string: #{e.message}"
356 end
356 end
357 ret
357 ret
358 end
358 end
359
359
360 def self.scm_available
360 def self.scm_available
361 ret = false
361 ret = false
362 begin
362 begin
363 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
363 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
364 rescue Exception => e
364 rescue Exception => e
365 logger.error "scm: error during get scm available: #{e.message}"
365 logger.error "scm: error during get scm available: #{e.message}"
366 end
366 end
367 ret
367 ret
368 end
368 end
369
369
370 def set_as_default?
370 def set_as_default?
371 new_record? && project && !Repository.first(:conditions => {:project_id => project.id})
371 new_record? && project && !Repository.first(:conditions => {:project_id => project.id})
372 end
372 end
373
373
374 protected
374 protected
375
375
376 def check_default
376 def check_default
377 if !is_default? && set_as_default?
377 if !is_default? && set_as_default?
378 self.is_default = true
378 self.is_default = true
379 end
379 end
380 if is_default? && is_default_changed?
380 if is_default? && is_default_changed?
381 Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
381 Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
382 end
382 end
383 end
383 end
384
384
385 def load_entries_changesets(entries)
385 def load_entries_changesets(entries)
386 if entries
386 if entries
387 entries.each do |entry|
387 entries.each do |entry|
388 if entry.lastrev && entry.lastrev.identifier
388 if entry.lastrev && entry.lastrev.identifier
389 entry.changeset = find_changeset_by_name(entry.lastrev.identifier)
389 entry.changeset = find_changeset_by_name(entry.lastrev.identifier)
390 end
390 end
391 end
391 end
392 end
392 end
393 end
393 end
394
394
395 private
395 private
396
396
397 # Deletes repository data
397 # Deletes repository data
398 def clear_changesets
398 def clear_changesets
399 cs = Changeset.table_name
399 cs = Changeset.table_name
400 ch = Change.table_name
400 ch = Change.table_name
401 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
401 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
402 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
402 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
403
403
404 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
404 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
405 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
405 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
406 connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
406 connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
407 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
407 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
408 end
408 end
409 end
409 end
@@ -1,331 +1,340
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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_identifier_should_accept_letters_digits_dashes_and_underscores
101 r = Repository::Subversion.new(
102 :project_id => 3,
103 :identifier => 'svn-123_45',
104 :url => 'file:///svn'
105 )
106 assert r.save
107 end
108
100 def test_destroy
109 def test_destroy
101 repository = Repository.find(10)
110 repository = Repository.find(10)
102 changesets = repository.changesets.count
111 changesets = repository.changesets.count
103 changes = repository.filechanges.count
112 changes = repository.filechanges.count
104
113
105 assert_difference 'Changeset.count', -changesets do
114 assert_difference 'Changeset.count', -changesets do
106 assert_difference 'Change.count', -changes do
115 assert_difference 'Change.count', -changes do
107 Repository.find(10).destroy
116 Repository.find(10).destroy
108 end
117 end
109 end
118 end
110 end
119 end
111
120
112 def test_destroy_should_delete_parents_associations
121 def test_destroy_should_delete_parents_associations
113 changeset = Changeset.find(102)
122 changeset = Changeset.find(102)
114 changeset.parents = Changeset.find_all_by_id([100, 101])
123 changeset.parents = Changeset.find_all_by_id([100, 101])
115
124
116 assert_difference 'Changeset.connection.select_all("select * from changeset_parents").size', -2 do
125 assert_difference 'Changeset.connection.select_all("select * from changeset_parents").size', -2 do
117 Repository.find(10).destroy
126 Repository.find(10).destroy
118 end
127 end
119 end
128 end
120
129
121 def test_destroy_should_delete_issues_associations
130 def test_destroy_should_delete_issues_associations
122 changeset = Changeset.find(102)
131 changeset = Changeset.find(102)
123 changeset.issues = Issue.find_all_by_id([1, 2])
132 changeset.issues = Issue.find_all_by_id([1, 2])
124
133
125 assert_difference 'Changeset.connection.select_all("select * from changesets_issues").size', -2 do
134 assert_difference 'Changeset.connection.select_all("select * from changesets_issues").size', -2 do
126 Repository.find(10).destroy
135 Repository.find(10).destroy
127 end
136 end
128 end
137 end
129
138
130 def test_should_not_create_with_disabled_scm
139 def test_should_not_create_with_disabled_scm
131 # disable Subversion
140 # disable Subversion
132 with_settings :enabled_scm => ['Darcs', 'Git'] do
141 with_settings :enabled_scm => ['Darcs', 'Git'] do
133 repository = Repository::Subversion.new(
142 repository = Repository::Subversion.new(
134 :project => Project.find(3), :url => "svn://localhost")
143 :project => Project.find(3), :url => "svn://localhost")
135 assert !repository.save
144 assert !repository.save
136 assert_include I18n.translate('activerecord.errors.messages.invalid'),
145 assert_include I18n.translate('activerecord.errors.messages.invalid'),
137 repository.errors[:type]
146 repository.errors[:type]
138 end
147 end
139 end
148 end
140
149
141 def test_scan_changesets_for_issue_ids
150 def test_scan_changesets_for_issue_ids
142 Setting.default_language = 'en'
151 Setting.default_language = 'en'
143 Setting.notified_events = ['issue_added','issue_updated']
152 Setting.notified_events = ['issue_added','issue_updated']
144
153
145 # choosing a status to apply to fix issues
154 # choosing a status to apply to fix issues
146 Setting.commit_fix_status_id = IssueStatus.find(
155 Setting.commit_fix_status_id = IssueStatus.find(
147 :first,
156 :first,
148 :conditions => ["is_closed = ?", true]).id
157 :conditions => ["is_closed = ?", true]).id
149 Setting.commit_fix_done_ratio = "90"
158 Setting.commit_fix_done_ratio = "90"
150 Setting.commit_ref_keywords = 'refs , references, IssueID'
159 Setting.commit_ref_keywords = 'refs , references, IssueID'
151 Setting.commit_fix_keywords = 'fixes , closes'
160 Setting.commit_fix_keywords = 'fixes , closes'
152 Setting.default_language = 'en'
161 Setting.default_language = 'en'
153 ActionMailer::Base.deliveries.clear
162 ActionMailer::Base.deliveries.clear
154
163
155 # make sure issue 1 is not already closed
164 # make sure issue 1 is not already closed
156 fixed_issue = Issue.find(1)
165 fixed_issue = Issue.find(1)
157 assert !fixed_issue.status.is_closed?
166 assert !fixed_issue.status.is_closed?
158 old_status = fixed_issue.status
167 old_status = fixed_issue.status
159
168
160 Repository.scan_changesets_for_issue_ids
169 Repository.scan_changesets_for_issue_ids
161 assert_equal [101, 102], Issue.find(3).changeset_ids
170 assert_equal [101, 102], Issue.find(3).changeset_ids
162
171
163 # fixed issues
172 # fixed issues
164 fixed_issue.reload
173 fixed_issue.reload
165 assert fixed_issue.status.is_closed?
174 assert fixed_issue.status.is_closed?
166 assert_equal 90, fixed_issue.done_ratio
175 assert_equal 90, fixed_issue.done_ratio
167 assert_equal [101], fixed_issue.changeset_ids
176 assert_equal [101], fixed_issue.changeset_ids
168
177
169 # issue change
178 # issue change
170 journal = fixed_issue.journals.find(:first, :order => 'created_on desc')
179 journal = fixed_issue.journals.find(:first, :order => 'created_on desc')
171 assert_equal User.find_by_login('dlopper'), journal.user
180 assert_equal User.find_by_login('dlopper'), journal.user
172 assert_equal 'Applied in changeset r2.', journal.notes
181 assert_equal 'Applied in changeset r2.', journal.notes
173
182
174 # 2 email notifications
183 # 2 email notifications
175 assert_equal 2, ActionMailer::Base.deliveries.size
184 assert_equal 2, ActionMailer::Base.deliveries.size
176 mail = ActionMailer::Base.deliveries.first
185 mail = ActionMailer::Base.deliveries.first
177 assert_not_nil mail
186 assert_not_nil mail
178 assert mail.subject.starts_with?(
187 assert mail.subject.starts_with?(
179 "[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
188 "[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
180 assert_mail_body_match(
189 assert_mail_body_match(
181 "Status changed from #{old_status} to #{fixed_issue.status}", mail)
190 "Status changed from #{old_status} to #{fixed_issue.status}", mail)
182
191
183 # ignoring commits referencing an issue of another project
192 # ignoring commits referencing an issue of another project
184 assert_equal [], Issue.find(4).changesets
193 assert_equal [], Issue.find(4).changesets
185 end
194 end
186
195
187 def test_for_changeset_comments_strip
196 def test_for_changeset_comments_strip
188 repository = Repository::Mercurial.create(
197 repository = Repository::Mercurial.create(
189 :project => Project.find( 4 ),
198 :project => Project.find( 4 ),
190 :url => '/foo/bar/baz' )
199 :url => '/foo/bar/baz' )
191 comment = <<-COMMENT
200 comment = <<-COMMENT
192 This is a loooooooooooooooooooooooooooong comment
201 This is a loooooooooooooooooooooooooooong comment
193
202
194
203
195 COMMENT
204 COMMENT
196 changeset = Changeset.new(
205 changeset = Changeset.new(
197 :comments => comment, :commit_date => Time.now,
206 :comments => comment, :commit_date => Time.now,
198 :revision => 0, :scmid => 'f39b7922fb3c',
207 :revision => 0, :scmid => 'f39b7922fb3c',
199 :committer => 'foo <foo@example.com>',
208 :committer => 'foo <foo@example.com>',
200 :committed_on => Time.now, :repository => repository )
209 :committed_on => Time.now, :repository => repository )
201 assert( changeset.save )
210 assert( changeset.save )
202 assert_not_equal( comment, changeset.comments )
211 assert_not_equal( comment, changeset.comments )
203 assert_equal( 'This is a loooooooooooooooooooooooooooong comment',
212 assert_equal( 'This is a loooooooooooooooooooooooooooong comment',
204 changeset.comments )
213 changeset.comments )
205 end
214 end
206
215
207 def test_for_urls_strip_cvs
216 def test_for_urls_strip_cvs
208 repository = Repository::Cvs.create(
217 repository = Repository::Cvs.create(
209 :project => Project.find(4),
218 :project => Project.find(4),
210 :url => ' :pserver:login:password@host:/path/to/the/repository',
219 :url => ' :pserver:login:password@host:/path/to/the/repository',
211 :root_url => 'foo ',
220 :root_url => 'foo ',
212 :log_encoding => 'UTF-8')
221 :log_encoding => 'UTF-8')
213 assert repository.save
222 assert repository.save
214 repository.reload
223 repository.reload
215 assert_equal ':pserver:login:password@host:/path/to/the/repository',
224 assert_equal ':pserver:login:password@host:/path/to/the/repository',
216 repository.url
225 repository.url
217 assert_equal 'foo', repository.root_url
226 assert_equal 'foo', repository.root_url
218 end
227 end
219
228
220 def test_for_urls_strip_subversion
229 def test_for_urls_strip_subversion
221 repository = Repository::Subversion.create(
230 repository = Repository::Subversion.create(
222 :project => Project.find(4),
231 :project => Project.find(4),
223 :url => ' file:///dummy ')
232 :url => ' file:///dummy ')
224 assert repository.save
233 assert repository.save
225 repository.reload
234 repository.reload
226 assert_equal 'file:///dummy', repository.url
235 assert_equal 'file:///dummy', repository.url
227 end
236 end
228
237
229 def test_for_urls_strip_git
238 def test_for_urls_strip_git
230 repository = Repository::Git.create(
239 repository = Repository::Git.create(
231 :project => Project.find(4),
240 :project => Project.find(4),
232 :url => ' c:\dummy ')
241 :url => ' c:\dummy ')
233 assert repository.save
242 assert repository.save
234 repository.reload
243 repository.reload
235 assert_equal 'c:\dummy', repository.url
244 assert_equal 'c:\dummy', repository.url
236 end
245 end
237
246
238 def test_manual_user_mapping
247 def test_manual_user_mapping
239 assert_no_difference "Changeset.count(:conditions => 'user_id <> 2')" do
248 assert_no_difference "Changeset.count(:conditions => 'user_id <> 2')" do
240 c = Changeset.create!(
249 c = Changeset.create!(
241 :repository => @repository,
250 :repository => @repository,
242 :committer => 'foo',
251 :committer => 'foo',
243 :committed_on => Time.now,
252 :committed_on => Time.now,
244 :revision => 100,
253 :revision => 100,
245 :comments => 'Committed by foo.'
254 :comments => 'Committed by foo.'
246 )
255 )
247 assert_nil c.user
256 assert_nil c.user
248 @repository.committer_ids = {'foo' => '2'}
257 @repository.committer_ids = {'foo' => '2'}
249 assert_equal User.find(2), c.reload.user
258 assert_equal User.find(2), c.reload.user
250 # committer is now mapped
259 # committer is now mapped
251 c = Changeset.create!(
260 c = Changeset.create!(
252 :repository => @repository,
261 :repository => @repository,
253 :committer => 'foo',
262 :committer => 'foo',
254 :committed_on => Time.now,
263 :committed_on => Time.now,
255 :revision => 101,
264 :revision => 101,
256 :comments => 'Another commit by foo.'
265 :comments => 'Another commit by foo.'
257 )
266 )
258 assert_equal User.find(2), c.user
267 assert_equal User.find(2), c.user
259 end
268 end
260 end
269 end
261
270
262 def test_auto_user_mapping_by_username
271 def test_auto_user_mapping_by_username
263 c = Changeset.create!(
272 c = Changeset.create!(
264 :repository => @repository,
273 :repository => @repository,
265 :committer => 'jsmith',
274 :committer => 'jsmith',
266 :committed_on => Time.now,
275 :committed_on => Time.now,
267 :revision => 100,
276 :revision => 100,
268 :comments => 'Committed by john.'
277 :comments => 'Committed by john.'
269 )
278 )
270 assert_equal User.find(2), c.user
279 assert_equal User.find(2), c.user
271 end
280 end
272
281
273 def test_auto_user_mapping_by_email
282 def test_auto_user_mapping_by_email
274 c = Changeset.create!(
283 c = Changeset.create!(
275 :repository => @repository,
284 :repository => @repository,
276 :committer => 'john <jsmith@somenet.foo>',
285 :committer => 'john <jsmith@somenet.foo>',
277 :committed_on => Time.now,
286 :committed_on => Time.now,
278 :revision => 100,
287 :revision => 100,
279 :comments => 'Committed by john.'
288 :comments => 'Committed by john.'
280 )
289 )
281 assert_equal User.find(2), c.user
290 assert_equal User.find(2), c.user
282 end
291 end
283
292
284 def test_filesystem_avaialbe
293 def test_filesystem_avaialbe
285 klass = Repository::Filesystem
294 klass = Repository::Filesystem
286 assert klass.scm_adapter_class
295 assert klass.scm_adapter_class
287 assert_equal true, klass.scm_available
296 assert_equal true, klass.scm_available
288 end
297 end
289
298
290 def test_merge_extra_info
299 def test_merge_extra_info
291 repo = Repository::Subversion.new(:project => Project.find(3))
300 repo = Repository::Subversion.new(:project => Project.find(3))
292 assert !repo.save
301 assert !repo.save
293 repo.url = "svn://localhost"
302 repo.url = "svn://localhost"
294 assert repo.save
303 assert repo.save
295 repo.reload
304 repo.reload
296 project = Project.find(3)
305 project = Project.find(3)
297 assert_equal repo, project.repository
306 assert_equal repo, project.repository
298 assert_nil repo.extra_info
307 assert_nil repo.extra_info
299 h1 = {"test_1" => {"test_11" => "test_value_11"}}
308 h1 = {"test_1" => {"test_11" => "test_value_11"}}
300 repo.merge_extra_info(h1)
309 repo.merge_extra_info(h1)
301 assert_equal h1, repo.extra_info
310 assert_equal h1, repo.extra_info
302 h2 = {"test_2" => {
311 h2 = {"test_2" => {
303 "test_21" => "test_value_21",
312 "test_21" => "test_value_21",
304 "test_22" => "test_value_22",
313 "test_22" => "test_value_22",
305 }}
314 }}
306 repo.merge_extra_info(h2)
315 repo.merge_extra_info(h2)
307 assert_equal (h = {"test_11" => "test_value_11"}),
316 assert_equal (h = {"test_11" => "test_value_11"}),
308 repo.extra_info["test_1"]
317 repo.extra_info["test_1"]
309 assert_equal "test_value_21",
318 assert_equal "test_value_21",
310 repo.extra_info["test_2"]["test_21"]
319 repo.extra_info["test_2"]["test_21"]
311 h3 = {"test_2" => {
320 h3 = {"test_2" => {
312 "test_23" => "test_value_23",
321 "test_23" => "test_value_23",
313 "test_24" => "test_value_24",
322 "test_24" => "test_value_24",
314 }}
323 }}
315 repo.merge_extra_info(h3)
324 repo.merge_extra_info(h3)
316 assert_equal (h = {"test_11" => "test_value_11"}),
325 assert_equal (h = {"test_11" => "test_value_11"}),
317 repo.extra_info["test_1"]
326 repo.extra_info["test_1"]
318 assert_nil repo.extra_info["test_2"]["test_21"]
327 assert_nil repo.extra_info["test_2"]["test_21"]
319 assert_equal "test_value_23",
328 assert_equal "test_value_23",
320 repo.extra_info["test_2"]["test_23"]
329 repo.extra_info["test_2"]["test_23"]
321 end
330 end
322
331
323 def test_sort_should_not_raise_an_error_with_nil_identifiers
332 def test_sort_should_not_raise_an_error_with_nil_identifiers
324 r1 = Repository.new
333 r1 = Repository.new
325 r2 = Repository.new
334 r2 = Repository.new
326
335
327 assert_nothing_raised do
336 assert_nothing_raised do
328 [r1, r2].sort
337 [r1, r2].sort
329 end
338 end
330 end
339 end
331 end
340 end
General Comments 0
You need to be logged in to leave comments. Login now