##// END OF EJS Templates
Merged r9619 from trunk....
Jean-Philippe Lang -
r9437:e9ac98b249a3
parent child
Show More
@@ -1,419 +1,419
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 alias :attributes_without_extra_info= :attributes=
59 alias :attributes_without_extra_info= :attributes=
60 def attributes=(new_attributes, guard_protected_attributes = true)
60 def attributes=(new_attributes, guard_protected_attributes = true)
61 return if new_attributes.nil?
61 return if new_attributes.nil?
62 attributes = new_attributes.dup
62 attributes = new_attributes.dup
63 attributes.stringify_keys!
63 attributes.stringify_keys!
64
64
65 p = {}
65 p = {}
66 p_extra = {}
66 p_extra = {}
67 attributes.each do |k, v|
67 attributes.each do |k, v|
68 if k =~ /^extra_/
68 if k =~ /^extra_/
69 p_extra[k] = v
69 p_extra[k] = v
70 else
70 else
71 p[k] = v
71 p[k] = v
72 end
72 end
73 end
73 end
74
74
75 send :attributes_without_extra_info=, p, guard_protected_attributes
75 send :attributes_without_extra_info=, p, guard_protected_attributes
76 if p_extra.keys.any?
76 if p_extra.keys.any?
77 merge_extra_info(p_extra)
77 merge_extra_info(p_extra)
78 end
78 end
79 end
79 end
80
80
81 # Removes leading and trailing whitespace
81 # Removes leading and trailing whitespace
82 def url=(arg)
82 def url=(arg)
83 write_attribute(:url, arg ? arg.to_s.strip : nil)
83 write_attribute(:url, arg ? arg.to_s.strip : nil)
84 end
84 end
85
85
86 # Removes leading and trailing whitespace
86 # Removes leading and trailing whitespace
87 def root_url=(arg)
87 def root_url=(arg)
88 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
88 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
89 end
89 end
90
90
91 def password
91 def password
92 read_ciphered_attribute(:password)
92 read_ciphered_attribute(:password)
93 end
93 end
94
94
95 def password=(arg)
95 def password=(arg)
96 write_ciphered_attribute(:password, arg)
96 write_ciphered_attribute(:password, arg)
97 end
97 end
98
98
99 def scm_adapter
99 def scm_adapter
100 self.class.scm_adapter_class
100 self.class.scm_adapter_class
101 end
101 end
102
102
103 def scm
103 def scm
104 unless @scm
104 unless @scm
105 @scm = self.scm_adapter.new(url, root_url,
105 @scm = self.scm_adapter.new(url, root_url,
106 login, password, path_encoding)
106 login, password, path_encoding)
107 if root_url.blank? && @scm.root_url.present?
107 if root_url.blank? && @scm.root_url.present?
108 update_attribute(:root_url, @scm.root_url)
108 update_attribute(:root_url, @scm.root_url)
109 end
109 end
110 end
110 end
111 @scm
111 @scm
112 end
112 end
113
113
114 def scm_name
114 def scm_name
115 self.class.scm_name
115 self.class.scm_name
116 end
116 end
117
117
118 def name
118 def name
119 if identifier.present?
119 if identifier.present?
120 identifier
120 identifier
121 elsif is_default?
121 elsif is_default?
122 l(:field_repository_is_default)
122 l(:field_repository_is_default)
123 else
123 else
124 scm_name
124 scm_name
125 end
125 end
126 end
126 end
127
127
128 def identifier_param
128 def identifier_param
129 if is_default?
129 if is_default?
130 nil
130 nil
131 elsif identifier.present?
131 elsif identifier.present?
132 identifier
132 identifier
133 else
133 else
134 id.to_s
134 id.to_s
135 end
135 end
136 end
136 end
137
137
138 def <=>(repository)
138 def <=>(repository)
139 if is_default?
139 if is_default?
140 -1
140 -1
141 elsif repository.is_default?
141 elsif repository.is_default?
142 1
142 1
143 else
143 else
144 identifier <=> repository.identifier
144 identifier.to_s <=> repository.identifier.to_s
145 end
145 end
146 end
146 end
147
147
148 def self.find_by_identifier_param(param)
148 def self.find_by_identifier_param(param)
149 if param.to_s =~ /^\d+$/
149 if param.to_s =~ /^\d+$/
150 find_by_id(param)
150 find_by_id(param)
151 else
151 else
152 find_by_identifier(param)
152 find_by_identifier(param)
153 end
153 end
154 end
154 end
155
155
156 def merge_extra_info(arg)
156 def merge_extra_info(arg)
157 h = extra_info || {}
157 h = extra_info || {}
158 return h if arg.nil?
158 return h if arg.nil?
159 h.merge!(arg)
159 h.merge!(arg)
160 write_attribute(:extra_info, h)
160 write_attribute(:extra_info, h)
161 end
161 end
162
162
163 def report_last_commit
163 def report_last_commit
164 true
164 true
165 end
165 end
166
166
167 def supports_cat?
167 def supports_cat?
168 scm.supports_cat?
168 scm.supports_cat?
169 end
169 end
170
170
171 def supports_annotate?
171 def supports_annotate?
172 scm.supports_annotate?
172 scm.supports_annotate?
173 end
173 end
174
174
175 def supports_all_revisions?
175 def supports_all_revisions?
176 true
176 true
177 end
177 end
178
178
179 def supports_directory_revisions?
179 def supports_directory_revisions?
180 false
180 false
181 end
181 end
182
182
183 def supports_revision_graph?
183 def supports_revision_graph?
184 false
184 false
185 end
185 end
186
186
187 def entry(path=nil, identifier=nil)
187 def entry(path=nil, identifier=nil)
188 scm.entry(path, identifier)
188 scm.entry(path, identifier)
189 end
189 end
190
190
191 def entries(path=nil, identifier=nil)
191 def entries(path=nil, identifier=nil)
192 scm.entries(path, identifier)
192 scm.entries(path, identifier)
193 end
193 end
194
194
195 def branches
195 def branches
196 scm.branches
196 scm.branches
197 end
197 end
198
198
199 def tags
199 def tags
200 scm.tags
200 scm.tags
201 end
201 end
202
202
203 def default_branch
203 def default_branch
204 nil
204 nil
205 end
205 end
206
206
207 def properties(path, identifier=nil)
207 def properties(path, identifier=nil)
208 scm.properties(path, identifier)
208 scm.properties(path, identifier)
209 end
209 end
210
210
211 def cat(path, identifier=nil)
211 def cat(path, identifier=nil)
212 scm.cat(path, identifier)
212 scm.cat(path, identifier)
213 end
213 end
214
214
215 def diff(path, rev, rev_to)
215 def diff(path, rev, rev_to)
216 scm.diff(path, rev, rev_to)
216 scm.diff(path, rev, rev_to)
217 end
217 end
218
218
219 def diff_format_revisions(cs, cs_to, sep=':')
219 def diff_format_revisions(cs, cs_to, sep=':')
220 text = ""
220 text = ""
221 text << cs_to.format_identifier + sep if cs_to
221 text << cs_to.format_identifier + sep if cs_to
222 text << cs.format_identifier if cs
222 text << cs.format_identifier if cs
223 text
223 text
224 end
224 end
225
225
226 # Returns a path relative to the url of the repository
226 # Returns a path relative to the url of the repository
227 def relative_path(path)
227 def relative_path(path)
228 path
228 path
229 end
229 end
230
230
231 # Finds and returns a revision with a number or the beginning of a hash
231 # Finds and returns a revision with a number or the beginning of a hash
232 def find_changeset_by_name(name)
232 def find_changeset_by_name(name)
233 return nil if name.blank?
233 return nil if name.blank?
234 s = name.to_s
234 s = name.to_s
235 changesets.find(:first, :conditions => (s.match(/^\d*$/) ?
235 changesets.find(:first, :conditions => (s.match(/^\d*$/) ?
236 ["revision = ?", s] : ["revision LIKE ?", s + '%']))
236 ["revision = ?", s] : ["revision LIKE ?", s + '%']))
237 end
237 end
238
238
239 def latest_changeset
239 def latest_changeset
240 @latest_changeset ||= changesets.find(:first)
240 @latest_changeset ||= changesets.find(:first)
241 end
241 end
242
242
243 # Returns the latest changesets for +path+
243 # Returns the latest changesets for +path+
244 # Default behaviour is to search in cached changesets
244 # Default behaviour is to search in cached changesets
245 def latest_changesets(path, rev, limit=10)
245 def latest_changesets(path, rev, limit=10)
246 if path.blank?
246 if path.blank?
247 changesets.find(
247 changesets.find(
248 :all,
248 :all,
249 :include => :user,
249 :include => :user,
250 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
250 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
251 :limit => limit)
251 :limit => limit)
252 else
252 else
253 changes.find(
253 changes.find(
254 :all,
254 :all,
255 :include => {:changeset => :user},
255 :include => {:changeset => :user},
256 :conditions => ["path = ?", path.with_leading_slash],
256 :conditions => ["path = ?", path.with_leading_slash],
257 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
257 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
258 :limit => limit
258 :limit => limit
259 ).collect(&:changeset)
259 ).collect(&:changeset)
260 end
260 end
261 end
261 end
262
262
263 def scan_changesets_for_issue_ids
263 def scan_changesets_for_issue_ids
264 self.changesets.each(&:scan_comment_for_issue_ids)
264 self.changesets.each(&:scan_comment_for_issue_ids)
265 end
265 end
266
266
267 # Returns an array of committers usernames and associated user_id
267 # Returns an array of committers usernames and associated user_id
268 def committers
268 def committers
269 @committers ||= Changeset.connection.select_rows(
269 @committers ||= Changeset.connection.select_rows(
270 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
270 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
271 end
271 end
272
272
273 # Maps committers username to a user ids
273 # Maps committers username to a user ids
274 def committer_ids=(h)
274 def committer_ids=(h)
275 if h.is_a?(Hash)
275 if h.is_a?(Hash)
276 committers.each do |committer, user_id|
276 committers.each do |committer, user_id|
277 new_user_id = h[committer]
277 new_user_id = h[committer]
278 if new_user_id && (new_user_id.to_i != user_id.to_i)
278 if new_user_id && (new_user_id.to_i != user_id.to_i)
279 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
279 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
280 Changeset.update_all(
280 Changeset.update_all(
281 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
281 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
282 ["repository_id = ? AND committer = ?", id, committer])
282 ["repository_id = ? AND committer = ?", id, committer])
283 end
283 end
284 end
284 end
285 @committers = nil
285 @committers = nil
286 @found_committer_users = nil
286 @found_committer_users = nil
287 true
287 true
288 else
288 else
289 false
289 false
290 end
290 end
291 end
291 end
292
292
293 # Returns the Redmine User corresponding to the given +committer+
293 # Returns the Redmine User corresponding to the given +committer+
294 # It will return nil if the committer is not yet mapped and if no User
294 # It will return nil if the committer is not yet mapped and if no User
295 # with the same username or email was found
295 # with the same username or email was found
296 def find_committer_user(committer)
296 def find_committer_user(committer)
297 unless committer.blank?
297 unless committer.blank?
298 @found_committer_users ||= {}
298 @found_committer_users ||= {}
299 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
299 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
300
300
301 user = nil
301 user = nil
302 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
302 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
303 if c && c.user
303 if c && c.user
304 user = c.user
304 user = c.user
305 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
305 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
306 username, email = $1.strip, $3
306 username, email = $1.strip, $3
307 u = User.find_by_login(username)
307 u = User.find_by_login(username)
308 u ||= User.find_by_mail(email) unless email.blank?
308 u ||= User.find_by_mail(email) unless email.blank?
309 user = u
309 user = u
310 end
310 end
311 @found_committer_users[committer] = user
311 @found_committer_users[committer] = user
312 user
312 user
313 end
313 end
314 end
314 end
315
315
316 def repo_log_encoding
316 def repo_log_encoding
317 encoding = log_encoding.to_s.strip
317 encoding = log_encoding.to_s.strip
318 encoding.blank? ? 'UTF-8' : encoding
318 encoding.blank? ? 'UTF-8' : encoding
319 end
319 end
320
320
321 # Fetches new changesets for all repositories of active projects
321 # Fetches new changesets for all repositories of active projects
322 # Can be called periodically by an external script
322 # Can be called periodically by an external script
323 # eg. ruby script/runner "Repository.fetch_changesets"
323 # eg. ruby script/runner "Repository.fetch_changesets"
324 def self.fetch_changesets
324 def self.fetch_changesets
325 Project.active.has_module(:repository).all.each do |project|
325 Project.active.has_module(:repository).all.each do |project|
326 project.repositories.each do |repository|
326 project.repositories.each do |repository|
327 begin
327 begin
328 repository.fetch_changesets
328 repository.fetch_changesets
329 rescue Redmine::Scm::Adapters::CommandFailed => e
329 rescue Redmine::Scm::Adapters::CommandFailed => e
330 logger.error "scm: error during fetching changesets: #{e.message}"
330 logger.error "scm: error during fetching changesets: #{e.message}"
331 end
331 end
332 end
332 end
333 end
333 end
334 end
334 end
335
335
336 # scan changeset comments to find related and fixed issues for all repositories
336 # scan changeset comments to find related and fixed issues for all repositories
337 def self.scan_changesets_for_issue_ids
337 def self.scan_changesets_for_issue_ids
338 find(:all).each(&:scan_changesets_for_issue_ids)
338 find(:all).each(&:scan_changesets_for_issue_ids)
339 end
339 end
340
340
341 def self.scm_name
341 def self.scm_name
342 'Abstract'
342 'Abstract'
343 end
343 end
344
344
345 def self.available_scm
345 def self.available_scm
346 subclasses.collect {|klass| [klass.scm_name, klass.name]}
346 subclasses.collect {|klass| [klass.scm_name, klass.name]}
347 end
347 end
348
348
349 def self.factory(klass_name, *args)
349 def self.factory(klass_name, *args)
350 klass = "Repository::#{klass_name}".constantize
350 klass = "Repository::#{klass_name}".constantize
351 klass.new(*args)
351 klass.new(*args)
352 rescue
352 rescue
353 nil
353 nil
354 end
354 end
355
355
356 def self.scm_adapter_class
356 def self.scm_adapter_class
357 nil
357 nil
358 end
358 end
359
359
360 def self.scm_command
360 def self.scm_command
361 ret = ""
361 ret = ""
362 begin
362 begin
363 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
363 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
364 rescue Exception => e
364 rescue Exception => e
365 logger.error "scm: error during get command: #{e.message}"
365 logger.error "scm: error during get command: #{e.message}"
366 end
366 end
367 ret
367 ret
368 end
368 end
369
369
370 def self.scm_version_string
370 def self.scm_version_string
371 ret = ""
371 ret = ""
372 begin
372 begin
373 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
373 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
374 rescue Exception => e
374 rescue Exception => e
375 logger.error "scm: error during get version string: #{e.message}"
375 logger.error "scm: error during get version string: #{e.message}"
376 end
376 end
377 ret
377 ret
378 end
378 end
379
379
380 def self.scm_available
380 def self.scm_available
381 ret = false
381 ret = false
382 begin
382 begin
383 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
383 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
384 rescue Exception => e
384 rescue Exception => e
385 logger.error "scm: error during get scm available: #{e.message}"
385 logger.error "scm: error during get scm available: #{e.message}"
386 end
386 end
387 ret
387 ret
388 end
388 end
389
389
390 def set_as_default?
390 def set_as_default?
391 new_record? && project && !Repository.first(:conditions => {:project_id => project.id})
391 new_record? && project && !Repository.first(:conditions => {:project_id => project.id})
392 end
392 end
393
393
394 protected
394 protected
395
395
396 def check_default
396 def check_default
397 if !is_default? && set_as_default?
397 if !is_default? && set_as_default?
398 self.is_default = true
398 self.is_default = true
399 end
399 end
400 if is_default? && is_default_changed?
400 if is_default? && is_default_changed?
401 Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
401 Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
402 end
402 end
403 end
403 end
404
404
405 private
405 private
406
406
407 # Deletes repository data
407 # Deletes repository data
408 def clear_changesets
408 def clear_changesets
409 cs = Changeset.table_name
409 cs = Changeset.table_name
410 ch = Change.table_name
410 ch = Change.table_name
411 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
411 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
412 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
412 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
413
413
414 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
414 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
415 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
415 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
416 connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
416 connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
417 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
417 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
418 end
418 end
419 end
419 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