##// END OF EJS Templates
strip: move tree strip logic to it's own function...
Durham Goode -
r32196:a2be2abe default
parent child Browse files
Show More
@@ -1,354 +1,358 b''
1 1 # repair.py - functions for repository repair for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 # Copyright 2007 Matt Mackall
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import errno
12 12 import hashlib
13 13
14 14 from .i18n import _
15 15 from .node import short
16 16 from . import (
17 17 bundle2,
18 18 changegroup,
19 19 error,
20 20 exchange,
21 21 obsolete,
22 22 util,
23 23 )
24 24
25 25 def _bundle(repo, bases, heads, node, suffix, compress=True):
26 26 """create a bundle with the specified revisions as a backup"""
27 27 cgversion = changegroup.safeversion(repo)
28 28
29 29 cg = changegroup.changegroupsubset(repo, bases, heads, 'strip',
30 30 version=cgversion)
31 31 backupdir = "strip-backup"
32 32 vfs = repo.vfs
33 33 if not vfs.isdir(backupdir):
34 34 vfs.mkdir(backupdir)
35 35
36 36 # Include a hash of all the nodes in the filename for uniqueness
37 37 allcommits = repo.set('%ln::%ln', bases, heads)
38 38 allhashes = sorted(c.hex() for c in allcommits)
39 39 totalhash = hashlib.sha1(''.join(allhashes)).hexdigest()
40 40 name = "%s/%s-%s-%s.hg" % (backupdir, short(node), totalhash[:8], suffix)
41 41
42 42 comp = None
43 43 if cgversion != '01':
44 44 bundletype = "HG20"
45 45 if compress:
46 46 comp = 'BZ'
47 47 elif compress:
48 48 bundletype = "HG10BZ"
49 49 else:
50 50 bundletype = "HG10UN"
51 51 return bundle2.writebundle(repo.ui, cg, name, bundletype, vfs,
52 52 compression=comp)
53 53
54 54 def _collectfiles(repo, striprev):
55 55 """find out the filelogs affected by the strip"""
56 56 files = set()
57 57
58 58 for x in xrange(striprev, len(repo)):
59 59 files.update(repo[x].files())
60 60
61 61 return sorted(files)
62 62
63 63 def _collectbrokencsets(repo, files, striprev):
64 64 """return the changesets which will be broken by the truncation"""
65 65 s = set()
66 66 def collectone(revlog):
67 67 _, brokenset = revlog.getstrippoint(striprev)
68 68 s.update([revlog.linkrev(r) for r in brokenset])
69 69
70 70 collectone(repo.manifestlog._revlog)
71 71 for fname in files:
72 72 collectone(repo.file(fname))
73 73
74 74 return s
75 75
76 76 def strip(ui, repo, nodelist, backup=True, topic='backup'):
77 77 # This function operates within a transaction of its own, but does
78 78 # not take any lock on the repo.
79 79 # Simple way to maintain backwards compatibility for this
80 80 # argument.
81 81 if backup in ['none', 'strip']:
82 82 backup = False
83 83
84 84 repo = repo.unfiltered()
85 85 repo.destroying()
86 86
87 87 cl = repo.changelog
88 88 # TODO handle undo of merge sets
89 89 if isinstance(nodelist, str):
90 90 nodelist = [nodelist]
91 91 striplist = [cl.rev(node) for node in nodelist]
92 92 striprev = min(striplist)
93 93
94 94 files = _collectfiles(repo, striprev)
95 95 saverevs = _collectbrokencsets(repo, files, striprev)
96 96
97 97 # Some revisions with rev > striprev may not be descendants of striprev.
98 98 # We have to find these revisions and put them in a bundle, so that
99 99 # we can restore them after the truncations.
100 100 # To create the bundle we use repo.changegroupsubset which requires
101 101 # the list of heads and bases of the set of interesting revisions.
102 102 # (head = revision in the set that has no descendant in the set;
103 103 # base = revision in the set that has no ancestor in the set)
104 104 tostrip = set(striplist)
105 105 saveheads = set(saverevs)
106 106 for r in cl.revs(start=striprev + 1):
107 107 if any(p in tostrip for p in cl.parentrevs(r)):
108 108 tostrip.add(r)
109 109
110 110 if r not in tostrip:
111 111 saverevs.add(r)
112 112 saveheads.difference_update(cl.parentrevs(r))
113 113 saveheads.add(r)
114 114 saveheads = [cl.node(r) for r in saveheads]
115 115
116 116 # compute base nodes
117 117 if saverevs:
118 118 descendants = set(cl.descendants(saverevs))
119 119 saverevs.difference_update(descendants)
120 120 savebases = [cl.node(r) for r in saverevs]
121 121 stripbases = [cl.node(r) for r in tostrip]
122 122
123 123 # For a set s, max(parents(s) - s) is the same as max(heads(::s - s)), but
124 124 # is much faster
125 125 newbmtarget = repo.revs('max(parents(%ld) - (%ld))', tostrip, tostrip)
126 126 if newbmtarget:
127 127 newbmtarget = repo[newbmtarget.first()].node()
128 128 else:
129 129 newbmtarget = '.'
130 130
131 131 bm = repo._bookmarks
132 132 updatebm = []
133 133 for m in bm:
134 134 rev = repo[bm[m]].rev()
135 135 if rev in tostrip:
136 136 updatebm.append(m)
137 137
138 138 # create a changegroup for all the branches we need to keep
139 139 backupfile = None
140 140 vfs = repo.vfs
141 141 node = nodelist[-1]
142 142 if backup:
143 143 backupfile = _bundle(repo, stripbases, cl.heads(), node, topic)
144 144 repo.ui.status(_("saved backup bundle to %s\n") %
145 145 vfs.join(backupfile))
146 146 repo.ui.log("backupbundle", "saved backup bundle to %s\n",
147 147 vfs.join(backupfile))
148 148 tmpbundlefile = None
149 149 if saveheads:
150 150 # do not compress temporary bundle if we remove it from disk later
151 151 tmpbundlefile = _bundle(repo, savebases, saveheads, node, 'temp',
152 152 compress=False)
153 153
154 154 mfst = repo.manifestlog._revlog
155 155
156 156 curtr = repo.currenttransaction()
157 157 if curtr is not None:
158 158 del curtr # avoid carrying reference to transaction for nothing
159 159 raise error.ProgrammingError('cannot strip from inside a transaction')
160 160
161 161 try:
162 162 with repo.transaction("strip") as tr:
163 163 offset = len(tr.entries)
164 164
165 165 tr.startgroup()
166 166 cl.strip(striprev, tr)
167 167 mfst.strip(striprev, tr)
168 if 'treemanifest' in repo.requirements: # safe but unnecessary
169 # otherwise
170 for unencoded, encoded, size in repo.store.datafiles():
171 if (unencoded.startswith('meta/') and
172 unencoded.endswith('00manifest.i')):
173 dir = unencoded[5:-12]
174 repo.manifestlog._revlog.dirlog(dir).strip(striprev, tr)
168 striptrees(repo, tr, striprev, files)
169
175 170 for fn in files:
176 171 repo.file(fn).strip(striprev, tr)
177 172 tr.endgroup()
178 173
179 174 for i in xrange(offset, len(tr.entries)):
180 175 file, troffset, ignore = tr.entries[i]
181 176 with repo.svfs(file, 'a', checkambig=True) as fp:
182 177 fp.truncate(troffset)
183 178 if troffset == 0:
184 179 repo.store.markremoved(file)
185 180
186 181 if tmpbundlefile:
187 182 ui.note(_("adding branch\n"))
188 183 f = vfs.open(tmpbundlefile, "rb")
189 184 gen = exchange.readbundle(ui, f, tmpbundlefile, vfs)
190 185 if not repo.ui.verbose:
191 186 # silence internal shuffling chatter
192 187 repo.ui.pushbuffer()
193 188 if isinstance(gen, bundle2.unbundle20):
194 189 with repo.transaction('strip') as tr:
195 190 tr.hookargs = {'source': 'strip',
196 191 'url': 'bundle:' + vfs.join(tmpbundlefile)}
197 192 bundle2.applybundle(repo, gen, tr, source='strip',
198 193 url='bundle:' + vfs.join(tmpbundlefile))
199 194 else:
200 195 gen.apply(repo, 'strip', 'bundle:' + vfs.join(tmpbundlefile),
201 196 True)
202 197 if not repo.ui.verbose:
203 198 repo.ui.popbuffer()
204 199 f.close()
205 200 repo._phasecache.invalidate()
206 201
207 202 for m in updatebm:
208 203 bm[m] = repo[newbmtarget].node()
209 204
210 205 with repo.lock():
211 206 with repo.transaction('repair') as tr:
212 207 bm.recordchange(tr)
213 208
214 209 # remove undo files
215 210 for undovfs, undofile in repo.undofiles():
216 211 try:
217 212 undovfs.unlink(undofile)
218 213 except OSError as e:
219 214 if e.errno != errno.ENOENT:
220 215 ui.warn(_('error removing %s: %s\n') %
221 216 (undovfs.join(undofile), str(e)))
222 217
223 218 except: # re-raises
224 219 if backupfile:
225 220 ui.warn(_("strip failed, backup bundle stored in '%s'\n")
226 221 % vfs.join(backupfile))
227 222 if tmpbundlefile:
228 223 ui.warn(_("strip failed, unrecovered changes stored in '%s'\n")
229 224 % vfs.join(tmpbundlefile))
230 225 ui.warn(_("(fix the problem, then recover the changesets with "
231 226 "\"hg unbundle '%s'\")\n") % vfs.join(tmpbundlefile))
232 227 raise
233 228 else:
234 229 if tmpbundlefile:
235 230 # Remove temporary bundle only if there were no exceptions
236 231 vfs.unlink(tmpbundlefile)
237 232
238 233 repo.destroyed()
239 234 # return the backup file path (or None if 'backup' was False) so
240 235 # extensions can use it
241 236 return backupfile
242 237
238 def striptrees(repo, tr, striprev, files):
239 if 'treemanifest' in repo.requirements: # safe but unnecessary
240 # otherwise
241 for unencoded, encoded, size in repo.store.datafiles():
242 if (unencoded.startswith('meta/') and
243 unencoded.endswith('00manifest.i')):
244 dir = unencoded[5:-12]
245 repo.manifestlog._revlog.dirlog(dir).strip(striprev, tr)
246
243 247 def rebuildfncache(ui, repo):
244 248 """Rebuilds the fncache file from repo history.
245 249
246 250 Missing entries will be added. Extra entries will be removed.
247 251 """
248 252 repo = repo.unfiltered()
249 253
250 254 if 'fncache' not in repo.requirements:
251 255 ui.warn(_('(not rebuilding fncache because repository does not '
252 256 'support fncache)\n'))
253 257 return
254 258
255 259 with repo.lock():
256 260 fnc = repo.store.fncache
257 261 # Trigger load of fncache.
258 262 if 'irrelevant' in fnc:
259 263 pass
260 264
261 265 oldentries = set(fnc.entries)
262 266 newentries = set()
263 267 seenfiles = set()
264 268
265 269 repolen = len(repo)
266 270 for rev in repo:
267 271 ui.progress(_('rebuilding'), rev, total=repolen,
268 272 unit=_('changesets'))
269 273
270 274 ctx = repo[rev]
271 275 for f in ctx.files():
272 276 # This is to minimize I/O.
273 277 if f in seenfiles:
274 278 continue
275 279 seenfiles.add(f)
276 280
277 281 i = 'data/%s.i' % f
278 282 d = 'data/%s.d' % f
279 283
280 284 if repo.store._exists(i):
281 285 newentries.add(i)
282 286 if repo.store._exists(d):
283 287 newentries.add(d)
284 288
285 289 ui.progress(_('rebuilding'), None)
286 290
287 291 if 'treemanifest' in repo.requirements: # safe but unnecessary otherwise
288 292 for dir in util.dirs(seenfiles):
289 293 i = 'meta/%s/00manifest.i' % dir
290 294 d = 'meta/%s/00manifest.d' % dir
291 295
292 296 if repo.store._exists(i):
293 297 newentries.add(i)
294 298 if repo.store._exists(d):
295 299 newentries.add(d)
296 300
297 301 addcount = len(newentries - oldentries)
298 302 removecount = len(oldentries - newentries)
299 303 for p in sorted(oldentries - newentries):
300 304 ui.write(_('removing %s\n') % p)
301 305 for p in sorted(newentries - oldentries):
302 306 ui.write(_('adding %s\n') % p)
303 307
304 308 if addcount or removecount:
305 309 ui.write(_('%d items added, %d removed from fncache\n') %
306 310 (addcount, removecount))
307 311 fnc.entries = newentries
308 312 fnc._dirty = True
309 313
310 314 with repo.transaction('fncache') as tr:
311 315 fnc.write(tr)
312 316 else:
313 317 ui.write(_('fncache already up to date\n'))
314 318
315 319 def stripbmrevset(repo, mark):
316 320 """
317 321 The revset to strip when strip is called with -B mark
318 322
319 323 Needs to live here so extensions can use it and wrap it even when strip is
320 324 not enabled or not present on a box.
321 325 """
322 326 return repo.revs("ancestors(bookmark(%s)) - "
323 327 "ancestors(head() and not bookmark(%s)) - "
324 328 "ancestors(bookmark() and not bookmark(%s))",
325 329 mark, mark, mark)
326 330
327 331 def deleteobsmarkers(obsstore, indices):
328 332 """Delete some obsmarkers from obsstore and return how many were deleted
329 333
330 334 'indices' is a list of ints which are the indices
331 335 of the markers to be deleted.
332 336
333 337 Every invocation of this function completely rewrites the obsstore file,
334 338 skipping the markers we want to be removed. The new temporary file is
335 339 created, remaining markers are written there and on .close() this file
336 340 gets atomically renamed to obsstore, thus guaranteeing consistency."""
337 341 if not indices:
338 342 # we don't want to rewrite the obsstore with the same content
339 343 return
340 344
341 345 left = []
342 346 current = obsstore._all
343 347 n = 0
344 348 for i, m in enumerate(current):
345 349 if i in indices:
346 350 n += 1
347 351 continue
348 352 left.append(m)
349 353
350 354 newobsstorefile = obsstore.svfs('obsstore', 'w', atomictemp=True)
351 355 for bytes in obsolete.encodemarkers(left, True, obsstore._version):
352 356 newobsstorefile.write(bytes)
353 357 newobsstorefile.close()
354 358 return n
General Comments 0
You need to be logged in to leave comments. Login now