##// END OF EJS Templates
with: use context manager for transaction in strip
Bryan O'Sullivan -
r27873:60ea60fe default
parent child Browse files
Show More
@@ -1,314 +1,310 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
13 13 from .i18n import _
14 14 from .node import short
15 15 from . import (
16 16 bundle2,
17 17 changegroup,
18 18 error,
19 19 exchange,
20 20 util,
21 21 )
22 22
23 23 def _bundle(repo, bases, heads, node, suffix, compress=True):
24 24 """create a bundle with the specified revisions as a backup"""
25 25 cgversion = '01'
26 26 if 'generaldelta' in repo.requirements:
27 27 cgversion = '02'
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 = util.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 changegroup.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.manifest)
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 # Some revisions with rev > striprev may not be descendants of striprev.
95 95 # We have to find these revisions and put them in a bundle, so that
96 96 # we can restore them after the truncations.
97 97 # To create the bundle we use repo.changegroupsubset which requires
98 98 # the list of heads and bases of the set of interesting revisions.
99 99 # (head = revision in the set that has no descendant in the set;
100 100 # base = revision in the set that has no ancestor in the set)
101 101 tostrip = set(striplist)
102 102 for rev in striplist:
103 103 for desc in cl.descendants([rev]):
104 104 tostrip.add(desc)
105 105
106 106 files = _collectfiles(repo, striprev)
107 107 saverevs = _collectbrokencsets(repo, files, striprev)
108 108
109 109 # compute heads
110 110 saveheads = set(saverevs)
111 111 for r in xrange(striprev + 1, len(cl)):
112 112 if r not in tostrip:
113 113 saverevs.add(r)
114 114 saveheads.difference_update(cl.parentrevs(r))
115 115 saveheads.add(r)
116 116 saveheads = [cl.node(r) for r in saveheads]
117 117
118 118 # compute base nodes
119 119 if saverevs:
120 120 descendants = set(cl.descendants(saverevs))
121 121 saverevs.difference_update(descendants)
122 122 savebases = [cl.node(r) for r in saverevs]
123 123 stripbases = [cl.node(r) for r in tostrip]
124 124
125 125 # For a set s, max(parents(s) - s) is the same as max(heads(::s - s)), but
126 126 # is much faster
127 127 newbmtarget = repo.revs('max(parents(%ld) - (%ld))', tostrip, tostrip)
128 128 if newbmtarget:
129 129 newbmtarget = repo[newbmtarget.first()].node()
130 130 else:
131 131 newbmtarget = '.'
132 132
133 133 bm = repo._bookmarks
134 134 updatebm = []
135 135 for m in bm:
136 136 rev = repo[bm[m]].rev()
137 137 if rev in tostrip:
138 138 updatebm.append(m)
139 139
140 140 # create a changegroup for all the branches we need to keep
141 141 backupfile = None
142 142 vfs = repo.vfs
143 143 node = nodelist[-1]
144 144 if backup:
145 145 backupfile = _bundle(repo, stripbases, cl.heads(), node, topic)
146 146 repo.ui.status(_("saved backup bundle to %s\n") %
147 147 vfs.join(backupfile))
148 148 repo.ui.log("backupbundle", "saved backup bundle to %s\n",
149 149 vfs.join(backupfile))
150 150 if saveheads or savebases:
151 151 # do not compress partial bundle if we remove it from disk later
152 152 chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp',
153 153 compress=False)
154 154
155 155 mfst = repo.manifest
156 156
157 157 curtr = repo.currenttransaction()
158 158 if curtr is not None:
159 159 del curtr # avoid carrying reference to transaction for nothing
160 160 msg = _('programming error: cannot strip from inside a transaction')
161 161 raise error.Abort(msg, hint=_('contact your extension maintainer'))
162 162
163 tr = repo.transaction("strip")
164 offset = len(tr.entries)
163 try:
164 with repo.transaction("strip") as tr:
165 offset = len(tr.entries)
165 166
166 try:
167 tr.startgroup()
168 cl.strip(striprev, tr)
169 mfst.strip(striprev, tr)
170 for fn in files:
171 repo.file(fn).strip(striprev, tr)
172 tr.endgroup()
167 tr.startgroup()
168 cl.strip(striprev, tr)
169 mfst.strip(striprev, tr)
170 for fn in files:
171 repo.file(fn).strip(striprev, tr)
172 tr.endgroup()
173 173
174 try:
175 174 for i in xrange(offset, len(tr.entries)):
176 175 file, troffset, ignore = tr.entries[i]
177 176 repo.svfs(file, 'a').truncate(troffset)
178 177 if troffset == 0:
179 178 repo.store.markremoved(file)
180 tr.close()
181 finally:
182 tr.release()
183 179
184 180 if saveheads or savebases:
185 181 ui.note(_("adding branch\n"))
186 182 f = vfs.open(chgrpfile, "rb")
187 183 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
188 184 if not repo.ui.verbose:
189 185 # silence internal shuffling chatter
190 186 repo.ui.pushbuffer()
191 187 if isinstance(gen, bundle2.unbundle20):
192 188 tr = repo.transaction('strip')
193 189 tr.hookargs = {'source': 'strip',
194 190 'url': 'bundle:' + vfs.join(chgrpfile)}
195 191 try:
196 192 bundle2.applybundle(repo, gen, tr, source='strip',
197 193 url='bundle:' + vfs.join(chgrpfile))
198 194 tr.close()
199 195 finally:
200 196 tr.release()
201 197 else:
202 198 gen.apply(repo, 'strip', 'bundle:' + vfs.join(chgrpfile), True)
203 199 if not repo.ui.verbose:
204 200 repo.ui.popbuffer()
205 201 f.close()
206 202
207 203 for m in updatebm:
208 204 bm[m] = repo[newbmtarget].node()
209 205 lock = tr = None
210 206 try:
211 207 lock = repo.lock()
212 208 tr = repo.transaction('repair')
213 209 bm.recordchange(tr)
214 210 tr.close()
215 211 finally:
216 212 tr.release()
217 213 lock.release()
218 214
219 215 # remove undo files
220 216 for undovfs, undofile in repo.undofiles():
221 217 try:
222 218 undovfs.unlink(undofile)
223 219 except OSError as e:
224 220 if e.errno != errno.ENOENT:
225 221 ui.warn(_('error removing %s: %s\n') %
226 222 (undovfs.join(undofile), str(e)))
227 223
228 224 except: # re-raises
229 225 if backupfile:
230 226 ui.warn(_("strip failed, full bundle stored in '%s'\n")
231 227 % vfs.join(backupfile))
232 228 elif saveheads:
233 229 ui.warn(_("strip failed, partial bundle stored in '%s'\n")
234 230 % vfs.join(chgrpfile))
235 231 raise
236 232 else:
237 233 if saveheads or savebases:
238 234 # Remove partial backup only if there were no exceptions
239 235 vfs.unlink(chgrpfile)
240 236
241 237 repo.destroyed()
242 238
243 239 def rebuildfncache(ui, repo):
244 240 """Rebuilds the fncache file from repo history.
245 241
246 242 Missing entries will be added. Extra entries will be removed.
247 243 """
248 244 repo = repo.unfiltered()
249 245
250 246 if 'fncache' not in repo.requirements:
251 247 ui.warn(_('(not rebuilding fncache because repository does not '
252 248 'support fncache)\n'))
253 249 return
254 250
255 251 with repo.lock():
256 252 fnc = repo.store.fncache
257 253 # Trigger load of fncache.
258 254 if 'irrelevant' in fnc:
259 255 pass
260 256
261 257 oldentries = set(fnc.entries)
262 258 newentries = set()
263 259 seenfiles = set()
264 260
265 261 repolen = len(repo)
266 262 for rev in repo:
267 263 ui.progress(_('changeset'), rev, total=repolen)
268 264
269 265 ctx = repo[rev]
270 266 for f in ctx.files():
271 267 # This is to minimize I/O.
272 268 if f in seenfiles:
273 269 continue
274 270 seenfiles.add(f)
275 271
276 272 i = 'data/%s.i' % f
277 273 d = 'data/%s.d' % f
278 274
279 275 if repo.store._exists(i):
280 276 newentries.add(i)
281 277 if repo.store._exists(d):
282 278 newentries.add(d)
283 279
284 280 ui.progress(_('changeset'), None)
285 281
286 282 addcount = len(newentries - oldentries)
287 283 removecount = len(oldentries - newentries)
288 284 for p in sorted(oldentries - newentries):
289 285 ui.write(_('removing %s\n') % p)
290 286 for p in sorted(newentries - oldentries):
291 287 ui.write(_('adding %s\n') % p)
292 288
293 289 if addcount or removecount:
294 290 ui.write(_('%d items added, %d removed from fncache\n') %
295 291 (addcount, removecount))
296 292 fnc.entries = newentries
297 293 fnc._dirty = True
298 294
299 295 with repo.transaction('fncache') as tr:
300 296 fnc.write(tr)
301 297 else:
302 298 ui.write(_('fncache already up to date\n'))
303 299
304 300 def stripbmrevset(repo, mark):
305 301 """
306 302 The revset to strip when strip is called with -B mark
307 303
308 304 Needs to live here so extensions can use it and wrap it even when strip is
309 305 not enabled or not present on a box.
310 306 """
311 307 return repo.revs("ancestors(bookmark(%s)) - "
312 308 "ancestors(head() and not bookmark(%s)) - "
313 309 "ancestors(bookmark() and not bookmark(%s))",
314 310 mark, mark, mark)
General Comments 0
You need to be logged in to leave comments. Login now