##// END OF EJS Templates
Rails3: scm: use .to_s for overriding human_attribute_name parameter at repository model...
Toshi MARUYAMA -
r8850:cd9b62d7344d
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
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 <=> 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 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
General Comments 0
You need to be logged in to leave comments. Login now