##// 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 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class ScmFetchError < Exception; end
19 19
20 20 class Repository < ActiveRecord::Base
21 21 include Redmine::Ciphering
22 22
23 23 belongs_to :project
24 24 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
25 25 has_many :changes, :through => :changesets
26 26
27 27 serialize :extra_info
28 28
29 29 before_save :check_default
30 30
31 31 # Raw SQL to delete changesets and changes in the database
32 32 # has_many :changesets, :dependent => :destroy is too slow for big repositories
33 33 before_destroy :clear_changesets
34 34
35 35 validates_length_of :password, :maximum => 255, :allow_nil => true
36 36 validates_length_of :identifier, :maximum => 255, :allow_blank => true
37 37 validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? }
38 38 validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true
39 39 validates_exclusion_of :identifier, :in => %w(show entry raw changes annotate diff show stats graph)
40 40 # donwcase letters, digits, dashes but not digits only
41 41 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :allow_blank => true
42 42 # Checks if the SCM is enabled when creating a repository
43 43 validate :repo_create_validation, :on => :create
44 44
45 45 def repo_create_validation
46 46 unless Setting.enabled_scm.include?(self.class.name.demodulize)
47 47 errors.add(:type, :invalid)
48 48 end
49 49 end
50 50
51 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 53 if attr_name == "log_encoding"
54 54 attr_name = "commit_logs_encoding"
55 55 end
56 56 super(attr_name, *args)
57 57 end
58 58
59 59 alias :attributes_without_extra_info= :attributes=
60 60 def attributes=(new_attributes, guard_protected_attributes = true)
61 61 return if new_attributes.nil?
62 62 attributes = new_attributes.dup
63 63 attributes.stringify_keys!
64 64
65 65 p = {}
66 66 p_extra = {}
67 67 attributes.each do |k, v|
68 68 if k =~ /^extra_/
69 69 p_extra[k] = v
70 70 else
71 71 p[k] = v
72 72 end
73 73 end
74 74
75 75 send :attributes_without_extra_info=, p, guard_protected_attributes
76 76 if p_extra.keys.any?
77 77 merge_extra_info(p_extra)
78 78 end
79 79 end
80 80
81 81 # Removes leading and trailing whitespace
82 82 def url=(arg)
83 83 write_attribute(:url, arg ? arg.to_s.strip : nil)
84 84 end
85 85
86 86 # Removes leading and trailing whitespace
87 87 def root_url=(arg)
88 88 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
89 89 end
90 90
91 91 def password
92 92 read_ciphered_attribute(:password)
93 93 end
94 94
95 95 def password=(arg)
96 96 write_ciphered_attribute(:password, arg)
97 97 end
98 98
99 99 def scm_adapter
100 100 self.class.scm_adapter_class
101 101 end
102 102
103 103 def scm
104 104 unless @scm
105 105 @scm = self.scm_adapter.new(url, root_url,
106 106 login, password, path_encoding)
107 107 if root_url.blank? && @scm.root_url.present?
108 108 update_attribute(:root_url, @scm.root_url)
109 109 end
110 110 end
111 111 @scm
112 112 end
113 113
114 114 def scm_name
115 115 self.class.scm_name
116 116 end
117 117
118 118 def name
119 119 if identifier.present?
120 120 identifier
121 121 elsif is_default?
122 122 l(:field_repository_is_default)
123 123 else
124 124 scm_name
125 125 end
126 126 end
127 127
128 128 def identifier_param
129 129 if is_default?
130 130 nil
131 131 elsif identifier.present?
132 132 identifier
133 133 else
134 134 id.to_s
135 135 end
136 136 end
137 137
138 138 def <=>(repository)
139 139 if is_default?
140 140 -1
141 141 elsif repository.is_default?
142 142 1
143 143 else
144 144 identifier <=> repository.identifier
145 145 end
146 146 end
147 147
148 148 def self.find_by_identifier_param(param)
149 149 if param.to_s =~ /^\d+$/
150 150 find_by_id(param)
151 151 else
152 152 find_by_identifier(param)
153 153 end
154 154 end
155 155
156 156 def merge_extra_info(arg)
157 157 h = extra_info || {}
158 158 return h if arg.nil?
159 159 h.merge!(arg)
160 160 write_attribute(:extra_info, h)
161 161 end
162 162
163 163 def report_last_commit
164 164 true
165 165 end
166 166
167 167 def supports_cat?
168 168 scm.supports_cat?
169 169 end
170 170
171 171 def supports_annotate?
172 172 scm.supports_annotate?
173 173 end
174 174
175 175 def supports_all_revisions?
176 176 true
177 177 end
178 178
179 179 def supports_directory_revisions?
180 180 false
181 181 end
182 182
183 183 def supports_revision_graph?
184 184 false
185 185 end
186 186
187 187 def entry(path=nil, identifier=nil)
188 188 scm.entry(path, identifier)
189 189 end
190 190
191 191 def entries(path=nil, identifier=nil)
192 192 scm.entries(path, identifier)
193 193 end
194 194
195 195 def branches
196 196 scm.branches
197 197 end
198 198
199 199 def tags
200 200 scm.tags
201 201 end
202 202
203 203 def default_branch
204 204 nil
205 205 end
206 206
207 207 def properties(path, identifier=nil)
208 208 scm.properties(path, identifier)
209 209 end
210 210
211 211 def cat(path, identifier=nil)
212 212 scm.cat(path, identifier)
213 213 end
214 214
215 215 def diff(path, rev, rev_to)
216 216 scm.diff(path, rev, rev_to)
217 217 end
218 218
219 219 def diff_format_revisions(cs, cs_to, sep=':')
220 220 text = ""
221 221 text << cs_to.format_identifier + sep if cs_to
222 222 text << cs.format_identifier if cs
223 223 text
224 224 end
225 225
226 226 # Returns a path relative to the url of the repository
227 227 def relative_path(path)
228 228 path
229 229 end
230 230
231 231 # Finds and returns a revision with a number or the beginning of a hash
232 232 def find_changeset_by_name(name)
233 233 return nil if name.blank?
234 234 s = name.to_s
235 235 changesets.find(:first, :conditions => (s.match(/^\d*$/) ?
236 236 ["revision = ?", s] : ["revision LIKE ?", s + '%']))
237 237 end
238 238
239 239 def latest_changeset
240 240 @latest_changeset ||= changesets.find(:first)
241 241 end
242 242
243 243 # Returns the latest changesets for +path+
244 244 # Default behaviour is to search in cached changesets
245 245 def latest_changesets(path, rev, limit=10)
246 246 if path.blank?
247 247 changesets.find(
248 248 :all,
249 249 :include => :user,
250 250 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
251 251 :limit => limit)
252 252 else
253 253 changes.find(
254 254 :all,
255 255 :include => {:changeset => :user},
256 256 :conditions => ["path = ?", path.with_leading_slash],
257 257 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
258 258 :limit => limit
259 259 ).collect(&:changeset)
260 260 end
261 261 end
262 262
263 263 def scan_changesets_for_issue_ids
264 264 self.changesets.each(&:scan_comment_for_issue_ids)
265 265 end
266 266
267 267 # Returns an array of committers usernames and associated user_id
268 268 def committers
269 269 @committers ||= Changeset.connection.select_rows(
270 270 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
271 271 end
272 272
273 273 # Maps committers username to a user ids
274 274 def committer_ids=(h)
275 275 if h.is_a?(Hash)
276 276 committers.each do |committer, user_id|
277 277 new_user_id = h[committer]
278 278 if new_user_id && (new_user_id.to_i != user_id.to_i)
279 279 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
280 280 Changeset.update_all(
281 281 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
282 282 ["repository_id = ? AND committer = ?", id, committer])
283 283 end
284 284 end
285 285 @committers = nil
286 286 @found_committer_users = nil
287 287 true
288 288 else
289 289 false
290 290 end
291 291 end
292 292
293 293 # Returns the Redmine User corresponding to the given +committer+
294 294 # It will return nil if the committer is not yet mapped and if no User
295 295 # with the same username or email was found
296 296 def find_committer_user(committer)
297 297 unless committer.blank?
298 298 @found_committer_users ||= {}
299 299 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
300 300
301 301 user = nil
302 302 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
303 303 if c && c.user
304 304 user = c.user
305 305 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
306 306 username, email = $1.strip, $3
307 307 u = User.find_by_login(username)
308 308 u ||= User.find_by_mail(email) unless email.blank?
309 309 user = u
310 310 end
311 311 @found_committer_users[committer] = user
312 312 user
313 313 end
314 314 end
315 315
316 316 def repo_log_encoding
317 317 encoding = log_encoding.to_s.strip
318 318 encoding.blank? ? 'UTF-8' : encoding
319 319 end
320 320
321 321 # Fetches new changesets for all repositories of active projects
322 322 # Can be called periodically by an external script
323 323 # eg. ruby script/runner "Repository.fetch_changesets"
324 324 def self.fetch_changesets
325 325 Project.active.has_module(:repository).all.each do |project|
326 326 project.repositories.each do |repository|
327 327 begin
328 328 repository.fetch_changesets
329 329 rescue Redmine::Scm::Adapters::CommandFailed => e
330 330 logger.error "scm: error during fetching changesets: #{e.message}"
331 331 end
332 332 end
333 333 end
334 334 end
335 335
336 336 # scan changeset comments to find related and fixed issues for all repositories
337 337 def self.scan_changesets_for_issue_ids
338 338 find(:all).each(&:scan_changesets_for_issue_ids)
339 339 end
340 340
341 341 def self.scm_name
342 342 'Abstract'
343 343 end
344 344
345 345 def self.available_scm
346 346 subclasses.collect {|klass| [klass.scm_name, klass.name]}
347 347 end
348 348
349 349 def self.factory(klass_name, *args)
350 350 klass = "Repository::#{klass_name}".constantize
351 351 klass.new(*args)
352 352 rescue
353 353 nil
354 354 end
355 355
356 356 def self.scm_adapter_class
357 357 nil
358 358 end
359 359
360 360 def self.scm_command
361 361 ret = ""
362 362 begin
363 363 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
364 364 rescue Exception => e
365 365 logger.error "scm: error during get command: #{e.message}"
366 366 end
367 367 ret
368 368 end
369 369
370 370 def self.scm_version_string
371 371 ret = ""
372 372 begin
373 373 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
374 374 rescue Exception => e
375 375 logger.error "scm: error during get version string: #{e.message}"
376 376 end
377 377 ret
378 378 end
379 379
380 380 def self.scm_available
381 381 ret = false
382 382 begin
383 383 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
384 384 rescue Exception => e
385 385 logger.error "scm: error during get scm available: #{e.message}"
386 386 end
387 387 ret
388 388 end
389 389
390 390 def set_as_default?
391 391 new_record? && project && !Repository.first(:conditions => {:project_id => project.id})
392 392 end
393 393
394 394 protected
395 395
396 396 def check_default
397 397 if !is_default? && set_as_default?
398 398 self.is_default = true
399 399 end
400 400 if is_default? && is_default_changed?
401 401 Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
402 402 end
403 403 end
404 404
405 405 private
406 406
407 407 # Deletes repository data
408 408 def clear_changesets
409 409 cs = Changeset.table_name
410 410 ch = Change.table_name
411 411 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
412 412 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
413 413
414 414 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
415 415 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
416 416 connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
417 417 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
418 418 end
419 419 end
General Comments 0
You need to be logged in to leave comments. Login now