##// END OF EJS Templates
Don't validate the repository when updating/clearing extra info (#19400)....
Jean-Philippe Lang -
r13761:98c28b467b30
parent child
Show More
@@ -1,263 +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 self.save
212 save(:validate => false)
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 self.save
254 save(:validate => false)
255 255 end
256 256 private :clear_extra_info_of_changesets
257 257
258 258 def clear_changesets
259 259 super
260 260 clear_extra_info_of_changesets
261 261 end
262 262 private :clear_changesets
263 263 end
General Comments 0
You need to be logged in to leave comments. Login now