##// END OF EJS Templates
scm: use to_s for revision in find_changeset_by_name method...
Toshi MARUYAMA -
r8811:a26100666600
parent child
Show More
@@ -1,418 +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
52 attr_name = attribute_key_name
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 <=> repository.identifier
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 changesets.find(:first, :conditions => (name.match(/^\d*$/) ?
234 s = name.to_s
235 ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
235 changesets.find(:first, :conditions => (s.match(/^\d*$/) ?
236 ["revision = ?", s] : ["revision LIKE ?", s + '%']))
236 end
237 end
237
238
238 def latest_changeset
239 def latest_changeset
239 @latest_changeset ||= changesets.find(:first)
240 @latest_changeset ||= changesets.find(:first)
240 end
241 end
241
242
242 # Returns the latest changesets for +path+
243 # Returns the latest changesets for +path+
243 # Default behaviour is to search in cached changesets
244 # Default behaviour is to search in cached changesets
244 def latest_changesets(path, rev, limit=10)
245 def latest_changesets(path, rev, limit=10)
245 if path.blank?
246 if path.blank?
246 changesets.find(
247 changesets.find(
247 :all,
248 :all,
248 :include => :user,
249 :include => :user,
249 :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",
250 :limit => limit)
251 :limit => limit)
251 else
252 else
252 changes.find(
253 changes.find(
253 :all,
254 :all,
254 :include => {:changeset => :user},
255 :include => {:changeset => :user},
255 :conditions => ["path = ?", path.with_leading_slash],
256 :conditions => ["path = ?", path.with_leading_slash],
256 :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",
257 :limit => limit
258 :limit => limit
258 ).collect(&:changeset)
259 ).collect(&:changeset)
259 end
260 end
260 end
261 end
261
262
262 def scan_changesets_for_issue_ids
263 def scan_changesets_for_issue_ids
263 self.changesets.each(&:scan_comment_for_issue_ids)
264 self.changesets.each(&:scan_comment_for_issue_ids)
264 end
265 end
265
266
266 # Returns an array of committers usernames and associated user_id
267 # Returns an array of committers usernames and associated user_id
267 def committers
268 def committers
268 @committers ||= Changeset.connection.select_rows(
269 @committers ||= Changeset.connection.select_rows(
269 "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}")
270 end
271 end
271
272
272 # Maps committers username to a user ids
273 # Maps committers username to a user ids
273 def committer_ids=(h)
274 def committer_ids=(h)
274 if h.is_a?(Hash)
275 if h.is_a?(Hash)
275 committers.each do |committer, user_id|
276 committers.each do |committer, user_id|
276 new_user_id = h[committer]
277 new_user_id = h[committer]
277 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)
278 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)
279 Changeset.update_all(
280 Changeset.update_all(
280 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
281 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
281 ["repository_id = ? AND committer = ?", id, committer])
282 ["repository_id = ? AND committer = ?", id, committer])
282 end
283 end
283 end
284 end
284 @committers = nil
285 @committers = nil
285 @found_committer_users = nil
286 @found_committer_users = nil
286 true
287 true
287 else
288 else
288 false
289 false
289 end
290 end
290 end
291 end
291
292
292 # Returns the Redmine User corresponding to the given +committer+
293 # Returns the Redmine User corresponding to the given +committer+
293 # 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
294 # with the same username or email was found
295 # with the same username or email was found
295 def find_committer_user(committer)
296 def find_committer_user(committer)
296 unless committer.blank?
297 unless committer.blank?
297 @found_committer_users ||= {}
298 @found_committer_users ||= {}
298 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)
299
300
300 user = nil
301 user = nil
301 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
302 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
302 if c && c.user
303 if c && c.user
303 user = c.user
304 user = c.user
304 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
305 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
305 username, email = $1.strip, $3
306 username, email = $1.strip, $3
306 u = User.find_by_login(username)
307 u = User.find_by_login(username)
307 u ||= User.find_by_mail(email) unless email.blank?
308 u ||= User.find_by_mail(email) unless email.blank?
308 user = u
309 user = u
309 end
310 end
310 @found_committer_users[committer] = user
311 @found_committer_users[committer] = user
311 user
312 user
312 end
313 end
313 end
314 end
314
315
315 def repo_log_encoding
316 def repo_log_encoding
316 encoding = log_encoding.to_s.strip
317 encoding = log_encoding.to_s.strip
317 encoding.blank? ? 'UTF-8' : encoding
318 encoding.blank? ? 'UTF-8' : encoding
318 end
319 end
319
320
320 # Fetches new changesets for all repositories of active projects
321 # Fetches new changesets for all repositories of active projects
321 # Can be called periodically by an external script
322 # Can be called periodically by an external script
322 # eg. ruby script/runner "Repository.fetch_changesets"
323 # eg. ruby script/runner "Repository.fetch_changesets"
323 def self.fetch_changesets
324 def self.fetch_changesets
324 Project.active.has_module(:repository).all.each do |project|
325 Project.active.has_module(:repository).all.each do |project|
325 project.repositories.each do |repository|
326 project.repositories.each do |repository|
326 begin
327 begin
327 repository.fetch_changesets
328 repository.fetch_changesets
328 rescue Redmine::Scm::Adapters::CommandFailed => e
329 rescue Redmine::Scm::Adapters::CommandFailed => e
329 logger.error "scm: error during fetching changesets: #{e.message}"
330 logger.error "scm: error during fetching changesets: #{e.message}"
330 end
331 end
331 end
332 end
332 end
333 end
333 end
334 end
334
335
335 # 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
336 def self.scan_changesets_for_issue_ids
337 def self.scan_changesets_for_issue_ids
337 find(:all).each(&:scan_changesets_for_issue_ids)
338 find(:all).each(&:scan_changesets_for_issue_ids)
338 end
339 end
339
340
340 def self.scm_name
341 def self.scm_name
341 'Abstract'
342 'Abstract'
342 end
343 end
343
344
344 def self.available_scm
345 def self.available_scm
345 subclasses.collect {|klass| [klass.scm_name, klass.name]}
346 subclasses.collect {|klass| [klass.scm_name, klass.name]}
346 end
347 end
347
348
348 def self.factory(klass_name, *args)
349 def self.factory(klass_name, *args)
349 klass = "Repository::#{klass_name}".constantize
350 klass = "Repository::#{klass_name}".constantize
350 klass.new(*args)
351 klass.new(*args)
351 rescue
352 rescue
352 nil
353 nil
353 end
354 end
354
355
355 def self.scm_adapter_class
356 def self.scm_adapter_class
356 nil
357 nil
357 end
358 end
358
359
359 def self.scm_command
360 def self.scm_command
360 ret = ""
361 ret = ""
361 begin
362 begin
362 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
363 rescue Exception => e
364 rescue Exception => e
364 logger.error "scm: error during get command: #{e.message}"
365 logger.error "scm: error during get command: #{e.message}"
365 end
366 end
366 ret
367 ret
367 end
368 end
368
369
369 def self.scm_version_string
370 def self.scm_version_string
370 ret = ""
371 ret = ""
371 begin
372 begin
372 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
373 rescue Exception => e
374 rescue Exception => e
374 logger.error "scm: error during get version string: #{e.message}"
375 logger.error "scm: error during get version string: #{e.message}"
375 end
376 end
376 ret
377 ret
377 end
378 end
378
379
379 def self.scm_available
380 def self.scm_available
380 ret = false
381 ret = false
381 begin
382 begin
382 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
383 rescue Exception => e
384 rescue Exception => e
384 logger.error "scm: error during get scm available: #{e.message}"
385 logger.error "scm: error during get scm available: #{e.message}"
385 end
386 end
386 ret
387 ret
387 end
388 end
388
389
389 def set_as_default?
390 def set_as_default?
390 new_record? && project && !Repository.first(:conditions => {:project_id => project.id})
391 new_record? && project && !Repository.first(:conditions => {:project_id => project.id})
391 end
392 end
392
393
393 protected
394 protected
394
395
395 def check_default
396 def check_default
396 if !is_default? && set_as_default?
397 if !is_default? && set_as_default?
397 self.is_default = true
398 self.is_default = true
398 end
399 end
399 if is_default? && is_default_changed?
400 if is_default? && is_default_changed?
400 Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
401 Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
401 end
402 end
402 end
403 end
403
404
404 private
405 private
405
406
406 # Deletes repository data
407 # Deletes repository data
407 def clear_changesets
408 def clear_changesets
408 cs = Changeset.table_name
409 cs = Changeset.table_name
409 ch = Change.table_name
410 ch = Change.table_name
410 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
411 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
411 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
412 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
412
413
413 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})")
414 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})")
415 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})")
416 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
417 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
417 end
418 end
418 end
419 end
General Comments 0
You need to be logged in to leave comments. Login now