##// END OF EJS Templates
Code cleanup....
Jean-Philippe Lang -
r13673:07795cfed510
parent child
Show More
@@ -1,509 +1,505
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 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 include Redmine::SafeAttributes
23 23
24 24 # Maximum length for repository identifiers
25 25 IDENTIFIER_MAX_LENGTH = 255
26 26
27 27 belongs_to :project
28 28 has_many :changesets, lambda{order("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC")}
29 29 has_many :filechanges, :class_name => 'Change', :through => :changesets
30 30
31 31 serialize :extra_info
32 32
33 33 before_save :check_default
34 34
35 35 # Raw SQL to delete changesets and changes in the database
36 36 # has_many :changesets, :dependent => :destroy is too slow for big repositories
37 37 before_destroy :clear_changesets
38 38
39 39 validates_length_of :password, :maximum => 255, :allow_nil => true
40 40 validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH, :allow_blank => true
41 41 validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true
42 42 validates_exclusion_of :identifier, :in => %w(browse show entry raw changes annotate diff statistics graph revisions revision)
43 43 # donwcase letters, digits, dashes, underscores but not digits only
44 44 validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :allow_blank => true
45 45 # Checks if the SCM is enabled when creating a repository
46 46 validate :repo_create_validation, :on => :create
47 47 validate :validate_repository_path
48 48 attr_protected :id
49 49
50 50 safe_attributes 'identifier',
51 51 'login',
52 52 'password',
53 53 'path_encoding',
54 54 'log_encoding',
55 55 'is_default'
56 56
57 57 safe_attributes 'url',
58 58 :if => lambda {|repository, user| repository.new_record?}
59 59
60 60 def repo_create_validation
61 61 unless Setting.enabled_scm.include?(self.class.name.demodulize)
62 62 errors.add(:type, :invalid)
63 63 end
64 64 end
65 65
66 66 def self.human_attribute_name(attribute_key_name, *args)
67 67 attr_name = attribute_key_name.to_s
68 68 if attr_name == "log_encoding"
69 69 attr_name = "commit_logs_encoding"
70 70 end
71 71 super(attr_name, *args)
72 72 end
73 73
74 74 # Removes leading and trailing whitespace
75 75 def url=(arg)
76 76 write_attribute(:url, arg ? arg.to_s.strip : nil)
77 77 end
78 78
79 79 # Removes leading and trailing whitespace
80 80 def root_url=(arg)
81 81 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
82 82 end
83 83
84 84 def password
85 85 read_ciphered_attribute(:password)
86 86 end
87 87
88 88 def password=(arg)
89 89 write_ciphered_attribute(:password, arg)
90 90 end
91 91
92 92 def scm_adapter
93 93 self.class.scm_adapter_class
94 94 end
95 95
96 96 def scm
97 97 unless @scm
98 98 @scm = self.scm_adapter.new(url, root_url,
99 99 login, password, path_encoding)
100 100 if root_url.blank? && @scm.root_url.present?
101 101 update_attribute(:root_url, @scm.root_url)
102 102 end
103 103 end
104 104 @scm
105 105 end
106 106
107 107 def scm_name
108 108 self.class.scm_name
109 109 end
110 110
111 111 def name
112 112 if identifier.present?
113 113 identifier
114 114 elsif is_default?
115 115 l(:field_repository_is_default)
116 116 else
117 117 scm_name
118 118 end
119 119 end
120 120
121 121 def identifier=(identifier)
122 122 super unless identifier_frozen?
123 123 end
124 124
125 125 def identifier_frozen?
126 126 errors[:identifier].blank? && !(new_record? || identifier.blank?)
127 127 end
128 128
129 129 def identifier_param
130 130 if is_default?
131 131 nil
132 132 elsif identifier.present?
133 133 identifier
134 134 else
135 135 id.to_s
136 136 end
137 137 end
138 138
139 139 def <=>(repository)
140 140 if is_default?
141 141 -1
142 142 elsif repository.is_default?
143 143 1
144 144 else
145 145 identifier.to_s <=> repository.identifier.to_s
146 146 end
147 147 end
148 148
149 149 def self.find_by_identifier_param(param)
150 150 if param.to_s =~ /^\d+$/
151 151 find_by_id(param)
152 152 else
153 153 find_by_identifier(param)
154 154 end
155 155 end
156 156
157 157 # TODO: should return an empty hash instead of nil to avoid many ||{}
158 158 def extra_info
159 159 h = read_attribute(:extra_info)
160 160 h.is_a?(Hash) ? h : nil
161 161 end
162 162
163 163 def merge_extra_info(arg)
164 164 h = extra_info || {}
165 165 return h if arg.nil?
166 166 h.merge!(arg)
167 167 write_attribute(:extra_info, h)
168 168 end
169 169
170 170 def report_last_commit
171 171 true
172 172 end
173 173
174 174 def supports_cat?
175 175 scm.supports_cat?
176 176 end
177 177
178 178 def supports_annotate?
179 179 scm.supports_annotate?
180 180 end
181 181
182 182 def supports_all_revisions?
183 183 true
184 184 end
185 185
186 186 def supports_directory_revisions?
187 187 false
188 188 end
189 189
190 190 def supports_revision_graph?
191 191 false
192 192 end
193 193
194 194 def entry(path=nil, identifier=nil)
195 195 scm.entry(path, identifier)
196 196 end
197 197
198 198 def scm_entries(path=nil, identifier=nil)
199 199 scm.entries(path, identifier)
200 200 end
201 201 protected :scm_entries
202 202
203 203 def entries(path=nil, identifier=nil)
204 204 entries = scm_entries(path, identifier)
205 205 load_entries_changesets(entries)
206 206 entries
207 207 end
208 208
209 209 def branches
210 210 scm.branches
211 211 end
212 212
213 213 def tags
214 214 scm.tags
215 215 end
216 216
217 217 def default_branch
218 218 nil
219 219 end
220 220
221 221 def properties(path, identifier=nil)
222 222 scm.properties(path, identifier)
223 223 end
224 224
225 225 def cat(path, identifier=nil)
226 226 scm.cat(path, identifier)
227 227 end
228 228
229 229 def diff(path, rev, rev_to)
230 230 scm.diff(path, rev, rev_to)
231 231 end
232 232
233 233 def diff_format_revisions(cs, cs_to, sep=':')
234 234 text = ""
235 235 text << cs_to.format_identifier + sep if cs_to
236 236 text << cs.format_identifier if cs
237 237 text
238 238 end
239 239
240 240 # Returns a path relative to the url of the repository
241 241 def relative_path(path)
242 242 path
243 243 end
244 244
245 245 # Finds and returns a revision with a number or the beginning of a hash
246 246 def find_changeset_by_name(name)
247 247 return nil if name.blank?
248 248 s = name.to_s
249 249 if s.match(/^\d*$/)
250 250 changesets.where("revision = ?", s).first
251 251 else
252 252 changesets.where("revision LIKE ?", s + '%').first
253 253 end
254 254 end
255 255
256 256 def latest_changeset
257 257 @latest_changeset ||= changesets.first
258 258 end
259 259
260 260 # Returns the latest changesets for +path+
261 261 # Default behaviour is to search in cached changesets
262 262 def latest_changesets(path, rev, limit=10)
263 263 if path.blank?
264 264 changesets.
265 265 reorder("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC").
266 266 limit(limit).
267 267 preload(:user).
268 268 to_a
269 269 else
270 270 filechanges.
271 271 where("path = ?", path.with_leading_slash).
272 272 reorder("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC").
273 273 limit(limit).
274 274 preload(:changeset => :user).
275 275 collect(&:changeset)
276 276 end
277 277 end
278 278
279 279 def scan_changesets_for_issue_ids
280 280 self.changesets.each(&:scan_comment_for_issue_ids)
281 281 end
282 282
283 283 # Returns an array of committers usernames and associated user_id
284 284 def committers
285 285 @committers ||= Changeset.where(:repository_id => id).uniq.pluck(:committer, :user_id)
286 286 end
287 287
288 288 # Maps committers username to a user ids
289 289 def committer_ids=(h)
290 290 if h.is_a?(Hash)
291 291 committers.each do |committer, user_id|
292 292 new_user_id = h[committer]
293 293 if new_user_id && (new_user_id.to_i != user_id.to_i)
294 294 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
295 295 Changeset.where(["repository_id = ? AND committer = ?", id, committer]).
296 296 update_all("user_id = #{new_user_id.nil? ? 'NULL' : new_user_id}")
297 297 end
298 298 end
299 299 @committers = nil
300 300 @found_committer_users = nil
301 301 true
302 302 else
303 303 false
304 304 end
305 305 end
306 306
307 307 # Returns the Redmine User corresponding to the given +committer+
308 308 # It will return nil if the committer is not yet mapped and if no User
309 309 # with the same username or email was found
310 310 def find_committer_user(committer)
311 311 unless committer.blank?
312 312 @found_committer_users ||= {}
313 313 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
314 314
315 315 user = nil
316 316 c = changesets.where(:committer => committer).
317 317 includes(:user).references(:user).first
318 318 if c && c.user
319 319 user = c.user
320 320 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
321 321 username, email = $1.strip, $3
322 322 u = User.find_by_login(username)
323 323 u ||= User.find_by_mail(email) unless email.blank?
324 324 user = u
325 325 end
326 326 @found_committer_users[committer] = user
327 327 user
328 328 end
329 329 end
330 330
331 331 def repo_log_encoding
332 332 encoding = log_encoding.to_s.strip
333 333 encoding.blank? ? 'UTF-8' : encoding
334 334 end
335 335
336 336 # Fetches new changesets for all repositories of active projects
337 337 # Can be called periodically by an external script
338 338 # eg. ruby script/runner "Repository.fetch_changesets"
339 339 def self.fetch_changesets
340 340 Project.active.has_module(:repository).all.each do |project|
341 341 project.repositories.each do |repository|
342 342 begin
343 343 repository.fetch_changesets
344 344 rescue Redmine::Scm::Adapters::CommandFailed => e
345 345 logger.error "scm: error during fetching changesets: #{e.message}"
346 346 end
347 347 end
348 348 end
349 349 end
350 350
351 351 # scan changeset comments to find related and fixed issues for all repositories
352 352 def self.scan_changesets_for_issue_ids
353 353 all.each(&:scan_changesets_for_issue_ids)
354 354 end
355 355
356 356 def self.scm_name
357 357 'Abstract'
358 358 end
359 359
360 360 def self.available_scm
361 361 subclasses.collect {|klass| [klass.scm_name, klass.name]}
362 362 end
363 363
364 364 def self.factory(klass_name, *args)
365 365 klass = "Repository::#{klass_name}".constantize
366 366 klass.new(*args)
367 367 rescue
368 368 nil
369 369 end
370 370
371 371 def self.scm_adapter_class
372 372 nil
373 373 end
374 374
375 375 def self.scm_command
376 376 ret = ""
377 377 begin
378 378 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
379 379 rescue Exception => e
380 380 logger.error "scm: error during get command: #{e.message}"
381 381 end
382 382 ret
383 383 end
384 384
385 385 def self.scm_version_string
386 386 ret = ""
387 387 begin
388 388 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
389 389 rescue Exception => e
390 390 logger.error "scm: error during get version string: #{e.message}"
391 391 end
392 392 ret
393 393 end
394 394
395 395 def self.scm_available
396 396 ret = false
397 397 begin
398 398 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
399 399 rescue Exception => e
400 400 logger.error "scm: error during get scm available: #{e.message}"
401 401 end
402 402 ret
403 403 end
404 404
405 405 def set_as_default?
406 406 new_record? && project && Repository.where(:project_id => project.id).empty?
407 407 end
408 408
409 409 # Returns a hash with statistics by author in the following form:
410 410 # {
411 411 # "John Smith" => { :commits => 45, :changes => 324 },
412 412 # "Bob" => { ... }
413 413 # }
414 414 #
415 415 # Notes:
416 416 # - this hash honnors the users mapping defined for the repository
417 417 def stats_by_author
418 418 commits = Changeset.where("repository_id = ?", id).select("committer, user_id, count(*) as count").group("committer, user_id")
419 419
420 420 #TODO: restore ordering ; this line probably never worked
421 421 #commits.to_a.sort! {|x, y| x.last <=> y.last}
422 422
423 423 changes = Change.joins(:changeset).where("#{Changeset.table_name}.repository_id = ?", id).select("committer, user_id, count(*) as count").group("committer, user_id")
424 424
425 425 user_ids = changesets.map(&:user_id).compact.uniq
426 426 authors_names = User.where(:id => user_ids).inject({}) do |memo, user|
427 427 memo[user.id] = user.to_s
428 428 memo
429 429 end
430 430
431 431 (commits + changes).inject({}) do |hash, element|
432 432 mapped_name = element.committer
433 433 if username = authors_names[element.user_id.to_i]
434 434 mapped_name = username
435 435 end
436 436 hash[mapped_name] ||= { :commits_count => 0, :changes_count => 0 }
437 437 if element.is_a?(Changeset)
438 438 hash[mapped_name][:commits_count] += element.count.to_i
439 439 else
440 440 hash[mapped_name][:changes_count] += element.count.to_i
441 441 end
442 442 hash
443 443 end
444 444 end
445 445
446 446 # Returns a scope of changesets that come from the same commit as the given changeset
447 447 # in different repositories that point to the same backend
448 448 def same_commits_in_scope(scope, changeset)
449 449 scope = scope.joins(:repository).where(:repositories => {:url => url, :root_url => root_url, :type => type})
450 450 if changeset.scmid.present?
451 451 scope = scope.where(:scmid => changeset.scmid)
452 452 else
453 453 scope = scope.where(:revision => changeset.revision)
454 454 end
455 455 scope
456 456 end
457 457
458 458 protected
459 459
460 460 # Validates repository url based against an optional regular expression
461 461 # that can be set in the Redmine configuration file.
462 462 def validate_repository_path(attribute=:url)
463 463 regexp = Redmine::Configuration["scm_#{scm_name.to_s.downcase}_path_regexp"]
464 464 if changes[attribute] && regexp.present?
465 465 regexp = regexp.to_s.strip.gsub('%project%') {Regexp.escape(project.try(:identifier).to_s)}
466 466 unless send(attribute).to_s.match(Regexp.new("\\A#{regexp}\\z"))
467 467 errors.add(attribute, :invalid)
468 468 end
469 469 end
470 470 end
471 471
472 472 def check_default
473 473 if !is_default? && set_as_default?
474 474 self.is_default = true
475 475 end
476 476 if is_default? && is_default_changed?
477 477 Repository.where(["project_id = ?", project_id]).update_all(["is_default = ?", false])
478 478 end
479 479 end
480 480
481 481 def load_entries_changesets(entries)
482 482 if entries
483 483 entries.each do |entry|
484 484 if entry.lastrev && entry.lastrev.identifier
485 485 entry.changeset = find_changeset_by_name(entry.lastrev.identifier)
486 486 end
487 487 end
488 488 end
489 489 end
490 490
491 491 private
492 492
493 493 # Deletes repository data
494 494 def clear_changesets
495 495 cs = Changeset.table_name
496 496 ch = Change.table_name
497 497 ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}"
498 498 cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}"
499 499
500 500 self.class.connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
501 501 self.class.connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
502 502 self.class.connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
503 503 self.class.connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
504 clear_extra_info_of_changesets
505 end
506
507 def clear_extra_info_of_changesets
508 504 end
509 505 end
@@ -1,257 +1,263
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 # Copyright (C) 2007 Patrick Aljord patcito@Ε‹mail.com
4 4 #
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; either version 2
8 8 # of the License, or (at your option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 18
19 19 require 'redmine/scm/adapters/git_adapter'
20 20
21 21 class Repository::Git < Repository
22 22 attr_protected :root_url
23 23 validates_presence_of :url
24 24
25 25 def self.human_attribute_name(attribute_key_name, *args)
26 26 attr_name = attribute_key_name.to_s
27 27 if attr_name == "url"
28 28 attr_name = "path_to_repository"
29 29 end
30 30 super(attr_name, *args)
31 31 end
32 32
33 33 def self.scm_adapter_class
34 34 Redmine::Scm::Adapters::GitAdapter
35 35 end
36 36
37 37 def self.scm_name
38 38 'Git'
39 39 end
40 40
41 41 def report_last_commit
42 42 extra_report_last_commit
43 43 end
44 44
45 45 def extra_report_last_commit
46 46 return false if extra_info.nil?
47 47 v = extra_info["extra_report_last_commit"]
48 48 return false if v.nil?
49 49 v.to_s != '0'
50 50 end
51 51
52 52 def supports_directory_revisions?
53 53 true
54 54 end
55 55
56 56 def supports_revision_graph?
57 57 true
58 58 end
59 59
60 60 def repo_log_encoding
61 61 'UTF-8'
62 62 end
63 63
64 64 # Returns the identifier for the given git changeset
65 65 def self.changeset_identifier(changeset)
66 66 changeset.scmid
67 67 end
68 68
69 69 # Returns the readable identifier for the given git changeset
70 70 def self.format_changeset_identifier(changeset)
71 71 changeset.revision[0, 8]
72 72 end
73 73
74 74 def branches
75 75 scm.branches
76 76 end
77 77
78 78 def tags
79 79 scm.tags
80 80 end
81 81
82 82 def default_branch
83 83 scm.default_branch
84 84 rescue Exception => e
85 85 logger.error "git: error during get default branch: #{e.message}"
86 86 nil
87 87 end
88 88
89 89 def find_changeset_by_name(name)
90 90 if name.present?
91 91 changesets.where(:revision => name.to_s).first ||
92 92 changesets.where('scmid LIKE ?', "#{name}%").first
93 93 end
94 94 end
95 95
96 96 def scm_entries(path=nil, identifier=nil)
97 97 scm.entries(path, identifier, :report_last_commit => extra_report_last_commit)
98 98 end
99 99 protected :scm_entries
100 100
101 101 # With SCMs that have a sequential commit numbering,
102 102 # such as Subversion and Mercurial,
103 103 # Redmine is able to be clever and only fetch changesets
104 104 # going forward from the most recent one it knows about.
105 105 #
106 106 # However, Git does not have a sequential commit numbering.
107 107 #
108 108 # In order to fetch only new adding revisions,
109 109 # Redmine needs to save "heads".
110 110 #
111 111 # In Git and Mercurial, revisions are not in date order.
112 112 # Redmine Mercurial fixed issues.
113 113 # * Redmine Takes Too Long On Large Mercurial Repository
114 114 # http://www.redmine.org/issues/3449
115 115 # * Sorting for changesets might go wrong on Mercurial repos
116 116 # http://www.redmine.org/issues/3567
117 117 #
118 118 # Database revision column is text, so Redmine can not sort by revision.
119 119 # Mercurial has revision number, and revision number guarantees revision order.
120 120 # Redmine Mercurial model stored revisions ordered by database id to database.
121 121 # So, Redmine Mercurial model can use correct ordering revisions.
122 122 #
123 123 # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
124 124 # to get limited revisions from old to new.
125 125 # But, Git 1.7.3.4 does not support --reverse with -n or --skip.
126 126 #
127 127 # The repository can still be fully reloaded by calling #clear_changesets
128 128 # before fetching changesets (eg. for offline resync)
129 129 def fetch_changesets
130 130 scm_brs = branches
131 131 return if scm_brs.nil? || scm_brs.empty?
132 132
133 133 h1 = extra_info || {}
134 134 h = h1.dup
135 135 repo_heads = scm_brs.map{ |br| br.scmid }
136 136 h["heads"] ||= []
137 137 prev_db_heads = h["heads"].dup
138 138 if prev_db_heads.empty?
139 139 prev_db_heads += heads_from_branches_hash
140 140 end
141 141 return if prev_db_heads.sort == repo_heads.sort
142 142
143 143 h["db_consistent"] ||= {}
144 144 if changesets.count == 0
145 145 h["db_consistent"]["ordering"] = 1
146 146 merge_extra_info(h)
147 147 self.save
148 148 elsif ! h["db_consistent"].has_key?("ordering")
149 149 h["db_consistent"]["ordering"] = 0
150 150 merge_extra_info(h)
151 151 self.save
152 152 end
153 153 save_revisions(prev_db_heads, repo_heads)
154 154 end
155 155
156 156 def save_revisions(prev_db_heads, repo_heads)
157 157 h = {}
158 158 opts = {}
159 159 opts[:reverse] = true
160 160 opts[:excludes] = prev_db_heads
161 161 opts[:includes] = repo_heads
162 162
163 163 revisions = scm.revisions('', nil, nil, opts)
164 164 return if revisions.blank?
165 165
166 166 # Make the search for existing revisions in the database in a more sufficient manner
167 167 #
168 168 # Git branch is the reference to the specific revision.
169 169 # Git can *delete* remote branch and *re-push* branch.
170 170 #
171 171 # $ git push remote :branch
172 172 # $ git push remote branch
173 173 #
174 174 # After deleting branch, revisions remain in repository until "git gc".
175 175 # On git 1.7.2.3, default pruning date is 2 weeks.
176 176 # So, "git log --not deleted_branch_head_revision" return code is 0.
177 177 #
178 178 # After re-pushing branch, "git log" returns revisions which are saved in database.
179 179 # So, Redmine needs to scan revisions and database every time.
180 180 #
181 181 # This is replacing the one-after-one queries.
182 182 # Find all revisions, that are in the database, and then remove them
183 183 # from the revision array.
184 184 # Then later we won't need any conditions for db existence.
185 185 # Query for several revisions at once, and remove them
186 186 # from the revisions array, if they are there.
187 187 # Do this in chunks, to avoid eventual memory problems
188 188 # (in case of tens of thousands of commits).
189 189 # If there are no revisions (because the original code's algorithm filtered them),
190 190 # then this part will be stepped over.
191 191 # We make queries, just if there is any revision.
192 192 limit = 100
193 193 offset = 0
194 194 revisions_copy = revisions.clone # revisions will change
195 195 while offset < revisions_copy.size
196 196 scmids = revisions_copy.slice(offset, limit).map{|x| x.scmid}
197 197 recent_changesets_slice = changesets.where(:scmid => scmids)
198 198 # Subtract revisions that redmine already knows about
199 199 recent_revisions = recent_changesets_slice.map{|c| c.scmid}
200 200 revisions.reject!{|r| recent_revisions.include?(r.scmid)}
201 201 offset += limit
202 202 end
203 203 revisions.each do |rev|
204 204 transaction do
205 205 # There is no search in the db for this revision, because above we ensured,
206 206 # that it's not in the db.
207 207 save_revision(rev)
208 208 end
209 209 end
210 210 h["heads"] = repo_heads.dup
211 211 merge_extra_info(h)
212 212 self.save
213 213 end
214 214 private :save_revisions
215 215
216 216 def save_revision(rev)
217 217 parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact
218 218 changeset = Changeset.create(
219 219 :repository => self,
220 220 :revision => rev.identifier,
221 221 :scmid => rev.scmid,
222 222 :committer => rev.author,
223 223 :committed_on => rev.time,
224 224 :comments => rev.message,
225 225 :parents => parents
226 226 )
227 227 unless changeset.new_record?
228 228 rev.paths.each { |change| changeset.create_change(change) }
229 229 end
230 230 changeset
231 231 end
232 232 private :save_revision
233 233
234 234 def heads_from_branches_hash
235 235 h1 = extra_info || {}
236 236 h = h1.dup
237 237 h["branches"] ||= {}
238 238 h['branches'].map{|br, hs| hs['last_scmid']}
239 239 end
240 240
241 241 def latest_changesets(path,rev,limit=10)
242 242 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
243 243 return [] if revisions.nil? || revisions.empty?
244 244 changesets.where(:scmid => revisions.map {|c| c.scmid}).to_a
245 245 end
246 246
247 247 def clear_extra_info_of_changesets
248 248 return if extra_info.nil?
249 249 v = extra_info["extra_report_last_commit"]
250 250 write_attribute(:extra_info, nil)
251 251 h = {}
252 252 h["extra_report_last_commit"] = v
253 253 merge_extra_info(h)
254 254 self.save
255 255 end
256 256 private :clear_extra_info_of_changesets
257
258 def clear_changesets
259 super
260 clear_extra_info_of_changesets
261 end
262 private :clear_changesets
257 263 end
General Comments 0
You need to be logged in to leave comments. Login now