##// END OF EJS Templates
scm: git: add the comment of the reason to scan database in fetching every time (#10470)...
Toshi MARUYAMA -
r9153:76435033e7a1
parent child
Show More
@@ -1,252 +1,266
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 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 return nil if name.nil? || name.empty?
91 91 e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
92 92 return e if e
93 93 changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"])
94 94 end
95 95
96 96 def entries(path=nil, identifier=nil)
97 97 scm.entries(path,
98 98 identifier,
99 99 options = {:report_last_commit => extra_report_last_commit})
100 100 end
101 101
102 102 # With SCMs that have a sequential commit numbering,
103 103 # such as Subversion and Mercurial,
104 104 # Redmine is able to be clever and only fetch changesets
105 105 # going forward from the most recent one it knows about.
106 106 #
107 107 # However, Git does not have a sequential commit numbering.
108 108 #
109 109 # In order to fetch only new adding revisions,
110 110 # Redmine needs to save "heads".
111 111 #
112 112 # In Git and Mercurial, revisions are not in date order.
113 113 # Redmine Mercurial fixed issues.
114 114 # * Redmine Takes Too Long On Large Mercurial Repository
115 115 # http://www.redmine.org/issues/3449
116 116 # * Sorting for changesets might go wrong on Mercurial repos
117 117 # http://www.redmine.org/issues/3567
118 118 #
119 119 # Database revision column is text, so Redmine can not sort by revision.
120 120 # Mercurial has revision number, and revision number guarantees revision order.
121 121 # Redmine Mercurial model stored revisions ordered by database id to database.
122 122 # So, Redmine Mercurial model can use correct ordering revisions.
123 123 #
124 124 # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
125 125 # to get limited revisions from old to new.
126 126 # But, Git 1.7.3.4 does not support --reverse with -n or --skip.
127 127 #
128 128 # The repository can still be fully reloaded by calling #clear_changesets
129 129 # before fetching changesets (eg. for offline resync)
130 130 def fetch_changesets
131 131 scm_brs = branches
132 132 return if scm_brs.nil? || scm_brs.empty?
133 133
134 134 h1 = extra_info || {}
135 135 h = h1.dup
136 136 repo_heads = scm_brs.map{ |br| br.scmid }
137 137 h["heads"] ||= []
138 138 prev_db_heads = h["heads"].dup
139 139 if prev_db_heads.empty?
140 140 prev_db_heads += heads_from_branches_hash
141 141 end
142 142 return if prev_db_heads.sort == repo_heads.sort
143 143
144 144 h["db_consistent"] ||= {}
145 145 if changesets.count == 0
146 146 h["db_consistent"]["ordering"] = 1
147 147 merge_extra_info(h)
148 148 self.save
149 149 elsif ! h["db_consistent"].has_key?("ordering")
150 150 h["db_consistent"]["ordering"] = 0
151 151 merge_extra_info(h)
152 152 self.save
153 153 end
154 154 save_revisions(prev_db_heads, repo_heads)
155 155 end
156 156
157 157 def save_revisions(prev_db_heads, repo_heads)
158 158 h = {}
159 159 opts = {}
160 160 opts[:reverse] = true
161 161 opts[:excludes] = prev_db_heads
162 162 opts[:includes] = repo_heads
163 163
164 164 revisions = scm.revisions('', nil, nil, opts)
165 165 return if revisions.blank?
166 166
167 167 # Make the search for existing revisions in the database in a more sufficient manner
168 #
169 # Git branch is the reference to the specific revision.
170 # Git can *delete* remote branch and *re-push* branch.
171 #
172 # $ git push remote :branch
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 #
168 182 # This is replacing the one-after-one queries.
169 183 # Find all revisions, that are in the database, and then remove them from the revision array.
170 184 # Then later we won't need any conditions for db existence.
171 185 # Query for several revisions at once, and remove them from the revisions array, if they are there.
172 186 # Do this in chunks, to avoid eventual memory problems (in case of tens of thousands of commits).
173 187 # If there are no revisions (because the original code's algorithm filtered them),
174 188 # then this part will be stepped over.
175 189 # We make queries, just if there is any revision.
176 190 limit = 100
177 191 offset = 0
178 192 revisions_copy = revisions.clone # revisions will change
179 193 while offset < revisions_copy.size
180 194 recent_changesets_slice = changesets.find(
181 195 :all,
182 196 :conditions => [
183 197 'scmid IN (?)',
184 198 revisions_copy.slice(offset, limit).map{|x| x.scmid}
185 199 ]
186 200 )
187 201 # Subtract revisions that redmine already knows about
188 202 recent_revisions = recent_changesets_slice.map{|c| c.scmid}
189 203 revisions.reject!{|r| recent_revisions.include?(r.scmid)}
190 204 offset += limit
191 205 end
192 206
193 207 revisions.each do |rev|
194 208 transaction do
195 209 # There is no search in the db for this revision, because above we ensured,
196 210 # that it's not in the db.
197 211 db_saved_rev = save_revision(rev)
198 212 parents = {}
199 213 parents[db_saved_rev] = rev.parents unless rev.parents.nil?
200 214 parents.each do |ch, chparents|
201 215 ch.parents = chparents.collect{|rp| find_changeset_by_name(rp)}.compact
202 216 end
203 217 end
204 218 end
205 219 h["heads"] = repo_heads.dup
206 220 merge_extra_info(h)
207 221 self.save
208 222 end
209 223 private :save_revisions
210 224
211 225 def save_revision(rev)
212 226 changeset = Changeset.new(
213 227 :repository => self,
214 228 :revision => rev.identifier,
215 229 :scmid => rev.scmid,
216 230 :committer => rev.author,
217 231 :committed_on => rev.time,
218 232 :comments => rev.message
219 233 )
220 234 if changeset.save
221 235 rev.paths.each do |file|
222 236 Change.create(
223 237 :changeset => changeset,
224 238 :action => file[:action],
225 239 :path => file[:path])
226 240 end
227 241 end
228 242 changeset
229 243 end
230 244 private :save_revision
231 245
232 246 def heads_from_branches_hash
233 247 h1 = extra_info || {}
234 248 h = h1.dup
235 249 h["branches"] ||= {}
236 250 h['branches'].map{|br, hs| hs['last_scmid']}
237 251 end
238 252
239 253 def latest_changesets(path,rev,limit=10)
240 254 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
241 255 return [] if revisions.nil? || revisions.empty?
242 256
243 257 changesets.find(
244 258 :all,
245 259 :conditions => [
246 260 "scmid IN (?)",
247 261 revisions.map!{|c| c.scmid}
248 262 ],
249 263 :order => 'committed_on DESC'
250 264 )
251 265 end
252 266 end
General Comments 0
You need to be logged in to leave comments. Login now