Mercurial > hg > soundsoftware-site
comparison app/models/repository/git.rb @ 1115:433d4f72a19b redmine-2.2
Update to Redmine SVN revision 11137 on 2.2-stable branch
author | Chris Cannam |
---|---|
date | Mon, 07 Jan 2013 12:01:42 +0000 |
parents | cbb26bc654de |
children | bb32da3bea34 622f24f53b42 261b3d9a4903 |
comparison
equal
deleted
inserted
replaced
929:5f33065ddc4b | 1115:433d4f72a19b |
---|---|
1 # Redmine - project management software | 1 # Redmine - project management software |
2 # Copyright (C) 2006-2011 Jean-Philippe Lang | 2 # Copyright (C) 2006-2012 Jean-Philippe Lang |
3 # Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com | 3 # Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com |
4 # | 4 # |
5 # This program is free software; you can redistribute it and/or | 5 # This program is free software; you can redistribute it and/or |
6 # modify it under the terms of the GNU General Public License | 6 # modify it under the terms of the GNU General Public License |
7 # as published by the Free Software Foundation; either version 2 | 7 # as published by the Free Software Foundation; either version 2 |
20 | 20 |
21 class Repository::Git < Repository | 21 class Repository::Git < Repository |
22 attr_protected :root_url | 22 attr_protected :root_url |
23 validates_presence_of :url | 23 validates_presence_of :url |
24 | 24 |
25 def self.human_attribute_name(attribute_key_name) | 25 def self.human_attribute_name(attribute_key_name, *args) |
26 attr_name = attribute_key_name | 26 attr_name = attribute_key_name.to_s |
27 if attr_name == "url" | 27 if attr_name == "url" |
28 attr_name = "path_to_repository" | 28 attr_name = "path_to_repository" |
29 end | 29 end |
30 super(attr_name) | 30 super(attr_name, *args) |
31 end | 31 end |
32 | 32 |
33 def self.scm_adapter_class | 33 def self.scm_adapter_class |
34 Redmine::Scm::Adapters::GitAdapter | 34 Redmine::Scm::Adapters::GitAdapter |
35 end | 35 end |
85 logger.error "git: error during get default branch: #{e.message}" | 85 logger.error "git: error during get default branch: #{e.message}" |
86 nil | 86 nil |
87 end | 87 end |
88 | 88 |
89 def find_changeset_by_name(name) | 89 def find_changeset_by_name(name) |
90 return nil if name.nil? || name.empty? | 90 if name.present? |
91 e = changesets.find(:first, :conditions => ['revision = ?', name.to_s]) | 91 changesets.where(:revision => name.to_s).first || |
92 return e if e | 92 changesets.where('scmid LIKE ?', "#{name}%").first |
93 changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"]) | 93 end |
94 end | 94 end |
95 | 95 |
96 def entries(path=nil, identifier=nil) | 96 def entries(path=nil, identifier=nil) |
97 scm.entries(path, | 97 entries = scm.entries(path, identifier, :report_last_commit => extra_report_last_commit) |
98 identifier, | 98 load_entries_changesets(entries) |
99 options = {:report_last_commit => extra_report_last_commit}) | 99 entries |
100 end | 100 end |
101 | 101 |
102 # With SCMs that have a sequential commit numbering, | 102 # With SCMs that have a sequential commit numbering, |
103 # such as Subversion and Mercurial, | 103 # such as Subversion and Mercurial, |
104 # Redmine is able to be clever and only fetch changesets | 104 # Redmine is able to be clever and only fetch changesets |
105 # going forward from the most recent one it knows about. | 105 # going forward from the most recent one it knows about. |
106 # | 106 # |
107 # However, Git does not have a sequential commit numbering. | 107 # However, Git does not have a sequential commit numbering. |
108 # | 108 # |
109 # In order to fetch only new adding revisions, | 109 # In order to fetch only new adding revisions, |
110 # Redmine needs to parse revisions per branch. | 110 # Redmine needs to save "heads". |
111 # Branch "last_scmid" is for this requirement. | |
112 # | 111 # |
113 # In Git and Mercurial, revisions are not in date order. | 112 # In Git and Mercurial, revisions are not in date order. |
114 # Redmine Mercurial fixed issues. | 113 # Redmine Mercurial fixed issues. |
115 # * Redmine Takes Too Long On Large Mercurial Repository | 114 # * Redmine Takes Too Long On Large Mercurial Repository |
116 # http://www.redmine.org/issues/3449 | 115 # http://www.redmine.org/issues/3449 |
129 # The repository can still be fully reloaded by calling #clear_changesets | 128 # The repository can still be fully reloaded by calling #clear_changesets |
130 # before fetching changesets (eg. for offline resync) | 129 # before fetching changesets (eg. for offline resync) |
131 def fetch_changesets | 130 def fetch_changesets |
132 scm_brs = branches | 131 scm_brs = branches |
133 return if scm_brs.nil? || scm_brs.empty? | 132 return if scm_brs.nil? || scm_brs.empty? |
133 | |
134 h1 = extra_info || {} | 134 h1 = extra_info || {} |
135 h = h1.dup | 135 h = h1.dup |
136 h["branches"] ||= {} | 136 repo_heads = scm_brs.map{ |br| br.scmid } |
137 h["heads"] ||= [] | |
138 prev_db_heads = h["heads"].dup | |
139 if prev_db_heads.empty? | |
140 prev_db_heads += heads_from_branches_hash | |
141 end | |
142 return if prev_db_heads.sort == repo_heads.sort | |
143 | |
137 h["db_consistent"] ||= {} | 144 h["db_consistent"] ||= {} |
138 if changesets.count == 0 | 145 if changesets.count == 0 |
139 h["db_consistent"]["ordering"] = 1 | 146 h["db_consistent"]["ordering"] = 1 |
140 merge_extra_info(h) | 147 merge_extra_info(h) |
141 self.save | 148 self.save |
142 elsif ! h["db_consistent"].has_key?("ordering") | 149 elsif ! h["db_consistent"].has_key?("ordering") |
143 h["db_consistent"]["ordering"] = 0 | 150 h["db_consistent"]["ordering"] = 0 |
144 merge_extra_info(h) | 151 merge_extra_info(h) |
145 self.save | 152 self.save |
146 end | 153 end |
147 scm_brs.each do |br1| | 154 save_revisions(prev_db_heads, repo_heads) |
148 br = br1.to_s | 155 end |
149 from_scmid = nil | 156 |
150 from_scmid = h["branches"][br]["last_scmid"] if h["branches"][br] | 157 def save_revisions(prev_db_heads, repo_heads) |
151 h["branches"][br] ||= {} | 158 h = {} |
152 scm.revisions('', from_scmid, br, {:reverse => true}) do |rev| | 159 opts = {} |
153 db_rev = find_changeset_by_name(rev.revision) | 160 opts[:reverse] = true |
154 transaction do | 161 opts[:excludes] = prev_db_heads |
155 if db_rev.nil? | 162 opts[:includes] = repo_heads |
156 db_saved_rev = save_revision(rev) | 163 |
157 parents = {} | 164 revisions = scm.revisions('', nil, nil, opts) |
158 parents[db_saved_rev] = rev.parents unless rev.parents.nil? | 165 return if revisions.blank? |
159 parents.each do |ch, chparents| | 166 |
160 ch.parents = chparents.collect{|rp| find_changeset_by_name(rp)}.compact | 167 # Make the search for existing revisions in the database in a more sufficient manner |
161 end | 168 # |
162 end | 169 # Git branch is the reference to the specific revision. |
163 h["branches"][br]["last_scmid"] = rev.scmid | 170 # Git can *delete* remote branch and *re-push* branch. |
164 merge_extra_info(h) | 171 # |
165 self.save | 172 # $ git push remote :branch |
166 end | 173 # $ git push remote branch |
174 # | |
175 # After deleting branch, revisions remain in repository until "git gc". | |
176 # On git 1.7.2.3, default pruning date is 2 weeks. | |
177 # So, "git log --not deleted_branch_head_revision" return code is 0. | |
178 # | |
179 # After re-pushing branch, "git log" returns revisions which are saved in database. | |
180 # So, Redmine needs to scan revisions and database every time. | |
181 # | |
182 # This is replacing the one-after-one queries. | |
183 # Find all revisions, that are in the database, and then remove them from the revision array. | |
184 # Then later we won't need any conditions for db existence. | |
185 # Query for several revisions at once, and remove them from the revisions array, if they are there. | |
186 # Do this in chunks, to avoid eventual memory problems (in case of tens of thousands of commits). | |
187 # If there are no revisions (because the original code's algorithm filtered them), | |
188 # then this part will be stepped over. | |
189 # We make queries, just if there is any revision. | |
190 limit = 100 | |
191 offset = 0 | |
192 revisions_copy = revisions.clone # revisions will change | |
193 while offset < revisions_copy.size | |
194 recent_changesets_slice = changesets.find( | |
195 :all, | |
196 :conditions => [ | |
197 'scmid IN (?)', | |
198 revisions_copy.slice(offset, limit).map{|x| x.scmid} | |
199 ] | |
200 ) | |
201 # Subtract revisions that redmine already knows about | |
202 recent_revisions = recent_changesets_slice.map{|c| c.scmid} | |
203 revisions.reject!{|r| recent_revisions.include?(r.scmid)} | |
204 offset += limit | |
205 end | |
206 | |
207 revisions.each do |rev| | |
208 transaction do | |
209 # There is no search in the db for this revision, because above we ensured, | |
210 # that it's not in the db. | |
211 save_revision(rev) | |
167 end | 212 end |
168 end | 213 end |
169 end | 214 h["heads"] = repo_heads.dup |
215 merge_extra_info(h) | |
216 self.save | |
217 end | |
218 private :save_revisions | |
170 | 219 |
171 def save_revision(rev) | 220 def save_revision(rev) |
172 changeset = Changeset.new( | 221 parents = (rev.parents || []).collect{|rp| find_changeset_by_name(rp)}.compact |
222 changeset = Changeset.create( | |
173 :repository => self, | 223 :repository => self, |
174 :revision => rev.identifier, | 224 :revision => rev.identifier, |
175 :scmid => rev.scmid, | 225 :scmid => rev.scmid, |
176 :committer => rev.author, | 226 :committer => rev.author, |
177 :committed_on => rev.time, | 227 :committed_on => rev.time, |
178 :comments => rev.message | 228 :comments => rev.message, |
229 :parents => parents | |
179 ) | 230 ) |
180 if changeset.save | 231 unless changeset.new_record? |
181 rev.paths.each do |file| | 232 rev.paths.each { |change| changeset.create_change(change) } |
182 Change.create( | |
183 :changeset => changeset, | |
184 :action => file[:action], | |
185 :path => file[:path]) | |
186 end | |
187 end | 233 end |
188 changeset | 234 changeset |
189 end | 235 end |
190 private :save_revision | 236 private :save_revision |
237 | |
238 def heads_from_branches_hash | |
239 h1 = extra_info || {} | |
240 h = h1.dup | |
241 h["branches"] ||= {} | |
242 h['branches'].map{|br, hs| hs['last_scmid']} | |
243 end | |
191 | 244 |
192 def latest_changesets(path,rev,limit=10) | 245 def latest_changesets(path,rev,limit=10) |
193 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false) | 246 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false) |
194 return [] if revisions.nil? || revisions.empty? | 247 return [] if revisions.nil? || revisions.empty? |
195 | 248 |
200 revisions.map!{|c| c.scmid} | 253 revisions.map!{|c| c.scmid} |
201 ], | 254 ], |
202 :order => 'committed_on DESC' | 255 :order => 'committed_on DESC' |
203 ) | 256 ) |
204 end | 257 end |
258 | |
259 def clear_extra_info_of_changesets | |
260 return if extra_info.nil? | |
261 v = extra_info["extra_report_last_commit"] | |
262 write_attribute(:extra_info, nil) | |
263 h = {} | |
264 h["extra_report_last_commit"] = v | |
265 merge_extra_info(h) | |
266 self.save | |
267 end | |
268 private :clear_extra_info_of_changesets | |
205 end | 269 end |