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