##// END OF EJS Templates
transaction: drop per-file extra data support...
Joerg Sonnenberger -
r46866:63edc384 default
parent child Browse files
Show More
@@ -1,541 +1,541 b''
1 # repair.py - functions for repository repair for mercurial
1 # repair.py - functions for repository repair for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 # Copyright 2007 Matt Mackall
4 # Copyright 2007 Matt Mackall
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12
12
13 from .i18n import _
13 from .i18n import _
14 from .node import (
14 from .node import (
15 hex,
15 hex,
16 short,
16 short,
17 )
17 )
18 from . import (
18 from . import (
19 bundle2,
19 bundle2,
20 changegroup,
20 changegroup,
21 discovery,
21 discovery,
22 error,
22 error,
23 exchange,
23 exchange,
24 obsolete,
24 obsolete,
25 obsutil,
25 obsutil,
26 pathutil,
26 pathutil,
27 phases,
27 phases,
28 pycompat,
28 pycompat,
29 requirements,
29 requirements,
30 scmutil,
30 scmutil,
31 util,
31 util,
32 )
32 )
33 from .utils import (
33 from .utils import (
34 hashutil,
34 hashutil,
35 stringutil,
35 stringutil,
36 )
36 )
37
37
38
38
39 def backupbundle(
39 def backupbundle(
40 repo, bases, heads, node, suffix, compress=True, obsolescence=True
40 repo, bases, heads, node, suffix, compress=True, obsolescence=True
41 ):
41 ):
42 """create a bundle with the specified revisions as a backup"""
42 """create a bundle with the specified revisions as a backup"""
43
43
44 backupdir = b"strip-backup"
44 backupdir = b"strip-backup"
45 vfs = repo.vfs
45 vfs = repo.vfs
46 if not vfs.isdir(backupdir):
46 if not vfs.isdir(backupdir):
47 vfs.mkdir(backupdir)
47 vfs.mkdir(backupdir)
48
48
49 # Include a hash of all the nodes in the filename for uniqueness
49 # Include a hash of all the nodes in the filename for uniqueness
50 allcommits = repo.set(b'%ln::%ln', bases, heads)
50 allcommits = repo.set(b'%ln::%ln', bases, heads)
51 allhashes = sorted(c.hex() for c in allcommits)
51 allhashes = sorted(c.hex() for c in allcommits)
52 totalhash = hashutil.sha1(b''.join(allhashes)).digest()
52 totalhash = hashutil.sha1(b''.join(allhashes)).digest()
53 name = b"%s/%s-%s-%s.hg" % (
53 name = b"%s/%s-%s-%s.hg" % (
54 backupdir,
54 backupdir,
55 short(node),
55 short(node),
56 hex(totalhash[:4]),
56 hex(totalhash[:4]),
57 suffix,
57 suffix,
58 )
58 )
59
59
60 cgversion = changegroup.localversion(repo)
60 cgversion = changegroup.localversion(repo)
61 comp = None
61 comp = None
62 if cgversion != b'01':
62 if cgversion != b'01':
63 bundletype = b"HG20"
63 bundletype = b"HG20"
64 if compress:
64 if compress:
65 comp = b'BZ'
65 comp = b'BZ'
66 elif compress:
66 elif compress:
67 bundletype = b"HG10BZ"
67 bundletype = b"HG10BZ"
68 else:
68 else:
69 bundletype = b"HG10UN"
69 bundletype = b"HG10UN"
70
70
71 outgoing = discovery.outgoing(repo, missingroots=bases, ancestorsof=heads)
71 outgoing = discovery.outgoing(repo, missingroots=bases, ancestorsof=heads)
72 contentopts = {
72 contentopts = {
73 b'cg.version': cgversion,
73 b'cg.version': cgversion,
74 b'obsolescence': obsolescence,
74 b'obsolescence': obsolescence,
75 b'phases': True,
75 b'phases': True,
76 }
76 }
77 return bundle2.writenewbundle(
77 return bundle2.writenewbundle(
78 repo.ui,
78 repo.ui,
79 repo,
79 repo,
80 b'strip',
80 b'strip',
81 name,
81 name,
82 bundletype,
82 bundletype,
83 outgoing,
83 outgoing,
84 contentopts,
84 contentopts,
85 vfs,
85 vfs,
86 compression=comp,
86 compression=comp,
87 )
87 )
88
88
89
89
90 def _collectfiles(repo, striprev):
90 def _collectfiles(repo, striprev):
91 """find out the filelogs affected by the strip"""
91 """find out the filelogs affected by the strip"""
92 files = set()
92 files = set()
93
93
94 for x in pycompat.xrange(striprev, len(repo)):
94 for x in pycompat.xrange(striprev, len(repo)):
95 files.update(repo[x].files())
95 files.update(repo[x].files())
96
96
97 return sorted(files)
97 return sorted(files)
98
98
99
99
100 def _collectrevlog(revlog, striprev):
100 def _collectrevlog(revlog, striprev):
101 _, brokenset = revlog.getstrippoint(striprev)
101 _, brokenset = revlog.getstrippoint(striprev)
102 return [revlog.linkrev(r) for r in brokenset]
102 return [revlog.linkrev(r) for r in brokenset]
103
103
104
104
105 def _collectbrokencsets(repo, files, striprev):
105 def _collectbrokencsets(repo, files, striprev):
106 """return the changesets which will be broken by the truncation"""
106 """return the changesets which will be broken by the truncation"""
107 s = set()
107 s = set()
108
108
109 for revlog in manifestrevlogs(repo):
109 for revlog in manifestrevlogs(repo):
110 s.update(_collectrevlog(revlog, striprev))
110 s.update(_collectrevlog(revlog, striprev))
111 for fname in files:
111 for fname in files:
112 s.update(_collectrevlog(repo.file(fname), striprev))
112 s.update(_collectrevlog(repo.file(fname), striprev))
113
113
114 return s
114 return s
115
115
116
116
117 def strip(ui, repo, nodelist, backup=True, topic=b'backup'):
117 def strip(ui, repo, nodelist, backup=True, topic=b'backup'):
118 # This function requires the caller to lock the repo, but it operates
118 # This function requires the caller to lock the repo, but it operates
119 # within a transaction of its own, and thus requires there to be no current
119 # within a transaction of its own, and thus requires there to be no current
120 # transaction when it is called.
120 # transaction when it is called.
121 if repo.currenttransaction() is not None:
121 if repo.currenttransaction() is not None:
122 raise error.ProgrammingError(b'cannot strip from inside a transaction')
122 raise error.ProgrammingError(b'cannot strip from inside a transaction')
123
123
124 # Simple way to maintain backwards compatibility for this
124 # Simple way to maintain backwards compatibility for this
125 # argument.
125 # argument.
126 if backup in [b'none', b'strip']:
126 if backup in [b'none', b'strip']:
127 backup = False
127 backup = False
128
128
129 repo = repo.unfiltered()
129 repo = repo.unfiltered()
130 repo.destroying()
130 repo.destroying()
131 vfs = repo.vfs
131 vfs = repo.vfs
132 # load bookmark before changelog to avoid side effect from outdated
132 # load bookmark before changelog to avoid side effect from outdated
133 # changelog (see repo._refreshchangelog)
133 # changelog (see repo._refreshchangelog)
134 repo._bookmarks
134 repo._bookmarks
135 cl = repo.changelog
135 cl = repo.changelog
136
136
137 # TODO handle undo of merge sets
137 # TODO handle undo of merge sets
138 if isinstance(nodelist, bytes):
138 if isinstance(nodelist, bytes):
139 nodelist = [nodelist]
139 nodelist = [nodelist]
140 striplist = [cl.rev(node) for node in nodelist]
140 striplist = [cl.rev(node) for node in nodelist]
141 striprev = min(striplist)
141 striprev = min(striplist)
142
142
143 files = _collectfiles(repo, striprev)
143 files = _collectfiles(repo, striprev)
144 saverevs = _collectbrokencsets(repo, files, striprev)
144 saverevs = _collectbrokencsets(repo, files, striprev)
145
145
146 # Some revisions with rev > striprev may not be descendants of striprev.
146 # Some revisions with rev > striprev may not be descendants of striprev.
147 # We have to find these revisions and put them in a bundle, so that
147 # We have to find these revisions and put them in a bundle, so that
148 # we can restore them after the truncations.
148 # we can restore them after the truncations.
149 # To create the bundle we use repo.changegroupsubset which requires
149 # To create the bundle we use repo.changegroupsubset which requires
150 # the list of heads and bases of the set of interesting revisions.
150 # the list of heads and bases of the set of interesting revisions.
151 # (head = revision in the set that has no descendant in the set;
151 # (head = revision in the set that has no descendant in the set;
152 # base = revision in the set that has no ancestor in the set)
152 # base = revision in the set that has no ancestor in the set)
153 tostrip = set(striplist)
153 tostrip = set(striplist)
154 saveheads = set(saverevs)
154 saveheads = set(saverevs)
155 for r in cl.revs(start=striprev + 1):
155 for r in cl.revs(start=striprev + 1):
156 if any(p in tostrip for p in cl.parentrevs(r)):
156 if any(p in tostrip for p in cl.parentrevs(r)):
157 tostrip.add(r)
157 tostrip.add(r)
158
158
159 if r not in tostrip:
159 if r not in tostrip:
160 saverevs.add(r)
160 saverevs.add(r)
161 saveheads.difference_update(cl.parentrevs(r))
161 saveheads.difference_update(cl.parentrevs(r))
162 saveheads.add(r)
162 saveheads.add(r)
163 saveheads = [cl.node(r) for r in saveheads]
163 saveheads = [cl.node(r) for r in saveheads]
164
164
165 # compute base nodes
165 # compute base nodes
166 if saverevs:
166 if saverevs:
167 descendants = set(cl.descendants(saverevs))
167 descendants = set(cl.descendants(saverevs))
168 saverevs.difference_update(descendants)
168 saverevs.difference_update(descendants)
169 savebases = [cl.node(r) for r in saverevs]
169 savebases = [cl.node(r) for r in saverevs]
170 stripbases = [cl.node(r) for r in tostrip]
170 stripbases = [cl.node(r) for r in tostrip]
171
171
172 stripobsidx = obsmarkers = ()
172 stripobsidx = obsmarkers = ()
173 if repo.ui.configbool(b'devel', b'strip-obsmarkers'):
173 if repo.ui.configbool(b'devel', b'strip-obsmarkers'):
174 obsmarkers = obsutil.exclusivemarkers(repo, stripbases)
174 obsmarkers = obsutil.exclusivemarkers(repo, stripbases)
175 if obsmarkers:
175 if obsmarkers:
176 stripobsidx = [
176 stripobsidx = [
177 i for i, m in enumerate(repo.obsstore) if m in obsmarkers
177 i for i, m in enumerate(repo.obsstore) if m in obsmarkers
178 ]
178 ]
179
179
180 newbmtarget, updatebm = _bookmarkmovements(repo, tostrip)
180 newbmtarget, updatebm = _bookmarkmovements(repo, tostrip)
181
181
182 backupfile = None
182 backupfile = None
183 node = nodelist[-1]
183 node = nodelist[-1]
184 if backup:
184 if backup:
185 backupfile = _createstripbackup(repo, stripbases, node, topic)
185 backupfile = _createstripbackup(repo, stripbases, node, topic)
186 # create a changegroup for all the branches we need to keep
186 # create a changegroup for all the branches we need to keep
187 tmpbundlefile = None
187 tmpbundlefile = None
188 if saveheads:
188 if saveheads:
189 # do not compress temporary bundle if we remove it from disk later
189 # do not compress temporary bundle if we remove it from disk later
190 #
190 #
191 # We do not include obsolescence, it might re-introduce prune markers
191 # We do not include obsolescence, it might re-introduce prune markers
192 # we are trying to strip. This is harmless since the stripped markers
192 # we are trying to strip. This is harmless since the stripped markers
193 # are already backed up and we did not touched the markers for the
193 # are already backed up and we did not touched the markers for the
194 # saved changesets.
194 # saved changesets.
195 tmpbundlefile = backupbundle(
195 tmpbundlefile = backupbundle(
196 repo,
196 repo,
197 savebases,
197 savebases,
198 saveheads,
198 saveheads,
199 node,
199 node,
200 b'temp',
200 b'temp',
201 compress=False,
201 compress=False,
202 obsolescence=False,
202 obsolescence=False,
203 )
203 )
204
204
205 with ui.uninterruptible():
205 with ui.uninterruptible():
206 try:
206 try:
207 with repo.transaction(b"strip") as tr:
207 with repo.transaction(b"strip") as tr:
208 # TODO this code violates the interface abstraction of the
208 # TODO this code violates the interface abstraction of the
209 # transaction and makes assumptions that file storage is
209 # transaction and makes assumptions that file storage is
210 # using append-only files. We'll need some kind of storage
210 # using append-only files. We'll need some kind of storage
211 # API to handle stripping for us.
211 # API to handle stripping for us.
212 offset = len(tr._entries)
212 offset = len(tr._entries)
213
213
214 tr.startgroup()
214 tr.startgroup()
215 cl.strip(striprev, tr)
215 cl.strip(striprev, tr)
216 stripmanifest(repo, striprev, tr, files)
216 stripmanifest(repo, striprev, tr, files)
217
217
218 for fn in files:
218 for fn in files:
219 repo.file(fn).strip(striprev, tr)
219 repo.file(fn).strip(striprev, tr)
220 tr.endgroup()
220 tr.endgroup()
221
221
222 for i in pycompat.xrange(offset, len(tr._entries)):
222 for i in pycompat.xrange(offset, len(tr._entries)):
223 file, troffset, ignore = tr._entries[i]
223 file, troffset = tr._entries[i]
224 with repo.svfs(file, b'a', checkambig=True) as fp:
224 with repo.svfs(file, b'a', checkambig=True) as fp:
225 fp.truncate(troffset)
225 fp.truncate(troffset)
226 if troffset == 0:
226 if troffset == 0:
227 repo.store.markremoved(file)
227 repo.store.markremoved(file)
228
228
229 deleteobsmarkers(repo.obsstore, stripobsidx)
229 deleteobsmarkers(repo.obsstore, stripobsidx)
230 del repo.obsstore
230 del repo.obsstore
231 repo.invalidatevolatilesets()
231 repo.invalidatevolatilesets()
232 repo._phasecache.filterunknown(repo)
232 repo._phasecache.filterunknown(repo)
233
233
234 if tmpbundlefile:
234 if tmpbundlefile:
235 ui.note(_(b"adding branch\n"))
235 ui.note(_(b"adding branch\n"))
236 f = vfs.open(tmpbundlefile, b"rb")
236 f = vfs.open(tmpbundlefile, b"rb")
237 gen = exchange.readbundle(ui, f, tmpbundlefile, vfs)
237 gen = exchange.readbundle(ui, f, tmpbundlefile, vfs)
238 if not repo.ui.verbose:
238 if not repo.ui.verbose:
239 # silence internal shuffling chatter
239 # silence internal shuffling chatter
240 repo.ui.pushbuffer()
240 repo.ui.pushbuffer()
241 tmpbundleurl = b'bundle:' + vfs.join(tmpbundlefile)
241 tmpbundleurl = b'bundle:' + vfs.join(tmpbundlefile)
242 txnname = b'strip'
242 txnname = b'strip'
243 if not isinstance(gen, bundle2.unbundle20):
243 if not isinstance(gen, bundle2.unbundle20):
244 txnname = b"strip\n%s" % util.hidepassword(tmpbundleurl)
244 txnname = b"strip\n%s" % util.hidepassword(tmpbundleurl)
245 with repo.transaction(txnname) as tr:
245 with repo.transaction(txnname) as tr:
246 bundle2.applybundle(
246 bundle2.applybundle(
247 repo, gen, tr, source=b'strip', url=tmpbundleurl
247 repo, gen, tr, source=b'strip', url=tmpbundleurl
248 )
248 )
249 if not repo.ui.verbose:
249 if not repo.ui.verbose:
250 repo.ui.popbuffer()
250 repo.ui.popbuffer()
251 f.close()
251 f.close()
252
252
253 with repo.transaction(b'repair') as tr:
253 with repo.transaction(b'repair') as tr:
254 bmchanges = [(m, repo[newbmtarget].node()) for m in updatebm]
254 bmchanges = [(m, repo[newbmtarget].node()) for m in updatebm]
255 repo._bookmarks.applychanges(repo, tr, bmchanges)
255 repo._bookmarks.applychanges(repo, tr, bmchanges)
256
256
257 # remove undo files
257 # remove undo files
258 for undovfs, undofile in repo.undofiles():
258 for undovfs, undofile in repo.undofiles():
259 try:
259 try:
260 undovfs.unlink(undofile)
260 undovfs.unlink(undofile)
261 except OSError as e:
261 except OSError as e:
262 if e.errno != errno.ENOENT:
262 if e.errno != errno.ENOENT:
263 ui.warn(
263 ui.warn(
264 _(b'error removing %s: %s\n')
264 _(b'error removing %s: %s\n')
265 % (
265 % (
266 undovfs.join(undofile),
266 undovfs.join(undofile),
267 stringutil.forcebytestr(e),
267 stringutil.forcebytestr(e),
268 )
268 )
269 )
269 )
270
270
271 except: # re-raises
271 except: # re-raises
272 if backupfile:
272 if backupfile:
273 ui.warn(
273 ui.warn(
274 _(b"strip failed, backup bundle stored in '%s'\n")
274 _(b"strip failed, backup bundle stored in '%s'\n")
275 % vfs.join(backupfile)
275 % vfs.join(backupfile)
276 )
276 )
277 if tmpbundlefile:
277 if tmpbundlefile:
278 ui.warn(
278 ui.warn(
279 _(b"strip failed, unrecovered changes stored in '%s'\n")
279 _(b"strip failed, unrecovered changes stored in '%s'\n")
280 % vfs.join(tmpbundlefile)
280 % vfs.join(tmpbundlefile)
281 )
281 )
282 ui.warn(
282 ui.warn(
283 _(
283 _(
284 b"(fix the problem, then recover the changesets with "
284 b"(fix the problem, then recover the changesets with "
285 b"\"hg unbundle '%s'\")\n"
285 b"\"hg unbundle '%s'\")\n"
286 )
286 )
287 % vfs.join(tmpbundlefile)
287 % vfs.join(tmpbundlefile)
288 )
288 )
289 raise
289 raise
290 else:
290 else:
291 if tmpbundlefile:
291 if tmpbundlefile:
292 # Remove temporary bundle only if there were no exceptions
292 # Remove temporary bundle only if there were no exceptions
293 vfs.unlink(tmpbundlefile)
293 vfs.unlink(tmpbundlefile)
294
294
295 repo.destroyed()
295 repo.destroyed()
296 # return the backup file path (or None if 'backup' was False) so
296 # return the backup file path (or None if 'backup' was False) so
297 # extensions can use it
297 # extensions can use it
298 return backupfile
298 return backupfile
299
299
300
300
301 def softstrip(ui, repo, nodelist, backup=True, topic=b'backup'):
301 def softstrip(ui, repo, nodelist, backup=True, topic=b'backup'):
302 """perform a "soft" strip using the archived phase"""
302 """perform a "soft" strip using the archived phase"""
303 tostrip = [c.node() for c in repo.set(b'sort(%ln::)', nodelist)]
303 tostrip = [c.node() for c in repo.set(b'sort(%ln::)', nodelist)]
304 if not tostrip:
304 if not tostrip:
305 return None
305 return None
306
306
307 newbmtarget, updatebm = _bookmarkmovements(repo, tostrip)
307 newbmtarget, updatebm = _bookmarkmovements(repo, tostrip)
308 if backup:
308 if backup:
309 node = tostrip[0]
309 node = tostrip[0]
310 backupfile = _createstripbackup(repo, tostrip, node, topic)
310 backupfile = _createstripbackup(repo, tostrip, node, topic)
311
311
312 with repo.transaction(b'strip') as tr:
312 with repo.transaction(b'strip') as tr:
313 phases.retractboundary(repo, tr, phases.archived, tostrip)
313 phases.retractboundary(repo, tr, phases.archived, tostrip)
314 bmchanges = [(m, repo[newbmtarget].node()) for m in updatebm]
314 bmchanges = [(m, repo[newbmtarget].node()) for m in updatebm]
315 repo._bookmarks.applychanges(repo, tr, bmchanges)
315 repo._bookmarks.applychanges(repo, tr, bmchanges)
316 return backupfile
316 return backupfile
317
317
318
318
319 def _bookmarkmovements(repo, tostrip):
319 def _bookmarkmovements(repo, tostrip):
320 # compute necessary bookmark movement
320 # compute necessary bookmark movement
321 bm = repo._bookmarks
321 bm = repo._bookmarks
322 updatebm = []
322 updatebm = []
323 for m in bm:
323 for m in bm:
324 rev = repo[bm[m]].rev()
324 rev = repo[bm[m]].rev()
325 if rev in tostrip:
325 if rev in tostrip:
326 updatebm.append(m)
326 updatebm.append(m)
327 newbmtarget = None
327 newbmtarget = None
328 # If we need to move bookmarks, compute bookmark
328 # If we need to move bookmarks, compute bookmark
329 # targets. Otherwise we can skip doing this logic.
329 # targets. Otherwise we can skip doing this logic.
330 if updatebm:
330 if updatebm:
331 # For a set s, max(parents(s) - s) is the same as max(heads(::s - s)),
331 # For a set s, max(parents(s) - s) is the same as max(heads(::s - s)),
332 # but is much faster
332 # but is much faster
333 newbmtarget = repo.revs(b'max(parents(%ld) - (%ld))', tostrip, tostrip)
333 newbmtarget = repo.revs(b'max(parents(%ld) - (%ld))', tostrip, tostrip)
334 if newbmtarget:
334 if newbmtarget:
335 newbmtarget = repo[newbmtarget.first()].node()
335 newbmtarget = repo[newbmtarget.first()].node()
336 else:
336 else:
337 newbmtarget = b'.'
337 newbmtarget = b'.'
338 return newbmtarget, updatebm
338 return newbmtarget, updatebm
339
339
340
340
341 def _createstripbackup(repo, stripbases, node, topic):
341 def _createstripbackup(repo, stripbases, node, topic):
342 # backup the changeset we are about to strip
342 # backup the changeset we are about to strip
343 vfs = repo.vfs
343 vfs = repo.vfs
344 cl = repo.changelog
344 cl = repo.changelog
345 backupfile = backupbundle(repo, stripbases, cl.heads(), node, topic)
345 backupfile = backupbundle(repo, stripbases, cl.heads(), node, topic)
346 repo.ui.status(_(b"saved backup bundle to %s\n") % vfs.join(backupfile))
346 repo.ui.status(_(b"saved backup bundle to %s\n") % vfs.join(backupfile))
347 repo.ui.log(
347 repo.ui.log(
348 b"backupbundle", b"saved backup bundle to %s\n", vfs.join(backupfile)
348 b"backupbundle", b"saved backup bundle to %s\n", vfs.join(backupfile)
349 )
349 )
350 return backupfile
350 return backupfile
351
351
352
352
353 def safestriproots(ui, repo, nodes):
353 def safestriproots(ui, repo, nodes):
354 """return list of roots of nodes where descendants are covered by nodes"""
354 """return list of roots of nodes where descendants are covered by nodes"""
355 torev = repo.unfiltered().changelog.rev
355 torev = repo.unfiltered().changelog.rev
356 revs = {torev(n) for n in nodes}
356 revs = {torev(n) for n in nodes}
357 # tostrip = wanted - unsafe = wanted - ancestors(orphaned)
357 # tostrip = wanted - unsafe = wanted - ancestors(orphaned)
358 # orphaned = affected - wanted
358 # orphaned = affected - wanted
359 # affected = descendants(roots(wanted))
359 # affected = descendants(roots(wanted))
360 # wanted = revs
360 # wanted = revs
361 revset = b'%ld - ( ::( (roots(%ld):: and not _phase(%s)) -%ld) )'
361 revset = b'%ld - ( ::( (roots(%ld):: and not _phase(%s)) -%ld) )'
362 tostrip = set(repo.revs(revset, revs, revs, phases.internal, revs))
362 tostrip = set(repo.revs(revset, revs, revs, phases.internal, revs))
363 notstrip = revs - tostrip
363 notstrip = revs - tostrip
364 if notstrip:
364 if notstrip:
365 nodestr = b', '.join(sorted(short(repo[n].node()) for n in notstrip))
365 nodestr = b', '.join(sorted(short(repo[n].node()) for n in notstrip))
366 ui.warn(
366 ui.warn(
367 _(b'warning: orphaned descendants detected, not stripping %s\n')
367 _(b'warning: orphaned descendants detected, not stripping %s\n')
368 % nodestr
368 % nodestr
369 )
369 )
370 return [c.node() for c in repo.set(b'roots(%ld)', tostrip)]
370 return [c.node() for c in repo.set(b'roots(%ld)', tostrip)]
371
371
372
372
373 class stripcallback(object):
373 class stripcallback(object):
374 """used as a transaction postclose callback"""
374 """used as a transaction postclose callback"""
375
375
376 def __init__(self, ui, repo, backup, topic):
376 def __init__(self, ui, repo, backup, topic):
377 self.ui = ui
377 self.ui = ui
378 self.repo = repo
378 self.repo = repo
379 self.backup = backup
379 self.backup = backup
380 self.topic = topic or b'backup'
380 self.topic = topic or b'backup'
381 self.nodelist = []
381 self.nodelist = []
382
382
383 def addnodes(self, nodes):
383 def addnodes(self, nodes):
384 self.nodelist.extend(nodes)
384 self.nodelist.extend(nodes)
385
385
386 def __call__(self, tr):
386 def __call__(self, tr):
387 roots = safestriproots(self.ui, self.repo, self.nodelist)
387 roots = safestriproots(self.ui, self.repo, self.nodelist)
388 if roots:
388 if roots:
389 strip(self.ui, self.repo, roots, self.backup, self.topic)
389 strip(self.ui, self.repo, roots, self.backup, self.topic)
390
390
391
391
392 def delayedstrip(ui, repo, nodelist, topic=None, backup=True):
392 def delayedstrip(ui, repo, nodelist, topic=None, backup=True):
393 """like strip, but works inside transaction and won't strip irreverent revs
393 """like strip, but works inside transaction and won't strip irreverent revs
394
394
395 nodelist must explicitly contain all descendants. Otherwise a warning will
395 nodelist must explicitly contain all descendants. Otherwise a warning will
396 be printed that some nodes are not stripped.
396 be printed that some nodes are not stripped.
397
397
398 Will do a backup if `backup` is True. The last non-None "topic" will be
398 Will do a backup if `backup` is True. The last non-None "topic" will be
399 used as the backup topic name. The default backup topic name is "backup".
399 used as the backup topic name. The default backup topic name is "backup".
400 """
400 """
401 tr = repo.currenttransaction()
401 tr = repo.currenttransaction()
402 if not tr:
402 if not tr:
403 nodes = safestriproots(ui, repo, nodelist)
403 nodes = safestriproots(ui, repo, nodelist)
404 return strip(ui, repo, nodes, backup=backup, topic=topic)
404 return strip(ui, repo, nodes, backup=backup, topic=topic)
405 # transaction postclose callbacks are called in alphabet order.
405 # transaction postclose callbacks are called in alphabet order.
406 # use '\xff' as prefix so we are likely to be called last.
406 # use '\xff' as prefix so we are likely to be called last.
407 callback = tr.getpostclose(b'\xffstrip')
407 callback = tr.getpostclose(b'\xffstrip')
408 if callback is None:
408 if callback is None:
409 callback = stripcallback(ui, repo, backup=backup, topic=topic)
409 callback = stripcallback(ui, repo, backup=backup, topic=topic)
410 tr.addpostclose(b'\xffstrip', callback)
410 tr.addpostclose(b'\xffstrip', callback)
411 if topic:
411 if topic:
412 callback.topic = topic
412 callback.topic = topic
413 callback.addnodes(nodelist)
413 callback.addnodes(nodelist)
414
414
415
415
416 def stripmanifest(repo, striprev, tr, files):
416 def stripmanifest(repo, striprev, tr, files):
417 for revlog in manifestrevlogs(repo):
417 for revlog in manifestrevlogs(repo):
418 revlog.strip(striprev, tr)
418 revlog.strip(striprev, tr)
419
419
420
420
421 def manifestrevlogs(repo):
421 def manifestrevlogs(repo):
422 yield repo.manifestlog.getstorage(b'')
422 yield repo.manifestlog.getstorage(b'')
423 if scmutil.istreemanifest(repo):
423 if scmutil.istreemanifest(repo):
424 # This logic is safe if treemanifest isn't enabled, but also
424 # This logic is safe if treemanifest isn't enabled, but also
425 # pointless, so we skip it if treemanifest isn't enabled.
425 # pointless, so we skip it if treemanifest isn't enabled.
426 for unencoded, encoded, size in repo.store.datafiles():
426 for unencoded, encoded, size in repo.store.datafiles():
427 if unencoded.startswith(b'meta/') and unencoded.endswith(
427 if unencoded.startswith(b'meta/') and unencoded.endswith(
428 b'00manifest.i'
428 b'00manifest.i'
429 ):
429 ):
430 dir = unencoded[5:-12]
430 dir = unencoded[5:-12]
431 yield repo.manifestlog.getstorage(dir)
431 yield repo.manifestlog.getstorage(dir)
432
432
433
433
434 def rebuildfncache(ui, repo):
434 def rebuildfncache(ui, repo):
435 """Rebuilds the fncache file from repo history.
435 """Rebuilds the fncache file from repo history.
436
436
437 Missing entries will be added. Extra entries will be removed.
437 Missing entries will be added. Extra entries will be removed.
438 """
438 """
439 repo = repo.unfiltered()
439 repo = repo.unfiltered()
440
440
441 if b'fncache' not in repo.requirements:
441 if b'fncache' not in repo.requirements:
442 ui.warn(
442 ui.warn(
443 _(
443 _(
444 b'(not rebuilding fncache because repository does not '
444 b'(not rebuilding fncache because repository does not '
445 b'support fncache)\n'
445 b'support fncache)\n'
446 )
446 )
447 )
447 )
448 return
448 return
449
449
450 with repo.lock():
450 with repo.lock():
451 fnc = repo.store.fncache
451 fnc = repo.store.fncache
452 fnc.ensureloaded(warn=ui.warn)
452 fnc.ensureloaded(warn=ui.warn)
453
453
454 oldentries = set(fnc.entries)
454 oldentries = set(fnc.entries)
455 newentries = set()
455 newentries = set()
456 seenfiles = set()
456 seenfiles = set()
457
457
458 progress = ui.makeprogress(
458 progress = ui.makeprogress(
459 _(b'rebuilding'), unit=_(b'changesets'), total=len(repo)
459 _(b'rebuilding'), unit=_(b'changesets'), total=len(repo)
460 )
460 )
461 for rev in repo:
461 for rev in repo:
462 progress.update(rev)
462 progress.update(rev)
463
463
464 ctx = repo[rev]
464 ctx = repo[rev]
465 for f in ctx.files():
465 for f in ctx.files():
466 # This is to minimize I/O.
466 # This is to minimize I/O.
467 if f in seenfiles:
467 if f in seenfiles:
468 continue
468 continue
469 seenfiles.add(f)
469 seenfiles.add(f)
470
470
471 i = b'data/%s.i' % f
471 i = b'data/%s.i' % f
472 d = b'data/%s.d' % f
472 d = b'data/%s.d' % f
473
473
474 if repo.store._exists(i):
474 if repo.store._exists(i):
475 newentries.add(i)
475 newentries.add(i)
476 if repo.store._exists(d):
476 if repo.store._exists(d):
477 newentries.add(d)
477 newentries.add(d)
478
478
479 progress.complete()
479 progress.complete()
480
480
481 if requirements.TREEMANIFEST_REQUIREMENT in repo.requirements:
481 if requirements.TREEMANIFEST_REQUIREMENT in repo.requirements:
482 # This logic is safe if treemanifest isn't enabled, but also
482 # This logic is safe if treemanifest isn't enabled, but also
483 # pointless, so we skip it if treemanifest isn't enabled.
483 # pointless, so we skip it if treemanifest isn't enabled.
484 for dir in pathutil.dirs(seenfiles):
484 for dir in pathutil.dirs(seenfiles):
485 i = b'meta/%s/00manifest.i' % dir
485 i = b'meta/%s/00manifest.i' % dir
486 d = b'meta/%s/00manifest.d' % dir
486 d = b'meta/%s/00manifest.d' % dir
487
487
488 if repo.store._exists(i):
488 if repo.store._exists(i):
489 newentries.add(i)
489 newentries.add(i)
490 if repo.store._exists(d):
490 if repo.store._exists(d):
491 newentries.add(d)
491 newentries.add(d)
492
492
493 addcount = len(newentries - oldentries)
493 addcount = len(newentries - oldentries)
494 removecount = len(oldentries - newentries)
494 removecount = len(oldentries - newentries)
495 for p in sorted(oldentries - newentries):
495 for p in sorted(oldentries - newentries):
496 ui.write(_(b'removing %s\n') % p)
496 ui.write(_(b'removing %s\n') % p)
497 for p in sorted(newentries - oldentries):
497 for p in sorted(newentries - oldentries):
498 ui.write(_(b'adding %s\n') % p)
498 ui.write(_(b'adding %s\n') % p)
499
499
500 if addcount or removecount:
500 if addcount or removecount:
501 ui.write(
501 ui.write(
502 _(b'%d items added, %d removed from fncache\n')
502 _(b'%d items added, %d removed from fncache\n')
503 % (addcount, removecount)
503 % (addcount, removecount)
504 )
504 )
505 fnc.entries = newentries
505 fnc.entries = newentries
506 fnc._dirty = True
506 fnc._dirty = True
507
507
508 with repo.transaction(b'fncache') as tr:
508 with repo.transaction(b'fncache') as tr:
509 fnc.write(tr)
509 fnc.write(tr)
510 else:
510 else:
511 ui.write(_(b'fncache already up to date\n'))
511 ui.write(_(b'fncache already up to date\n'))
512
512
513
513
514 def deleteobsmarkers(obsstore, indices):
514 def deleteobsmarkers(obsstore, indices):
515 """Delete some obsmarkers from obsstore and return how many were deleted
515 """Delete some obsmarkers from obsstore and return how many were deleted
516
516
517 'indices' is a list of ints which are the indices
517 'indices' is a list of ints which are the indices
518 of the markers to be deleted.
518 of the markers to be deleted.
519
519
520 Every invocation of this function completely rewrites the obsstore file,
520 Every invocation of this function completely rewrites the obsstore file,
521 skipping the markers we want to be removed. The new temporary file is
521 skipping the markers we want to be removed. The new temporary file is
522 created, remaining markers are written there and on .close() this file
522 created, remaining markers are written there and on .close() this file
523 gets atomically renamed to obsstore, thus guaranteeing consistency."""
523 gets atomically renamed to obsstore, thus guaranteeing consistency."""
524 if not indices:
524 if not indices:
525 # we don't want to rewrite the obsstore with the same content
525 # we don't want to rewrite the obsstore with the same content
526 return
526 return
527
527
528 left = []
528 left = []
529 current = obsstore._all
529 current = obsstore._all
530 n = 0
530 n = 0
531 for i, m in enumerate(current):
531 for i, m in enumerate(current):
532 if i in indices:
532 if i in indices:
533 n += 1
533 n += 1
534 continue
534 continue
535 left.append(m)
535 left.append(m)
536
536
537 newobsstorefile = obsstore.svfs(b'obsstore', b'w', atomictemp=True)
537 newobsstorefile = obsstore.svfs(b'obsstore', b'w', atomictemp=True)
538 for bytes in obsolete.encodemarkers(left, True, obsstore._version):
538 for bytes in obsolete.encodemarkers(left, True, obsstore._version):
539 newobsstorefile.write(bytes)
539 newobsstorefile.write(bytes)
540 newobsstorefile.close()
540 newobsstorefile.close()
541 return n
541 return n
@@ -1,3087 +1,3082 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import collections
16 import collections
17 import contextlib
17 import contextlib
18 import errno
18 import errno
19 import io
19 import io
20 import os
20 import os
21 import struct
21 import struct
22 import zlib
22 import zlib
23
23
24 # import stuff from node for others to import from revlog
24 # import stuff from node for others to import from revlog
25 from .node import (
25 from .node import (
26 bin,
26 bin,
27 hex,
27 hex,
28 nullhex,
28 nullhex,
29 nullid,
29 nullid,
30 nullrev,
30 nullrev,
31 short,
31 short,
32 wdirfilenodeids,
32 wdirfilenodeids,
33 wdirhex,
33 wdirhex,
34 wdirid,
34 wdirid,
35 wdirrev,
35 wdirrev,
36 )
36 )
37 from .i18n import _
37 from .i18n import _
38 from .pycompat import getattr
38 from .pycompat import getattr
39 from .revlogutils.constants import (
39 from .revlogutils.constants import (
40 FLAG_GENERALDELTA,
40 FLAG_GENERALDELTA,
41 FLAG_INLINE_DATA,
41 FLAG_INLINE_DATA,
42 REVLOGV0,
42 REVLOGV0,
43 REVLOGV1,
43 REVLOGV1,
44 REVLOGV1_FLAGS,
44 REVLOGV1_FLAGS,
45 REVLOGV2,
45 REVLOGV2,
46 REVLOGV2_FLAGS,
46 REVLOGV2_FLAGS,
47 REVLOG_DEFAULT_FLAGS,
47 REVLOG_DEFAULT_FLAGS,
48 REVLOG_DEFAULT_FORMAT,
48 REVLOG_DEFAULT_FORMAT,
49 REVLOG_DEFAULT_VERSION,
49 REVLOG_DEFAULT_VERSION,
50 )
50 )
51 from .revlogutils.flagutil import (
51 from .revlogutils.flagutil import (
52 REVIDX_DEFAULT_FLAGS,
52 REVIDX_DEFAULT_FLAGS,
53 REVIDX_ELLIPSIS,
53 REVIDX_ELLIPSIS,
54 REVIDX_EXTSTORED,
54 REVIDX_EXTSTORED,
55 REVIDX_FLAGS_ORDER,
55 REVIDX_FLAGS_ORDER,
56 REVIDX_HASCOPIESINFO,
56 REVIDX_HASCOPIESINFO,
57 REVIDX_ISCENSORED,
57 REVIDX_ISCENSORED,
58 REVIDX_RAWTEXT_CHANGING_FLAGS,
58 REVIDX_RAWTEXT_CHANGING_FLAGS,
59 REVIDX_SIDEDATA,
59 REVIDX_SIDEDATA,
60 )
60 )
61 from .thirdparty import attr
61 from .thirdparty import attr
62 from . import (
62 from . import (
63 ancestor,
63 ancestor,
64 dagop,
64 dagop,
65 error,
65 error,
66 mdiff,
66 mdiff,
67 policy,
67 policy,
68 pycompat,
68 pycompat,
69 templatefilters,
69 templatefilters,
70 util,
70 util,
71 )
71 )
72 from .interfaces import (
72 from .interfaces import (
73 repository,
73 repository,
74 util as interfaceutil,
74 util as interfaceutil,
75 )
75 )
76 from .revlogutils import (
76 from .revlogutils import (
77 deltas as deltautil,
77 deltas as deltautil,
78 flagutil,
78 flagutil,
79 nodemap as nodemaputil,
79 nodemap as nodemaputil,
80 sidedata as sidedatautil,
80 sidedata as sidedatautil,
81 )
81 )
82 from .utils import (
82 from .utils import (
83 storageutil,
83 storageutil,
84 stringutil,
84 stringutil,
85 )
85 )
86
86
87 # blanked usage of all the name to prevent pyflakes constraints
87 # blanked usage of all the name to prevent pyflakes constraints
88 # We need these name available in the module for extensions.
88 # We need these name available in the module for extensions.
89 REVLOGV0
89 REVLOGV0
90 REVLOGV1
90 REVLOGV1
91 REVLOGV2
91 REVLOGV2
92 FLAG_INLINE_DATA
92 FLAG_INLINE_DATA
93 FLAG_GENERALDELTA
93 FLAG_GENERALDELTA
94 REVLOG_DEFAULT_FLAGS
94 REVLOG_DEFAULT_FLAGS
95 REVLOG_DEFAULT_FORMAT
95 REVLOG_DEFAULT_FORMAT
96 REVLOG_DEFAULT_VERSION
96 REVLOG_DEFAULT_VERSION
97 REVLOGV1_FLAGS
97 REVLOGV1_FLAGS
98 REVLOGV2_FLAGS
98 REVLOGV2_FLAGS
99 REVIDX_ISCENSORED
99 REVIDX_ISCENSORED
100 REVIDX_ELLIPSIS
100 REVIDX_ELLIPSIS
101 REVIDX_SIDEDATA
101 REVIDX_SIDEDATA
102 REVIDX_HASCOPIESINFO
102 REVIDX_HASCOPIESINFO
103 REVIDX_EXTSTORED
103 REVIDX_EXTSTORED
104 REVIDX_DEFAULT_FLAGS
104 REVIDX_DEFAULT_FLAGS
105 REVIDX_FLAGS_ORDER
105 REVIDX_FLAGS_ORDER
106 REVIDX_RAWTEXT_CHANGING_FLAGS
106 REVIDX_RAWTEXT_CHANGING_FLAGS
107
107
108 parsers = policy.importmod('parsers')
108 parsers = policy.importmod('parsers')
109 rustancestor = policy.importrust('ancestor')
109 rustancestor = policy.importrust('ancestor')
110 rustdagop = policy.importrust('dagop')
110 rustdagop = policy.importrust('dagop')
111 rustrevlog = policy.importrust('revlog')
111 rustrevlog = policy.importrust('revlog')
112
112
113 # Aliased for performance.
113 # Aliased for performance.
114 _zlibdecompress = zlib.decompress
114 _zlibdecompress = zlib.decompress
115
115
116 # max size of revlog with inline data
116 # max size of revlog with inline data
117 _maxinline = 131072
117 _maxinline = 131072
118 _chunksize = 1048576
118 _chunksize = 1048576
119
119
120 # Flag processors for REVIDX_ELLIPSIS.
120 # Flag processors for REVIDX_ELLIPSIS.
121 def ellipsisreadprocessor(rl, text):
121 def ellipsisreadprocessor(rl, text):
122 return text, False, {}
122 return text, False, {}
123
123
124
124
125 def ellipsiswriteprocessor(rl, text, sidedata):
125 def ellipsiswriteprocessor(rl, text, sidedata):
126 return text, False
126 return text, False
127
127
128
128
129 def ellipsisrawprocessor(rl, text):
129 def ellipsisrawprocessor(rl, text):
130 return False
130 return False
131
131
132
132
133 ellipsisprocessor = (
133 ellipsisprocessor = (
134 ellipsisreadprocessor,
134 ellipsisreadprocessor,
135 ellipsiswriteprocessor,
135 ellipsiswriteprocessor,
136 ellipsisrawprocessor,
136 ellipsisrawprocessor,
137 )
137 )
138
138
139
139
140 def getoffset(q):
140 def getoffset(q):
141 return int(q >> 16)
141 return int(q >> 16)
142
142
143
143
144 def gettype(q):
144 def gettype(q):
145 return int(q & 0xFFFF)
145 return int(q & 0xFFFF)
146
146
147
147
148 def offset_type(offset, type):
148 def offset_type(offset, type):
149 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
149 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
150 raise ValueError(b'unknown revlog index flags')
150 raise ValueError(b'unknown revlog index flags')
151 return int(int(offset) << 16 | type)
151 return int(int(offset) << 16 | type)
152
152
153
153
154 def _verify_revision(rl, skipflags, state, node):
154 def _verify_revision(rl, skipflags, state, node):
155 """Verify the integrity of the given revlog ``node`` while providing a hook
155 """Verify the integrity of the given revlog ``node`` while providing a hook
156 point for extensions to influence the operation."""
156 point for extensions to influence the operation."""
157 if skipflags:
157 if skipflags:
158 state[b'skipread'].add(node)
158 state[b'skipread'].add(node)
159 else:
159 else:
160 # Side-effect: read content and verify hash.
160 # Side-effect: read content and verify hash.
161 rl.revision(node)
161 rl.revision(node)
162
162
163
163
164 @attr.s(slots=True, frozen=True)
164 @attr.s(slots=True, frozen=True)
165 class _revisioninfo(object):
165 class _revisioninfo(object):
166 """Information about a revision that allows building its fulltext
166 """Information about a revision that allows building its fulltext
167 node: expected hash of the revision
167 node: expected hash of the revision
168 p1, p2: parent revs of the revision
168 p1, p2: parent revs of the revision
169 btext: built text cache consisting of a one-element list
169 btext: built text cache consisting of a one-element list
170 cachedelta: (baserev, uncompressed_delta) or None
170 cachedelta: (baserev, uncompressed_delta) or None
171 flags: flags associated to the revision storage
171 flags: flags associated to the revision storage
172
172
173 One of btext[0] or cachedelta must be set.
173 One of btext[0] or cachedelta must be set.
174 """
174 """
175
175
176 node = attr.ib()
176 node = attr.ib()
177 p1 = attr.ib()
177 p1 = attr.ib()
178 p2 = attr.ib()
178 p2 = attr.ib()
179 btext = attr.ib()
179 btext = attr.ib()
180 textlen = attr.ib()
180 textlen = attr.ib()
181 cachedelta = attr.ib()
181 cachedelta = attr.ib()
182 flags = attr.ib()
182 flags = attr.ib()
183
183
184
184
185 @interfaceutil.implementer(repository.irevisiondelta)
185 @interfaceutil.implementer(repository.irevisiondelta)
186 @attr.s(slots=True)
186 @attr.s(slots=True)
187 class revlogrevisiondelta(object):
187 class revlogrevisiondelta(object):
188 node = attr.ib()
188 node = attr.ib()
189 p1node = attr.ib()
189 p1node = attr.ib()
190 p2node = attr.ib()
190 p2node = attr.ib()
191 basenode = attr.ib()
191 basenode = attr.ib()
192 flags = attr.ib()
192 flags = attr.ib()
193 baserevisionsize = attr.ib()
193 baserevisionsize = attr.ib()
194 revision = attr.ib()
194 revision = attr.ib()
195 delta = attr.ib()
195 delta = attr.ib()
196 linknode = attr.ib(default=None)
196 linknode = attr.ib(default=None)
197
197
198
198
199 @interfaceutil.implementer(repository.iverifyproblem)
199 @interfaceutil.implementer(repository.iverifyproblem)
200 @attr.s(frozen=True)
200 @attr.s(frozen=True)
201 class revlogproblem(object):
201 class revlogproblem(object):
202 warning = attr.ib(default=None)
202 warning = attr.ib(default=None)
203 error = attr.ib(default=None)
203 error = attr.ib(default=None)
204 node = attr.ib(default=None)
204 node = attr.ib(default=None)
205
205
206
206
207 # index v0:
207 # index v0:
208 # 4 bytes: offset
208 # 4 bytes: offset
209 # 4 bytes: compressed length
209 # 4 bytes: compressed length
210 # 4 bytes: base rev
210 # 4 bytes: base rev
211 # 4 bytes: link rev
211 # 4 bytes: link rev
212 # 20 bytes: parent 1 nodeid
212 # 20 bytes: parent 1 nodeid
213 # 20 bytes: parent 2 nodeid
213 # 20 bytes: parent 2 nodeid
214 # 20 bytes: nodeid
214 # 20 bytes: nodeid
215 indexformatv0 = struct.Struct(b">4l20s20s20s")
215 indexformatv0 = struct.Struct(b">4l20s20s20s")
216 indexformatv0_pack = indexformatv0.pack
216 indexformatv0_pack = indexformatv0.pack
217 indexformatv0_unpack = indexformatv0.unpack
217 indexformatv0_unpack = indexformatv0.unpack
218
218
219
219
220 class revlogoldindex(list):
220 class revlogoldindex(list):
221 @property
221 @property
222 def nodemap(self):
222 def nodemap(self):
223 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
223 msg = b"index.nodemap is deprecated, use index.[has_node|rev|get_rev]"
224 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
224 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
225 return self._nodemap
225 return self._nodemap
226
226
227 @util.propertycache
227 @util.propertycache
228 def _nodemap(self):
228 def _nodemap(self):
229 nodemap = nodemaputil.NodeMap({nullid: nullrev})
229 nodemap = nodemaputil.NodeMap({nullid: nullrev})
230 for r in range(0, len(self)):
230 for r in range(0, len(self)):
231 n = self[r][7]
231 n = self[r][7]
232 nodemap[n] = r
232 nodemap[n] = r
233 return nodemap
233 return nodemap
234
234
235 def has_node(self, node):
235 def has_node(self, node):
236 """return True if the node exist in the index"""
236 """return True if the node exist in the index"""
237 return node in self._nodemap
237 return node in self._nodemap
238
238
239 def rev(self, node):
239 def rev(self, node):
240 """return a revision for a node
240 """return a revision for a node
241
241
242 If the node is unknown, raise a RevlogError"""
242 If the node is unknown, raise a RevlogError"""
243 return self._nodemap[node]
243 return self._nodemap[node]
244
244
245 def get_rev(self, node):
245 def get_rev(self, node):
246 """return a revision for a node
246 """return a revision for a node
247
247
248 If the node is unknown, return None"""
248 If the node is unknown, return None"""
249 return self._nodemap.get(node)
249 return self._nodemap.get(node)
250
250
251 def append(self, tup):
251 def append(self, tup):
252 self._nodemap[tup[7]] = len(self)
252 self._nodemap[tup[7]] = len(self)
253 super(revlogoldindex, self).append(tup)
253 super(revlogoldindex, self).append(tup)
254
254
255 def __delitem__(self, i):
255 def __delitem__(self, i):
256 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
256 if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
257 raise ValueError(b"deleting slices only supports a:-1 with step 1")
257 raise ValueError(b"deleting slices only supports a:-1 with step 1")
258 for r in pycompat.xrange(i.start, len(self)):
258 for r in pycompat.xrange(i.start, len(self)):
259 del self._nodemap[self[r][7]]
259 del self._nodemap[self[r][7]]
260 super(revlogoldindex, self).__delitem__(i)
260 super(revlogoldindex, self).__delitem__(i)
261
261
262 def clearcaches(self):
262 def clearcaches(self):
263 self.__dict__.pop('_nodemap', None)
263 self.__dict__.pop('_nodemap', None)
264
264
265 def __getitem__(self, i):
265 def __getitem__(self, i):
266 if i == -1:
266 if i == -1:
267 return (0, 0, 0, -1, -1, -1, -1, nullid)
267 return (0, 0, 0, -1, -1, -1, -1, nullid)
268 return list.__getitem__(self, i)
268 return list.__getitem__(self, i)
269
269
270
270
271 class revlogoldio(object):
271 class revlogoldio(object):
272 def __init__(self):
272 def __init__(self):
273 self.size = indexformatv0.size
273 self.size = indexformatv0.size
274
274
275 def parseindex(self, data, inline):
275 def parseindex(self, data, inline):
276 s = self.size
276 s = self.size
277 index = []
277 index = []
278 nodemap = nodemaputil.NodeMap({nullid: nullrev})
278 nodemap = nodemaputil.NodeMap({nullid: nullrev})
279 n = off = 0
279 n = off = 0
280 l = len(data)
280 l = len(data)
281 while off + s <= l:
281 while off + s <= l:
282 cur = data[off : off + s]
282 cur = data[off : off + s]
283 off += s
283 off += s
284 e = indexformatv0_unpack(cur)
284 e = indexformatv0_unpack(cur)
285 # transform to revlogv1 format
285 # transform to revlogv1 format
286 e2 = (
286 e2 = (
287 offset_type(e[0], 0),
287 offset_type(e[0], 0),
288 e[1],
288 e[1],
289 -1,
289 -1,
290 e[2],
290 e[2],
291 e[3],
291 e[3],
292 nodemap.get(e[4], nullrev),
292 nodemap.get(e[4], nullrev),
293 nodemap.get(e[5], nullrev),
293 nodemap.get(e[5], nullrev),
294 e[6],
294 e[6],
295 )
295 )
296 index.append(e2)
296 index.append(e2)
297 nodemap[e[6]] = n
297 nodemap[e[6]] = n
298 n += 1
298 n += 1
299
299
300 index = revlogoldindex(index)
300 index = revlogoldindex(index)
301 return index, None
301 return index, None
302
302
303 def packentry(self, entry, node, version, rev):
303 def packentry(self, entry, node, version, rev):
304 if gettype(entry[0]):
304 if gettype(entry[0]):
305 raise error.RevlogError(
305 raise error.RevlogError(
306 _(b'index entry flags need revlog version 1')
306 _(b'index entry flags need revlog version 1')
307 )
307 )
308 e2 = (
308 e2 = (
309 getoffset(entry[0]),
309 getoffset(entry[0]),
310 entry[1],
310 entry[1],
311 entry[3],
311 entry[3],
312 entry[4],
312 entry[4],
313 node(entry[5]),
313 node(entry[5]),
314 node(entry[6]),
314 node(entry[6]),
315 entry[7],
315 entry[7],
316 )
316 )
317 return indexformatv0_pack(*e2)
317 return indexformatv0_pack(*e2)
318
318
319
319
320 # index ng:
320 # index ng:
321 # 6 bytes: offset
321 # 6 bytes: offset
322 # 2 bytes: flags
322 # 2 bytes: flags
323 # 4 bytes: compressed length
323 # 4 bytes: compressed length
324 # 4 bytes: uncompressed length
324 # 4 bytes: uncompressed length
325 # 4 bytes: base rev
325 # 4 bytes: base rev
326 # 4 bytes: link rev
326 # 4 bytes: link rev
327 # 4 bytes: parent 1 rev
327 # 4 bytes: parent 1 rev
328 # 4 bytes: parent 2 rev
328 # 4 bytes: parent 2 rev
329 # 32 bytes: nodeid
329 # 32 bytes: nodeid
330 indexformatng = struct.Struct(b">Qiiiiii20s12x")
330 indexformatng = struct.Struct(b">Qiiiiii20s12x")
331 indexformatng_pack = indexformatng.pack
331 indexformatng_pack = indexformatng.pack
332 versionformat = struct.Struct(b">I")
332 versionformat = struct.Struct(b">I")
333 versionformat_pack = versionformat.pack
333 versionformat_pack = versionformat.pack
334 versionformat_unpack = versionformat.unpack
334 versionformat_unpack = versionformat.unpack
335
335
336 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
336 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
337 # signed integer)
337 # signed integer)
338 _maxentrysize = 0x7FFFFFFF
338 _maxentrysize = 0x7FFFFFFF
339
339
340
340
341 class revlogio(object):
341 class revlogio(object):
342 def __init__(self):
342 def __init__(self):
343 self.size = indexformatng.size
343 self.size = indexformatng.size
344
344
345 def parseindex(self, data, inline):
345 def parseindex(self, data, inline):
346 # call the C implementation to parse the index data
346 # call the C implementation to parse the index data
347 index, cache = parsers.parse_index2(data, inline)
347 index, cache = parsers.parse_index2(data, inline)
348 return index, cache
348 return index, cache
349
349
350 def packentry(self, entry, node, version, rev):
350 def packentry(self, entry, node, version, rev):
351 p = indexformatng_pack(*entry)
351 p = indexformatng_pack(*entry)
352 if rev == 0:
352 if rev == 0:
353 p = versionformat_pack(version) + p[4:]
353 p = versionformat_pack(version) + p[4:]
354 return p
354 return p
355
355
356
356
357 NodemapRevlogIO = None
357 NodemapRevlogIO = None
358
358
359 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
359 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
360
360
361 class NodemapRevlogIO(revlogio):
361 class NodemapRevlogIO(revlogio):
362 """A debug oriented IO class that return a PersistentNodeMapIndexObject
362 """A debug oriented IO class that return a PersistentNodeMapIndexObject
363
363
364 The PersistentNodeMapIndexObject object is meant to test the persistent nodemap feature.
364 The PersistentNodeMapIndexObject object is meant to test the persistent nodemap feature.
365 """
365 """
366
366
367 def parseindex(self, data, inline):
367 def parseindex(self, data, inline):
368 index, cache = parsers.parse_index_devel_nodemap(data, inline)
368 index, cache = parsers.parse_index_devel_nodemap(data, inline)
369 return index, cache
369 return index, cache
370
370
371
371
372 class rustrevlogio(revlogio):
372 class rustrevlogio(revlogio):
373 def parseindex(self, data, inline):
373 def parseindex(self, data, inline):
374 index, cache = super(rustrevlogio, self).parseindex(data, inline)
374 index, cache = super(rustrevlogio, self).parseindex(data, inline)
375 return rustrevlog.MixedIndex(index), cache
375 return rustrevlog.MixedIndex(index), cache
376
376
377
377
378 class revlog(object):
378 class revlog(object):
379 """
379 """
380 the underlying revision storage object
380 the underlying revision storage object
381
381
382 A revlog consists of two parts, an index and the revision data.
382 A revlog consists of two parts, an index and the revision data.
383
383
384 The index is a file with a fixed record size containing
384 The index is a file with a fixed record size containing
385 information on each revision, including its nodeid (hash), the
385 information on each revision, including its nodeid (hash), the
386 nodeids of its parents, the position and offset of its data within
386 nodeids of its parents, the position and offset of its data within
387 the data file, and the revision it's based on. Finally, each entry
387 the data file, and the revision it's based on. Finally, each entry
388 contains a linkrev entry that can serve as a pointer to external
388 contains a linkrev entry that can serve as a pointer to external
389 data.
389 data.
390
390
391 The revision data itself is a linear collection of data chunks.
391 The revision data itself is a linear collection of data chunks.
392 Each chunk represents a revision and is usually represented as a
392 Each chunk represents a revision and is usually represented as a
393 delta against the previous chunk. To bound lookup time, runs of
393 delta against the previous chunk. To bound lookup time, runs of
394 deltas are limited to about 2 times the length of the original
394 deltas are limited to about 2 times the length of the original
395 version data. This makes retrieval of a version proportional to
395 version data. This makes retrieval of a version proportional to
396 its size, or O(1) relative to the number of revisions.
396 its size, or O(1) relative to the number of revisions.
397
397
398 Both pieces of the revlog are written to in an append-only
398 Both pieces of the revlog are written to in an append-only
399 fashion, which means we never need to rewrite a file to insert or
399 fashion, which means we never need to rewrite a file to insert or
400 remove data, and can use some simple techniques to avoid the need
400 remove data, and can use some simple techniques to avoid the need
401 for locking while reading.
401 for locking while reading.
402
402
403 If checkambig, indexfile is opened with checkambig=True at
403 If checkambig, indexfile is opened with checkambig=True at
404 writing, to avoid file stat ambiguity.
404 writing, to avoid file stat ambiguity.
405
405
406 If mmaplargeindex is True, and an mmapindexthreshold is set, the
406 If mmaplargeindex is True, and an mmapindexthreshold is set, the
407 index will be mmapped rather than read if it is larger than the
407 index will be mmapped rather than read if it is larger than the
408 configured threshold.
408 configured threshold.
409
409
410 If censorable is True, the revlog can have censored revisions.
410 If censorable is True, the revlog can have censored revisions.
411
411
412 If `upperboundcomp` is not None, this is the expected maximal gain from
412 If `upperboundcomp` is not None, this is the expected maximal gain from
413 compression for the data content.
413 compression for the data content.
414 """
414 """
415
415
416 _flagserrorclass = error.RevlogError
416 _flagserrorclass = error.RevlogError
417
417
418 def __init__(
418 def __init__(
419 self,
419 self,
420 opener,
420 opener,
421 indexfile,
421 indexfile,
422 datafile=None,
422 datafile=None,
423 checkambig=False,
423 checkambig=False,
424 mmaplargeindex=False,
424 mmaplargeindex=False,
425 censorable=False,
425 censorable=False,
426 upperboundcomp=None,
426 upperboundcomp=None,
427 persistentnodemap=False,
427 persistentnodemap=False,
428 ):
428 ):
429 """
429 """
430 create a revlog object
430 create a revlog object
431
431
432 opener is a function that abstracts the file opening operation
432 opener is a function that abstracts the file opening operation
433 and can be used to implement COW semantics or the like.
433 and can be used to implement COW semantics or the like.
434
434
435 """
435 """
436 self.upperboundcomp = upperboundcomp
436 self.upperboundcomp = upperboundcomp
437 self.indexfile = indexfile
437 self.indexfile = indexfile
438 self.datafile = datafile or (indexfile[:-2] + b".d")
438 self.datafile = datafile or (indexfile[:-2] + b".d")
439 self.nodemap_file = None
439 self.nodemap_file = None
440 if persistentnodemap:
440 if persistentnodemap:
441 if indexfile.endswith(b'.a'):
441 if indexfile.endswith(b'.a'):
442 pending_path = indexfile[:-4] + b".n.a"
442 pending_path = indexfile[:-4] + b".n.a"
443 if opener.exists(pending_path):
443 if opener.exists(pending_path):
444 self.nodemap_file = pending_path
444 self.nodemap_file = pending_path
445 else:
445 else:
446 self.nodemap_file = indexfile[:-4] + b".n"
446 self.nodemap_file = indexfile[:-4] + b".n"
447 else:
447 else:
448 self.nodemap_file = indexfile[:-2] + b".n"
448 self.nodemap_file = indexfile[:-2] + b".n"
449
449
450 self.opener = opener
450 self.opener = opener
451 # When True, indexfile is opened with checkambig=True at writing, to
451 # When True, indexfile is opened with checkambig=True at writing, to
452 # avoid file stat ambiguity.
452 # avoid file stat ambiguity.
453 self._checkambig = checkambig
453 self._checkambig = checkambig
454 self._mmaplargeindex = mmaplargeindex
454 self._mmaplargeindex = mmaplargeindex
455 self._censorable = censorable
455 self._censorable = censorable
456 # 3-tuple of (node, rev, text) for a raw revision.
456 # 3-tuple of (node, rev, text) for a raw revision.
457 self._revisioncache = None
457 self._revisioncache = None
458 # Maps rev to chain base rev.
458 # Maps rev to chain base rev.
459 self._chainbasecache = util.lrucachedict(100)
459 self._chainbasecache = util.lrucachedict(100)
460 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
460 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
461 self._chunkcache = (0, b'')
461 self._chunkcache = (0, b'')
462 # How much data to read and cache into the raw revlog data cache.
462 # How much data to read and cache into the raw revlog data cache.
463 self._chunkcachesize = 65536
463 self._chunkcachesize = 65536
464 self._maxchainlen = None
464 self._maxchainlen = None
465 self._deltabothparents = True
465 self._deltabothparents = True
466 self.index = None
466 self.index = None
467 self._nodemap_docket = None
467 self._nodemap_docket = None
468 # Mapping of partial identifiers to full nodes.
468 # Mapping of partial identifiers to full nodes.
469 self._pcache = {}
469 self._pcache = {}
470 # Mapping of revision integer to full node.
470 # Mapping of revision integer to full node.
471 self._compengine = b'zlib'
471 self._compengine = b'zlib'
472 self._compengineopts = {}
472 self._compengineopts = {}
473 self._maxdeltachainspan = -1
473 self._maxdeltachainspan = -1
474 self._withsparseread = False
474 self._withsparseread = False
475 self._sparserevlog = False
475 self._sparserevlog = False
476 self._srdensitythreshold = 0.50
476 self._srdensitythreshold = 0.50
477 self._srmingapsize = 262144
477 self._srmingapsize = 262144
478
478
479 # Make copy of flag processors so each revlog instance can support
479 # Make copy of flag processors so each revlog instance can support
480 # custom flags.
480 # custom flags.
481 self._flagprocessors = dict(flagutil.flagprocessors)
481 self._flagprocessors = dict(flagutil.flagprocessors)
482
482
483 # 2-tuple of file handles being used for active writing.
483 # 2-tuple of file handles being used for active writing.
484 self._writinghandles = None
484 self._writinghandles = None
485
485
486 self._loadindex()
486 self._loadindex()
487
487
488 def _loadindex(self):
488 def _loadindex(self):
489 mmapindexthreshold = None
489 mmapindexthreshold = None
490 opts = self.opener.options
490 opts = self.opener.options
491
491
492 if b'revlogv2' in opts:
492 if b'revlogv2' in opts:
493 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
493 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
494 elif b'revlogv1' in opts:
494 elif b'revlogv1' in opts:
495 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
495 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
496 if b'generaldelta' in opts:
496 if b'generaldelta' in opts:
497 newversionflags |= FLAG_GENERALDELTA
497 newversionflags |= FLAG_GENERALDELTA
498 elif b'revlogv0' in self.opener.options:
498 elif b'revlogv0' in self.opener.options:
499 newversionflags = REVLOGV0
499 newversionflags = REVLOGV0
500 else:
500 else:
501 newversionflags = REVLOG_DEFAULT_VERSION
501 newversionflags = REVLOG_DEFAULT_VERSION
502
502
503 if b'chunkcachesize' in opts:
503 if b'chunkcachesize' in opts:
504 self._chunkcachesize = opts[b'chunkcachesize']
504 self._chunkcachesize = opts[b'chunkcachesize']
505 if b'maxchainlen' in opts:
505 if b'maxchainlen' in opts:
506 self._maxchainlen = opts[b'maxchainlen']
506 self._maxchainlen = opts[b'maxchainlen']
507 if b'deltabothparents' in opts:
507 if b'deltabothparents' in opts:
508 self._deltabothparents = opts[b'deltabothparents']
508 self._deltabothparents = opts[b'deltabothparents']
509 self._lazydelta = bool(opts.get(b'lazydelta', True))
509 self._lazydelta = bool(opts.get(b'lazydelta', True))
510 self._lazydeltabase = False
510 self._lazydeltabase = False
511 if self._lazydelta:
511 if self._lazydelta:
512 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
512 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
513 if b'compengine' in opts:
513 if b'compengine' in opts:
514 self._compengine = opts[b'compengine']
514 self._compengine = opts[b'compengine']
515 if b'zlib.level' in opts:
515 if b'zlib.level' in opts:
516 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
516 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
517 if b'zstd.level' in opts:
517 if b'zstd.level' in opts:
518 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
518 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
519 if b'maxdeltachainspan' in opts:
519 if b'maxdeltachainspan' in opts:
520 self._maxdeltachainspan = opts[b'maxdeltachainspan']
520 self._maxdeltachainspan = opts[b'maxdeltachainspan']
521 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
521 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
522 mmapindexthreshold = opts[b'mmapindexthreshold']
522 mmapindexthreshold = opts[b'mmapindexthreshold']
523 self.hassidedata = bool(opts.get(b'side-data', False))
523 self.hassidedata = bool(opts.get(b'side-data', False))
524 if self.hassidedata:
524 if self.hassidedata:
525 self._flagprocessors[REVIDX_SIDEDATA] = sidedatautil.processors
525 self._flagprocessors[REVIDX_SIDEDATA] = sidedatautil.processors
526 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
526 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
527 withsparseread = bool(opts.get(b'with-sparse-read', False))
527 withsparseread = bool(opts.get(b'with-sparse-read', False))
528 # sparse-revlog forces sparse-read
528 # sparse-revlog forces sparse-read
529 self._withsparseread = self._sparserevlog or withsparseread
529 self._withsparseread = self._sparserevlog or withsparseread
530 if b'sparse-read-density-threshold' in opts:
530 if b'sparse-read-density-threshold' in opts:
531 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
531 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
532 if b'sparse-read-min-gap-size' in opts:
532 if b'sparse-read-min-gap-size' in opts:
533 self._srmingapsize = opts[b'sparse-read-min-gap-size']
533 self._srmingapsize = opts[b'sparse-read-min-gap-size']
534 if opts.get(b'enableellipsis'):
534 if opts.get(b'enableellipsis'):
535 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
535 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
536
536
537 # revlog v0 doesn't have flag processors
537 # revlog v0 doesn't have flag processors
538 for flag, processor in pycompat.iteritems(
538 for flag, processor in pycompat.iteritems(
539 opts.get(b'flagprocessors', {})
539 opts.get(b'flagprocessors', {})
540 ):
540 ):
541 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
541 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
542
542
543 if self._chunkcachesize <= 0:
543 if self._chunkcachesize <= 0:
544 raise error.RevlogError(
544 raise error.RevlogError(
545 _(b'revlog chunk cache size %r is not greater than 0')
545 _(b'revlog chunk cache size %r is not greater than 0')
546 % self._chunkcachesize
546 % self._chunkcachesize
547 )
547 )
548 elif self._chunkcachesize & (self._chunkcachesize - 1):
548 elif self._chunkcachesize & (self._chunkcachesize - 1):
549 raise error.RevlogError(
549 raise error.RevlogError(
550 _(b'revlog chunk cache size %r is not a power of 2')
550 _(b'revlog chunk cache size %r is not a power of 2')
551 % self._chunkcachesize
551 % self._chunkcachesize
552 )
552 )
553
553
554 indexdata = b''
554 indexdata = b''
555 self._initempty = True
555 self._initempty = True
556 try:
556 try:
557 with self._indexfp() as f:
557 with self._indexfp() as f:
558 if (
558 if (
559 mmapindexthreshold is not None
559 mmapindexthreshold is not None
560 and self.opener.fstat(f).st_size >= mmapindexthreshold
560 and self.opener.fstat(f).st_size >= mmapindexthreshold
561 ):
561 ):
562 # TODO: should .close() to release resources without
562 # TODO: should .close() to release resources without
563 # relying on Python GC
563 # relying on Python GC
564 indexdata = util.buffer(util.mmapread(f))
564 indexdata = util.buffer(util.mmapread(f))
565 else:
565 else:
566 indexdata = f.read()
566 indexdata = f.read()
567 if len(indexdata) > 0:
567 if len(indexdata) > 0:
568 versionflags = versionformat_unpack(indexdata[:4])[0]
568 versionflags = versionformat_unpack(indexdata[:4])[0]
569 self._initempty = False
569 self._initempty = False
570 else:
570 else:
571 versionflags = newversionflags
571 versionflags = newversionflags
572 except IOError as inst:
572 except IOError as inst:
573 if inst.errno != errno.ENOENT:
573 if inst.errno != errno.ENOENT:
574 raise
574 raise
575
575
576 versionflags = newversionflags
576 versionflags = newversionflags
577
577
578 self.version = versionflags
578 self.version = versionflags
579
579
580 flags = versionflags & ~0xFFFF
580 flags = versionflags & ~0xFFFF
581 fmt = versionflags & 0xFFFF
581 fmt = versionflags & 0xFFFF
582
582
583 if fmt == REVLOGV0:
583 if fmt == REVLOGV0:
584 if flags:
584 if flags:
585 raise error.RevlogError(
585 raise error.RevlogError(
586 _(b'unknown flags (%#04x) in version %d revlog %s')
586 _(b'unknown flags (%#04x) in version %d revlog %s')
587 % (flags >> 16, fmt, self.indexfile)
587 % (flags >> 16, fmt, self.indexfile)
588 )
588 )
589
589
590 self._inline = False
590 self._inline = False
591 self._generaldelta = False
591 self._generaldelta = False
592
592
593 elif fmt == REVLOGV1:
593 elif fmt == REVLOGV1:
594 if flags & ~REVLOGV1_FLAGS:
594 if flags & ~REVLOGV1_FLAGS:
595 raise error.RevlogError(
595 raise error.RevlogError(
596 _(b'unknown flags (%#04x) in version %d revlog %s')
596 _(b'unknown flags (%#04x) in version %d revlog %s')
597 % (flags >> 16, fmt, self.indexfile)
597 % (flags >> 16, fmt, self.indexfile)
598 )
598 )
599
599
600 self._inline = versionflags & FLAG_INLINE_DATA
600 self._inline = versionflags & FLAG_INLINE_DATA
601 self._generaldelta = versionflags & FLAG_GENERALDELTA
601 self._generaldelta = versionflags & FLAG_GENERALDELTA
602
602
603 elif fmt == REVLOGV2:
603 elif fmt == REVLOGV2:
604 if flags & ~REVLOGV2_FLAGS:
604 if flags & ~REVLOGV2_FLAGS:
605 raise error.RevlogError(
605 raise error.RevlogError(
606 _(b'unknown flags (%#04x) in version %d revlog %s')
606 _(b'unknown flags (%#04x) in version %d revlog %s')
607 % (flags >> 16, fmt, self.indexfile)
607 % (flags >> 16, fmt, self.indexfile)
608 )
608 )
609
609
610 self._inline = versionflags & FLAG_INLINE_DATA
610 self._inline = versionflags & FLAG_INLINE_DATA
611 # generaldelta implied by version 2 revlogs.
611 # generaldelta implied by version 2 revlogs.
612 self._generaldelta = True
612 self._generaldelta = True
613
613
614 else:
614 else:
615 raise error.RevlogError(
615 raise error.RevlogError(
616 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
616 _(b'unknown version (%d) in revlog %s') % (fmt, self.indexfile)
617 )
617 )
618 # sparse-revlog can't be on without general-delta (issue6056)
618 # sparse-revlog can't be on without general-delta (issue6056)
619 if not self._generaldelta:
619 if not self._generaldelta:
620 self._sparserevlog = False
620 self._sparserevlog = False
621
621
622 self._storedeltachains = True
622 self._storedeltachains = True
623
623
624 devel_nodemap = (
624 devel_nodemap = (
625 self.nodemap_file
625 self.nodemap_file
626 and opts.get(b'devel-force-nodemap', False)
626 and opts.get(b'devel-force-nodemap', False)
627 and NodemapRevlogIO is not None
627 and NodemapRevlogIO is not None
628 )
628 )
629
629
630 use_rust_index = False
630 use_rust_index = False
631 if rustrevlog is not None:
631 if rustrevlog is not None:
632 if self.nodemap_file is not None:
632 if self.nodemap_file is not None:
633 use_rust_index = True
633 use_rust_index = True
634 else:
634 else:
635 use_rust_index = self.opener.options.get(b'rust.index')
635 use_rust_index = self.opener.options.get(b'rust.index')
636
636
637 self._io = revlogio()
637 self._io = revlogio()
638 if self.version == REVLOGV0:
638 if self.version == REVLOGV0:
639 self._io = revlogoldio()
639 self._io = revlogoldio()
640 elif devel_nodemap:
640 elif devel_nodemap:
641 self._io = NodemapRevlogIO()
641 self._io = NodemapRevlogIO()
642 elif use_rust_index:
642 elif use_rust_index:
643 self._io = rustrevlogio()
643 self._io = rustrevlogio()
644 try:
644 try:
645 d = self._io.parseindex(indexdata, self._inline)
645 d = self._io.parseindex(indexdata, self._inline)
646 index, _chunkcache = d
646 index, _chunkcache = d
647 use_nodemap = (
647 use_nodemap = (
648 not self._inline
648 not self._inline
649 and self.nodemap_file is not None
649 and self.nodemap_file is not None
650 and util.safehasattr(index, 'update_nodemap_data')
650 and util.safehasattr(index, 'update_nodemap_data')
651 )
651 )
652 if use_nodemap:
652 if use_nodemap:
653 nodemap_data = nodemaputil.persisted_data(self)
653 nodemap_data = nodemaputil.persisted_data(self)
654 if nodemap_data is not None:
654 if nodemap_data is not None:
655 docket = nodemap_data[0]
655 docket = nodemap_data[0]
656 if (
656 if (
657 len(d[0]) > docket.tip_rev
657 len(d[0]) > docket.tip_rev
658 and d[0][docket.tip_rev][7] == docket.tip_node
658 and d[0][docket.tip_rev][7] == docket.tip_node
659 ):
659 ):
660 # no changelog tampering
660 # no changelog tampering
661 self._nodemap_docket = docket
661 self._nodemap_docket = docket
662 index.update_nodemap_data(*nodemap_data)
662 index.update_nodemap_data(*nodemap_data)
663 except (ValueError, IndexError):
663 except (ValueError, IndexError):
664 raise error.RevlogError(
664 raise error.RevlogError(
665 _(b"index %s is corrupted") % self.indexfile
665 _(b"index %s is corrupted") % self.indexfile
666 )
666 )
667 self.index, self._chunkcache = d
667 self.index, self._chunkcache = d
668 if not self._chunkcache:
668 if not self._chunkcache:
669 self._chunkclear()
669 self._chunkclear()
670 # revnum -> (chain-length, sum-delta-length)
670 # revnum -> (chain-length, sum-delta-length)
671 self._chaininfocache = util.lrucachedict(500)
671 self._chaininfocache = util.lrucachedict(500)
672 # revlog header -> revlog compressor
672 # revlog header -> revlog compressor
673 self._decompressors = {}
673 self._decompressors = {}
674
674
675 @util.propertycache
675 @util.propertycache
676 def _compressor(self):
676 def _compressor(self):
677 engine = util.compengines[self._compengine]
677 engine = util.compengines[self._compengine]
678 return engine.revlogcompressor(self._compengineopts)
678 return engine.revlogcompressor(self._compengineopts)
679
679
680 def _indexfp(self, mode=b'r'):
680 def _indexfp(self, mode=b'r'):
681 """file object for the revlog's index file"""
681 """file object for the revlog's index file"""
682 args = {'mode': mode}
682 args = {'mode': mode}
683 if mode != b'r':
683 if mode != b'r':
684 args['checkambig'] = self._checkambig
684 args['checkambig'] = self._checkambig
685 if mode == b'w':
685 if mode == b'w':
686 args['atomictemp'] = True
686 args['atomictemp'] = True
687 return self.opener(self.indexfile, **args)
687 return self.opener(self.indexfile, **args)
688
688
689 def _datafp(self, mode=b'r'):
689 def _datafp(self, mode=b'r'):
690 """file object for the revlog's data file"""
690 """file object for the revlog's data file"""
691 return self.opener(self.datafile, mode=mode)
691 return self.opener(self.datafile, mode=mode)
692
692
693 @contextlib.contextmanager
693 @contextlib.contextmanager
694 def _datareadfp(self, existingfp=None):
694 def _datareadfp(self, existingfp=None):
695 """file object suitable to read data"""
695 """file object suitable to read data"""
696 # Use explicit file handle, if given.
696 # Use explicit file handle, if given.
697 if existingfp is not None:
697 if existingfp is not None:
698 yield existingfp
698 yield existingfp
699
699
700 # Use a file handle being actively used for writes, if available.
700 # Use a file handle being actively used for writes, if available.
701 # There is some danger to doing this because reads will seek the
701 # There is some danger to doing this because reads will seek the
702 # file. However, _writeentry() performs a SEEK_END before all writes,
702 # file. However, _writeentry() performs a SEEK_END before all writes,
703 # so we should be safe.
703 # so we should be safe.
704 elif self._writinghandles:
704 elif self._writinghandles:
705 if self._inline:
705 if self._inline:
706 yield self._writinghandles[0]
706 yield self._writinghandles[0]
707 else:
707 else:
708 yield self._writinghandles[1]
708 yield self._writinghandles[1]
709
709
710 # Otherwise open a new file handle.
710 # Otherwise open a new file handle.
711 else:
711 else:
712 if self._inline:
712 if self._inline:
713 func = self._indexfp
713 func = self._indexfp
714 else:
714 else:
715 func = self._datafp
715 func = self._datafp
716 with func() as fp:
716 with func() as fp:
717 yield fp
717 yield fp
718
718
719 def tiprev(self):
719 def tiprev(self):
720 return len(self.index) - 1
720 return len(self.index) - 1
721
721
722 def tip(self):
722 def tip(self):
723 return self.node(self.tiprev())
723 return self.node(self.tiprev())
724
724
725 def __contains__(self, rev):
725 def __contains__(self, rev):
726 return 0 <= rev < len(self)
726 return 0 <= rev < len(self)
727
727
728 def __len__(self):
728 def __len__(self):
729 return len(self.index)
729 return len(self.index)
730
730
731 def __iter__(self):
731 def __iter__(self):
732 return iter(pycompat.xrange(len(self)))
732 return iter(pycompat.xrange(len(self)))
733
733
734 def revs(self, start=0, stop=None):
734 def revs(self, start=0, stop=None):
735 """iterate over all rev in this revlog (from start to stop)"""
735 """iterate over all rev in this revlog (from start to stop)"""
736 return storageutil.iterrevs(len(self), start=start, stop=stop)
736 return storageutil.iterrevs(len(self), start=start, stop=stop)
737
737
738 @property
738 @property
739 def nodemap(self):
739 def nodemap(self):
740 msg = (
740 msg = (
741 b"revlog.nodemap is deprecated, "
741 b"revlog.nodemap is deprecated, "
742 b"use revlog.index.[has_node|rev|get_rev]"
742 b"use revlog.index.[has_node|rev|get_rev]"
743 )
743 )
744 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
744 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
745 return self.index.nodemap
745 return self.index.nodemap
746
746
747 @property
747 @property
748 def _nodecache(self):
748 def _nodecache(self):
749 msg = b"revlog._nodecache is deprecated, use revlog.index.nodemap"
749 msg = b"revlog._nodecache is deprecated, use revlog.index.nodemap"
750 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
750 util.nouideprecwarn(msg, b'5.3', stacklevel=2)
751 return self.index.nodemap
751 return self.index.nodemap
752
752
753 def hasnode(self, node):
753 def hasnode(self, node):
754 try:
754 try:
755 self.rev(node)
755 self.rev(node)
756 return True
756 return True
757 except KeyError:
757 except KeyError:
758 return False
758 return False
759
759
760 def candelta(self, baserev, rev):
760 def candelta(self, baserev, rev):
761 """whether two revisions (baserev, rev) can be delta-ed or not"""
761 """whether two revisions (baserev, rev) can be delta-ed or not"""
762 # Disable delta if either rev requires a content-changing flag
762 # Disable delta if either rev requires a content-changing flag
763 # processor (ex. LFS). This is because such flag processor can alter
763 # processor (ex. LFS). This is because such flag processor can alter
764 # the rawtext content that the delta will be based on, and two clients
764 # the rawtext content that the delta will be based on, and two clients
765 # could have a same revlog node with different flags (i.e. different
765 # could have a same revlog node with different flags (i.e. different
766 # rawtext contents) and the delta could be incompatible.
766 # rawtext contents) and the delta could be incompatible.
767 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
767 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
768 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
768 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
769 ):
769 ):
770 return False
770 return False
771 return True
771 return True
772
772
773 def update_caches(self, transaction):
773 def update_caches(self, transaction):
774 if self.nodemap_file is not None:
774 if self.nodemap_file is not None:
775 if transaction is None:
775 if transaction is None:
776 nodemaputil.update_persistent_nodemap(self)
776 nodemaputil.update_persistent_nodemap(self)
777 else:
777 else:
778 nodemaputil.setup_persistent_nodemap(transaction, self)
778 nodemaputil.setup_persistent_nodemap(transaction, self)
779
779
780 def clearcaches(self):
780 def clearcaches(self):
781 self._revisioncache = None
781 self._revisioncache = None
782 self._chainbasecache.clear()
782 self._chainbasecache.clear()
783 self._chunkcache = (0, b'')
783 self._chunkcache = (0, b'')
784 self._pcache = {}
784 self._pcache = {}
785 self._nodemap_docket = None
785 self._nodemap_docket = None
786 self.index.clearcaches()
786 self.index.clearcaches()
787 # The python code is the one responsible for validating the docket, we
787 # The python code is the one responsible for validating the docket, we
788 # end up having to refresh it here.
788 # end up having to refresh it here.
789 use_nodemap = (
789 use_nodemap = (
790 not self._inline
790 not self._inline
791 and self.nodemap_file is not None
791 and self.nodemap_file is not None
792 and util.safehasattr(self.index, 'update_nodemap_data')
792 and util.safehasattr(self.index, 'update_nodemap_data')
793 )
793 )
794 if use_nodemap:
794 if use_nodemap:
795 nodemap_data = nodemaputil.persisted_data(self)
795 nodemap_data = nodemaputil.persisted_data(self)
796 if nodemap_data is not None:
796 if nodemap_data is not None:
797 self._nodemap_docket = nodemap_data[0]
797 self._nodemap_docket = nodemap_data[0]
798 self.index.update_nodemap_data(*nodemap_data)
798 self.index.update_nodemap_data(*nodemap_data)
799
799
800 def rev(self, node):
800 def rev(self, node):
801 try:
801 try:
802 return self.index.rev(node)
802 return self.index.rev(node)
803 except TypeError:
803 except TypeError:
804 raise
804 raise
805 except error.RevlogError:
805 except error.RevlogError:
806 # parsers.c radix tree lookup failed
806 # parsers.c radix tree lookup failed
807 if node == wdirid or node in wdirfilenodeids:
807 if node == wdirid or node in wdirfilenodeids:
808 raise error.WdirUnsupported
808 raise error.WdirUnsupported
809 raise error.LookupError(node, self.indexfile, _(b'no node'))
809 raise error.LookupError(node, self.indexfile, _(b'no node'))
810
810
811 # Accessors for index entries.
811 # Accessors for index entries.
812
812
813 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
813 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
814 # are flags.
814 # are flags.
815 def start(self, rev):
815 def start(self, rev):
816 return int(self.index[rev][0] >> 16)
816 return int(self.index[rev][0] >> 16)
817
817
818 def flags(self, rev):
818 def flags(self, rev):
819 return self.index[rev][0] & 0xFFFF
819 return self.index[rev][0] & 0xFFFF
820
820
821 def length(self, rev):
821 def length(self, rev):
822 return self.index[rev][1]
822 return self.index[rev][1]
823
823
824 def rawsize(self, rev):
824 def rawsize(self, rev):
825 """return the length of the uncompressed text for a given revision"""
825 """return the length of the uncompressed text for a given revision"""
826 l = self.index[rev][2]
826 l = self.index[rev][2]
827 if l >= 0:
827 if l >= 0:
828 return l
828 return l
829
829
830 t = self.rawdata(rev)
830 t = self.rawdata(rev)
831 return len(t)
831 return len(t)
832
832
833 def size(self, rev):
833 def size(self, rev):
834 """length of non-raw text (processed by a "read" flag processor)"""
834 """length of non-raw text (processed by a "read" flag processor)"""
835 # fast path: if no "read" flag processor could change the content,
835 # fast path: if no "read" flag processor could change the content,
836 # size is rawsize. note: ELLIPSIS is known to not change the content.
836 # size is rawsize. note: ELLIPSIS is known to not change the content.
837 flags = self.flags(rev)
837 flags = self.flags(rev)
838 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
838 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
839 return self.rawsize(rev)
839 return self.rawsize(rev)
840
840
841 return len(self.revision(rev, raw=False))
841 return len(self.revision(rev, raw=False))
842
842
843 def chainbase(self, rev):
843 def chainbase(self, rev):
844 base = self._chainbasecache.get(rev)
844 base = self._chainbasecache.get(rev)
845 if base is not None:
845 if base is not None:
846 return base
846 return base
847
847
848 index = self.index
848 index = self.index
849 iterrev = rev
849 iterrev = rev
850 base = index[iterrev][3]
850 base = index[iterrev][3]
851 while base != iterrev:
851 while base != iterrev:
852 iterrev = base
852 iterrev = base
853 base = index[iterrev][3]
853 base = index[iterrev][3]
854
854
855 self._chainbasecache[rev] = base
855 self._chainbasecache[rev] = base
856 return base
856 return base
857
857
858 def linkrev(self, rev):
858 def linkrev(self, rev):
859 return self.index[rev][4]
859 return self.index[rev][4]
860
860
861 def parentrevs(self, rev):
861 def parentrevs(self, rev):
862 try:
862 try:
863 entry = self.index[rev]
863 entry = self.index[rev]
864 except IndexError:
864 except IndexError:
865 if rev == wdirrev:
865 if rev == wdirrev:
866 raise error.WdirUnsupported
866 raise error.WdirUnsupported
867 raise
867 raise
868
868
869 return entry[5], entry[6]
869 return entry[5], entry[6]
870
870
871 # fast parentrevs(rev) where rev isn't filtered
871 # fast parentrevs(rev) where rev isn't filtered
872 _uncheckedparentrevs = parentrevs
872 _uncheckedparentrevs = parentrevs
873
873
874 def node(self, rev):
874 def node(self, rev):
875 try:
875 try:
876 return self.index[rev][7]
876 return self.index[rev][7]
877 except IndexError:
877 except IndexError:
878 if rev == wdirrev:
878 if rev == wdirrev:
879 raise error.WdirUnsupported
879 raise error.WdirUnsupported
880 raise
880 raise
881
881
882 # Derived from index values.
882 # Derived from index values.
883
883
884 def end(self, rev):
884 def end(self, rev):
885 return self.start(rev) + self.length(rev)
885 return self.start(rev) + self.length(rev)
886
886
887 def parents(self, node):
887 def parents(self, node):
888 i = self.index
888 i = self.index
889 d = i[self.rev(node)]
889 d = i[self.rev(node)]
890 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
890 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
891
891
892 def chainlen(self, rev):
892 def chainlen(self, rev):
893 return self._chaininfo(rev)[0]
893 return self._chaininfo(rev)[0]
894
894
895 def _chaininfo(self, rev):
895 def _chaininfo(self, rev):
896 chaininfocache = self._chaininfocache
896 chaininfocache = self._chaininfocache
897 if rev in chaininfocache:
897 if rev in chaininfocache:
898 return chaininfocache[rev]
898 return chaininfocache[rev]
899 index = self.index
899 index = self.index
900 generaldelta = self._generaldelta
900 generaldelta = self._generaldelta
901 iterrev = rev
901 iterrev = rev
902 e = index[iterrev]
902 e = index[iterrev]
903 clen = 0
903 clen = 0
904 compresseddeltalen = 0
904 compresseddeltalen = 0
905 while iterrev != e[3]:
905 while iterrev != e[3]:
906 clen += 1
906 clen += 1
907 compresseddeltalen += e[1]
907 compresseddeltalen += e[1]
908 if generaldelta:
908 if generaldelta:
909 iterrev = e[3]
909 iterrev = e[3]
910 else:
910 else:
911 iterrev -= 1
911 iterrev -= 1
912 if iterrev in chaininfocache:
912 if iterrev in chaininfocache:
913 t = chaininfocache[iterrev]
913 t = chaininfocache[iterrev]
914 clen += t[0]
914 clen += t[0]
915 compresseddeltalen += t[1]
915 compresseddeltalen += t[1]
916 break
916 break
917 e = index[iterrev]
917 e = index[iterrev]
918 else:
918 else:
919 # Add text length of base since decompressing that also takes
919 # Add text length of base since decompressing that also takes
920 # work. For cache hits the length is already included.
920 # work. For cache hits the length is already included.
921 compresseddeltalen += e[1]
921 compresseddeltalen += e[1]
922 r = (clen, compresseddeltalen)
922 r = (clen, compresseddeltalen)
923 chaininfocache[rev] = r
923 chaininfocache[rev] = r
924 return r
924 return r
925
925
926 def _deltachain(self, rev, stoprev=None):
926 def _deltachain(self, rev, stoprev=None):
927 """Obtain the delta chain for a revision.
927 """Obtain the delta chain for a revision.
928
928
929 ``stoprev`` specifies a revision to stop at. If not specified, we
929 ``stoprev`` specifies a revision to stop at. If not specified, we
930 stop at the base of the chain.
930 stop at the base of the chain.
931
931
932 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
932 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
933 revs in ascending order and ``stopped`` is a bool indicating whether
933 revs in ascending order and ``stopped`` is a bool indicating whether
934 ``stoprev`` was hit.
934 ``stoprev`` was hit.
935 """
935 """
936 # Try C implementation.
936 # Try C implementation.
937 try:
937 try:
938 return self.index.deltachain(rev, stoprev, self._generaldelta)
938 return self.index.deltachain(rev, stoprev, self._generaldelta)
939 except AttributeError:
939 except AttributeError:
940 pass
940 pass
941
941
942 chain = []
942 chain = []
943
943
944 # Alias to prevent attribute lookup in tight loop.
944 # Alias to prevent attribute lookup in tight loop.
945 index = self.index
945 index = self.index
946 generaldelta = self._generaldelta
946 generaldelta = self._generaldelta
947
947
948 iterrev = rev
948 iterrev = rev
949 e = index[iterrev]
949 e = index[iterrev]
950 while iterrev != e[3] and iterrev != stoprev:
950 while iterrev != e[3] and iterrev != stoprev:
951 chain.append(iterrev)
951 chain.append(iterrev)
952 if generaldelta:
952 if generaldelta:
953 iterrev = e[3]
953 iterrev = e[3]
954 else:
954 else:
955 iterrev -= 1
955 iterrev -= 1
956 e = index[iterrev]
956 e = index[iterrev]
957
957
958 if iterrev == stoprev:
958 if iterrev == stoprev:
959 stopped = True
959 stopped = True
960 else:
960 else:
961 chain.append(iterrev)
961 chain.append(iterrev)
962 stopped = False
962 stopped = False
963
963
964 chain.reverse()
964 chain.reverse()
965 return chain, stopped
965 return chain, stopped
966
966
967 def ancestors(self, revs, stoprev=0, inclusive=False):
967 def ancestors(self, revs, stoprev=0, inclusive=False):
968 """Generate the ancestors of 'revs' in reverse revision order.
968 """Generate the ancestors of 'revs' in reverse revision order.
969 Does not generate revs lower than stoprev.
969 Does not generate revs lower than stoprev.
970
970
971 See the documentation for ancestor.lazyancestors for more details."""
971 See the documentation for ancestor.lazyancestors for more details."""
972
972
973 # first, make sure start revisions aren't filtered
973 # first, make sure start revisions aren't filtered
974 revs = list(revs)
974 revs = list(revs)
975 checkrev = self.node
975 checkrev = self.node
976 for r in revs:
976 for r in revs:
977 checkrev(r)
977 checkrev(r)
978 # and we're sure ancestors aren't filtered as well
978 # and we're sure ancestors aren't filtered as well
979
979
980 if rustancestor is not None:
980 if rustancestor is not None:
981 lazyancestors = rustancestor.LazyAncestors
981 lazyancestors = rustancestor.LazyAncestors
982 arg = self.index
982 arg = self.index
983 else:
983 else:
984 lazyancestors = ancestor.lazyancestors
984 lazyancestors = ancestor.lazyancestors
985 arg = self._uncheckedparentrevs
985 arg = self._uncheckedparentrevs
986 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
986 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
987
987
988 def descendants(self, revs):
988 def descendants(self, revs):
989 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
989 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
990
990
991 def findcommonmissing(self, common=None, heads=None):
991 def findcommonmissing(self, common=None, heads=None):
992 """Return a tuple of the ancestors of common and the ancestors of heads
992 """Return a tuple of the ancestors of common and the ancestors of heads
993 that are not ancestors of common. In revset terminology, we return the
993 that are not ancestors of common. In revset terminology, we return the
994 tuple:
994 tuple:
995
995
996 ::common, (::heads) - (::common)
996 ::common, (::heads) - (::common)
997
997
998 The list is sorted by revision number, meaning it is
998 The list is sorted by revision number, meaning it is
999 topologically sorted.
999 topologically sorted.
1000
1000
1001 'heads' and 'common' are both lists of node IDs. If heads is
1001 'heads' and 'common' are both lists of node IDs. If heads is
1002 not supplied, uses all of the revlog's heads. If common is not
1002 not supplied, uses all of the revlog's heads. If common is not
1003 supplied, uses nullid."""
1003 supplied, uses nullid."""
1004 if common is None:
1004 if common is None:
1005 common = [nullid]
1005 common = [nullid]
1006 if heads is None:
1006 if heads is None:
1007 heads = self.heads()
1007 heads = self.heads()
1008
1008
1009 common = [self.rev(n) for n in common]
1009 common = [self.rev(n) for n in common]
1010 heads = [self.rev(n) for n in heads]
1010 heads = [self.rev(n) for n in heads]
1011
1011
1012 # we want the ancestors, but inclusive
1012 # we want the ancestors, but inclusive
1013 class lazyset(object):
1013 class lazyset(object):
1014 def __init__(self, lazyvalues):
1014 def __init__(self, lazyvalues):
1015 self.addedvalues = set()
1015 self.addedvalues = set()
1016 self.lazyvalues = lazyvalues
1016 self.lazyvalues = lazyvalues
1017
1017
1018 def __contains__(self, value):
1018 def __contains__(self, value):
1019 return value in self.addedvalues or value in self.lazyvalues
1019 return value in self.addedvalues or value in self.lazyvalues
1020
1020
1021 def __iter__(self):
1021 def __iter__(self):
1022 added = self.addedvalues
1022 added = self.addedvalues
1023 for r in added:
1023 for r in added:
1024 yield r
1024 yield r
1025 for r in self.lazyvalues:
1025 for r in self.lazyvalues:
1026 if not r in added:
1026 if not r in added:
1027 yield r
1027 yield r
1028
1028
1029 def add(self, value):
1029 def add(self, value):
1030 self.addedvalues.add(value)
1030 self.addedvalues.add(value)
1031
1031
1032 def update(self, values):
1032 def update(self, values):
1033 self.addedvalues.update(values)
1033 self.addedvalues.update(values)
1034
1034
1035 has = lazyset(self.ancestors(common))
1035 has = lazyset(self.ancestors(common))
1036 has.add(nullrev)
1036 has.add(nullrev)
1037 has.update(common)
1037 has.update(common)
1038
1038
1039 # take all ancestors from heads that aren't in has
1039 # take all ancestors from heads that aren't in has
1040 missing = set()
1040 missing = set()
1041 visit = collections.deque(r for r in heads if r not in has)
1041 visit = collections.deque(r for r in heads if r not in has)
1042 while visit:
1042 while visit:
1043 r = visit.popleft()
1043 r = visit.popleft()
1044 if r in missing:
1044 if r in missing:
1045 continue
1045 continue
1046 else:
1046 else:
1047 missing.add(r)
1047 missing.add(r)
1048 for p in self.parentrevs(r):
1048 for p in self.parentrevs(r):
1049 if p not in has:
1049 if p not in has:
1050 visit.append(p)
1050 visit.append(p)
1051 missing = list(missing)
1051 missing = list(missing)
1052 missing.sort()
1052 missing.sort()
1053 return has, [self.node(miss) for miss in missing]
1053 return has, [self.node(miss) for miss in missing]
1054
1054
1055 def incrementalmissingrevs(self, common=None):
1055 def incrementalmissingrevs(self, common=None):
1056 """Return an object that can be used to incrementally compute the
1056 """Return an object that can be used to incrementally compute the
1057 revision numbers of the ancestors of arbitrary sets that are not
1057 revision numbers of the ancestors of arbitrary sets that are not
1058 ancestors of common. This is an ancestor.incrementalmissingancestors
1058 ancestors of common. This is an ancestor.incrementalmissingancestors
1059 object.
1059 object.
1060
1060
1061 'common' is a list of revision numbers. If common is not supplied, uses
1061 'common' is a list of revision numbers. If common is not supplied, uses
1062 nullrev.
1062 nullrev.
1063 """
1063 """
1064 if common is None:
1064 if common is None:
1065 common = [nullrev]
1065 common = [nullrev]
1066
1066
1067 if rustancestor is not None:
1067 if rustancestor is not None:
1068 return rustancestor.MissingAncestors(self.index, common)
1068 return rustancestor.MissingAncestors(self.index, common)
1069 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1069 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1070
1070
1071 def findmissingrevs(self, common=None, heads=None):
1071 def findmissingrevs(self, common=None, heads=None):
1072 """Return the revision numbers of the ancestors of heads that
1072 """Return the revision numbers of the ancestors of heads that
1073 are not ancestors of common.
1073 are not ancestors of common.
1074
1074
1075 More specifically, return a list of revision numbers corresponding to
1075 More specifically, return a list of revision numbers corresponding to
1076 nodes N such that every N satisfies the following constraints:
1076 nodes N such that every N satisfies the following constraints:
1077
1077
1078 1. N is an ancestor of some node in 'heads'
1078 1. N is an ancestor of some node in 'heads'
1079 2. N is not an ancestor of any node in 'common'
1079 2. N is not an ancestor of any node in 'common'
1080
1080
1081 The list is sorted by revision number, meaning it is
1081 The list is sorted by revision number, meaning it is
1082 topologically sorted.
1082 topologically sorted.
1083
1083
1084 'heads' and 'common' are both lists of revision numbers. If heads is
1084 'heads' and 'common' are both lists of revision numbers. If heads is
1085 not supplied, uses all of the revlog's heads. If common is not
1085 not supplied, uses all of the revlog's heads. If common is not
1086 supplied, uses nullid."""
1086 supplied, uses nullid."""
1087 if common is None:
1087 if common is None:
1088 common = [nullrev]
1088 common = [nullrev]
1089 if heads is None:
1089 if heads is None:
1090 heads = self.headrevs()
1090 heads = self.headrevs()
1091
1091
1092 inc = self.incrementalmissingrevs(common=common)
1092 inc = self.incrementalmissingrevs(common=common)
1093 return inc.missingancestors(heads)
1093 return inc.missingancestors(heads)
1094
1094
1095 def findmissing(self, common=None, heads=None):
1095 def findmissing(self, common=None, heads=None):
1096 """Return the ancestors of heads that are not ancestors of common.
1096 """Return the ancestors of heads that are not ancestors of common.
1097
1097
1098 More specifically, return a list of nodes N such that every N
1098 More specifically, return a list of nodes N such that every N
1099 satisfies the following constraints:
1099 satisfies the following constraints:
1100
1100
1101 1. N is an ancestor of some node in 'heads'
1101 1. N is an ancestor of some node in 'heads'
1102 2. N is not an ancestor of any node in 'common'
1102 2. N is not an ancestor of any node in 'common'
1103
1103
1104 The list is sorted by revision number, meaning it is
1104 The list is sorted by revision number, meaning it is
1105 topologically sorted.
1105 topologically sorted.
1106
1106
1107 'heads' and 'common' are both lists of node IDs. If heads is
1107 'heads' and 'common' are both lists of node IDs. If heads is
1108 not supplied, uses all of the revlog's heads. If common is not
1108 not supplied, uses all of the revlog's heads. If common is not
1109 supplied, uses nullid."""
1109 supplied, uses nullid."""
1110 if common is None:
1110 if common is None:
1111 common = [nullid]
1111 common = [nullid]
1112 if heads is None:
1112 if heads is None:
1113 heads = self.heads()
1113 heads = self.heads()
1114
1114
1115 common = [self.rev(n) for n in common]
1115 common = [self.rev(n) for n in common]
1116 heads = [self.rev(n) for n in heads]
1116 heads = [self.rev(n) for n in heads]
1117
1117
1118 inc = self.incrementalmissingrevs(common=common)
1118 inc = self.incrementalmissingrevs(common=common)
1119 return [self.node(r) for r in inc.missingancestors(heads)]
1119 return [self.node(r) for r in inc.missingancestors(heads)]
1120
1120
1121 def nodesbetween(self, roots=None, heads=None):
1121 def nodesbetween(self, roots=None, heads=None):
1122 """Return a topological path from 'roots' to 'heads'.
1122 """Return a topological path from 'roots' to 'heads'.
1123
1123
1124 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1124 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1125 topologically sorted list of all nodes N that satisfy both of
1125 topologically sorted list of all nodes N that satisfy both of
1126 these constraints:
1126 these constraints:
1127
1127
1128 1. N is a descendant of some node in 'roots'
1128 1. N is a descendant of some node in 'roots'
1129 2. N is an ancestor of some node in 'heads'
1129 2. N is an ancestor of some node in 'heads'
1130
1130
1131 Every node is considered to be both a descendant and an ancestor
1131 Every node is considered to be both a descendant and an ancestor
1132 of itself, so every reachable node in 'roots' and 'heads' will be
1132 of itself, so every reachable node in 'roots' and 'heads' will be
1133 included in 'nodes'.
1133 included in 'nodes'.
1134
1134
1135 'outroots' is the list of reachable nodes in 'roots', i.e., the
1135 'outroots' is the list of reachable nodes in 'roots', i.e., the
1136 subset of 'roots' that is returned in 'nodes'. Likewise,
1136 subset of 'roots' that is returned in 'nodes'. Likewise,
1137 'outheads' is the subset of 'heads' that is also in 'nodes'.
1137 'outheads' is the subset of 'heads' that is also in 'nodes'.
1138
1138
1139 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1139 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1140 unspecified, uses nullid as the only root. If 'heads' is
1140 unspecified, uses nullid as the only root. If 'heads' is
1141 unspecified, uses list of all of the revlog's heads."""
1141 unspecified, uses list of all of the revlog's heads."""
1142 nonodes = ([], [], [])
1142 nonodes = ([], [], [])
1143 if roots is not None:
1143 if roots is not None:
1144 roots = list(roots)
1144 roots = list(roots)
1145 if not roots:
1145 if not roots:
1146 return nonodes
1146 return nonodes
1147 lowestrev = min([self.rev(n) for n in roots])
1147 lowestrev = min([self.rev(n) for n in roots])
1148 else:
1148 else:
1149 roots = [nullid] # Everybody's a descendant of nullid
1149 roots = [nullid] # Everybody's a descendant of nullid
1150 lowestrev = nullrev
1150 lowestrev = nullrev
1151 if (lowestrev == nullrev) and (heads is None):
1151 if (lowestrev == nullrev) and (heads is None):
1152 # We want _all_ the nodes!
1152 # We want _all_ the nodes!
1153 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1153 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1154 if heads is None:
1154 if heads is None:
1155 # All nodes are ancestors, so the latest ancestor is the last
1155 # All nodes are ancestors, so the latest ancestor is the last
1156 # node.
1156 # node.
1157 highestrev = len(self) - 1
1157 highestrev = len(self) - 1
1158 # Set ancestors to None to signal that every node is an ancestor.
1158 # Set ancestors to None to signal that every node is an ancestor.
1159 ancestors = None
1159 ancestors = None
1160 # Set heads to an empty dictionary for later discovery of heads
1160 # Set heads to an empty dictionary for later discovery of heads
1161 heads = {}
1161 heads = {}
1162 else:
1162 else:
1163 heads = list(heads)
1163 heads = list(heads)
1164 if not heads:
1164 if not heads:
1165 return nonodes
1165 return nonodes
1166 ancestors = set()
1166 ancestors = set()
1167 # Turn heads into a dictionary so we can remove 'fake' heads.
1167 # Turn heads into a dictionary so we can remove 'fake' heads.
1168 # Also, later we will be using it to filter out the heads we can't
1168 # Also, later we will be using it to filter out the heads we can't
1169 # find from roots.
1169 # find from roots.
1170 heads = dict.fromkeys(heads, False)
1170 heads = dict.fromkeys(heads, False)
1171 # Start at the top and keep marking parents until we're done.
1171 # Start at the top and keep marking parents until we're done.
1172 nodestotag = set(heads)
1172 nodestotag = set(heads)
1173 # Remember where the top was so we can use it as a limit later.
1173 # Remember where the top was so we can use it as a limit later.
1174 highestrev = max([self.rev(n) for n in nodestotag])
1174 highestrev = max([self.rev(n) for n in nodestotag])
1175 while nodestotag:
1175 while nodestotag:
1176 # grab a node to tag
1176 # grab a node to tag
1177 n = nodestotag.pop()
1177 n = nodestotag.pop()
1178 # Never tag nullid
1178 # Never tag nullid
1179 if n == nullid:
1179 if n == nullid:
1180 continue
1180 continue
1181 # A node's revision number represents its place in a
1181 # A node's revision number represents its place in a
1182 # topologically sorted list of nodes.
1182 # topologically sorted list of nodes.
1183 r = self.rev(n)
1183 r = self.rev(n)
1184 if r >= lowestrev:
1184 if r >= lowestrev:
1185 if n not in ancestors:
1185 if n not in ancestors:
1186 # If we are possibly a descendant of one of the roots
1186 # If we are possibly a descendant of one of the roots
1187 # and we haven't already been marked as an ancestor
1187 # and we haven't already been marked as an ancestor
1188 ancestors.add(n) # Mark as ancestor
1188 ancestors.add(n) # Mark as ancestor
1189 # Add non-nullid parents to list of nodes to tag.
1189 # Add non-nullid parents to list of nodes to tag.
1190 nodestotag.update(
1190 nodestotag.update(
1191 [p for p in self.parents(n) if p != nullid]
1191 [p for p in self.parents(n) if p != nullid]
1192 )
1192 )
1193 elif n in heads: # We've seen it before, is it a fake head?
1193 elif n in heads: # We've seen it before, is it a fake head?
1194 # So it is, real heads should not be the ancestors of
1194 # So it is, real heads should not be the ancestors of
1195 # any other heads.
1195 # any other heads.
1196 heads.pop(n)
1196 heads.pop(n)
1197 if not ancestors:
1197 if not ancestors:
1198 return nonodes
1198 return nonodes
1199 # Now that we have our set of ancestors, we want to remove any
1199 # Now that we have our set of ancestors, we want to remove any
1200 # roots that are not ancestors.
1200 # roots that are not ancestors.
1201
1201
1202 # If one of the roots was nullid, everything is included anyway.
1202 # If one of the roots was nullid, everything is included anyway.
1203 if lowestrev > nullrev:
1203 if lowestrev > nullrev:
1204 # But, since we weren't, let's recompute the lowest rev to not
1204 # But, since we weren't, let's recompute the lowest rev to not
1205 # include roots that aren't ancestors.
1205 # include roots that aren't ancestors.
1206
1206
1207 # Filter out roots that aren't ancestors of heads
1207 # Filter out roots that aren't ancestors of heads
1208 roots = [root for root in roots if root in ancestors]
1208 roots = [root for root in roots if root in ancestors]
1209 # Recompute the lowest revision
1209 # Recompute the lowest revision
1210 if roots:
1210 if roots:
1211 lowestrev = min([self.rev(root) for root in roots])
1211 lowestrev = min([self.rev(root) for root in roots])
1212 else:
1212 else:
1213 # No more roots? Return empty list
1213 # No more roots? Return empty list
1214 return nonodes
1214 return nonodes
1215 else:
1215 else:
1216 # We are descending from nullid, and don't need to care about
1216 # We are descending from nullid, and don't need to care about
1217 # any other roots.
1217 # any other roots.
1218 lowestrev = nullrev
1218 lowestrev = nullrev
1219 roots = [nullid]
1219 roots = [nullid]
1220 # Transform our roots list into a set.
1220 # Transform our roots list into a set.
1221 descendants = set(roots)
1221 descendants = set(roots)
1222 # Also, keep the original roots so we can filter out roots that aren't
1222 # Also, keep the original roots so we can filter out roots that aren't
1223 # 'real' roots (i.e. are descended from other roots).
1223 # 'real' roots (i.e. are descended from other roots).
1224 roots = descendants.copy()
1224 roots = descendants.copy()
1225 # Our topologically sorted list of output nodes.
1225 # Our topologically sorted list of output nodes.
1226 orderedout = []
1226 orderedout = []
1227 # Don't start at nullid since we don't want nullid in our output list,
1227 # Don't start at nullid since we don't want nullid in our output list,
1228 # and if nullid shows up in descendants, empty parents will look like
1228 # and if nullid shows up in descendants, empty parents will look like
1229 # they're descendants.
1229 # they're descendants.
1230 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1230 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1231 n = self.node(r)
1231 n = self.node(r)
1232 isdescendant = False
1232 isdescendant = False
1233 if lowestrev == nullrev: # Everybody is a descendant of nullid
1233 if lowestrev == nullrev: # Everybody is a descendant of nullid
1234 isdescendant = True
1234 isdescendant = True
1235 elif n in descendants:
1235 elif n in descendants:
1236 # n is already a descendant
1236 # n is already a descendant
1237 isdescendant = True
1237 isdescendant = True
1238 # This check only needs to be done here because all the roots
1238 # This check only needs to be done here because all the roots
1239 # will start being marked is descendants before the loop.
1239 # will start being marked is descendants before the loop.
1240 if n in roots:
1240 if n in roots:
1241 # If n was a root, check if it's a 'real' root.
1241 # If n was a root, check if it's a 'real' root.
1242 p = tuple(self.parents(n))
1242 p = tuple(self.parents(n))
1243 # If any of its parents are descendants, it's not a root.
1243 # If any of its parents are descendants, it's not a root.
1244 if (p[0] in descendants) or (p[1] in descendants):
1244 if (p[0] in descendants) or (p[1] in descendants):
1245 roots.remove(n)
1245 roots.remove(n)
1246 else:
1246 else:
1247 p = tuple(self.parents(n))
1247 p = tuple(self.parents(n))
1248 # A node is a descendant if either of its parents are
1248 # A node is a descendant if either of its parents are
1249 # descendants. (We seeded the dependents list with the roots
1249 # descendants. (We seeded the dependents list with the roots
1250 # up there, remember?)
1250 # up there, remember?)
1251 if (p[0] in descendants) or (p[1] in descendants):
1251 if (p[0] in descendants) or (p[1] in descendants):
1252 descendants.add(n)
1252 descendants.add(n)
1253 isdescendant = True
1253 isdescendant = True
1254 if isdescendant and ((ancestors is None) or (n in ancestors)):
1254 if isdescendant and ((ancestors is None) or (n in ancestors)):
1255 # Only include nodes that are both descendants and ancestors.
1255 # Only include nodes that are both descendants and ancestors.
1256 orderedout.append(n)
1256 orderedout.append(n)
1257 if (ancestors is not None) and (n in heads):
1257 if (ancestors is not None) and (n in heads):
1258 # We're trying to figure out which heads are reachable
1258 # We're trying to figure out which heads are reachable
1259 # from roots.
1259 # from roots.
1260 # Mark this head as having been reached
1260 # Mark this head as having been reached
1261 heads[n] = True
1261 heads[n] = True
1262 elif ancestors is None:
1262 elif ancestors is None:
1263 # Otherwise, we're trying to discover the heads.
1263 # Otherwise, we're trying to discover the heads.
1264 # Assume this is a head because if it isn't, the next step
1264 # Assume this is a head because if it isn't, the next step
1265 # will eventually remove it.
1265 # will eventually remove it.
1266 heads[n] = True
1266 heads[n] = True
1267 # But, obviously its parents aren't.
1267 # But, obviously its parents aren't.
1268 for p in self.parents(n):
1268 for p in self.parents(n):
1269 heads.pop(p, None)
1269 heads.pop(p, None)
1270 heads = [head for head, flag in pycompat.iteritems(heads) if flag]
1270 heads = [head for head, flag in pycompat.iteritems(heads) if flag]
1271 roots = list(roots)
1271 roots = list(roots)
1272 assert orderedout
1272 assert orderedout
1273 assert roots
1273 assert roots
1274 assert heads
1274 assert heads
1275 return (orderedout, roots, heads)
1275 return (orderedout, roots, heads)
1276
1276
1277 def headrevs(self, revs=None):
1277 def headrevs(self, revs=None):
1278 if revs is None:
1278 if revs is None:
1279 try:
1279 try:
1280 return self.index.headrevs()
1280 return self.index.headrevs()
1281 except AttributeError:
1281 except AttributeError:
1282 return self._headrevs()
1282 return self._headrevs()
1283 if rustdagop is not None:
1283 if rustdagop is not None:
1284 return rustdagop.headrevs(self.index, revs)
1284 return rustdagop.headrevs(self.index, revs)
1285 return dagop.headrevs(revs, self._uncheckedparentrevs)
1285 return dagop.headrevs(revs, self._uncheckedparentrevs)
1286
1286
1287 def computephases(self, roots):
1287 def computephases(self, roots):
1288 return self.index.computephasesmapsets(roots)
1288 return self.index.computephasesmapsets(roots)
1289
1289
1290 def _headrevs(self):
1290 def _headrevs(self):
1291 count = len(self)
1291 count = len(self)
1292 if not count:
1292 if not count:
1293 return [nullrev]
1293 return [nullrev]
1294 # we won't iter over filtered rev so nobody is a head at start
1294 # we won't iter over filtered rev so nobody is a head at start
1295 ishead = [0] * (count + 1)
1295 ishead = [0] * (count + 1)
1296 index = self.index
1296 index = self.index
1297 for r in self:
1297 for r in self:
1298 ishead[r] = 1 # I may be an head
1298 ishead[r] = 1 # I may be an head
1299 e = index[r]
1299 e = index[r]
1300 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1300 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1301 return [r for r, val in enumerate(ishead) if val]
1301 return [r for r, val in enumerate(ishead) if val]
1302
1302
1303 def heads(self, start=None, stop=None):
1303 def heads(self, start=None, stop=None):
1304 """return the list of all nodes that have no children
1304 """return the list of all nodes that have no children
1305
1305
1306 if start is specified, only heads that are descendants of
1306 if start is specified, only heads that are descendants of
1307 start will be returned
1307 start will be returned
1308 if stop is specified, it will consider all the revs from stop
1308 if stop is specified, it will consider all the revs from stop
1309 as if they had no children
1309 as if they had no children
1310 """
1310 """
1311 if start is None and stop is None:
1311 if start is None and stop is None:
1312 if not len(self):
1312 if not len(self):
1313 return [nullid]
1313 return [nullid]
1314 return [self.node(r) for r in self.headrevs()]
1314 return [self.node(r) for r in self.headrevs()]
1315
1315
1316 if start is None:
1316 if start is None:
1317 start = nullrev
1317 start = nullrev
1318 else:
1318 else:
1319 start = self.rev(start)
1319 start = self.rev(start)
1320
1320
1321 stoprevs = {self.rev(n) for n in stop or []}
1321 stoprevs = {self.rev(n) for n in stop or []}
1322
1322
1323 revs = dagop.headrevssubset(
1323 revs = dagop.headrevssubset(
1324 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1324 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1325 )
1325 )
1326
1326
1327 return [self.node(rev) for rev in revs]
1327 return [self.node(rev) for rev in revs]
1328
1328
1329 def children(self, node):
1329 def children(self, node):
1330 """find the children of a given node"""
1330 """find the children of a given node"""
1331 c = []
1331 c = []
1332 p = self.rev(node)
1332 p = self.rev(node)
1333 for r in self.revs(start=p + 1):
1333 for r in self.revs(start=p + 1):
1334 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1334 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1335 if prevs:
1335 if prevs:
1336 for pr in prevs:
1336 for pr in prevs:
1337 if pr == p:
1337 if pr == p:
1338 c.append(self.node(r))
1338 c.append(self.node(r))
1339 elif p == nullrev:
1339 elif p == nullrev:
1340 c.append(self.node(r))
1340 c.append(self.node(r))
1341 return c
1341 return c
1342
1342
1343 def commonancestorsheads(self, a, b):
1343 def commonancestorsheads(self, a, b):
1344 """calculate all the heads of the common ancestors of nodes a and b"""
1344 """calculate all the heads of the common ancestors of nodes a and b"""
1345 a, b = self.rev(a), self.rev(b)
1345 a, b = self.rev(a), self.rev(b)
1346 ancs = self._commonancestorsheads(a, b)
1346 ancs = self._commonancestorsheads(a, b)
1347 return pycompat.maplist(self.node, ancs)
1347 return pycompat.maplist(self.node, ancs)
1348
1348
1349 def _commonancestorsheads(self, *revs):
1349 def _commonancestorsheads(self, *revs):
1350 """calculate all the heads of the common ancestors of revs"""
1350 """calculate all the heads of the common ancestors of revs"""
1351 try:
1351 try:
1352 ancs = self.index.commonancestorsheads(*revs)
1352 ancs = self.index.commonancestorsheads(*revs)
1353 except (AttributeError, OverflowError): # C implementation failed
1353 except (AttributeError, OverflowError): # C implementation failed
1354 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1354 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1355 return ancs
1355 return ancs
1356
1356
1357 def isancestor(self, a, b):
1357 def isancestor(self, a, b):
1358 """return True if node a is an ancestor of node b
1358 """return True if node a is an ancestor of node b
1359
1359
1360 A revision is considered an ancestor of itself."""
1360 A revision is considered an ancestor of itself."""
1361 a, b = self.rev(a), self.rev(b)
1361 a, b = self.rev(a), self.rev(b)
1362 return self.isancestorrev(a, b)
1362 return self.isancestorrev(a, b)
1363
1363
1364 def isancestorrev(self, a, b):
1364 def isancestorrev(self, a, b):
1365 """return True if revision a is an ancestor of revision b
1365 """return True if revision a is an ancestor of revision b
1366
1366
1367 A revision is considered an ancestor of itself.
1367 A revision is considered an ancestor of itself.
1368
1368
1369 The implementation of this is trivial but the use of
1369 The implementation of this is trivial but the use of
1370 reachableroots is not."""
1370 reachableroots is not."""
1371 if a == nullrev:
1371 if a == nullrev:
1372 return True
1372 return True
1373 elif a == b:
1373 elif a == b:
1374 return True
1374 return True
1375 elif a > b:
1375 elif a > b:
1376 return False
1376 return False
1377 return bool(self.reachableroots(a, [b], [a], includepath=False))
1377 return bool(self.reachableroots(a, [b], [a], includepath=False))
1378
1378
1379 def reachableroots(self, minroot, heads, roots, includepath=False):
1379 def reachableroots(self, minroot, heads, roots, includepath=False):
1380 """return (heads(::(<roots> and <roots>::<heads>)))
1380 """return (heads(::(<roots> and <roots>::<heads>)))
1381
1381
1382 If includepath is True, return (<roots>::<heads>)."""
1382 If includepath is True, return (<roots>::<heads>)."""
1383 try:
1383 try:
1384 return self.index.reachableroots2(
1384 return self.index.reachableroots2(
1385 minroot, heads, roots, includepath
1385 minroot, heads, roots, includepath
1386 )
1386 )
1387 except AttributeError:
1387 except AttributeError:
1388 return dagop._reachablerootspure(
1388 return dagop._reachablerootspure(
1389 self.parentrevs, minroot, roots, heads, includepath
1389 self.parentrevs, minroot, roots, heads, includepath
1390 )
1390 )
1391
1391
1392 def ancestor(self, a, b):
1392 def ancestor(self, a, b):
1393 """calculate the "best" common ancestor of nodes a and b"""
1393 """calculate the "best" common ancestor of nodes a and b"""
1394
1394
1395 a, b = self.rev(a), self.rev(b)
1395 a, b = self.rev(a), self.rev(b)
1396 try:
1396 try:
1397 ancs = self.index.ancestors(a, b)
1397 ancs = self.index.ancestors(a, b)
1398 except (AttributeError, OverflowError):
1398 except (AttributeError, OverflowError):
1399 ancs = ancestor.ancestors(self.parentrevs, a, b)
1399 ancs = ancestor.ancestors(self.parentrevs, a, b)
1400 if ancs:
1400 if ancs:
1401 # choose a consistent winner when there's a tie
1401 # choose a consistent winner when there's a tie
1402 return min(map(self.node, ancs))
1402 return min(map(self.node, ancs))
1403 return nullid
1403 return nullid
1404
1404
1405 def _match(self, id):
1405 def _match(self, id):
1406 if isinstance(id, int):
1406 if isinstance(id, int):
1407 # rev
1407 # rev
1408 return self.node(id)
1408 return self.node(id)
1409 if len(id) == 20:
1409 if len(id) == 20:
1410 # possibly a binary node
1410 # possibly a binary node
1411 # odds of a binary node being all hex in ASCII are 1 in 10**25
1411 # odds of a binary node being all hex in ASCII are 1 in 10**25
1412 try:
1412 try:
1413 node = id
1413 node = id
1414 self.rev(node) # quick search the index
1414 self.rev(node) # quick search the index
1415 return node
1415 return node
1416 except error.LookupError:
1416 except error.LookupError:
1417 pass # may be partial hex id
1417 pass # may be partial hex id
1418 try:
1418 try:
1419 # str(rev)
1419 # str(rev)
1420 rev = int(id)
1420 rev = int(id)
1421 if b"%d" % rev != id:
1421 if b"%d" % rev != id:
1422 raise ValueError
1422 raise ValueError
1423 if rev < 0:
1423 if rev < 0:
1424 rev = len(self) + rev
1424 rev = len(self) + rev
1425 if rev < 0 or rev >= len(self):
1425 if rev < 0 or rev >= len(self):
1426 raise ValueError
1426 raise ValueError
1427 return self.node(rev)
1427 return self.node(rev)
1428 except (ValueError, OverflowError):
1428 except (ValueError, OverflowError):
1429 pass
1429 pass
1430 if len(id) == 40:
1430 if len(id) == 40:
1431 try:
1431 try:
1432 # a full hex nodeid?
1432 # a full hex nodeid?
1433 node = bin(id)
1433 node = bin(id)
1434 self.rev(node)
1434 self.rev(node)
1435 return node
1435 return node
1436 except (TypeError, error.LookupError):
1436 except (TypeError, error.LookupError):
1437 pass
1437 pass
1438
1438
1439 def _partialmatch(self, id):
1439 def _partialmatch(self, id):
1440 # we don't care wdirfilenodeids as they should be always full hash
1440 # we don't care wdirfilenodeids as they should be always full hash
1441 maybewdir = wdirhex.startswith(id)
1441 maybewdir = wdirhex.startswith(id)
1442 try:
1442 try:
1443 partial = self.index.partialmatch(id)
1443 partial = self.index.partialmatch(id)
1444 if partial and self.hasnode(partial):
1444 if partial and self.hasnode(partial):
1445 if maybewdir:
1445 if maybewdir:
1446 # single 'ff...' match in radix tree, ambiguous with wdir
1446 # single 'ff...' match in radix tree, ambiguous with wdir
1447 raise error.RevlogError
1447 raise error.RevlogError
1448 return partial
1448 return partial
1449 if maybewdir:
1449 if maybewdir:
1450 # no 'ff...' match in radix tree, wdir identified
1450 # no 'ff...' match in radix tree, wdir identified
1451 raise error.WdirUnsupported
1451 raise error.WdirUnsupported
1452 return None
1452 return None
1453 except error.RevlogError:
1453 except error.RevlogError:
1454 # parsers.c radix tree lookup gave multiple matches
1454 # parsers.c radix tree lookup gave multiple matches
1455 # fast path: for unfiltered changelog, radix tree is accurate
1455 # fast path: for unfiltered changelog, radix tree is accurate
1456 if not getattr(self, 'filteredrevs', None):
1456 if not getattr(self, 'filteredrevs', None):
1457 raise error.AmbiguousPrefixLookupError(
1457 raise error.AmbiguousPrefixLookupError(
1458 id, self.indexfile, _(b'ambiguous identifier')
1458 id, self.indexfile, _(b'ambiguous identifier')
1459 )
1459 )
1460 # fall through to slow path that filters hidden revisions
1460 # fall through to slow path that filters hidden revisions
1461 except (AttributeError, ValueError):
1461 except (AttributeError, ValueError):
1462 # we are pure python, or key was too short to search radix tree
1462 # we are pure python, or key was too short to search radix tree
1463 pass
1463 pass
1464
1464
1465 if id in self._pcache:
1465 if id in self._pcache:
1466 return self._pcache[id]
1466 return self._pcache[id]
1467
1467
1468 if len(id) <= 40:
1468 if len(id) <= 40:
1469 try:
1469 try:
1470 # hex(node)[:...]
1470 # hex(node)[:...]
1471 l = len(id) // 2 # grab an even number of digits
1471 l = len(id) // 2 # grab an even number of digits
1472 prefix = bin(id[: l * 2])
1472 prefix = bin(id[: l * 2])
1473 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1473 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1474 nl = [
1474 nl = [
1475 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1475 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1476 ]
1476 ]
1477 if nullhex.startswith(id):
1477 if nullhex.startswith(id):
1478 nl.append(nullid)
1478 nl.append(nullid)
1479 if len(nl) > 0:
1479 if len(nl) > 0:
1480 if len(nl) == 1 and not maybewdir:
1480 if len(nl) == 1 and not maybewdir:
1481 self._pcache[id] = nl[0]
1481 self._pcache[id] = nl[0]
1482 return nl[0]
1482 return nl[0]
1483 raise error.AmbiguousPrefixLookupError(
1483 raise error.AmbiguousPrefixLookupError(
1484 id, self.indexfile, _(b'ambiguous identifier')
1484 id, self.indexfile, _(b'ambiguous identifier')
1485 )
1485 )
1486 if maybewdir:
1486 if maybewdir:
1487 raise error.WdirUnsupported
1487 raise error.WdirUnsupported
1488 return None
1488 return None
1489 except TypeError:
1489 except TypeError:
1490 pass
1490 pass
1491
1491
1492 def lookup(self, id):
1492 def lookup(self, id):
1493 """locate a node based on:
1493 """locate a node based on:
1494 - revision number or str(revision number)
1494 - revision number or str(revision number)
1495 - nodeid or subset of hex nodeid
1495 - nodeid or subset of hex nodeid
1496 """
1496 """
1497 n = self._match(id)
1497 n = self._match(id)
1498 if n is not None:
1498 if n is not None:
1499 return n
1499 return n
1500 n = self._partialmatch(id)
1500 n = self._partialmatch(id)
1501 if n:
1501 if n:
1502 return n
1502 return n
1503
1503
1504 raise error.LookupError(id, self.indexfile, _(b'no match found'))
1504 raise error.LookupError(id, self.indexfile, _(b'no match found'))
1505
1505
1506 def shortest(self, node, minlength=1):
1506 def shortest(self, node, minlength=1):
1507 """Find the shortest unambiguous prefix that matches node."""
1507 """Find the shortest unambiguous prefix that matches node."""
1508
1508
1509 def isvalid(prefix):
1509 def isvalid(prefix):
1510 try:
1510 try:
1511 matchednode = self._partialmatch(prefix)
1511 matchednode = self._partialmatch(prefix)
1512 except error.AmbiguousPrefixLookupError:
1512 except error.AmbiguousPrefixLookupError:
1513 return False
1513 return False
1514 except error.WdirUnsupported:
1514 except error.WdirUnsupported:
1515 # single 'ff...' match
1515 # single 'ff...' match
1516 return True
1516 return True
1517 if matchednode is None:
1517 if matchednode is None:
1518 raise error.LookupError(node, self.indexfile, _(b'no node'))
1518 raise error.LookupError(node, self.indexfile, _(b'no node'))
1519 return True
1519 return True
1520
1520
1521 def maybewdir(prefix):
1521 def maybewdir(prefix):
1522 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1522 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1523
1523
1524 hexnode = hex(node)
1524 hexnode = hex(node)
1525
1525
1526 def disambiguate(hexnode, minlength):
1526 def disambiguate(hexnode, minlength):
1527 """Disambiguate against wdirid."""
1527 """Disambiguate against wdirid."""
1528 for length in range(minlength, len(hexnode) + 1):
1528 for length in range(minlength, len(hexnode) + 1):
1529 prefix = hexnode[:length]
1529 prefix = hexnode[:length]
1530 if not maybewdir(prefix):
1530 if not maybewdir(prefix):
1531 return prefix
1531 return prefix
1532
1532
1533 if not getattr(self, 'filteredrevs', None):
1533 if not getattr(self, 'filteredrevs', None):
1534 try:
1534 try:
1535 length = max(self.index.shortest(node), minlength)
1535 length = max(self.index.shortest(node), minlength)
1536 return disambiguate(hexnode, length)
1536 return disambiguate(hexnode, length)
1537 except error.RevlogError:
1537 except error.RevlogError:
1538 if node != wdirid:
1538 if node != wdirid:
1539 raise error.LookupError(node, self.indexfile, _(b'no node'))
1539 raise error.LookupError(node, self.indexfile, _(b'no node'))
1540 except AttributeError:
1540 except AttributeError:
1541 # Fall through to pure code
1541 # Fall through to pure code
1542 pass
1542 pass
1543
1543
1544 if node == wdirid:
1544 if node == wdirid:
1545 for length in range(minlength, len(hexnode) + 1):
1545 for length in range(minlength, len(hexnode) + 1):
1546 prefix = hexnode[:length]
1546 prefix = hexnode[:length]
1547 if isvalid(prefix):
1547 if isvalid(prefix):
1548 return prefix
1548 return prefix
1549
1549
1550 for length in range(minlength, len(hexnode) + 1):
1550 for length in range(minlength, len(hexnode) + 1):
1551 prefix = hexnode[:length]
1551 prefix = hexnode[:length]
1552 if isvalid(prefix):
1552 if isvalid(prefix):
1553 return disambiguate(hexnode, length)
1553 return disambiguate(hexnode, length)
1554
1554
1555 def cmp(self, node, text):
1555 def cmp(self, node, text):
1556 """compare text with a given file revision
1556 """compare text with a given file revision
1557
1557
1558 returns True if text is different than what is stored.
1558 returns True if text is different than what is stored.
1559 """
1559 """
1560 p1, p2 = self.parents(node)
1560 p1, p2 = self.parents(node)
1561 return storageutil.hashrevisionsha1(text, p1, p2) != node
1561 return storageutil.hashrevisionsha1(text, p1, p2) != node
1562
1562
1563 def _cachesegment(self, offset, data):
1563 def _cachesegment(self, offset, data):
1564 """Add a segment to the revlog cache.
1564 """Add a segment to the revlog cache.
1565
1565
1566 Accepts an absolute offset and the data that is at that location.
1566 Accepts an absolute offset and the data that is at that location.
1567 """
1567 """
1568 o, d = self._chunkcache
1568 o, d = self._chunkcache
1569 # try to add to existing cache
1569 # try to add to existing cache
1570 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1570 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1571 self._chunkcache = o, d + data
1571 self._chunkcache = o, d + data
1572 else:
1572 else:
1573 self._chunkcache = offset, data
1573 self._chunkcache = offset, data
1574
1574
1575 def _readsegment(self, offset, length, df=None):
1575 def _readsegment(self, offset, length, df=None):
1576 """Load a segment of raw data from the revlog.
1576 """Load a segment of raw data from the revlog.
1577
1577
1578 Accepts an absolute offset, length to read, and an optional existing
1578 Accepts an absolute offset, length to read, and an optional existing
1579 file handle to read from.
1579 file handle to read from.
1580
1580
1581 If an existing file handle is passed, it will be seeked and the
1581 If an existing file handle is passed, it will be seeked and the
1582 original seek position will NOT be restored.
1582 original seek position will NOT be restored.
1583
1583
1584 Returns a str or buffer of raw byte data.
1584 Returns a str or buffer of raw byte data.
1585
1585
1586 Raises if the requested number of bytes could not be read.
1586 Raises if the requested number of bytes could not be read.
1587 """
1587 """
1588 # Cache data both forward and backward around the requested
1588 # Cache data both forward and backward around the requested
1589 # data, in a fixed size window. This helps speed up operations
1589 # data, in a fixed size window. This helps speed up operations
1590 # involving reading the revlog backwards.
1590 # involving reading the revlog backwards.
1591 cachesize = self._chunkcachesize
1591 cachesize = self._chunkcachesize
1592 realoffset = offset & ~(cachesize - 1)
1592 realoffset = offset & ~(cachesize - 1)
1593 reallength = (
1593 reallength = (
1594 (offset + length + cachesize) & ~(cachesize - 1)
1594 (offset + length + cachesize) & ~(cachesize - 1)
1595 ) - realoffset
1595 ) - realoffset
1596 with self._datareadfp(df) as df:
1596 with self._datareadfp(df) as df:
1597 df.seek(realoffset)
1597 df.seek(realoffset)
1598 d = df.read(reallength)
1598 d = df.read(reallength)
1599
1599
1600 self._cachesegment(realoffset, d)
1600 self._cachesegment(realoffset, d)
1601 if offset != realoffset or reallength != length:
1601 if offset != realoffset or reallength != length:
1602 startoffset = offset - realoffset
1602 startoffset = offset - realoffset
1603 if len(d) - startoffset < length:
1603 if len(d) - startoffset < length:
1604 raise error.RevlogError(
1604 raise error.RevlogError(
1605 _(
1605 _(
1606 b'partial read of revlog %s; expected %d bytes from '
1606 b'partial read of revlog %s; expected %d bytes from '
1607 b'offset %d, got %d'
1607 b'offset %d, got %d'
1608 )
1608 )
1609 % (
1609 % (
1610 self.indexfile if self._inline else self.datafile,
1610 self.indexfile if self._inline else self.datafile,
1611 length,
1611 length,
1612 realoffset,
1612 realoffset,
1613 len(d) - startoffset,
1613 len(d) - startoffset,
1614 )
1614 )
1615 )
1615 )
1616
1616
1617 return util.buffer(d, startoffset, length)
1617 return util.buffer(d, startoffset, length)
1618
1618
1619 if len(d) < length:
1619 if len(d) < length:
1620 raise error.RevlogError(
1620 raise error.RevlogError(
1621 _(
1621 _(
1622 b'partial read of revlog %s; expected %d bytes from offset '
1622 b'partial read of revlog %s; expected %d bytes from offset '
1623 b'%d, got %d'
1623 b'%d, got %d'
1624 )
1624 )
1625 % (
1625 % (
1626 self.indexfile if self._inline else self.datafile,
1626 self.indexfile if self._inline else self.datafile,
1627 length,
1627 length,
1628 offset,
1628 offset,
1629 len(d),
1629 len(d),
1630 )
1630 )
1631 )
1631 )
1632
1632
1633 return d
1633 return d
1634
1634
1635 def _getsegment(self, offset, length, df=None):
1635 def _getsegment(self, offset, length, df=None):
1636 """Obtain a segment of raw data from the revlog.
1636 """Obtain a segment of raw data from the revlog.
1637
1637
1638 Accepts an absolute offset, length of bytes to obtain, and an
1638 Accepts an absolute offset, length of bytes to obtain, and an
1639 optional file handle to the already-opened revlog. If the file
1639 optional file handle to the already-opened revlog. If the file
1640 handle is used, it's original seek position will not be preserved.
1640 handle is used, it's original seek position will not be preserved.
1641
1641
1642 Requests for data may be returned from a cache.
1642 Requests for data may be returned from a cache.
1643
1643
1644 Returns a str or a buffer instance of raw byte data.
1644 Returns a str or a buffer instance of raw byte data.
1645 """
1645 """
1646 o, d = self._chunkcache
1646 o, d = self._chunkcache
1647 l = len(d)
1647 l = len(d)
1648
1648
1649 # is it in the cache?
1649 # is it in the cache?
1650 cachestart = offset - o
1650 cachestart = offset - o
1651 cacheend = cachestart + length
1651 cacheend = cachestart + length
1652 if cachestart >= 0 and cacheend <= l:
1652 if cachestart >= 0 and cacheend <= l:
1653 if cachestart == 0 and cacheend == l:
1653 if cachestart == 0 and cacheend == l:
1654 return d # avoid a copy
1654 return d # avoid a copy
1655 return util.buffer(d, cachestart, cacheend - cachestart)
1655 return util.buffer(d, cachestart, cacheend - cachestart)
1656
1656
1657 return self._readsegment(offset, length, df=df)
1657 return self._readsegment(offset, length, df=df)
1658
1658
1659 def _getsegmentforrevs(self, startrev, endrev, df=None):
1659 def _getsegmentforrevs(self, startrev, endrev, df=None):
1660 """Obtain a segment of raw data corresponding to a range of revisions.
1660 """Obtain a segment of raw data corresponding to a range of revisions.
1661
1661
1662 Accepts the start and end revisions and an optional already-open
1662 Accepts the start and end revisions and an optional already-open
1663 file handle to be used for reading. If the file handle is read, its
1663 file handle to be used for reading. If the file handle is read, its
1664 seek position will not be preserved.
1664 seek position will not be preserved.
1665
1665
1666 Requests for data may be satisfied by a cache.
1666 Requests for data may be satisfied by a cache.
1667
1667
1668 Returns a 2-tuple of (offset, data) for the requested range of
1668 Returns a 2-tuple of (offset, data) for the requested range of
1669 revisions. Offset is the integer offset from the beginning of the
1669 revisions. Offset is the integer offset from the beginning of the
1670 revlog and data is a str or buffer of the raw byte data.
1670 revlog and data is a str or buffer of the raw byte data.
1671
1671
1672 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1672 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1673 to determine where each revision's data begins and ends.
1673 to determine where each revision's data begins and ends.
1674 """
1674 """
1675 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1675 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1676 # (functions are expensive).
1676 # (functions are expensive).
1677 index = self.index
1677 index = self.index
1678 istart = index[startrev]
1678 istart = index[startrev]
1679 start = int(istart[0] >> 16)
1679 start = int(istart[0] >> 16)
1680 if startrev == endrev:
1680 if startrev == endrev:
1681 end = start + istart[1]
1681 end = start + istart[1]
1682 else:
1682 else:
1683 iend = index[endrev]
1683 iend = index[endrev]
1684 end = int(iend[0] >> 16) + iend[1]
1684 end = int(iend[0] >> 16) + iend[1]
1685
1685
1686 if self._inline:
1686 if self._inline:
1687 start += (startrev + 1) * self._io.size
1687 start += (startrev + 1) * self._io.size
1688 end += (endrev + 1) * self._io.size
1688 end += (endrev + 1) * self._io.size
1689 length = end - start
1689 length = end - start
1690
1690
1691 return start, self._getsegment(start, length, df=df)
1691 return start, self._getsegment(start, length, df=df)
1692
1692
1693 def _chunk(self, rev, df=None):
1693 def _chunk(self, rev, df=None):
1694 """Obtain a single decompressed chunk for a revision.
1694 """Obtain a single decompressed chunk for a revision.
1695
1695
1696 Accepts an integer revision and an optional already-open file handle
1696 Accepts an integer revision and an optional already-open file handle
1697 to be used for reading. If used, the seek position of the file will not
1697 to be used for reading. If used, the seek position of the file will not
1698 be preserved.
1698 be preserved.
1699
1699
1700 Returns a str holding uncompressed data for the requested revision.
1700 Returns a str holding uncompressed data for the requested revision.
1701 """
1701 """
1702 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1702 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1703
1703
1704 def _chunks(self, revs, df=None, targetsize=None):
1704 def _chunks(self, revs, df=None, targetsize=None):
1705 """Obtain decompressed chunks for the specified revisions.
1705 """Obtain decompressed chunks for the specified revisions.
1706
1706
1707 Accepts an iterable of numeric revisions that are assumed to be in
1707 Accepts an iterable of numeric revisions that are assumed to be in
1708 ascending order. Also accepts an optional already-open file handle
1708 ascending order. Also accepts an optional already-open file handle
1709 to be used for reading. If used, the seek position of the file will
1709 to be used for reading. If used, the seek position of the file will
1710 not be preserved.
1710 not be preserved.
1711
1711
1712 This function is similar to calling ``self._chunk()`` multiple times,
1712 This function is similar to calling ``self._chunk()`` multiple times,
1713 but is faster.
1713 but is faster.
1714
1714
1715 Returns a list with decompressed data for each requested revision.
1715 Returns a list with decompressed data for each requested revision.
1716 """
1716 """
1717 if not revs:
1717 if not revs:
1718 return []
1718 return []
1719 start = self.start
1719 start = self.start
1720 length = self.length
1720 length = self.length
1721 inline = self._inline
1721 inline = self._inline
1722 iosize = self._io.size
1722 iosize = self._io.size
1723 buffer = util.buffer
1723 buffer = util.buffer
1724
1724
1725 l = []
1725 l = []
1726 ladd = l.append
1726 ladd = l.append
1727
1727
1728 if not self._withsparseread:
1728 if not self._withsparseread:
1729 slicedchunks = (revs,)
1729 slicedchunks = (revs,)
1730 else:
1730 else:
1731 slicedchunks = deltautil.slicechunk(
1731 slicedchunks = deltautil.slicechunk(
1732 self, revs, targetsize=targetsize
1732 self, revs, targetsize=targetsize
1733 )
1733 )
1734
1734
1735 for revschunk in slicedchunks:
1735 for revschunk in slicedchunks:
1736 firstrev = revschunk[0]
1736 firstrev = revschunk[0]
1737 # Skip trailing revisions with empty diff
1737 # Skip trailing revisions with empty diff
1738 for lastrev in revschunk[::-1]:
1738 for lastrev in revschunk[::-1]:
1739 if length(lastrev) != 0:
1739 if length(lastrev) != 0:
1740 break
1740 break
1741
1741
1742 try:
1742 try:
1743 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1743 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1744 except OverflowError:
1744 except OverflowError:
1745 # issue4215 - we can't cache a run of chunks greater than
1745 # issue4215 - we can't cache a run of chunks greater than
1746 # 2G on Windows
1746 # 2G on Windows
1747 return [self._chunk(rev, df=df) for rev in revschunk]
1747 return [self._chunk(rev, df=df) for rev in revschunk]
1748
1748
1749 decomp = self.decompress
1749 decomp = self.decompress
1750 for rev in revschunk:
1750 for rev in revschunk:
1751 chunkstart = start(rev)
1751 chunkstart = start(rev)
1752 if inline:
1752 if inline:
1753 chunkstart += (rev + 1) * iosize
1753 chunkstart += (rev + 1) * iosize
1754 chunklength = length(rev)
1754 chunklength = length(rev)
1755 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1755 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1756
1756
1757 return l
1757 return l
1758
1758
1759 def _chunkclear(self):
1759 def _chunkclear(self):
1760 """Clear the raw chunk cache."""
1760 """Clear the raw chunk cache."""
1761 self._chunkcache = (0, b'')
1761 self._chunkcache = (0, b'')
1762
1762
1763 def deltaparent(self, rev):
1763 def deltaparent(self, rev):
1764 """return deltaparent of the given revision"""
1764 """return deltaparent of the given revision"""
1765 base = self.index[rev][3]
1765 base = self.index[rev][3]
1766 if base == rev:
1766 if base == rev:
1767 return nullrev
1767 return nullrev
1768 elif self._generaldelta:
1768 elif self._generaldelta:
1769 return base
1769 return base
1770 else:
1770 else:
1771 return rev - 1
1771 return rev - 1
1772
1772
1773 def issnapshot(self, rev):
1773 def issnapshot(self, rev):
1774 """tells whether rev is a snapshot
1774 """tells whether rev is a snapshot
1775 """
1775 """
1776 if not self._sparserevlog:
1776 if not self._sparserevlog:
1777 return self.deltaparent(rev) == nullrev
1777 return self.deltaparent(rev) == nullrev
1778 elif util.safehasattr(self.index, b'issnapshot'):
1778 elif util.safehasattr(self.index, b'issnapshot'):
1779 # directly assign the method to cache the testing and access
1779 # directly assign the method to cache the testing and access
1780 self.issnapshot = self.index.issnapshot
1780 self.issnapshot = self.index.issnapshot
1781 return self.issnapshot(rev)
1781 return self.issnapshot(rev)
1782 if rev == nullrev:
1782 if rev == nullrev:
1783 return True
1783 return True
1784 entry = self.index[rev]
1784 entry = self.index[rev]
1785 base = entry[3]
1785 base = entry[3]
1786 if base == rev:
1786 if base == rev:
1787 return True
1787 return True
1788 if base == nullrev:
1788 if base == nullrev:
1789 return True
1789 return True
1790 p1 = entry[5]
1790 p1 = entry[5]
1791 p2 = entry[6]
1791 p2 = entry[6]
1792 if base == p1 or base == p2:
1792 if base == p1 or base == p2:
1793 return False
1793 return False
1794 return self.issnapshot(base)
1794 return self.issnapshot(base)
1795
1795
1796 def snapshotdepth(self, rev):
1796 def snapshotdepth(self, rev):
1797 """number of snapshot in the chain before this one"""
1797 """number of snapshot in the chain before this one"""
1798 if not self.issnapshot(rev):
1798 if not self.issnapshot(rev):
1799 raise error.ProgrammingError(b'revision %d not a snapshot')
1799 raise error.ProgrammingError(b'revision %d not a snapshot')
1800 return len(self._deltachain(rev)[0]) - 1
1800 return len(self._deltachain(rev)[0]) - 1
1801
1801
1802 def revdiff(self, rev1, rev2):
1802 def revdiff(self, rev1, rev2):
1803 """return or calculate a delta between two revisions
1803 """return or calculate a delta between two revisions
1804
1804
1805 The delta calculated is in binary form and is intended to be written to
1805 The delta calculated is in binary form and is intended to be written to
1806 revlog data directly. So this function needs raw revision data.
1806 revlog data directly. So this function needs raw revision data.
1807 """
1807 """
1808 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1808 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1809 return bytes(self._chunk(rev2))
1809 return bytes(self._chunk(rev2))
1810
1810
1811 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1811 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1812
1812
1813 def _processflags(self, text, flags, operation, raw=False):
1813 def _processflags(self, text, flags, operation, raw=False):
1814 """deprecated entry point to access flag processors"""
1814 """deprecated entry point to access flag processors"""
1815 msg = b'_processflag(...) use the specialized variant'
1815 msg = b'_processflag(...) use the specialized variant'
1816 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1816 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1817 if raw:
1817 if raw:
1818 return text, flagutil.processflagsraw(self, text, flags)
1818 return text, flagutil.processflagsraw(self, text, flags)
1819 elif operation == b'read':
1819 elif operation == b'read':
1820 return flagutil.processflagsread(self, text, flags)
1820 return flagutil.processflagsread(self, text, flags)
1821 else: # write operation
1821 else: # write operation
1822 return flagutil.processflagswrite(self, text, flags, None)
1822 return flagutil.processflagswrite(self, text, flags, None)
1823
1823
1824 def revision(self, nodeorrev, _df=None, raw=False):
1824 def revision(self, nodeorrev, _df=None, raw=False):
1825 """return an uncompressed revision of a given node or revision
1825 """return an uncompressed revision of a given node or revision
1826 number.
1826 number.
1827
1827
1828 _df - an existing file handle to read from. (internal-only)
1828 _df - an existing file handle to read from. (internal-only)
1829 raw - an optional argument specifying if the revision data is to be
1829 raw - an optional argument specifying if the revision data is to be
1830 treated as raw data when applying flag transforms. 'raw' should be set
1830 treated as raw data when applying flag transforms. 'raw' should be set
1831 to True when generating changegroups or in debug commands.
1831 to True when generating changegroups or in debug commands.
1832 """
1832 """
1833 if raw:
1833 if raw:
1834 msg = (
1834 msg = (
1835 b'revlog.revision(..., raw=True) is deprecated, '
1835 b'revlog.revision(..., raw=True) is deprecated, '
1836 b'use revlog.rawdata(...)'
1836 b'use revlog.rawdata(...)'
1837 )
1837 )
1838 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1838 util.nouideprecwarn(msg, b'5.2', stacklevel=2)
1839 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1839 return self._revisiondata(nodeorrev, _df, raw=raw)[0]
1840
1840
1841 def sidedata(self, nodeorrev, _df=None):
1841 def sidedata(self, nodeorrev, _df=None):
1842 """a map of extra data related to the changeset but not part of the hash
1842 """a map of extra data related to the changeset but not part of the hash
1843
1843
1844 This function currently return a dictionary. However, more advanced
1844 This function currently return a dictionary. However, more advanced
1845 mapping object will likely be used in the future for a more
1845 mapping object will likely be used in the future for a more
1846 efficient/lazy code.
1846 efficient/lazy code.
1847 """
1847 """
1848 return self._revisiondata(nodeorrev, _df)[1]
1848 return self._revisiondata(nodeorrev, _df)[1]
1849
1849
1850 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1850 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1851 # deal with <nodeorrev> argument type
1851 # deal with <nodeorrev> argument type
1852 if isinstance(nodeorrev, int):
1852 if isinstance(nodeorrev, int):
1853 rev = nodeorrev
1853 rev = nodeorrev
1854 node = self.node(rev)
1854 node = self.node(rev)
1855 else:
1855 else:
1856 node = nodeorrev
1856 node = nodeorrev
1857 rev = None
1857 rev = None
1858
1858
1859 # fast path the special `nullid` rev
1859 # fast path the special `nullid` rev
1860 if node == nullid:
1860 if node == nullid:
1861 return b"", {}
1861 return b"", {}
1862
1862
1863 # ``rawtext`` is the text as stored inside the revlog. Might be the
1863 # ``rawtext`` is the text as stored inside the revlog. Might be the
1864 # revision or might need to be processed to retrieve the revision.
1864 # revision or might need to be processed to retrieve the revision.
1865 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1865 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1866
1866
1867 if raw and validated:
1867 if raw and validated:
1868 # if we don't want to process the raw text and that raw
1868 # if we don't want to process the raw text and that raw
1869 # text is cached, we can exit early.
1869 # text is cached, we can exit early.
1870 return rawtext, {}
1870 return rawtext, {}
1871 if rev is None:
1871 if rev is None:
1872 rev = self.rev(node)
1872 rev = self.rev(node)
1873 # the revlog's flag for this revision
1873 # the revlog's flag for this revision
1874 # (usually alter its state or content)
1874 # (usually alter its state or content)
1875 flags = self.flags(rev)
1875 flags = self.flags(rev)
1876
1876
1877 if validated and flags == REVIDX_DEFAULT_FLAGS:
1877 if validated and flags == REVIDX_DEFAULT_FLAGS:
1878 # no extra flags set, no flag processor runs, text = rawtext
1878 # no extra flags set, no flag processor runs, text = rawtext
1879 return rawtext, {}
1879 return rawtext, {}
1880
1880
1881 sidedata = {}
1881 sidedata = {}
1882 if raw:
1882 if raw:
1883 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1883 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1884 text = rawtext
1884 text = rawtext
1885 else:
1885 else:
1886 try:
1886 try:
1887 r = flagutil.processflagsread(self, rawtext, flags)
1887 r = flagutil.processflagsread(self, rawtext, flags)
1888 except error.SidedataHashError as exc:
1888 except error.SidedataHashError as exc:
1889 msg = _(b"integrity check failed on %s:%s sidedata key %d")
1889 msg = _(b"integrity check failed on %s:%s sidedata key %d")
1890 msg %= (self.indexfile, pycompat.bytestr(rev), exc.sidedatakey)
1890 msg %= (self.indexfile, pycompat.bytestr(rev), exc.sidedatakey)
1891 raise error.RevlogError(msg)
1891 raise error.RevlogError(msg)
1892 text, validatehash, sidedata = r
1892 text, validatehash, sidedata = r
1893 if validatehash:
1893 if validatehash:
1894 self.checkhash(text, node, rev=rev)
1894 self.checkhash(text, node, rev=rev)
1895 if not validated:
1895 if not validated:
1896 self._revisioncache = (node, rev, rawtext)
1896 self._revisioncache = (node, rev, rawtext)
1897
1897
1898 return text, sidedata
1898 return text, sidedata
1899
1899
1900 def _rawtext(self, node, rev, _df=None):
1900 def _rawtext(self, node, rev, _df=None):
1901 """return the possibly unvalidated rawtext for a revision
1901 """return the possibly unvalidated rawtext for a revision
1902
1902
1903 returns (rev, rawtext, validated)
1903 returns (rev, rawtext, validated)
1904 """
1904 """
1905
1905
1906 # revision in the cache (could be useful to apply delta)
1906 # revision in the cache (could be useful to apply delta)
1907 cachedrev = None
1907 cachedrev = None
1908 # An intermediate text to apply deltas to
1908 # An intermediate text to apply deltas to
1909 basetext = None
1909 basetext = None
1910
1910
1911 # Check if we have the entry in cache
1911 # Check if we have the entry in cache
1912 # The cache entry looks like (node, rev, rawtext)
1912 # The cache entry looks like (node, rev, rawtext)
1913 if self._revisioncache:
1913 if self._revisioncache:
1914 if self._revisioncache[0] == node:
1914 if self._revisioncache[0] == node:
1915 return (rev, self._revisioncache[2], True)
1915 return (rev, self._revisioncache[2], True)
1916 cachedrev = self._revisioncache[1]
1916 cachedrev = self._revisioncache[1]
1917
1917
1918 if rev is None:
1918 if rev is None:
1919 rev = self.rev(node)
1919 rev = self.rev(node)
1920
1920
1921 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1921 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1922 if stopped:
1922 if stopped:
1923 basetext = self._revisioncache[2]
1923 basetext = self._revisioncache[2]
1924
1924
1925 # drop cache to save memory, the caller is expected to
1925 # drop cache to save memory, the caller is expected to
1926 # update self._revisioncache after validating the text
1926 # update self._revisioncache after validating the text
1927 self._revisioncache = None
1927 self._revisioncache = None
1928
1928
1929 targetsize = None
1929 targetsize = None
1930 rawsize = self.index[rev][2]
1930 rawsize = self.index[rev][2]
1931 if 0 <= rawsize:
1931 if 0 <= rawsize:
1932 targetsize = 4 * rawsize
1932 targetsize = 4 * rawsize
1933
1933
1934 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1934 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1935 if basetext is None:
1935 if basetext is None:
1936 basetext = bytes(bins[0])
1936 basetext = bytes(bins[0])
1937 bins = bins[1:]
1937 bins = bins[1:]
1938
1938
1939 rawtext = mdiff.patches(basetext, bins)
1939 rawtext = mdiff.patches(basetext, bins)
1940 del basetext # let us have a chance to free memory early
1940 del basetext # let us have a chance to free memory early
1941 return (rev, rawtext, False)
1941 return (rev, rawtext, False)
1942
1942
1943 def rawdata(self, nodeorrev, _df=None):
1943 def rawdata(self, nodeorrev, _df=None):
1944 """return an uncompressed raw data of a given node or revision number.
1944 """return an uncompressed raw data of a given node or revision number.
1945
1945
1946 _df - an existing file handle to read from. (internal-only)
1946 _df - an existing file handle to read from. (internal-only)
1947 """
1947 """
1948 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1948 return self._revisiondata(nodeorrev, _df, raw=True)[0]
1949
1949
1950 def hash(self, text, p1, p2):
1950 def hash(self, text, p1, p2):
1951 """Compute a node hash.
1951 """Compute a node hash.
1952
1952
1953 Available as a function so that subclasses can replace the hash
1953 Available as a function so that subclasses can replace the hash
1954 as needed.
1954 as needed.
1955 """
1955 """
1956 return storageutil.hashrevisionsha1(text, p1, p2)
1956 return storageutil.hashrevisionsha1(text, p1, p2)
1957
1957
1958 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1958 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1959 """Check node hash integrity.
1959 """Check node hash integrity.
1960
1960
1961 Available as a function so that subclasses can extend hash mismatch
1961 Available as a function so that subclasses can extend hash mismatch
1962 behaviors as needed.
1962 behaviors as needed.
1963 """
1963 """
1964 try:
1964 try:
1965 if p1 is None and p2 is None:
1965 if p1 is None and p2 is None:
1966 p1, p2 = self.parents(node)
1966 p1, p2 = self.parents(node)
1967 if node != self.hash(text, p1, p2):
1967 if node != self.hash(text, p1, p2):
1968 # Clear the revision cache on hash failure. The revision cache
1968 # Clear the revision cache on hash failure. The revision cache
1969 # only stores the raw revision and clearing the cache does have
1969 # only stores the raw revision and clearing the cache does have
1970 # the side-effect that we won't have a cache hit when the raw
1970 # the side-effect that we won't have a cache hit when the raw
1971 # revision data is accessed. But this case should be rare and
1971 # revision data is accessed. But this case should be rare and
1972 # it is extra work to teach the cache about the hash
1972 # it is extra work to teach the cache about the hash
1973 # verification state.
1973 # verification state.
1974 if self._revisioncache and self._revisioncache[0] == node:
1974 if self._revisioncache and self._revisioncache[0] == node:
1975 self._revisioncache = None
1975 self._revisioncache = None
1976
1976
1977 revornode = rev
1977 revornode = rev
1978 if revornode is None:
1978 if revornode is None:
1979 revornode = templatefilters.short(hex(node))
1979 revornode = templatefilters.short(hex(node))
1980 raise error.RevlogError(
1980 raise error.RevlogError(
1981 _(b"integrity check failed on %s:%s")
1981 _(b"integrity check failed on %s:%s")
1982 % (self.indexfile, pycompat.bytestr(revornode))
1982 % (self.indexfile, pycompat.bytestr(revornode))
1983 )
1983 )
1984 except error.RevlogError:
1984 except error.RevlogError:
1985 if self._censorable and storageutil.iscensoredtext(text):
1985 if self._censorable and storageutil.iscensoredtext(text):
1986 raise error.CensoredNodeError(self.indexfile, node, text)
1986 raise error.CensoredNodeError(self.indexfile, node, text)
1987 raise
1987 raise
1988
1988
1989 def _enforceinlinesize(self, tr, fp=None):
1989 def _enforceinlinesize(self, tr, fp=None):
1990 """Check if the revlog is too big for inline and convert if so.
1990 """Check if the revlog is too big for inline and convert if so.
1991
1991
1992 This should be called after revisions are added to the revlog. If the
1992 This should be called after revisions are added to the revlog. If the
1993 revlog has grown too large to be an inline revlog, it will convert it
1993 revlog has grown too large to be an inline revlog, it will convert it
1994 to use multiple index and data files.
1994 to use multiple index and data files.
1995 """
1995 """
1996 tiprev = len(self) - 1
1996 tiprev = len(self) - 1
1997 if (
1997 if (
1998 not self._inline
1998 not self._inline
1999 or (self.start(tiprev) + self.length(tiprev)) < _maxinline
1999 or (self.start(tiprev) + self.length(tiprev)) < _maxinline
2000 ):
2000 ):
2001 return
2001 return
2002
2002
2003 trinfo = tr.find(self.indexfile)
2003 trinfo = tr.find(self.indexfile)
2004 if trinfo is None:
2004 if trinfo is None:
2005 raise error.RevlogError(
2005 raise error.RevlogError(
2006 _(b"%s not found in the transaction") % self.indexfile
2006 _(b"%s not found in the transaction") % self.indexfile
2007 )
2007 )
2008
2008 troffset = trinfo[1]
2009 trindex = trinfo[2]
2009 trindex = 0
2010 if trindex is not None:
2010 tr.add(self.datafile, 0)
2011 dataoff = self.start(trindex)
2012 else:
2013 # revlog was stripped at start of transaction, use all leftover data
2014 trindex = len(self) - 1
2015 dataoff = self.end(tiprev)
2016
2017 tr.add(self.datafile, dataoff)
2018
2011
2019 if fp:
2012 if fp:
2020 fp.flush()
2013 fp.flush()
2021 fp.close()
2014 fp.close()
2022 # We can't use the cached file handle after close(). So prevent
2015 # We can't use the cached file handle after close(). So prevent
2023 # its usage.
2016 # its usage.
2024 self._writinghandles = None
2017 self._writinghandles = None
2025
2018
2026 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
2019 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
2027 for r in self:
2020 for r in self:
2028 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
2021 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
2022 if troffset <= self.start(r):
2023 trindex = r
2029
2024
2030 with self._indexfp(b'w') as fp:
2025 with self._indexfp(b'w') as fp:
2031 self.version &= ~FLAG_INLINE_DATA
2026 self.version &= ~FLAG_INLINE_DATA
2032 self._inline = False
2027 self._inline = False
2033 io = self._io
2028 io = self._io
2034 for i in self:
2029 for i in self:
2035 e = io.packentry(self.index[i], self.node, self.version, i)
2030 e = io.packentry(self.index[i], self.node, self.version, i)
2036 fp.write(e)
2031 fp.write(e)
2037
2032
2038 # the temp file replace the real index when we exit the context
2033 # the temp file replace the real index when we exit the context
2039 # manager
2034 # manager
2040
2035
2041 tr.replace(self.indexfile, trindex * self._io.size)
2036 tr.replace(self.indexfile, trindex * self._io.size)
2042 nodemaputil.setup_persistent_nodemap(tr, self)
2037 nodemaputil.setup_persistent_nodemap(tr, self)
2043 self._chunkclear()
2038 self._chunkclear()
2044
2039
2045 def _nodeduplicatecallback(self, transaction, node):
2040 def _nodeduplicatecallback(self, transaction, node):
2046 """called when trying to add a node already stored.
2041 """called when trying to add a node already stored.
2047 """
2042 """
2048
2043
2049 def addrevision(
2044 def addrevision(
2050 self,
2045 self,
2051 text,
2046 text,
2052 transaction,
2047 transaction,
2053 link,
2048 link,
2054 p1,
2049 p1,
2055 p2,
2050 p2,
2056 cachedelta=None,
2051 cachedelta=None,
2057 node=None,
2052 node=None,
2058 flags=REVIDX_DEFAULT_FLAGS,
2053 flags=REVIDX_DEFAULT_FLAGS,
2059 deltacomputer=None,
2054 deltacomputer=None,
2060 sidedata=None,
2055 sidedata=None,
2061 ):
2056 ):
2062 """add a revision to the log
2057 """add a revision to the log
2063
2058
2064 text - the revision data to add
2059 text - the revision data to add
2065 transaction - the transaction object used for rollback
2060 transaction - the transaction object used for rollback
2066 link - the linkrev data to add
2061 link - the linkrev data to add
2067 p1, p2 - the parent nodeids of the revision
2062 p1, p2 - the parent nodeids of the revision
2068 cachedelta - an optional precomputed delta
2063 cachedelta - an optional precomputed delta
2069 node - nodeid of revision; typically node is not specified, and it is
2064 node - nodeid of revision; typically node is not specified, and it is
2070 computed by default as hash(text, p1, p2), however subclasses might
2065 computed by default as hash(text, p1, p2), however subclasses might
2071 use different hashing method (and override checkhash() in such case)
2066 use different hashing method (and override checkhash() in such case)
2072 flags - the known flags to set on the revision
2067 flags - the known flags to set on the revision
2073 deltacomputer - an optional deltacomputer instance shared between
2068 deltacomputer - an optional deltacomputer instance shared between
2074 multiple calls
2069 multiple calls
2075 """
2070 """
2076 if link == nullrev:
2071 if link == nullrev:
2077 raise error.RevlogError(
2072 raise error.RevlogError(
2078 _(b"attempted to add linkrev -1 to %s") % self.indexfile
2073 _(b"attempted to add linkrev -1 to %s") % self.indexfile
2079 )
2074 )
2080
2075
2081 if sidedata is None:
2076 if sidedata is None:
2082 sidedata = {}
2077 sidedata = {}
2083 flags = flags & ~REVIDX_SIDEDATA
2078 flags = flags & ~REVIDX_SIDEDATA
2084 elif not self.hassidedata:
2079 elif not self.hassidedata:
2085 raise error.ProgrammingError(
2080 raise error.ProgrammingError(
2086 _(b"trying to add sidedata to a revlog who don't support them")
2081 _(b"trying to add sidedata to a revlog who don't support them")
2087 )
2082 )
2088 else:
2083 else:
2089 flags |= REVIDX_SIDEDATA
2084 flags |= REVIDX_SIDEDATA
2090
2085
2091 if flags:
2086 if flags:
2092 node = node or self.hash(text, p1, p2)
2087 node = node or self.hash(text, p1, p2)
2093
2088
2094 rawtext, validatehash = flagutil.processflagswrite(
2089 rawtext, validatehash = flagutil.processflagswrite(
2095 self, text, flags, sidedata=sidedata
2090 self, text, flags, sidedata=sidedata
2096 )
2091 )
2097
2092
2098 # If the flag processor modifies the revision data, ignore any provided
2093 # If the flag processor modifies the revision data, ignore any provided
2099 # cachedelta.
2094 # cachedelta.
2100 if rawtext != text:
2095 if rawtext != text:
2101 cachedelta = None
2096 cachedelta = None
2102
2097
2103 if len(rawtext) > _maxentrysize:
2098 if len(rawtext) > _maxentrysize:
2104 raise error.RevlogError(
2099 raise error.RevlogError(
2105 _(
2100 _(
2106 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2101 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2107 )
2102 )
2108 % (self.indexfile, len(rawtext))
2103 % (self.indexfile, len(rawtext))
2109 )
2104 )
2110
2105
2111 node = node or self.hash(rawtext, p1, p2)
2106 node = node or self.hash(rawtext, p1, p2)
2112 if self.index.has_node(node):
2107 if self.index.has_node(node):
2113 return node
2108 return node
2114
2109
2115 if validatehash:
2110 if validatehash:
2116 self.checkhash(rawtext, node, p1=p1, p2=p2)
2111 self.checkhash(rawtext, node, p1=p1, p2=p2)
2117
2112
2118 return self.addrawrevision(
2113 return self.addrawrevision(
2119 rawtext,
2114 rawtext,
2120 transaction,
2115 transaction,
2121 link,
2116 link,
2122 p1,
2117 p1,
2123 p2,
2118 p2,
2124 node,
2119 node,
2125 flags,
2120 flags,
2126 cachedelta=cachedelta,
2121 cachedelta=cachedelta,
2127 deltacomputer=deltacomputer,
2122 deltacomputer=deltacomputer,
2128 )
2123 )
2129
2124
2130 def addrawrevision(
2125 def addrawrevision(
2131 self,
2126 self,
2132 rawtext,
2127 rawtext,
2133 transaction,
2128 transaction,
2134 link,
2129 link,
2135 p1,
2130 p1,
2136 p2,
2131 p2,
2137 node,
2132 node,
2138 flags,
2133 flags,
2139 cachedelta=None,
2134 cachedelta=None,
2140 deltacomputer=None,
2135 deltacomputer=None,
2141 ):
2136 ):
2142 """add a raw revision with known flags, node and parents
2137 """add a raw revision with known flags, node and parents
2143 useful when reusing a revision not stored in this revlog (ex: received
2138 useful when reusing a revision not stored in this revlog (ex: received
2144 over wire, or read from an external bundle).
2139 over wire, or read from an external bundle).
2145 """
2140 """
2146 dfh = None
2141 dfh = None
2147 if not self._inline:
2142 if not self._inline:
2148 dfh = self._datafp(b"a+")
2143 dfh = self._datafp(b"a+")
2149 ifh = self._indexfp(b"a+")
2144 ifh = self._indexfp(b"a+")
2150 try:
2145 try:
2151 return self._addrevision(
2146 return self._addrevision(
2152 node,
2147 node,
2153 rawtext,
2148 rawtext,
2154 transaction,
2149 transaction,
2155 link,
2150 link,
2156 p1,
2151 p1,
2157 p2,
2152 p2,
2158 flags,
2153 flags,
2159 cachedelta,
2154 cachedelta,
2160 ifh,
2155 ifh,
2161 dfh,
2156 dfh,
2162 deltacomputer=deltacomputer,
2157 deltacomputer=deltacomputer,
2163 )
2158 )
2164 finally:
2159 finally:
2165 if dfh:
2160 if dfh:
2166 dfh.close()
2161 dfh.close()
2167 ifh.close()
2162 ifh.close()
2168
2163
2169 def compress(self, data):
2164 def compress(self, data):
2170 """Generate a possibly-compressed representation of data."""
2165 """Generate a possibly-compressed representation of data."""
2171 if not data:
2166 if not data:
2172 return b'', data
2167 return b'', data
2173
2168
2174 compressed = self._compressor.compress(data)
2169 compressed = self._compressor.compress(data)
2175
2170
2176 if compressed:
2171 if compressed:
2177 # The revlog compressor added the header in the returned data.
2172 # The revlog compressor added the header in the returned data.
2178 return b'', compressed
2173 return b'', compressed
2179
2174
2180 if data[0:1] == b'\0':
2175 if data[0:1] == b'\0':
2181 return b'', data
2176 return b'', data
2182 return b'u', data
2177 return b'u', data
2183
2178
2184 def decompress(self, data):
2179 def decompress(self, data):
2185 """Decompress a revlog chunk.
2180 """Decompress a revlog chunk.
2186
2181
2187 The chunk is expected to begin with a header identifying the
2182 The chunk is expected to begin with a header identifying the
2188 format type so it can be routed to an appropriate decompressor.
2183 format type so it can be routed to an appropriate decompressor.
2189 """
2184 """
2190 if not data:
2185 if not data:
2191 return data
2186 return data
2192
2187
2193 # Revlogs are read much more frequently than they are written and many
2188 # Revlogs are read much more frequently than they are written and many
2194 # chunks only take microseconds to decompress, so performance is
2189 # chunks only take microseconds to decompress, so performance is
2195 # important here.
2190 # important here.
2196 #
2191 #
2197 # We can make a few assumptions about revlogs:
2192 # We can make a few assumptions about revlogs:
2198 #
2193 #
2199 # 1) the majority of chunks will be compressed (as opposed to inline
2194 # 1) the majority of chunks will be compressed (as opposed to inline
2200 # raw data).
2195 # raw data).
2201 # 2) decompressing *any* data will likely by at least 10x slower than
2196 # 2) decompressing *any* data will likely by at least 10x slower than
2202 # returning raw inline data.
2197 # returning raw inline data.
2203 # 3) we want to prioritize common and officially supported compression
2198 # 3) we want to prioritize common and officially supported compression
2204 # engines
2199 # engines
2205 #
2200 #
2206 # It follows that we want to optimize for "decompress compressed data
2201 # It follows that we want to optimize for "decompress compressed data
2207 # when encoded with common and officially supported compression engines"
2202 # when encoded with common and officially supported compression engines"
2208 # case over "raw data" and "data encoded by less common or non-official
2203 # case over "raw data" and "data encoded by less common or non-official
2209 # compression engines." That is why we have the inline lookup first
2204 # compression engines." That is why we have the inline lookup first
2210 # followed by the compengines lookup.
2205 # followed by the compengines lookup.
2211 #
2206 #
2212 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2207 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2213 # compressed chunks. And this matters for changelog and manifest reads.
2208 # compressed chunks. And this matters for changelog and manifest reads.
2214 t = data[0:1]
2209 t = data[0:1]
2215
2210
2216 if t == b'x':
2211 if t == b'x':
2217 try:
2212 try:
2218 return _zlibdecompress(data)
2213 return _zlibdecompress(data)
2219 except zlib.error as e:
2214 except zlib.error as e:
2220 raise error.RevlogError(
2215 raise error.RevlogError(
2221 _(b'revlog decompress error: %s')
2216 _(b'revlog decompress error: %s')
2222 % stringutil.forcebytestr(e)
2217 % stringutil.forcebytestr(e)
2223 )
2218 )
2224 # '\0' is more common than 'u' so it goes first.
2219 # '\0' is more common than 'u' so it goes first.
2225 elif t == b'\0':
2220 elif t == b'\0':
2226 return data
2221 return data
2227 elif t == b'u':
2222 elif t == b'u':
2228 return util.buffer(data, 1)
2223 return util.buffer(data, 1)
2229
2224
2230 try:
2225 try:
2231 compressor = self._decompressors[t]
2226 compressor = self._decompressors[t]
2232 except KeyError:
2227 except KeyError:
2233 try:
2228 try:
2234 engine = util.compengines.forrevlogheader(t)
2229 engine = util.compengines.forrevlogheader(t)
2235 compressor = engine.revlogcompressor(self._compengineopts)
2230 compressor = engine.revlogcompressor(self._compengineopts)
2236 self._decompressors[t] = compressor
2231 self._decompressors[t] = compressor
2237 except KeyError:
2232 except KeyError:
2238 raise error.RevlogError(_(b'unknown compression type %r') % t)
2233 raise error.RevlogError(_(b'unknown compression type %r') % t)
2239
2234
2240 return compressor.decompress(data)
2235 return compressor.decompress(data)
2241
2236
2242 def _addrevision(
2237 def _addrevision(
2243 self,
2238 self,
2244 node,
2239 node,
2245 rawtext,
2240 rawtext,
2246 transaction,
2241 transaction,
2247 link,
2242 link,
2248 p1,
2243 p1,
2249 p2,
2244 p2,
2250 flags,
2245 flags,
2251 cachedelta,
2246 cachedelta,
2252 ifh,
2247 ifh,
2253 dfh,
2248 dfh,
2254 alwayscache=False,
2249 alwayscache=False,
2255 deltacomputer=None,
2250 deltacomputer=None,
2256 ):
2251 ):
2257 """internal function to add revisions to the log
2252 """internal function to add revisions to the log
2258
2253
2259 see addrevision for argument descriptions.
2254 see addrevision for argument descriptions.
2260
2255
2261 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2256 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2262
2257
2263 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2258 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2264 be used.
2259 be used.
2265
2260
2266 invariants:
2261 invariants:
2267 - rawtext is optional (can be None); if not set, cachedelta must be set.
2262 - rawtext is optional (can be None); if not set, cachedelta must be set.
2268 if both are set, they must correspond to each other.
2263 if both are set, they must correspond to each other.
2269 """
2264 """
2270 if node == nullid:
2265 if node == nullid:
2271 raise error.RevlogError(
2266 raise error.RevlogError(
2272 _(b"%s: attempt to add null revision") % self.indexfile
2267 _(b"%s: attempt to add null revision") % self.indexfile
2273 )
2268 )
2274 if node == wdirid or node in wdirfilenodeids:
2269 if node == wdirid or node in wdirfilenodeids:
2275 raise error.RevlogError(
2270 raise error.RevlogError(
2276 _(b"%s: attempt to add wdir revision") % self.indexfile
2271 _(b"%s: attempt to add wdir revision") % self.indexfile
2277 )
2272 )
2278
2273
2279 if self._inline:
2274 if self._inline:
2280 fh = ifh
2275 fh = ifh
2281 else:
2276 else:
2282 fh = dfh
2277 fh = dfh
2283
2278
2284 btext = [rawtext]
2279 btext = [rawtext]
2285
2280
2286 curr = len(self)
2281 curr = len(self)
2287 prev = curr - 1
2282 prev = curr - 1
2288 offset = self.end(prev)
2283 offset = self.end(prev)
2289 p1r, p2r = self.rev(p1), self.rev(p2)
2284 p1r, p2r = self.rev(p1), self.rev(p2)
2290
2285
2291 # full versions are inserted when the needed deltas
2286 # full versions are inserted when the needed deltas
2292 # become comparable to the uncompressed text
2287 # become comparable to the uncompressed text
2293 if rawtext is None:
2288 if rawtext is None:
2294 # need rawtext size, before changed by flag processors, which is
2289 # need rawtext size, before changed by flag processors, which is
2295 # the non-raw size. use revlog explicitly to avoid filelog's extra
2290 # the non-raw size. use revlog explicitly to avoid filelog's extra
2296 # logic that might remove metadata size.
2291 # logic that might remove metadata size.
2297 textlen = mdiff.patchedsize(
2292 textlen = mdiff.patchedsize(
2298 revlog.size(self, cachedelta[0]), cachedelta[1]
2293 revlog.size(self, cachedelta[0]), cachedelta[1]
2299 )
2294 )
2300 else:
2295 else:
2301 textlen = len(rawtext)
2296 textlen = len(rawtext)
2302
2297
2303 if deltacomputer is None:
2298 if deltacomputer is None:
2304 deltacomputer = deltautil.deltacomputer(self)
2299 deltacomputer = deltautil.deltacomputer(self)
2305
2300
2306 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2301 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2307
2302
2308 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2303 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2309
2304
2310 e = (
2305 e = (
2311 offset_type(offset, flags),
2306 offset_type(offset, flags),
2312 deltainfo.deltalen,
2307 deltainfo.deltalen,
2313 textlen,
2308 textlen,
2314 deltainfo.base,
2309 deltainfo.base,
2315 link,
2310 link,
2316 p1r,
2311 p1r,
2317 p2r,
2312 p2r,
2318 node,
2313 node,
2319 )
2314 )
2320 self.index.append(e)
2315 self.index.append(e)
2321
2316
2322 entry = self._io.packentry(e, self.node, self.version, curr)
2317 entry = self._io.packentry(e, self.node, self.version, curr)
2323 self._writeentry(
2318 self._writeentry(
2324 transaction, ifh, dfh, entry, deltainfo.data, link, offset
2319 transaction, ifh, dfh, entry, deltainfo.data, link, offset
2325 )
2320 )
2326
2321
2327 rawtext = btext[0]
2322 rawtext = btext[0]
2328
2323
2329 if alwayscache and rawtext is None:
2324 if alwayscache and rawtext is None:
2330 rawtext = deltacomputer.buildtext(revinfo, fh)
2325 rawtext = deltacomputer.buildtext(revinfo, fh)
2331
2326
2332 if type(rawtext) == bytes: # only accept immutable objects
2327 if type(rawtext) == bytes: # only accept immutable objects
2333 self._revisioncache = (node, curr, rawtext)
2328 self._revisioncache = (node, curr, rawtext)
2334 self._chainbasecache[curr] = deltainfo.chainbase
2329 self._chainbasecache[curr] = deltainfo.chainbase
2335 return node
2330 return node
2336
2331
2337 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2332 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2338 # Files opened in a+ mode have inconsistent behavior on various
2333 # Files opened in a+ mode have inconsistent behavior on various
2339 # platforms. Windows requires that a file positioning call be made
2334 # platforms. Windows requires that a file positioning call be made
2340 # when the file handle transitions between reads and writes. See
2335 # when the file handle transitions between reads and writes. See
2341 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2336 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2342 # platforms, Python or the platform itself can be buggy. Some versions
2337 # platforms, Python or the platform itself can be buggy. Some versions
2343 # of Solaris have been observed to not append at the end of the file
2338 # of Solaris have been observed to not append at the end of the file
2344 # if the file was seeked to before the end. See issue4943 for more.
2339 # if the file was seeked to before the end. See issue4943 for more.
2345 #
2340 #
2346 # We work around this issue by inserting a seek() before writing.
2341 # We work around this issue by inserting a seek() before writing.
2347 # Note: This is likely not necessary on Python 3. However, because
2342 # Note: This is likely not necessary on Python 3. However, because
2348 # the file handle is reused for reads and may be seeked there, we need
2343 # the file handle is reused for reads and may be seeked there, we need
2349 # to be careful before changing this.
2344 # to be careful before changing this.
2350 ifh.seek(0, os.SEEK_END)
2345 ifh.seek(0, os.SEEK_END)
2351 if dfh:
2346 if dfh:
2352 dfh.seek(0, os.SEEK_END)
2347 dfh.seek(0, os.SEEK_END)
2353
2348
2354 curr = len(self) - 1
2349 curr = len(self) - 1
2355 if not self._inline:
2350 if not self._inline:
2356 transaction.add(self.datafile, offset)
2351 transaction.add(self.datafile, offset)
2357 transaction.add(self.indexfile, curr * len(entry))
2352 transaction.add(self.indexfile, curr * len(entry))
2358 if data[0]:
2353 if data[0]:
2359 dfh.write(data[0])
2354 dfh.write(data[0])
2360 dfh.write(data[1])
2355 dfh.write(data[1])
2361 ifh.write(entry)
2356 ifh.write(entry)
2362 else:
2357 else:
2363 offset += curr * self._io.size
2358 offset += curr * self._io.size
2364 transaction.add(self.indexfile, offset, curr)
2359 transaction.add(self.indexfile, offset)
2365 ifh.write(entry)
2360 ifh.write(entry)
2366 ifh.write(data[0])
2361 ifh.write(data[0])
2367 ifh.write(data[1])
2362 ifh.write(data[1])
2368 self._enforceinlinesize(transaction, ifh)
2363 self._enforceinlinesize(transaction, ifh)
2369 nodemaputil.setup_persistent_nodemap(transaction, self)
2364 nodemaputil.setup_persistent_nodemap(transaction, self)
2370
2365
2371 def addgroup(
2366 def addgroup(
2372 self,
2367 self,
2373 deltas,
2368 deltas,
2374 linkmapper,
2369 linkmapper,
2375 transaction,
2370 transaction,
2376 addrevisioncb=None,
2371 addrevisioncb=None,
2377 duplicaterevisioncb=None,
2372 duplicaterevisioncb=None,
2378 ):
2373 ):
2379 """
2374 """
2380 add a delta group
2375 add a delta group
2381
2376
2382 given a set of deltas, add them to the revision log. the
2377 given a set of deltas, add them to the revision log. the
2383 first delta is against its parent, which should be in our
2378 first delta is against its parent, which should be in our
2384 log, the rest are against the previous delta.
2379 log, the rest are against the previous delta.
2385
2380
2386 If ``addrevisioncb`` is defined, it will be called with arguments of
2381 If ``addrevisioncb`` is defined, it will be called with arguments of
2387 this revlog and the node that was added.
2382 this revlog and the node that was added.
2388 """
2383 """
2389
2384
2390 if self._writinghandles:
2385 if self._writinghandles:
2391 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2386 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2392
2387
2393 r = len(self)
2388 r = len(self)
2394 end = 0
2389 end = 0
2395 if r:
2390 if r:
2396 end = self.end(r - 1)
2391 end = self.end(r - 1)
2397 ifh = self._indexfp(b"a+")
2392 ifh = self._indexfp(b"a+")
2398 isize = r * self._io.size
2393 isize = r * self._io.size
2399 if self._inline:
2394 if self._inline:
2400 transaction.add(self.indexfile, end + isize, r)
2395 transaction.add(self.indexfile, end + isize)
2401 dfh = None
2396 dfh = None
2402 else:
2397 else:
2403 transaction.add(self.indexfile, isize, r)
2398 transaction.add(self.indexfile, isize)
2404 transaction.add(self.datafile, end)
2399 transaction.add(self.datafile, end)
2405 dfh = self._datafp(b"a+")
2400 dfh = self._datafp(b"a+")
2406
2401
2407 def flush():
2402 def flush():
2408 if dfh:
2403 if dfh:
2409 dfh.flush()
2404 dfh.flush()
2410 ifh.flush()
2405 ifh.flush()
2411
2406
2412 self._writinghandles = (ifh, dfh)
2407 self._writinghandles = (ifh, dfh)
2413 empty = True
2408 empty = True
2414
2409
2415 try:
2410 try:
2416 deltacomputer = deltautil.deltacomputer(self)
2411 deltacomputer = deltautil.deltacomputer(self)
2417 # loop through our set of deltas
2412 # loop through our set of deltas
2418 for data in deltas:
2413 for data in deltas:
2419 node, p1, p2, linknode, deltabase, delta, flags = data
2414 node, p1, p2, linknode, deltabase, delta, flags = data
2420 link = linkmapper(linknode)
2415 link = linkmapper(linknode)
2421 flags = flags or REVIDX_DEFAULT_FLAGS
2416 flags = flags or REVIDX_DEFAULT_FLAGS
2422
2417
2423 if self.index.has_node(node):
2418 if self.index.has_node(node):
2424 # this can happen if two branches make the same change
2419 # this can happen if two branches make the same change
2425 self._nodeduplicatecallback(transaction, node)
2420 self._nodeduplicatecallback(transaction, node)
2426 if duplicaterevisioncb:
2421 if duplicaterevisioncb:
2427 duplicaterevisioncb(self, node)
2422 duplicaterevisioncb(self, node)
2428 empty = False
2423 empty = False
2429 continue
2424 continue
2430
2425
2431 for p in (p1, p2):
2426 for p in (p1, p2):
2432 if not self.index.has_node(p):
2427 if not self.index.has_node(p):
2433 raise error.LookupError(
2428 raise error.LookupError(
2434 p, self.indexfile, _(b'unknown parent')
2429 p, self.indexfile, _(b'unknown parent')
2435 )
2430 )
2436
2431
2437 if not self.index.has_node(deltabase):
2432 if not self.index.has_node(deltabase):
2438 raise error.LookupError(
2433 raise error.LookupError(
2439 deltabase, self.indexfile, _(b'unknown delta base')
2434 deltabase, self.indexfile, _(b'unknown delta base')
2440 )
2435 )
2441
2436
2442 baserev = self.rev(deltabase)
2437 baserev = self.rev(deltabase)
2443
2438
2444 if baserev != nullrev and self.iscensored(baserev):
2439 if baserev != nullrev and self.iscensored(baserev):
2445 # if base is censored, delta must be full replacement in a
2440 # if base is censored, delta must be full replacement in a
2446 # single patch operation
2441 # single patch operation
2447 hlen = struct.calcsize(b">lll")
2442 hlen = struct.calcsize(b">lll")
2448 oldlen = self.rawsize(baserev)
2443 oldlen = self.rawsize(baserev)
2449 newlen = len(delta) - hlen
2444 newlen = len(delta) - hlen
2450 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2445 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2451 raise error.CensoredBaseError(
2446 raise error.CensoredBaseError(
2452 self.indexfile, self.node(baserev)
2447 self.indexfile, self.node(baserev)
2453 )
2448 )
2454
2449
2455 if not flags and self._peek_iscensored(baserev, delta, flush):
2450 if not flags and self._peek_iscensored(baserev, delta, flush):
2456 flags |= REVIDX_ISCENSORED
2451 flags |= REVIDX_ISCENSORED
2457
2452
2458 # We assume consumers of addrevisioncb will want to retrieve
2453 # We assume consumers of addrevisioncb will want to retrieve
2459 # the added revision, which will require a call to
2454 # the added revision, which will require a call to
2460 # revision(). revision() will fast path if there is a cache
2455 # revision(). revision() will fast path if there is a cache
2461 # hit. So, we tell _addrevision() to always cache in this case.
2456 # hit. So, we tell _addrevision() to always cache in this case.
2462 # We're only using addgroup() in the context of changegroup
2457 # We're only using addgroup() in the context of changegroup
2463 # generation so the revision data can always be handled as raw
2458 # generation so the revision data can always be handled as raw
2464 # by the flagprocessor.
2459 # by the flagprocessor.
2465 self._addrevision(
2460 self._addrevision(
2466 node,
2461 node,
2467 None,
2462 None,
2468 transaction,
2463 transaction,
2469 link,
2464 link,
2470 p1,
2465 p1,
2471 p2,
2466 p2,
2472 flags,
2467 flags,
2473 (baserev, delta),
2468 (baserev, delta),
2474 ifh,
2469 ifh,
2475 dfh,
2470 dfh,
2476 alwayscache=bool(addrevisioncb),
2471 alwayscache=bool(addrevisioncb),
2477 deltacomputer=deltacomputer,
2472 deltacomputer=deltacomputer,
2478 )
2473 )
2479
2474
2480 if addrevisioncb:
2475 if addrevisioncb:
2481 addrevisioncb(self, node)
2476 addrevisioncb(self, node)
2482 empty = False
2477 empty = False
2483
2478
2484 if not dfh and not self._inline:
2479 if not dfh and not self._inline:
2485 # addrevision switched from inline to conventional
2480 # addrevision switched from inline to conventional
2486 # reopen the index
2481 # reopen the index
2487 ifh.close()
2482 ifh.close()
2488 dfh = self._datafp(b"a+")
2483 dfh = self._datafp(b"a+")
2489 ifh = self._indexfp(b"a+")
2484 ifh = self._indexfp(b"a+")
2490 self._writinghandles = (ifh, dfh)
2485 self._writinghandles = (ifh, dfh)
2491 finally:
2486 finally:
2492 self._writinghandles = None
2487 self._writinghandles = None
2493
2488
2494 if dfh:
2489 if dfh:
2495 dfh.close()
2490 dfh.close()
2496 ifh.close()
2491 ifh.close()
2497 return not empty
2492 return not empty
2498
2493
2499 def iscensored(self, rev):
2494 def iscensored(self, rev):
2500 """Check if a file revision is censored."""
2495 """Check if a file revision is censored."""
2501 if not self._censorable:
2496 if not self._censorable:
2502 return False
2497 return False
2503
2498
2504 return self.flags(rev) & REVIDX_ISCENSORED
2499 return self.flags(rev) & REVIDX_ISCENSORED
2505
2500
2506 def _peek_iscensored(self, baserev, delta, flush):
2501 def _peek_iscensored(self, baserev, delta, flush):
2507 """Quickly check if a delta produces a censored revision."""
2502 """Quickly check if a delta produces a censored revision."""
2508 if not self._censorable:
2503 if not self._censorable:
2509 return False
2504 return False
2510
2505
2511 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2506 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2512
2507
2513 def getstrippoint(self, minlink):
2508 def getstrippoint(self, minlink):
2514 """find the minimum rev that must be stripped to strip the linkrev
2509 """find the minimum rev that must be stripped to strip the linkrev
2515
2510
2516 Returns a tuple containing the minimum rev and a set of all revs that
2511 Returns a tuple containing the minimum rev and a set of all revs that
2517 have linkrevs that will be broken by this strip.
2512 have linkrevs that will be broken by this strip.
2518 """
2513 """
2519 return storageutil.resolvestripinfo(
2514 return storageutil.resolvestripinfo(
2520 minlink,
2515 minlink,
2521 len(self) - 1,
2516 len(self) - 1,
2522 self.headrevs(),
2517 self.headrevs(),
2523 self.linkrev,
2518 self.linkrev,
2524 self.parentrevs,
2519 self.parentrevs,
2525 )
2520 )
2526
2521
2527 def strip(self, minlink, transaction):
2522 def strip(self, minlink, transaction):
2528 """truncate the revlog on the first revision with a linkrev >= minlink
2523 """truncate the revlog on the first revision with a linkrev >= minlink
2529
2524
2530 This function is called when we're stripping revision minlink and
2525 This function is called when we're stripping revision minlink and
2531 its descendants from the repository.
2526 its descendants from the repository.
2532
2527
2533 We have to remove all revisions with linkrev >= minlink, because
2528 We have to remove all revisions with linkrev >= minlink, because
2534 the equivalent changelog revisions will be renumbered after the
2529 the equivalent changelog revisions will be renumbered after the
2535 strip.
2530 strip.
2536
2531
2537 So we truncate the revlog on the first of these revisions, and
2532 So we truncate the revlog on the first of these revisions, and
2538 trust that the caller has saved the revisions that shouldn't be
2533 trust that the caller has saved the revisions that shouldn't be
2539 removed and that it'll re-add them after this truncation.
2534 removed and that it'll re-add them after this truncation.
2540 """
2535 """
2541 if len(self) == 0:
2536 if len(self) == 0:
2542 return
2537 return
2543
2538
2544 rev, _ = self.getstrippoint(minlink)
2539 rev, _ = self.getstrippoint(minlink)
2545 if rev == len(self):
2540 if rev == len(self):
2546 return
2541 return
2547
2542
2548 # first truncate the files on disk
2543 # first truncate the files on disk
2549 end = self.start(rev)
2544 end = self.start(rev)
2550 if not self._inline:
2545 if not self._inline:
2551 transaction.add(self.datafile, end)
2546 transaction.add(self.datafile, end)
2552 end = rev * self._io.size
2547 end = rev * self._io.size
2553 else:
2548 else:
2554 end += rev * self._io.size
2549 end += rev * self._io.size
2555
2550
2556 transaction.add(self.indexfile, end)
2551 transaction.add(self.indexfile, end)
2557
2552
2558 # then reset internal state in memory to forget those revisions
2553 # then reset internal state in memory to forget those revisions
2559 self._revisioncache = None
2554 self._revisioncache = None
2560 self._chaininfocache = util.lrucachedict(500)
2555 self._chaininfocache = util.lrucachedict(500)
2561 self._chunkclear()
2556 self._chunkclear()
2562
2557
2563 del self.index[rev:-1]
2558 del self.index[rev:-1]
2564
2559
2565 def checksize(self):
2560 def checksize(self):
2566 """Check size of index and data files
2561 """Check size of index and data files
2567
2562
2568 return a (dd, di) tuple.
2563 return a (dd, di) tuple.
2569 - dd: extra bytes for the "data" file
2564 - dd: extra bytes for the "data" file
2570 - di: extra bytes for the "index" file
2565 - di: extra bytes for the "index" file
2571
2566
2572 A healthy revlog will return (0, 0).
2567 A healthy revlog will return (0, 0).
2573 """
2568 """
2574 expected = 0
2569 expected = 0
2575 if len(self):
2570 if len(self):
2576 expected = max(0, self.end(len(self) - 1))
2571 expected = max(0, self.end(len(self) - 1))
2577
2572
2578 try:
2573 try:
2579 with self._datafp() as f:
2574 with self._datafp() as f:
2580 f.seek(0, io.SEEK_END)
2575 f.seek(0, io.SEEK_END)
2581 actual = f.tell()
2576 actual = f.tell()
2582 dd = actual - expected
2577 dd = actual - expected
2583 except IOError as inst:
2578 except IOError as inst:
2584 if inst.errno != errno.ENOENT:
2579 if inst.errno != errno.ENOENT:
2585 raise
2580 raise
2586 dd = 0
2581 dd = 0
2587
2582
2588 try:
2583 try:
2589 f = self.opener(self.indexfile)
2584 f = self.opener(self.indexfile)
2590 f.seek(0, io.SEEK_END)
2585 f.seek(0, io.SEEK_END)
2591 actual = f.tell()
2586 actual = f.tell()
2592 f.close()
2587 f.close()
2593 s = self._io.size
2588 s = self._io.size
2594 i = max(0, actual // s)
2589 i = max(0, actual // s)
2595 di = actual - (i * s)
2590 di = actual - (i * s)
2596 if self._inline:
2591 if self._inline:
2597 databytes = 0
2592 databytes = 0
2598 for r in self:
2593 for r in self:
2599 databytes += max(0, self.length(r))
2594 databytes += max(0, self.length(r))
2600 dd = 0
2595 dd = 0
2601 di = actual - len(self) * s - databytes
2596 di = actual - len(self) * s - databytes
2602 except IOError as inst:
2597 except IOError as inst:
2603 if inst.errno != errno.ENOENT:
2598 if inst.errno != errno.ENOENT:
2604 raise
2599 raise
2605 di = 0
2600 di = 0
2606
2601
2607 return (dd, di)
2602 return (dd, di)
2608
2603
2609 def files(self):
2604 def files(self):
2610 res = [self.indexfile]
2605 res = [self.indexfile]
2611 if not self._inline:
2606 if not self._inline:
2612 res.append(self.datafile)
2607 res.append(self.datafile)
2613 return res
2608 return res
2614
2609
2615 def emitrevisions(
2610 def emitrevisions(
2616 self,
2611 self,
2617 nodes,
2612 nodes,
2618 nodesorder=None,
2613 nodesorder=None,
2619 revisiondata=False,
2614 revisiondata=False,
2620 assumehaveparentrevisions=False,
2615 assumehaveparentrevisions=False,
2621 deltamode=repository.CG_DELTAMODE_STD,
2616 deltamode=repository.CG_DELTAMODE_STD,
2622 ):
2617 ):
2623 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2618 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2624 raise error.ProgrammingError(
2619 raise error.ProgrammingError(
2625 b'unhandled value for nodesorder: %s' % nodesorder
2620 b'unhandled value for nodesorder: %s' % nodesorder
2626 )
2621 )
2627
2622
2628 if nodesorder is None and not self._generaldelta:
2623 if nodesorder is None and not self._generaldelta:
2629 nodesorder = b'storage'
2624 nodesorder = b'storage'
2630
2625
2631 if (
2626 if (
2632 not self._storedeltachains
2627 not self._storedeltachains
2633 and deltamode != repository.CG_DELTAMODE_PREV
2628 and deltamode != repository.CG_DELTAMODE_PREV
2634 ):
2629 ):
2635 deltamode = repository.CG_DELTAMODE_FULL
2630 deltamode = repository.CG_DELTAMODE_FULL
2636
2631
2637 return storageutil.emitrevisions(
2632 return storageutil.emitrevisions(
2638 self,
2633 self,
2639 nodes,
2634 nodes,
2640 nodesorder,
2635 nodesorder,
2641 revlogrevisiondelta,
2636 revlogrevisiondelta,
2642 deltaparentfn=self.deltaparent,
2637 deltaparentfn=self.deltaparent,
2643 candeltafn=self.candelta,
2638 candeltafn=self.candelta,
2644 rawsizefn=self.rawsize,
2639 rawsizefn=self.rawsize,
2645 revdifffn=self.revdiff,
2640 revdifffn=self.revdiff,
2646 flagsfn=self.flags,
2641 flagsfn=self.flags,
2647 deltamode=deltamode,
2642 deltamode=deltamode,
2648 revisiondata=revisiondata,
2643 revisiondata=revisiondata,
2649 assumehaveparentrevisions=assumehaveparentrevisions,
2644 assumehaveparentrevisions=assumehaveparentrevisions,
2650 )
2645 )
2651
2646
2652 DELTAREUSEALWAYS = b'always'
2647 DELTAREUSEALWAYS = b'always'
2653 DELTAREUSESAMEREVS = b'samerevs'
2648 DELTAREUSESAMEREVS = b'samerevs'
2654 DELTAREUSENEVER = b'never'
2649 DELTAREUSENEVER = b'never'
2655
2650
2656 DELTAREUSEFULLADD = b'fulladd'
2651 DELTAREUSEFULLADD = b'fulladd'
2657
2652
2658 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2653 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2659
2654
2660 def clone(
2655 def clone(
2661 self,
2656 self,
2662 tr,
2657 tr,
2663 destrevlog,
2658 destrevlog,
2664 addrevisioncb=None,
2659 addrevisioncb=None,
2665 deltareuse=DELTAREUSESAMEREVS,
2660 deltareuse=DELTAREUSESAMEREVS,
2666 forcedeltabothparents=None,
2661 forcedeltabothparents=None,
2667 sidedatacompanion=None,
2662 sidedatacompanion=None,
2668 ):
2663 ):
2669 """Copy this revlog to another, possibly with format changes.
2664 """Copy this revlog to another, possibly with format changes.
2670
2665
2671 The destination revlog will contain the same revisions and nodes.
2666 The destination revlog will contain the same revisions and nodes.
2672 However, it may not be bit-for-bit identical due to e.g. delta encoding
2667 However, it may not be bit-for-bit identical due to e.g. delta encoding
2673 differences.
2668 differences.
2674
2669
2675 The ``deltareuse`` argument control how deltas from the existing revlog
2670 The ``deltareuse`` argument control how deltas from the existing revlog
2676 are preserved in the destination revlog. The argument can have the
2671 are preserved in the destination revlog. The argument can have the
2677 following values:
2672 following values:
2678
2673
2679 DELTAREUSEALWAYS
2674 DELTAREUSEALWAYS
2680 Deltas will always be reused (if possible), even if the destination
2675 Deltas will always be reused (if possible), even if the destination
2681 revlog would not select the same revisions for the delta. This is the
2676 revlog would not select the same revisions for the delta. This is the
2682 fastest mode of operation.
2677 fastest mode of operation.
2683 DELTAREUSESAMEREVS
2678 DELTAREUSESAMEREVS
2684 Deltas will be reused if the destination revlog would pick the same
2679 Deltas will be reused if the destination revlog would pick the same
2685 revisions for the delta. This mode strikes a balance between speed
2680 revisions for the delta. This mode strikes a balance between speed
2686 and optimization.
2681 and optimization.
2687 DELTAREUSENEVER
2682 DELTAREUSENEVER
2688 Deltas will never be reused. This is the slowest mode of execution.
2683 Deltas will never be reused. This is the slowest mode of execution.
2689 This mode can be used to recompute deltas (e.g. if the diff/delta
2684 This mode can be used to recompute deltas (e.g. if the diff/delta
2690 algorithm changes).
2685 algorithm changes).
2691 DELTAREUSEFULLADD
2686 DELTAREUSEFULLADD
2692 Revision will be re-added as if their were new content. This is
2687 Revision will be re-added as if their were new content. This is
2693 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2688 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2694 eg: large file detection and handling.
2689 eg: large file detection and handling.
2695
2690
2696 Delta computation can be slow, so the choice of delta reuse policy can
2691 Delta computation can be slow, so the choice of delta reuse policy can
2697 significantly affect run time.
2692 significantly affect run time.
2698
2693
2699 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2694 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2700 two extremes. Deltas will be reused if they are appropriate. But if the
2695 two extremes. Deltas will be reused if they are appropriate. But if the
2701 delta could choose a better revision, it will do so. This means if you
2696 delta could choose a better revision, it will do so. This means if you
2702 are converting a non-generaldelta revlog to a generaldelta revlog,
2697 are converting a non-generaldelta revlog to a generaldelta revlog,
2703 deltas will be recomputed if the delta's parent isn't a parent of the
2698 deltas will be recomputed if the delta's parent isn't a parent of the
2704 revision.
2699 revision.
2705
2700
2706 In addition to the delta policy, the ``forcedeltabothparents``
2701 In addition to the delta policy, the ``forcedeltabothparents``
2707 argument controls whether to force compute deltas against both parents
2702 argument controls whether to force compute deltas against both parents
2708 for merges. By default, the current default is used.
2703 for merges. By default, the current default is used.
2709
2704
2710 If not None, the `sidedatacompanion` is callable that accept two
2705 If not None, the `sidedatacompanion` is callable that accept two
2711 arguments:
2706 arguments:
2712
2707
2713 (srcrevlog, rev)
2708 (srcrevlog, rev)
2714
2709
2715 and return a quintet that control changes to sidedata content from the
2710 and return a quintet that control changes to sidedata content from the
2716 old revision to the new clone result:
2711 old revision to the new clone result:
2717
2712
2718 (dropall, filterout, update, new_flags, dropped_flags)
2713 (dropall, filterout, update, new_flags, dropped_flags)
2719
2714
2720 * if `dropall` is True, all sidedata should be dropped
2715 * if `dropall` is True, all sidedata should be dropped
2721 * `filterout` is a set of sidedata keys that should be dropped
2716 * `filterout` is a set of sidedata keys that should be dropped
2722 * `update` is a mapping of additionnal/new key -> value
2717 * `update` is a mapping of additionnal/new key -> value
2723 * new_flags is a bitfields of new flags that the revision should get
2718 * new_flags is a bitfields of new flags that the revision should get
2724 * dropped_flags is a bitfields of new flags that the revision shoudl not longer have
2719 * dropped_flags is a bitfields of new flags that the revision shoudl not longer have
2725 """
2720 """
2726 if deltareuse not in self.DELTAREUSEALL:
2721 if deltareuse not in self.DELTAREUSEALL:
2727 raise ValueError(
2722 raise ValueError(
2728 _(b'value for deltareuse invalid: %s') % deltareuse
2723 _(b'value for deltareuse invalid: %s') % deltareuse
2729 )
2724 )
2730
2725
2731 if len(destrevlog):
2726 if len(destrevlog):
2732 raise ValueError(_(b'destination revlog is not empty'))
2727 raise ValueError(_(b'destination revlog is not empty'))
2733
2728
2734 if getattr(self, 'filteredrevs', None):
2729 if getattr(self, 'filteredrevs', None):
2735 raise ValueError(_(b'source revlog has filtered revisions'))
2730 raise ValueError(_(b'source revlog has filtered revisions'))
2736 if getattr(destrevlog, 'filteredrevs', None):
2731 if getattr(destrevlog, 'filteredrevs', None):
2737 raise ValueError(_(b'destination revlog has filtered revisions'))
2732 raise ValueError(_(b'destination revlog has filtered revisions'))
2738
2733
2739 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2734 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2740 # if possible.
2735 # if possible.
2741 oldlazydelta = destrevlog._lazydelta
2736 oldlazydelta = destrevlog._lazydelta
2742 oldlazydeltabase = destrevlog._lazydeltabase
2737 oldlazydeltabase = destrevlog._lazydeltabase
2743 oldamd = destrevlog._deltabothparents
2738 oldamd = destrevlog._deltabothparents
2744
2739
2745 try:
2740 try:
2746 if deltareuse == self.DELTAREUSEALWAYS:
2741 if deltareuse == self.DELTAREUSEALWAYS:
2747 destrevlog._lazydeltabase = True
2742 destrevlog._lazydeltabase = True
2748 destrevlog._lazydelta = True
2743 destrevlog._lazydelta = True
2749 elif deltareuse == self.DELTAREUSESAMEREVS:
2744 elif deltareuse == self.DELTAREUSESAMEREVS:
2750 destrevlog._lazydeltabase = False
2745 destrevlog._lazydeltabase = False
2751 destrevlog._lazydelta = True
2746 destrevlog._lazydelta = True
2752 elif deltareuse == self.DELTAREUSENEVER:
2747 elif deltareuse == self.DELTAREUSENEVER:
2753 destrevlog._lazydeltabase = False
2748 destrevlog._lazydeltabase = False
2754 destrevlog._lazydelta = False
2749 destrevlog._lazydelta = False
2755
2750
2756 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2751 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2757
2752
2758 self._clone(
2753 self._clone(
2759 tr,
2754 tr,
2760 destrevlog,
2755 destrevlog,
2761 addrevisioncb,
2756 addrevisioncb,
2762 deltareuse,
2757 deltareuse,
2763 forcedeltabothparents,
2758 forcedeltabothparents,
2764 sidedatacompanion,
2759 sidedatacompanion,
2765 )
2760 )
2766
2761
2767 finally:
2762 finally:
2768 destrevlog._lazydelta = oldlazydelta
2763 destrevlog._lazydelta = oldlazydelta
2769 destrevlog._lazydeltabase = oldlazydeltabase
2764 destrevlog._lazydeltabase = oldlazydeltabase
2770 destrevlog._deltabothparents = oldamd
2765 destrevlog._deltabothparents = oldamd
2771
2766
2772 def _clone(
2767 def _clone(
2773 self,
2768 self,
2774 tr,
2769 tr,
2775 destrevlog,
2770 destrevlog,
2776 addrevisioncb,
2771 addrevisioncb,
2777 deltareuse,
2772 deltareuse,
2778 forcedeltabothparents,
2773 forcedeltabothparents,
2779 sidedatacompanion,
2774 sidedatacompanion,
2780 ):
2775 ):
2781 """perform the core duty of `revlog.clone` after parameter processing"""
2776 """perform the core duty of `revlog.clone` after parameter processing"""
2782 deltacomputer = deltautil.deltacomputer(destrevlog)
2777 deltacomputer = deltautil.deltacomputer(destrevlog)
2783 index = self.index
2778 index = self.index
2784 for rev in self:
2779 for rev in self:
2785 entry = index[rev]
2780 entry = index[rev]
2786
2781
2787 # Some classes override linkrev to take filtered revs into
2782 # Some classes override linkrev to take filtered revs into
2788 # account. Use raw entry from index.
2783 # account. Use raw entry from index.
2789 flags = entry[0] & 0xFFFF
2784 flags = entry[0] & 0xFFFF
2790 linkrev = entry[4]
2785 linkrev = entry[4]
2791 p1 = index[entry[5]][7]
2786 p1 = index[entry[5]][7]
2792 p2 = index[entry[6]][7]
2787 p2 = index[entry[6]][7]
2793 node = entry[7]
2788 node = entry[7]
2794
2789
2795 sidedataactions = (False, [], {}, 0, 0)
2790 sidedataactions = (False, [], {}, 0, 0)
2796 if sidedatacompanion is not None:
2791 if sidedatacompanion is not None:
2797 sidedataactions = sidedatacompanion(self, rev)
2792 sidedataactions = sidedatacompanion(self, rev)
2798
2793
2799 # (Possibly) reuse the delta from the revlog if allowed and
2794 # (Possibly) reuse the delta from the revlog if allowed and
2800 # the revlog chunk is a delta.
2795 # the revlog chunk is a delta.
2801 cachedelta = None
2796 cachedelta = None
2802 rawtext = None
2797 rawtext = None
2803 if any(sidedataactions) or deltareuse == self.DELTAREUSEFULLADD:
2798 if any(sidedataactions) or deltareuse == self.DELTAREUSEFULLADD:
2804 dropall = sidedataactions[0]
2799 dropall = sidedataactions[0]
2805 filterout = sidedataactions[1]
2800 filterout = sidedataactions[1]
2806 update = sidedataactions[2]
2801 update = sidedataactions[2]
2807 new_flags = sidedataactions[3]
2802 new_flags = sidedataactions[3]
2808 dropped_flags = sidedataactions[4]
2803 dropped_flags = sidedataactions[4]
2809 text, sidedata = self._revisiondata(rev)
2804 text, sidedata = self._revisiondata(rev)
2810 if dropall:
2805 if dropall:
2811 sidedata = {}
2806 sidedata = {}
2812 for key in filterout:
2807 for key in filterout:
2813 sidedata.pop(key, None)
2808 sidedata.pop(key, None)
2814 sidedata.update(update)
2809 sidedata.update(update)
2815 if not sidedata:
2810 if not sidedata:
2816 sidedata = None
2811 sidedata = None
2817
2812
2818 flags |= new_flags
2813 flags |= new_flags
2819 flags &= ~dropped_flags
2814 flags &= ~dropped_flags
2820
2815
2821 destrevlog.addrevision(
2816 destrevlog.addrevision(
2822 text,
2817 text,
2823 tr,
2818 tr,
2824 linkrev,
2819 linkrev,
2825 p1,
2820 p1,
2826 p2,
2821 p2,
2827 cachedelta=cachedelta,
2822 cachedelta=cachedelta,
2828 node=node,
2823 node=node,
2829 flags=flags,
2824 flags=flags,
2830 deltacomputer=deltacomputer,
2825 deltacomputer=deltacomputer,
2831 sidedata=sidedata,
2826 sidedata=sidedata,
2832 )
2827 )
2833 else:
2828 else:
2834 if destrevlog._lazydelta:
2829 if destrevlog._lazydelta:
2835 dp = self.deltaparent(rev)
2830 dp = self.deltaparent(rev)
2836 if dp != nullrev:
2831 if dp != nullrev:
2837 cachedelta = (dp, bytes(self._chunk(rev)))
2832 cachedelta = (dp, bytes(self._chunk(rev)))
2838
2833
2839 if not cachedelta:
2834 if not cachedelta:
2840 rawtext = self.rawdata(rev)
2835 rawtext = self.rawdata(rev)
2841
2836
2842 ifh = destrevlog.opener(
2837 ifh = destrevlog.opener(
2843 destrevlog.indexfile, b'a+', checkambig=False
2838 destrevlog.indexfile, b'a+', checkambig=False
2844 )
2839 )
2845 dfh = None
2840 dfh = None
2846 if not destrevlog._inline:
2841 if not destrevlog._inline:
2847 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2842 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2848 try:
2843 try:
2849 destrevlog._addrevision(
2844 destrevlog._addrevision(
2850 node,
2845 node,
2851 rawtext,
2846 rawtext,
2852 tr,
2847 tr,
2853 linkrev,
2848 linkrev,
2854 p1,
2849 p1,
2855 p2,
2850 p2,
2856 flags,
2851 flags,
2857 cachedelta,
2852 cachedelta,
2858 ifh,
2853 ifh,
2859 dfh,
2854 dfh,
2860 deltacomputer=deltacomputer,
2855 deltacomputer=deltacomputer,
2861 )
2856 )
2862 finally:
2857 finally:
2863 if dfh:
2858 if dfh:
2864 dfh.close()
2859 dfh.close()
2865 ifh.close()
2860 ifh.close()
2866
2861
2867 if addrevisioncb:
2862 if addrevisioncb:
2868 addrevisioncb(self, rev, node)
2863 addrevisioncb(self, rev, node)
2869
2864
2870 def censorrevision(self, tr, censornode, tombstone=b''):
2865 def censorrevision(self, tr, censornode, tombstone=b''):
2871 if (self.version & 0xFFFF) == REVLOGV0:
2866 if (self.version & 0xFFFF) == REVLOGV0:
2872 raise error.RevlogError(
2867 raise error.RevlogError(
2873 _(b'cannot censor with version %d revlogs') % self.version
2868 _(b'cannot censor with version %d revlogs') % self.version
2874 )
2869 )
2875
2870
2876 censorrev = self.rev(censornode)
2871 censorrev = self.rev(censornode)
2877 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2872 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2878
2873
2879 if len(tombstone) > self.rawsize(censorrev):
2874 if len(tombstone) > self.rawsize(censorrev):
2880 raise error.Abort(
2875 raise error.Abort(
2881 _(b'censor tombstone must be no longer than censored data')
2876 _(b'censor tombstone must be no longer than censored data')
2882 )
2877 )
2883
2878
2884 # Rewriting the revlog in place is hard. Our strategy for censoring is
2879 # Rewriting the revlog in place is hard. Our strategy for censoring is
2885 # to create a new revlog, copy all revisions to it, then replace the
2880 # to create a new revlog, copy all revisions to it, then replace the
2886 # revlogs on transaction close.
2881 # revlogs on transaction close.
2887
2882
2888 newindexfile = self.indexfile + b'.tmpcensored'
2883 newindexfile = self.indexfile + b'.tmpcensored'
2889 newdatafile = self.datafile + b'.tmpcensored'
2884 newdatafile = self.datafile + b'.tmpcensored'
2890
2885
2891 # This is a bit dangerous. We could easily have a mismatch of state.
2886 # This is a bit dangerous. We could easily have a mismatch of state.
2892 newrl = revlog(self.opener, newindexfile, newdatafile, censorable=True)
2887 newrl = revlog(self.opener, newindexfile, newdatafile, censorable=True)
2893 newrl.version = self.version
2888 newrl.version = self.version
2894 newrl._generaldelta = self._generaldelta
2889 newrl._generaldelta = self._generaldelta
2895 newrl._io = self._io
2890 newrl._io = self._io
2896
2891
2897 for rev in self.revs():
2892 for rev in self.revs():
2898 node = self.node(rev)
2893 node = self.node(rev)
2899 p1, p2 = self.parents(node)
2894 p1, p2 = self.parents(node)
2900
2895
2901 if rev == censorrev:
2896 if rev == censorrev:
2902 newrl.addrawrevision(
2897 newrl.addrawrevision(
2903 tombstone,
2898 tombstone,
2904 tr,
2899 tr,
2905 self.linkrev(censorrev),
2900 self.linkrev(censorrev),
2906 p1,
2901 p1,
2907 p2,
2902 p2,
2908 censornode,
2903 censornode,
2909 REVIDX_ISCENSORED,
2904 REVIDX_ISCENSORED,
2910 )
2905 )
2911
2906
2912 if newrl.deltaparent(rev) != nullrev:
2907 if newrl.deltaparent(rev) != nullrev:
2913 raise error.Abort(
2908 raise error.Abort(
2914 _(
2909 _(
2915 b'censored revision stored as delta; '
2910 b'censored revision stored as delta; '
2916 b'cannot censor'
2911 b'cannot censor'
2917 ),
2912 ),
2918 hint=_(
2913 hint=_(
2919 b'censoring of revlogs is not '
2914 b'censoring of revlogs is not '
2920 b'fully implemented; please report '
2915 b'fully implemented; please report '
2921 b'this bug'
2916 b'this bug'
2922 ),
2917 ),
2923 )
2918 )
2924 continue
2919 continue
2925
2920
2926 if self.iscensored(rev):
2921 if self.iscensored(rev):
2927 if self.deltaparent(rev) != nullrev:
2922 if self.deltaparent(rev) != nullrev:
2928 raise error.Abort(
2923 raise error.Abort(
2929 _(
2924 _(
2930 b'cannot censor due to censored '
2925 b'cannot censor due to censored '
2931 b'revision having delta stored'
2926 b'revision having delta stored'
2932 )
2927 )
2933 )
2928 )
2934 rawtext = self._chunk(rev)
2929 rawtext = self._chunk(rev)
2935 else:
2930 else:
2936 rawtext = self.rawdata(rev)
2931 rawtext = self.rawdata(rev)
2937
2932
2938 newrl.addrawrevision(
2933 newrl.addrawrevision(
2939 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2934 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2940 )
2935 )
2941
2936
2942 tr.addbackup(self.indexfile, location=b'store')
2937 tr.addbackup(self.indexfile, location=b'store')
2943 if not self._inline:
2938 if not self._inline:
2944 tr.addbackup(self.datafile, location=b'store')
2939 tr.addbackup(self.datafile, location=b'store')
2945
2940
2946 self.opener.rename(newrl.indexfile, self.indexfile)
2941 self.opener.rename(newrl.indexfile, self.indexfile)
2947 if not self._inline:
2942 if not self._inline:
2948 self.opener.rename(newrl.datafile, self.datafile)
2943 self.opener.rename(newrl.datafile, self.datafile)
2949
2944
2950 self.clearcaches()
2945 self.clearcaches()
2951 self._loadindex()
2946 self._loadindex()
2952
2947
2953 def verifyintegrity(self, state):
2948 def verifyintegrity(self, state):
2954 """Verifies the integrity of the revlog.
2949 """Verifies the integrity of the revlog.
2955
2950
2956 Yields ``revlogproblem`` instances describing problems that are
2951 Yields ``revlogproblem`` instances describing problems that are
2957 found.
2952 found.
2958 """
2953 """
2959 dd, di = self.checksize()
2954 dd, di = self.checksize()
2960 if dd:
2955 if dd:
2961 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2956 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2962 if di:
2957 if di:
2963 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2958 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2964
2959
2965 version = self.version & 0xFFFF
2960 version = self.version & 0xFFFF
2966
2961
2967 # The verifier tells us what version revlog we should be.
2962 # The verifier tells us what version revlog we should be.
2968 if version != state[b'expectedversion']:
2963 if version != state[b'expectedversion']:
2969 yield revlogproblem(
2964 yield revlogproblem(
2970 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2965 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2971 % (self.indexfile, version, state[b'expectedversion'])
2966 % (self.indexfile, version, state[b'expectedversion'])
2972 )
2967 )
2973
2968
2974 state[b'skipread'] = set()
2969 state[b'skipread'] = set()
2975 state[b'safe_renamed'] = set()
2970 state[b'safe_renamed'] = set()
2976
2971
2977 for rev in self:
2972 for rev in self:
2978 node = self.node(rev)
2973 node = self.node(rev)
2979
2974
2980 # Verify contents. 4 cases to care about:
2975 # Verify contents. 4 cases to care about:
2981 #
2976 #
2982 # common: the most common case
2977 # common: the most common case
2983 # rename: with a rename
2978 # rename: with a rename
2984 # meta: file content starts with b'\1\n', the metadata
2979 # meta: file content starts with b'\1\n', the metadata
2985 # header defined in filelog.py, but without a rename
2980 # header defined in filelog.py, but without a rename
2986 # ext: content stored externally
2981 # ext: content stored externally
2987 #
2982 #
2988 # More formally, their differences are shown below:
2983 # More formally, their differences are shown below:
2989 #
2984 #
2990 # | common | rename | meta | ext
2985 # | common | rename | meta | ext
2991 # -------------------------------------------------------
2986 # -------------------------------------------------------
2992 # flags() | 0 | 0 | 0 | not 0
2987 # flags() | 0 | 0 | 0 | not 0
2993 # renamed() | False | True | False | ?
2988 # renamed() | False | True | False | ?
2994 # rawtext[0:2]=='\1\n'| False | True | True | ?
2989 # rawtext[0:2]=='\1\n'| False | True | True | ?
2995 #
2990 #
2996 # "rawtext" means the raw text stored in revlog data, which
2991 # "rawtext" means the raw text stored in revlog data, which
2997 # could be retrieved by "rawdata(rev)". "text"
2992 # could be retrieved by "rawdata(rev)". "text"
2998 # mentioned below is "revision(rev)".
2993 # mentioned below is "revision(rev)".
2999 #
2994 #
3000 # There are 3 different lengths stored physically:
2995 # There are 3 different lengths stored physically:
3001 # 1. L1: rawsize, stored in revlog index
2996 # 1. L1: rawsize, stored in revlog index
3002 # 2. L2: len(rawtext), stored in revlog data
2997 # 2. L2: len(rawtext), stored in revlog data
3003 # 3. L3: len(text), stored in revlog data if flags==0, or
2998 # 3. L3: len(text), stored in revlog data if flags==0, or
3004 # possibly somewhere else if flags!=0
2999 # possibly somewhere else if flags!=0
3005 #
3000 #
3006 # L1 should be equal to L2. L3 could be different from them.
3001 # L1 should be equal to L2. L3 could be different from them.
3007 # "text" may or may not affect commit hash depending on flag
3002 # "text" may or may not affect commit hash depending on flag
3008 # processors (see flagutil.addflagprocessor).
3003 # processors (see flagutil.addflagprocessor).
3009 #
3004 #
3010 # | common | rename | meta | ext
3005 # | common | rename | meta | ext
3011 # -------------------------------------------------
3006 # -------------------------------------------------
3012 # rawsize() | L1 | L1 | L1 | L1
3007 # rawsize() | L1 | L1 | L1 | L1
3013 # size() | L1 | L2-LM | L1(*) | L1 (?)
3008 # size() | L1 | L2-LM | L1(*) | L1 (?)
3014 # len(rawtext) | L2 | L2 | L2 | L2
3009 # len(rawtext) | L2 | L2 | L2 | L2
3015 # len(text) | L2 | L2 | L2 | L3
3010 # len(text) | L2 | L2 | L2 | L3
3016 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3011 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3017 #
3012 #
3018 # LM: length of metadata, depending on rawtext
3013 # LM: length of metadata, depending on rawtext
3019 # (*): not ideal, see comment in filelog.size
3014 # (*): not ideal, see comment in filelog.size
3020 # (?): could be "- len(meta)" if the resolved content has
3015 # (?): could be "- len(meta)" if the resolved content has
3021 # rename metadata
3016 # rename metadata
3022 #
3017 #
3023 # Checks needed to be done:
3018 # Checks needed to be done:
3024 # 1. length check: L1 == L2, in all cases.
3019 # 1. length check: L1 == L2, in all cases.
3025 # 2. hash check: depending on flag processor, we may need to
3020 # 2. hash check: depending on flag processor, we may need to
3026 # use either "text" (external), or "rawtext" (in revlog).
3021 # use either "text" (external), or "rawtext" (in revlog).
3027
3022
3028 try:
3023 try:
3029 skipflags = state.get(b'skipflags', 0)
3024 skipflags = state.get(b'skipflags', 0)
3030 if skipflags:
3025 if skipflags:
3031 skipflags &= self.flags(rev)
3026 skipflags &= self.flags(rev)
3032
3027
3033 _verify_revision(self, skipflags, state, node)
3028 _verify_revision(self, skipflags, state, node)
3034
3029
3035 l1 = self.rawsize(rev)
3030 l1 = self.rawsize(rev)
3036 l2 = len(self.rawdata(node))
3031 l2 = len(self.rawdata(node))
3037
3032
3038 if l1 != l2:
3033 if l1 != l2:
3039 yield revlogproblem(
3034 yield revlogproblem(
3040 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3035 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3041 node=node,
3036 node=node,
3042 )
3037 )
3043
3038
3044 except error.CensoredNodeError:
3039 except error.CensoredNodeError:
3045 if state[b'erroroncensored']:
3040 if state[b'erroroncensored']:
3046 yield revlogproblem(
3041 yield revlogproblem(
3047 error=_(b'censored file data'), node=node
3042 error=_(b'censored file data'), node=node
3048 )
3043 )
3049 state[b'skipread'].add(node)
3044 state[b'skipread'].add(node)
3050 except Exception as e:
3045 except Exception as e:
3051 yield revlogproblem(
3046 yield revlogproblem(
3052 error=_(b'unpacking %s: %s')
3047 error=_(b'unpacking %s: %s')
3053 % (short(node), stringutil.forcebytestr(e)),
3048 % (short(node), stringutil.forcebytestr(e)),
3054 node=node,
3049 node=node,
3055 )
3050 )
3056 state[b'skipread'].add(node)
3051 state[b'skipread'].add(node)
3057
3052
3058 def storageinfo(
3053 def storageinfo(
3059 self,
3054 self,
3060 exclusivefiles=False,
3055 exclusivefiles=False,
3061 sharedfiles=False,
3056 sharedfiles=False,
3062 revisionscount=False,
3057 revisionscount=False,
3063 trackedsize=False,
3058 trackedsize=False,
3064 storedsize=False,
3059 storedsize=False,
3065 ):
3060 ):
3066 d = {}
3061 d = {}
3067
3062
3068 if exclusivefiles:
3063 if exclusivefiles:
3069 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
3064 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
3070 if not self._inline:
3065 if not self._inline:
3071 d[b'exclusivefiles'].append((self.opener, self.datafile))
3066 d[b'exclusivefiles'].append((self.opener, self.datafile))
3072
3067
3073 if sharedfiles:
3068 if sharedfiles:
3074 d[b'sharedfiles'] = []
3069 d[b'sharedfiles'] = []
3075
3070
3076 if revisionscount:
3071 if revisionscount:
3077 d[b'revisionscount'] = len(self)
3072 d[b'revisionscount'] = len(self)
3078
3073
3079 if trackedsize:
3074 if trackedsize:
3080 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3075 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3081
3076
3082 if storedsize:
3077 if storedsize:
3083 d[b'storedsize'] = sum(
3078 d[b'storedsize'] = sum(
3084 self.opener.stat(path).st_size for path in self.files()
3079 self.opener.stat(path).st_size for path in self.files()
3085 )
3080 )
3086
3081
3087 return d
3082 return d
@@ -1,734 +1,734 b''
1 # transaction.py - simple journaling scheme for mercurial
1 # transaction.py - simple journaling scheme for mercurial
2 #
2 #
3 # This transaction scheme is intended to gracefully handle program
3 # This transaction scheme is intended to gracefully handle program
4 # errors and interruptions. More serious failures like system crashes
4 # errors and interruptions. More serious failures like system crashes
5 # can be recovered with an fsck-like tool. As the whole repository is
5 # can be recovered with an fsck-like tool. As the whole repository is
6 # effectively log-structured, this should amount to simply truncating
6 # effectively log-structured, this should amount to simply truncating
7 # anything that isn't referenced in the changelog.
7 # anything that isn't referenced in the changelog.
8 #
8 #
9 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
9 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
10 #
10 #
11 # This software may be used and distributed according to the terms of the
11 # This software may be used and distributed according to the terms of the
12 # GNU General Public License version 2 or any later version.
12 # GNU General Public License version 2 or any later version.
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import errno
16 import errno
17
17
18 from .i18n import _
18 from .i18n import _
19 from . import (
19 from . import (
20 error,
20 error,
21 pycompat,
21 pycompat,
22 util,
22 util,
23 )
23 )
24 from .utils import stringutil
24 from .utils import stringutil
25
25
26 version = 2
26 version = 2
27
27
28 # These are the file generators that should only be executed after the
28 # These are the file generators that should only be executed after the
29 # finalizers are done, since they rely on the output of the finalizers (like
29 # finalizers are done, since they rely on the output of the finalizers (like
30 # the changelog having been written).
30 # the changelog having been written).
31 postfinalizegenerators = {b'bookmarks', b'dirstate'}
31 postfinalizegenerators = {b'bookmarks', b'dirstate'}
32
32
33 GEN_GROUP_ALL = b'all'
33 GEN_GROUP_ALL = b'all'
34 GEN_GROUP_PRE_FINALIZE = b'prefinalize'
34 GEN_GROUP_PRE_FINALIZE = b'prefinalize'
35 GEN_GROUP_POST_FINALIZE = b'postfinalize'
35 GEN_GROUP_POST_FINALIZE = b'postfinalize'
36
36
37
37
38 def active(func):
38 def active(func):
39 def _active(self, *args, **kwds):
39 def _active(self, *args, **kwds):
40 if self._count == 0:
40 if self._count == 0:
41 raise error.ProgrammingError(
41 raise error.ProgrammingError(
42 b'cannot use transaction when it is already committed/aborted'
42 b'cannot use transaction when it is already committed/aborted'
43 )
43 )
44 return func(self, *args, **kwds)
44 return func(self, *args, **kwds)
45
45
46 return _active
46 return _active
47
47
48
48
49 def _playback(
49 def _playback(
50 journal,
50 journal,
51 report,
51 report,
52 opener,
52 opener,
53 vfsmap,
53 vfsmap,
54 entries,
54 entries,
55 backupentries,
55 backupentries,
56 unlink=True,
56 unlink=True,
57 checkambigfiles=None,
57 checkambigfiles=None,
58 ):
58 ):
59 for f, o, _ignore in entries:
59 for f, o in entries:
60 if o or not unlink:
60 if o or not unlink:
61 checkambig = checkambigfiles and (f, b'') in checkambigfiles
61 checkambig = checkambigfiles and (f, b'') in checkambigfiles
62 try:
62 try:
63 fp = opener(f, b'a', checkambig=checkambig)
63 fp = opener(f, b'a', checkambig=checkambig)
64 if fp.tell() < o:
64 if fp.tell() < o:
65 raise error.Abort(
65 raise error.Abort(
66 _(
66 _(
67 b"attempted to truncate %s to %d bytes, but it was "
67 b"attempted to truncate %s to %d bytes, but it was "
68 b"already %d bytes\n"
68 b"already %d bytes\n"
69 )
69 )
70 % (f, o, fp.tell())
70 % (f, o, fp.tell())
71 )
71 )
72 fp.truncate(o)
72 fp.truncate(o)
73 fp.close()
73 fp.close()
74 except IOError:
74 except IOError:
75 report(_(b"failed to truncate %s\n") % f)
75 report(_(b"failed to truncate %s\n") % f)
76 raise
76 raise
77 else:
77 else:
78 try:
78 try:
79 opener.unlink(f)
79 opener.unlink(f)
80 except (IOError, OSError) as inst:
80 except (IOError, OSError) as inst:
81 if inst.errno != errno.ENOENT:
81 if inst.errno != errno.ENOENT:
82 raise
82 raise
83
83
84 backupfiles = []
84 backupfiles = []
85 for l, f, b, c in backupentries:
85 for l, f, b, c in backupentries:
86 if l not in vfsmap and c:
86 if l not in vfsmap and c:
87 report(b"couldn't handle %s: unknown cache location %s\n" % (b, l))
87 report(b"couldn't handle %s: unknown cache location %s\n" % (b, l))
88 vfs = vfsmap[l]
88 vfs = vfsmap[l]
89 try:
89 try:
90 if f and b:
90 if f and b:
91 filepath = vfs.join(f)
91 filepath = vfs.join(f)
92 backuppath = vfs.join(b)
92 backuppath = vfs.join(b)
93 checkambig = checkambigfiles and (f, l) in checkambigfiles
93 checkambig = checkambigfiles and (f, l) in checkambigfiles
94 try:
94 try:
95 util.copyfile(backuppath, filepath, checkambig=checkambig)
95 util.copyfile(backuppath, filepath, checkambig=checkambig)
96 backupfiles.append(b)
96 backupfiles.append(b)
97 except IOError:
97 except IOError:
98 report(_(b"failed to recover %s\n") % f)
98 report(_(b"failed to recover %s\n") % f)
99 else:
99 else:
100 target = f or b
100 target = f or b
101 try:
101 try:
102 vfs.unlink(target)
102 vfs.unlink(target)
103 except (IOError, OSError) as inst:
103 except (IOError, OSError) as inst:
104 if inst.errno != errno.ENOENT:
104 if inst.errno != errno.ENOENT:
105 raise
105 raise
106 except (IOError, OSError, error.Abort):
106 except (IOError, OSError, error.Abort):
107 if not c:
107 if not c:
108 raise
108 raise
109
109
110 backuppath = b"%s.backupfiles" % journal
110 backuppath = b"%s.backupfiles" % journal
111 if opener.exists(backuppath):
111 if opener.exists(backuppath):
112 opener.unlink(backuppath)
112 opener.unlink(backuppath)
113 opener.unlink(journal)
113 opener.unlink(journal)
114 try:
114 try:
115 for f in backupfiles:
115 for f in backupfiles:
116 if opener.exists(f):
116 if opener.exists(f):
117 opener.unlink(f)
117 opener.unlink(f)
118 except (IOError, OSError, error.Abort):
118 except (IOError, OSError, error.Abort):
119 # only pure backup file remains, it is sage to ignore any error
119 # only pure backup file remains, it is sage to ignore any error
120 pass
120 pass
121
121
122
122
123 class transaction(util.transactional):
123 class transaction(util.transactional):
124 def __init__(
124 def __init__(
125 self,
125 self,
126 report,
126 report,
127 opener,
127 opener,
128 vfsmap,
128 vfsmap,
129 journalname,
129 journalname,
130 undoname=None,
130 undoname=None,
131 after=None,
131 after=None,
132 createmode=None,
132 createmode=None,
133 validator=None,
133 validator=None,
134 releasefn=None,
134 releasefn=None,
135 checkambigfiles=None,
135 checkambigfiles=None,
136 name='<unnamed>',
136 name='<unnamed>',
137 ):
137 ):
138 """Begin a new transaction
138 """Begin a new transaction
139
139
140 Begins a new transaction that allows rolling back writes in the event of
140 Begins a new transaction that allows rolling back writes in the event of
141 an exception.
141 an exception.
142
142
143 * `after`: called after the transaction has been committed
143 * `after`: called after the transaction has been committed
144 * `createmode`: the mode of the journal file that will be created
144 * `createmode`: the mode of the journal file that will be created
145 * `releasefn`: called after releasing (with transaction and result)
145 * `releasefn`: called after releasing (with transaction and result)
146
146
147 `checkambigfiles` is a set of (path, vfs-location) tuples,
147 `checkambigfiles` is a set of (path, vfs-location) tuples,
148 which determine whether file stat ambiguity should be avoided
148 which determine whether file stat ambiguity should be avoided
149 for corresponded files.
149 for corresponded files.
150 """
150 """
151 self._count = 1
151 self._count = 1
152 self._usages = 1
152 self._usages = 1
153 self._report = report
153 self._report = report
154 # a vfs to the store content
154 # a vfs to the store content
155 self._opener = opener
155 self._opener = opener
156 # a map to access file in various {location -> vfs}
156 # a map to access file in various {location -> vfs}
157 vfsmap = vfsmap.copy()
157 vfsmap = vfsmap.copy()
158 vfsmap[b''] = opener # set default value
158 vfsmap[b''] = opener # set default value
159 self._vfsmap = vfsmap
159 self._vfsmap = vfsmap
160 self._after = after
160 self._after = after
161 self._entries = []
161 self._entries = []
162 self._map = {}
162 self._map = {}
163 self._journal = journalname
163 self._journal = journalname
164 self._undoname = undoname
164 self._undoname = undoname
165 self._queue = []
165 self._queue = []
166 # A callback to do something just after releasing transaction.
166 # A callback to do something just after releasing transaction.
167 if releasefn is None:
167 if releasefn is None:
168 releasefn = lambda tr, success: None
168 releasefn = lambda tr, success: None
169 self._releasefn = releasefn
169 self._releasefn = releasefn
170
170
171 self._checkambigfiles = set()
171 self._checkambigfiles = set()
172 if checkambigfiles:
172 if checkambigfiles:
173 self._checkambigfiles.update(checkambigfiles)
173 self._checkambigfiles.update(checkambigfiles)
174
174
175 self._names = [name]
175 self._names = [name]
176
176
177 # A dict dedicated to precisely tracking the changes introduced in the
177 # A dict dedicated to precisely tracking the changes introduced in the
178 # transaction.
178 # transaction.
179 self.changes = {}
179 self.changes = {}
180
180
181 # a dict of arguments to be passed to hooks
181 # a dict of arguments to be passed to hooks
182 self.hookargs = {}
182 self.hookargs = {}
183 self._file = opener.open(self._journal, b"w")
183 self._file = opener.open(self._journal, b"w")
184
184
185 # a list of ('location', 'path', 'backuppath', cache) entries.
185 # a list of ('location', 'path', 'backuppath', cache) entries.
186 # - if 'backuppath' is empty, no file existed at backup time
186 # - if 'backuppath' is empty, no file existed at backup time
187 # - if 'path' is empty, this is a temporary transaction file
187 # - if 'path' is empty, this is a temporary transaction file
188 # - if 'location' is not empty, the path is outside main opener reach.
188 # - if 'location' is not empty, the path is outside main opener reach.
189 # use 'location' value as a key in a vfsmap to find the right 'vfs'
189 # use 'location' value as a key in a vfsmap to find the right 'vfs'
190 # (cache is currently unused)
190 # (cache is currently unused)
191 self._backupentries = []
191 self._backupentries = []
192 self._backupmap = {}
192 self._backupmap = {}
193 self._backupjournal = b"%s.backupfiles" % self._journal
193 self._backupjournal = b"%s.backupfiles" % self._journal
194 self._backupsfile = opener.open(self._backupjournal, b'w')
194 self._backupsfile = opener.open(self._backupjournal, b'w')
195 self._backupsfile.write(b'%d\n' % version)
195 self._backupsfile.write(b'%d\n' % version)
196
196
197 if createmode is not None:
197 if createmode is not None:
198 opener.chmod(self._journal, createmode & 0o666)
198 opener.chmod(self._journal, createmode & 0o666)
199 opener.chmod(self._backupjournal, createmode & 0o666)
199 opener.chmod(self._backupjournal, createmode & 0o666)
200
200
201 # hold file generations to be performed on commit
201 # hold file generations to be performed on commit
202 self._filegenerators = {}
202 self._filegenerators = {}
203 # hold callback to write pending data for hooks
203 # hold callback to write pending data for hooks
204 self._pendingcallback = {}
204 self._pendingcallback = {}
205 # True is any pending data have been written ever
205 # True is any pending data have been written ever
206 self._anypending = False
206 self._anypending = False
207 # holds callback to call when writing the transaction
207 # holds callback to call when writing the transaction
208 self._finalizecallback = {}
208 self._finalizecallback = {}
209 # holds callback to call when validating the transaction
209 # holds callback to call when validating the transaction
210 # should raise exception if anything is wrong
210 # should raise exception if anything is wrong
211 self._validatecallback = {}
211 self._validatecallback = {}
212 if validator is not None:
212 if validator is not None:
213 self._validatecallback[b'001-userhooks'] = validator
213 self._validatecallback[b'001-userhooks'] = validator
214 # hold callback for post transaction close
214 # hold callback for post transaction close
215 self._postclosecallback = {}
215 self._postclosecallback = {}
216 # holds callbacks to call during abort
216 # holds callbacks to call during abort
217 self._abortcallback = {}
217 self._abortcallback = {}
218
218
219 def __repr__(self):
219 def __repr__(self):
220 name = '/'.join(self._names)
220 name = '/'.join(self._names)
221 return '<transaction name=%s, count=%d, usages=%d>' % (
221 return '<transaction name=%s, count=%d, usages=%d>' % (
222 name,
222 name,
223 self._count,
223 self._count,
224 self._usages,
224 self._usages,
225 )
225 )
226
226
227 def __del__(self):
227 def __del__(self):
228 if self._journal:
228 if self._journal:
229 self._abort()
229 self._abort()
230
230
231 @active
231 @active
232 def startgroup(self):
232 def startgroup(self):
233 """delay registration of file entry
233 """delay registration of file entry
234
234
235 This is used by strip to delay vision of strip offset. The transaction
235 This is used by strip to delay vision of strip offset. The transaction
236 sees either none or all of the strip actions to be done."""
236 sees either none or all of the strip actions to be done."""
237 self._queue.append([])
237 self._queue.append([])
238
238
239 @active
239 @active
240 def endgroup(self):
240 def endgroup(self):
241 """apply delayed registration of file entry.
241 """apply delayed registration of file entry.
242
242
243 This is used by strip to delay vision of strip offset. The transaction
243 This is used by strip to delay vision of strip offset. The transaction
244 sees either none or all of the strip actions to be done."""
244 sees either none or all of the strip actions to be done."""
245 q = self._queue.pop()
245 q = self._queue.pop()
246 for f, o, data in q:
246 for f, o in q:
247 self._addentry(f, o, data)
247 self._addentry(f, o)
248
248
249 @active
249 @active
250 def add(self, file, offset, data=None):
250 def add(self, file, offset):
251 """record the state of an append-only file before update"""
251 """record the state of an append-only file before update"""
252 if file in self._map or file in self._backupmap:
252 if file in self._map or file in self._backupmap:
253 return
253 return
254 if self._queue:
254 if self._queue:
255 self._queue[-1].append((file, offset, data))
255 self._queue[-1].append((file, offset))
256 return
256 return
257
257
258 self._addentry(file, offset, data)
258 self._addentry(file, offset)
259
259
260 def _addentry(self, file, offset, data):
260 def _addentry(self, file, offset):
261 """add a append-only entry to memory and on-disk state"""
261 """add a append-only entry to memory and on-disk state"""
262 if file in self._map or file in self._backupmap:
262 if file in self._map or file in self._backupmap:
263 return
263 return
264 self._entries.append((file, offset, data))
264 self._entries.append((file, offset))
265 self._map[file] = len(self._entries) - 1
265 self._map[file] = len(self._entries) - 1
266 # add enough data to the journal to do the truncate
266 # add enough data to the journal to do the truncate
267 self._file.write(b"%s\0%d\n" % (file, offset))
267 self._file.write(b"%s\0%d\n" % (file, offset))
268 self._file.flush()
268 self._file.flush()
269
269
270 @active
270 @active
271 def addbackup(self, file, hardlink=True, location=b''):
271 def addbackup(self, file, hardlink=True, location=b''):
272 """Adds a backup of the file to the transaction
272 """Adds a backup of the file to the transaction
273
273
274 Calling addbackup() creates a hardlink backup of the specified file
274 Calling addbackup() creates a hardlink backup of the specified file
275 that is used to recover the file in the event of the transaction
275 that is used to recover the file in the event of the transaction
276 aborting.
276 aborting.
277
277
278 * `file`: the file path, relative to .hg/store
278 * `file`: the file path, relative to .hg/store
279 * `hardlink`: use a hardlink to quickly create the backup
279 * `hardlink`: use a hardlink to quickly create the backup
280 """
280 """
281 if self._queue:
281 if self._queue:
282 msg = b'cannot use transaction.addbackup inside "group"'
282 msg = b'cannot use transaction.addbackup inside "group"'
283 raise error.ProgrammingError(msg)
283 raise error.ProgrammingError(msg)
284
284
285 if file in self._map or file in self._backupmap:
285 if file in self._map or file in self._backupmap:
286 return
286 return
287 vfs = self._vfsmap[location]
287 vfs = self._vfsmap[location]
288 dirname, filename = vfs.split(file)
288 dirname, filename = vfs.split(file)
289 backupfilename = b"%s.backup.%s" % (self._journal, filename)
289 backupfilename = b"%s.backup.%s" % (self._journal, filename)
290 backupfile = vfs.reljoin(dirname, backupfilename)
290 backupfile = vfs.reljoin(dirname, backupfilename)
291 if vfs.exists(file):
291 if vfs.exists(file):
292 filepath = vfs.join(file)
292 filepath = vfs.join(file)
293 backuppath = vfs.join(backupfile)
293 backuppath = vfs.join(backupfile)
294 util.copyfile(filepath, backuppath, hardlink=hardlink)
294 util.copyfile(filepath, backuppath, hardlink=hardlink)
295 else:
295 else:
296 backupfile = b''
296 backupfile = b''
297
297
298 self._addbackupentry((location, file, backupfile, False))
298 self._addbackupentry((location, file, backupfile, False))
299
299
300 def _addbackupentry(self, entry):
300 def _addbackupentry(self, entry):
301 """register a new backup entry and write it to disk"""
301 """register a new backup entry and write it to disk"""
302 self._backupentries.append(entry)
302 self._backupentries.append(entry)
303 self._backupmap[entry[1]] = len(self._backupentries) - 1
303 self._backupmap[entry[1]] = len(self._backupentries) - 1
304 self._backupsfile.write(b"%s\0%s\0%s\0%d\n" % entry)
304 self._backupsfile.write(b"%s\0%s\0%s\0%d\n" % entry)
305 self._backupsfile.flush()
305 self._backupsfile.flush()
306
306
307 @active
307 @active
308 def registertmp(self, tmpfile, location=b''):
308 def registertmp(self, tmpfile, location=b''):
309 """register a temporary transaction file
309 """register a temporary transaction file
310
310
311 Such files will be deleted when the transaction exits (on both
311 Such files will be deleted when the transaction exits (on both
312 failure and success).
312 failure and success).
313 """
313 """
314 self._addbackupentry((location, b'', tmpfile, False))
314 self._addbackupentry((location, b'', tmpfile, False))
315
315
316 @active
316 @active
317 def addfilegenerator(
317 def addfilegenerator(
318 self, genid, filenames, genfunc, order=0, location=b''
318 self, genid, filenames, genfunc, order=0, location=b''
319 ):
319 ):
320 """add a function to generates some files at transaction commit
320 """add a function to generates some files at transaction commit
321
321
322 The `genfunc` argument is a function capable of generating proper
322 The `genfunc` argument is a function capable of generating proper
323 content of each entry in the `filename` tuple.
323 content of each entry in the `filename` tuple.
324
324
325 At transaction close time, `genfunc` will be called with one file
325 At transaction close time, `genfunc` will be called with one file
326 object argument per entries in `filenames`.
326 object argument per entries in `filenames`.
327
327
328 The transaction itself is responsible for the backup, creation and
328 The transaction itself is responsible for the backup, creation and
329 final write of such file.
329 final write of such file.
330
330
331 The `genid` argument is used to ensure the same set of file is only
331 The `genid` argument is used to ensure the same set of file is only
332 generated once. Call to `addfilegenerator` for a `genid` already
332 generated once. Call to `addfilegenerator` for a `genid` already
333 present will overwrite the old entry.
333 present will overwrite the old entry.
334
334
335 The `order` argument may be used to control the order in which multiple
335 The `order` argument may be used to control the order in which multiple
336 generator will be executed.
336 generator will be executed.
337
337
338 The `location` arguments may be used to indicate the files are located
338 The `location` arguments may be used to indicate the files are located
339 outside of the the standard directory for transaction. It should match
339 outside of the the standard directory for transaction. It should match
340 one of the key of the `transaction.vfsmap` dictionary.
340 one of the key of the `transaction.vfsmap` dictionary.
341 """
341 """
342 # For now, we are unable to do proper backup and restore of custom vfs
342 # For now, we are unable to do proper backup and restore of custom vfs
343 # but for bookmarks that are handled outside this mechanism.
343 # but for bookmarks that are handled outside this mechanism.
344 self._filegenerators[genid] = (order, filenames, genfunc, location)
344 self._filegenerators[genid] = (order, filenames, genfunc, location)
345
345
346 @active
346 @active
347 def removefilegenerator(self, genid):
347 def removefilegenerator(self, genid):
348 """reverse of addfilegenerator, remove a file generator function"""
348 """reverse of addfilegenerator, remove a file generator function"""
349 if genid in self._filegenerators:
349 if genid in self._filegenerators:
350 del self._filegenerators[genid]
350 del self._filegenerators[genid]
351
351
352 def _generatefiles(self, suffix=b'', group=GEN_GROUP_ALL):
352 def _generatefiles(self, suffix=b'', group=GEN_GROUP_ALL):
353 # write files registered for generation
353 # write files registered for generation
354 any = False
354 any = False
355
355
356 if group == GEN_GROUP_ALL:
356 if group == GEN_GROUP_ALL:
357 skip_post = skip_pre = False
357 skip_post = skip_pre = False
358 else:
358 else:
359 skip_pre = group == GEN_GROUP_POST_FINALIZE
359 skip_pre = group == GEN_GROUP_POST_FINALIZE
360 skip_post = group == GEN_GROUP_PRE_FINALIZE
360 skip_post = group == GEN_GROUP_PRE_FINALIZE
361
361
362 for id, entry in sorted(pycompat.iteritems(self._filegenerators)):
362 for id, entry in sorted(pycompat.iteritems(self._filegenerators)):
363 any = True
363 any = True
364 order, filenames, genfunc, location = entry
364 order, filenames, genfunc, location = entry
365
365
366 # for generation at closing, check if it's before or after finalize
366 # for generation at closing, check if it's before or after finalize
367 is_post = id in postfinalizegenerators
367 is_post = id in postfinalizegenerators
368 if skip_post and is_post:
368 if skip_post and is_post:
369 continue
369 continue
370 elif skip_pre and not is_post:
370 elif skip_pre and not is_post:
371 continue
371 continue
372
372
373 vfs = self._vfsmap[location]
373 vfs = self._vfsmap[location]
374 files = []
374 files = []
375 try:
375 try:
376 for name in filenames:
376 for name in filenames:
377 name += suffix
377 name += suffix
378 if suffix:
378 if suffix:
379 self.registertmp(name, location=location)
379 self.registertmp(name, location=location)
380 checkambig = False
380 checkambig = False
381 else:
381 else:
382 self.addbackup(name, location=location)
382 self.addbackup(name, location=location)
383 checkambig = (name, location) in self._checkambigfiles
383 checkambig = (name, location) in self._checkambigfiles
384 files.append(
384 files.append(
385 vfs(name, b'w', atomictemp=True, checkambig=checkambig)
385 vfs(name, b'w', atomictemp=True, checkambig=checkambig)
386 )
386 )
387 genfunc(*files)
387 genfunc(*files)
388 for f in files:
388 for f in files:
389 f.close()
389 f.close()
390 # skip discard() loop since we're sure no open file remains
390 # skip discard() loop since we're sure no open file remains
391 del files[:]
391 del files[:]
392 finally:
392 finally:
393 for f in files:
393 for f in files:
394 f.discard()
394 f.discard()
395 return any
395 return any
396
396
397 @active
397 @active
398 def find(self, file):
398 def find(self, file):
399 if file in self._map:
399 if file in self._map:
400 return self._entries[self._map[file]]
400 return self._entries[self._map[file]]
401 if file in self._backupmap:
401 if file in self._backupmap:
402 return self._backupentries[self._backupmap[file]]
402 return self._backupentries[self._backupmap[file]]
403 return None
403 return None
404
404
405 @active
405 @active
406 def replace(self, file, offset, data=None):
406 def replace(self, file, offset):
407 '''
407 '''
408 replace can only replace already committed entries
408 replace can only replace already committed entries
409 that are not pending in the queue
409 that are not pending in the queue
410 '''
410 '''
411
411
412 if file not in self._map:
412 if file not in self._map:
413 raise KeyError(file)
413 raise KeyError(file)
414 index = self._map[file]
414 index = self._map[file]
415 self._entries[index] = (file, offset, data)
415 self._entries[index] = (file, offset)
416 self._file.write(b"%s\0%d\n" % (file, offset))
416 self._file.write(b"%s\0%d\n" % (file, offset))
417 self._file.flush()
417 self._file.flush()
418
418
419 @active
419 @active
420 def nest(self, name='<unnamed>'):
420 def nest(self, name='<unnamed>'):
421 self._count += 1
421 self._count += 1
422 self._usages += 1
422 self._usages += 1
423 self._names.append(name)
423 self._names.append(name)
424 return self
424 return self
425
425
426 def release(self):
426 def release(self):
427 if self._count > 0:
427 if self._count > 0:
428 self._usages -= 1
428 self._usages -= 1
429 if self._names:
429 if self._names:
430 self._names.pop()
430 self._names.pop()
431 # if the transaction scopes are left without being closed, fail
431 # if the transaction scopes are left without being closed, fail
432 if self._count > 0 and self._usages == 0:
432 if self._count > 0 and self._usages == 0:
433 self._abort()
433 self._abort()
434
434
435 def running(self):
435 def running(self):
436 return self._count > 0
436 return self._count > 0
437
437
438 def addpending(self, category, callback):
438 def addpending(self, category, callback):
439 """add a callback to be called when the transaction is pending
439 """add a callback to be called when the transaction is pending
440
440
441 The transaction will be given as callback's first argument.
441 The transaction will be given as callback's first argument.
442
442
443 Category is a unique identifier to allow overwriting an old callback
443 Category is a unique identifier to allow overwriting an old callback
444 with a newer callback.
444 with a newer callback.
445 """
445 """
446 self._pendingcallback[category] = callback
446 self._pendingcallback[category] = callback
447
447
448 @active
448 @active
449 def writepending(self):
449 def writepending(self):
450 '''write pending file to temporary version
450 '''write pending file to temporary version
451
451
452 This is used to allow hooks to view a transaction before commit'''
452 This is used to allow hooks to view a transaction before commit'''
453 categories = sorted(self._pendingcallback)
453 categories = sorted(self._pendingcallback)
454 for cat in categories:
454 for cat in categories:
455 # remove callback since the data will have been flushed
455 # remove callback since the data will have been flushed
456 any = self._pendingcallback.pop(cat)(self)
456 any = self._pendingcallback.pop(cat)(self)
457 self._anypending = self._anypending or any
457 self._anypending = self._anypending or any
458 self._anypending |= self._generatefiles(suffix=b'.pending')
458 self._anypending |= self._generatefiles(suffix=b'.pending')
459 return self._anypending
459 return self._anypending
460
460
461 @active
461 @active
462 def hasfinalize(self, category):
462 def hasfinalize(self, category):
463 """check is a callback already exist for a category
463 """check is a callback already exist for a category
464 """
464 """
465 return category in self._finalizecallback
465 return category in self._finalizecallback
466
466
467 @active
467 @active
468 def addfinalize(self, category, callback):
468 def addfinalize(self, category, callback):
469 """add a callback to be called when the transaction is closed
469 """add a callback to be called when the transaction is closed
470
470
471 The transaction will be given as callback's first argument.
471 The transaction will be given as callback's first argument.
472
472
473 Category is a unique identifier to allow overwriting old callbacks with
473 Category is a unique identifier to allow overwriting old callbacks with
474 newer callbacks.
474 newer callbacks.
475 """
475 """
476 self._finalizecallback[category] = callback
476 self._finalizecallback[category] = callback
477
477
478 @active
478 @active
479 def addpostclose(self, category, callback):
479 def addpostclose(self, category, callback):
480 """add or replace a callback to be called after the transaction closed
480 """add or replace a callback to be called after the transaction closed
481
481
482 The transaction will be given as callback's first argument.
482 The transaction will be given as callback's first argument.
483
483
484 Category is a unique identifier to allow overwriting an old callback
484 Category is a unique identifier to allow overwriting an old callback
485 with a newer callback.
485 with a newer callback.
486 """
486 """
487 self._postclosecallback[category] = callback
487 self._postclosecallback[category] = callback
488
488
489 @active
489 @active
490 def getpostclose(self, category):
490 def getpostclose(self, category):
491 """return a postclose callback added before, or None"""
491 """return a postclose callback added before, or None"""
492 return self._postclosecallback.get(category, None)
492 return self._postclosecallback.get(category, None)
493
493
494 @active
494 @active
495 def addabort(self, category, callback):
495 def addabort(self, category, callback):
496 """add a callback to be called when the transaction is aborted.
496 """add a callback to be called when the transaction is aborted.
497
497
498 The transaction will be given as the first argument to the callback.
498 The transaction will be given as the first argument to the callback.
499
499
500 Category is a unique identifier to allow overwriting an old callback
500 Category is a unique identifier to allow overwriting an old callback
501 with a newer callback.
501 with a newer callback.
502 """
502 """
503 self._abortcallback[category] = callback
503 self._abortcallback[category] = callback
504
504
505 @active
505 @active
506 def addvalidator(self, category, callback):
506 def addvalidator(self, category, callback):
507 """ adds a callback to be called when validating the transaction.
507 """ adds a callback to be called when validating the transaction.
508
508
509 The transaction will be given as the first argument to the callback.
509 The transaction will be given as the first argument to the callback.
510
510
511 callback should raise exception if to abort transaction """
511 callback should raise exception if to abort transaction """
512 self._validatecallback[category] = callback
512 self._validatecallback[category] = callback
513
513
514 @active
514 @active
515 def close(self):
515 def close(self):
516 '''commit the transaction'''
516 '''commit the transaction'''
517 if self._count == 1:
517 if self._count == 1:
518 for category in sorted(self._validatecallback):
518 for category in sorted(self._validatecallback):
519 self._validatecallback[category](self)
519 self._validatecallback[category](self)
520 self._validatecallback = None # Help prevent cycles.
520 self._validatecallback = None # Help prevent cycles.
521 self._generatefiles(group=GEN_GROUP_PRE_FINALIZE)
521 self._generatefiles(group=GEN_GROUP_PRE_FINALIZE)
522 while self._finalizecallback:
522 while self._finalizecallback:
523 callbacks = self._finalizecallback
523 callbacks = self._finalizecallback
524 self._finalizecallback = {}
524 self._finalizecallback = {}
525 categories = sorted(callbacks)
525 categories = sorted(callbacks)
526 for cat in categories:
526 for cat in categories:
527 callbacks[cat](self)
527 callbacks[cat](self)
528 # Prevent double usage and help clear cycles.
528 # Prevent double usage and help clear cycles.
529 self._finalizecallback = None
529 self._finalizecallback = None
530 self._generatefiles(group=GEN_GROUP_POST_FINALIZE)
530 self._generatefiles(group=GEN_GROUP_POST_FINALIZE)
531
531
532 self._count -= 1
532 self._count -= 1
533 if self._count != 0:
533 if self._count != 0:
534 return
534 return
535 self._file.close()
535 self._file.close()
536 self._backupsfile.close()
536 self._backupsfile.close()
537 # cleanup temporary files
537 # cleanup temporary files
538 for l, f, b, c in self._backupentries:
538 for l, f, b, c in self._backupentries:
539 if l not in self._vfsmap and c:
539 if l not in self._vfsmap and c:
540 self._report(
540 self._report(
541 b"couldn't remove %s: unknown cache location %s\n" % (b, l)
541 b"couldn't remove %s: unknown cache location %s\n" % (b, l)
542 )
542 )
543 continue
543 continue
544 vfs = self._vfsmap[l]
544 vfs = self._vfsmap[l]
545 if not f and b and vfs.exists(b):
545 if not f and b and vfs.exists(b):
546 try:
546 try:
547 vfs.unlink(b)
547 vfs.unlink(b)
548 except (IOError, OSError, error.Abort) as inst:
548 except (IOError, OSError, error.Abort) as inst:
549 if not c:
549 if not c:
550 raise
550 raise
551 # Abort may be raise by read only opener
551 # Abort may be raise by read only opener
552 self._report(
552 self._report(
553 b"couldn't remove %s: %s\n" % (vfs.join(b), inst)
553 b"couldn't remove %s: %s\n" % (vfs.join(b), inst)
554 )
554 )
555 self._entries = []
555 self._entries = []
556 self._writeundo()
556 self._writeundo()
557 if self._after:
557 if self._after:
558 self._after()
558 self._after()
559 self._after = None # Help prevent cycles.
559 self._after = None # Help prevent cycles.
560 if self._opener.isfile(self._backupjournal):
560 if self._opener.isfile(self._backupjournal):
561 self._opener.unlink(self._backupjournal)
561 self._opener.unlink(self._backupjournal)
562 if self._opener.isfile(self._journal):
562 if self._opener.isfile(self._journal):
563 self._opener.unlink(self._journal)
563 self._opener.unlink(self._journal)
564 for l, _f, b, c in self._backupentries:
564 for l, _f, b, c in self._backupentries:
565 if l not in self._vfsmap and c:
565 if l not in self._vfsmap and c:
566 self._report(
566 self._report(
567 b"couldn't remove %s: unknown cache location"
567 b"couldn't remove %s: unknown cache location"
568 b"%s\n" % (b, l)
568 b"%s\n" % (b, l)
569 )
569 )
570 continue
570 continue
571 vfs = self._vfsmap[l]
571 vfs = self._vfsmap[l]
572 if b and vfs.exists(b):
572 if b and vfs.exists(b):
573 try:
573 try:
574 vfs.unlink(b)
574 vfs.unlink(b)
575 except (IOError, OSError, error.Abort) as inst:
575 except (IOError, OSError, error.Abort) as inst:
576 if not c:
576 if not c:
577 raise
577 raise
578 # Abort may be raise by read only opener
578 # Abort may be raise by read only opener
579 self._report(
579 self._report(
580 b"couldn't remove %s: %s\n" % (vfs.join(b), inst)
580 b"couldn't remove %s: %s\n" % (vfs.join(b), inst)
581 )
581 )
582 self._backupentries = []
582 self._backupentries = []
583 self._journal = None
583 self._journal = None
584
584
585 self._releasefn(self, True) # notify success of closing transaction
585 self._releasefn(self, True) # notify success of closing transaction
586 self._releasefn = None # Help prevent cycles.
586 self._releasefn = None # Help prevent cycles.
587
587
588 # run post close action
588 # run post close action
589 categories = sorted(self._postclosecallback)
589 categories = sorted(self._postclosecallback)
590 for cat in categories:
590 for cat in categories:
591 self._postclosecallback[cat](self)
591 self._postclosecallback[cat](self)
592 # Prevent double usage and help clear cycles.
592 # Prevent double usage and help clear cycles.
593 self._postclosecallback = None
593 self._postclosecallback = None
594
594
595 @active
595 @active
596 def abort(self):
596 def abort(self):
597 '''abort the transaction (generally called on error, or when the
597 '''abort the transaction (generally called on error, or when the
598 transaction is not explicitly committed before going out of
598 transaction is not explicitly committed before going out of
599 scope)'''
599 scope)'''
600 self._abort()
600 self._abort()
601
601
602 def _writeundo(self):
602 def _writeundo(self):
603 """write transaction data for possible future undo call"""
603 """write transaction data for possible future undo call"""
604 if self._undoname is None:
604 if self._undoname is None:
605 return
605 return
606 undobackupfile = self._opener.open(
606 undobackupfile = self._opener.open(
607 b"%s.backupfiles" % self._undoname, b'w'
607 b"%s.backupfiles" % self._undoname, b'w'
608 )
608 )
609 undobackupfile.write(b'%d\n' % version)
609 undobackupfile.write(b'%d\n' % version)
610 for l, f, b, c in self._backupentries:
610 for l, f, b, c in self._backupentries:
611 if not f: # temporary file
611 if not f: # temporary file
612 continue
612 continue
613 if not b:
613 if not b:
614 u = b''
614 u = b''
615 else:
615 else:
616 if l not in self._vfsmap and c:
616 if l not in self._vfsmap and c:
617 self._report(
617 self._report(
618 b"couldn't remove %s: unknown cache location"
618 b"couldn't remove %s: unknown cache location"
619 b"%s\n" % (b, l)
619 b"%s\n" % (b, l)
620 )
620 )
621 continue
621 continue
622 vfs = self._vfsmap[l]
622 vfs = self._vfsmap[l]
623 base, name = vfs.split(b)
623 base, name = vfs.split(b)
624 assert name.startswith(self._journal), name
624 assert name.startswith(self._journal), name
625 uname = name.replace(self._journal, self._undoname, 1)
625 uname = name.replace(self._journal, self._undoname, 1)
626 u = vfs.reljoin(base, uname)
626 u = vfs.reljoin(base, uname)
627 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True)
627 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True)
628 undobackupfile.write(b"%s\0%s\0%s\0%d\n" % (l, f, u, c))
628 undobackupfile.write(b"%s\0%s\0%s\0%d\n" % (l, f, u, c))
629 undobackupfile.close()
629 undobackupfile.close()
630
630
631 def _abort(self):
631 def _abort(self):
632 self._count = 0
632 self._count = 0
633 self._usages = 0
633 self._usages = 0
634 self._file.close()
634 self._file.close()
635 self._backupsfile.close()
635 self._backupsfile.close()
636
636
637 try:
637 try:
638 if not self._entries and not self._backupentries:
638 if not self._entries and not self._backupentries:
639 if self._backupjournal:
639 if self._backupjournal:
640 self._opener.unlink(self._backupjournal)
640 self._opener.unlink(self._backupjournal)
641 if self._journal:
641 if self._journal:
642 self._opener.unlink(self._journal)
642 self._opener.unlink(self._journal)
643 return
643 return
644
644
645 self._report(_(b"transaction abort!\n"))
645 self._report(_(b"transaction abort!\n"))
646
646
647 try:
647 try:
648 for cat in sorted(self._abortcallback):
648 for cat in sorted(self._abortcallback):
649 self._abortcallback[cat](self)
649 self._abortcallback[cat](self)
650 # Prevent double usage and help clear cycles.
650 # Prevent double usage and help clear cycles.
651 self._abortcallback = None
651 self._abortcallback = None
652 _playback(
652 _playback(
653 self._journal,
653 self._journal,
654 self._report,
654 self._report,
655 self._opener,
655 self._opener,
656 self._vfsmap,
656 self._vfsmap,
657 self._entries,
657 self._entries,
658 self._backupentries,
658 self._backupentries,
659 False,
659 False,
660 checkambigfiles=self._checkambigfiles,
660 checkambigfiles=self._checkambigfiles,
661 )
661 )
662 self._report(_(b"rollback completed\n"))
662 self._report(_(b"rollback completed\n"))
663 except BaseException as exc:
663 except BaseException as exc:
664 self._report(_(b"rollback failed - please run hg recover\n"))
664 self._report(_(b"rollback failed - please run hg recover\n"))
665 self._report(
665 self._report(
666 _(b"(failure reason: %s)\n") % stringutil.forcebytestr(exc)
666 _(b"(failure reason: %s)\n") % stringutil.forcebytestr(exc)
667 )
667 )
668 finally:
668 finally:
669 self._journal = None
669 self._journal = None
670 self._releasefn(self, False) # notify failure of transaction
670 self._releasefn(self, False) # notify failure of transaction
671 self._releasefn = None # Help prevent cycles.
671 self._releasefn = None # Help prevent cycles.
672
672
673
673
674 def rollback(opener, vfsmap, file, report, checkambigfiles=None):
674 def rollback(opener, vfsmap, file, report, checkambigfiles=None):
675 """Rolls back the transaction contained in the given file
675 """Rolls back the transaction contained in the given file
676
676
677 Reads the entries in the specified file, and the corresponding
677 Reads the entries in the specified file, and the corresponding
678 '*.backupfiles' file, to recover from an incomplete transaction.
678 '*.backupfiles' file, to recover from an incomplete transaction.
679
679
680 * `file`: a file containing a list of entries, specifying where
680 * `file`: a file containing a list of entries, specifying where
681 to truncate each file. The file should contain a list of
681 to truncate each file. The file should contain a list of
682 file\0offset pairs, delimited by newlines. The corresponding
682 file\0offset pairs, delimited by newlines. The corresponding
683 '*.backupfiles' file should contain a list of file\0backupfile
683 '*.backupfiles' file should contain a list of file\0backupfile
684 pairs, delimited by \0.
684 pairs, delimited by \0.
685
685
686 `checkambigfiles` is a set of (path, vfs-location) tuples,
686 `checkambigfiles` is a set of (path, vfs-location) tuples,
687 which determine whether file stat ambiguity should be avoided at
687 which determine whether file stat ambiguity should be avoided at
688 restoring corresponded files.
688 restoring corresponded files.
689 """
689 """
690 entries = []
690 entries = []
691 backupentries = []
691 backupentries = []
692
692
693 fp = opener.open(file)
693 fp = opener.open(file)
694 lines = fp.readlines()
694 lines = fp.readlines()
695 fp.close()
695 fp.close()
696 for l in lines:
696 for l in lines:
697 try:
697 try:
698 f, o = l.split(b'\0')
698 f, o = l.split(b'\0')
699 entries.append((f, int(o), None))
699 entries.append((f, int(o)))
700 except ValueError:
700 except ValueError:
701 report(
701 report(
702 _(b"couldn't read journal entry %r!\n") % pycompat.bytestr(l)
702 _(b"couldn't read journal entry %r!\n") % pycompat.bytestr(l)
703 )
703 )
704
704
705 backupjournal = b"%s.backupfiles" % file
705 backupjournal = b"%s.backupfiles" % file
706 if opener.exists(backupjournal):
706 if opener.exists(backupjournal):
707 fp = opener.open(backupjournal)
707 fp = opener.open(backupjournal)
708 lines = fp.readlines()
708 lines = fp.readlines()
709 if lines:
709 if lines:
710 ver = lines[0][:-1]
710 ver = lines[0][:-1]
711 if ver == (b'%d' % version):
711 if ver == (b'%d' % version):
712 for line in lines[1:]:
712 for line in lines[1:]:
713 if line:
713 if line:
714 # Shave off the trailing newline
714 # Shave off the trailing newline
715 line = line[:-1]
715 line = line[:-1]
716 l, f, b, c = line.split(b'\0')
716 l, f, b, c = line.split(b'\0')
717 backupentries.append((l, f, b, bool(c)))
717 backupentries.append((l, f, b, bool(c)))
718 else:
718 else:
719 report(
719 report(
720 _(
720 _(
721 b"journal was created by a different version of "
721 b"journal was created by a different version of "
722 b"Mercurial\n"
722 b"Mercurial\n"
723 )
723 )
724 )
724 )
725
725
726 _playback(
726 _playback(
727 file,
727 file,
728 report,
728 report,
729 opener,
729 opener,
730 vfsmap,
730 vfsmap,
731 entries,
731 entries,
732 backupentries,
732 backupentries,
733 checkambigfiles=checkambigfiles,
733 checkambigfiles=checkambigfiles,
734 )
734 )
@@ -1,471 +1,471 b''
1 Test that qpush cleans things up if it doesn't complete
1 Test that qpush cleans things up if it doesn't complete
2
2
3 $ echo "[extensions]" >> $HGRCPATH
3 $ echo "[extensions]" >> $HGRCPATH
4 $ echo "mq=" >> $HGRCPATH
4 $ echo "mq=" >> $HGRCPATH
5 $ hg init repo
5 $ hg init repo
6 $ cd repo
6 $ cd repo
7 $ echo foo > foo
7 $ echo foo > foo
8 $ hg ci -Am 'add foo'
8 $ hg ci -Am 'add foo'
9 adding foo
9 adding foo
10 $ touch untracked-file
10 $ touch untracked-file
11 $ echo 'syntax: glob' > .hgignore
11 $ echo 'syntax: glob' > .hgignore
12 $ echo '.hgignore' >> .hgignore
12 $ echo '.hgignore' >> .hgignore
13 $ hg qinit
13 $ hg qinit
14
14
15 test qpush on empty series
15 test qpush on empty series
16
16
17 $ hg qpush
17 $ hg qpush
18 no patches in series
18 no patches in series
19 $ hg qnew patch1
19 $ hg qnew patch1
20 $ echo >> foo
20 $ echo >> foo
21 $ hg qrefresh -m 'patch 1'
21 $ hg qrefresh -m 'patch 1'
22 $ hg qnew patch2
22 $ hg qnew patch2
23 $ echo bar > bar
23 $ echo bar > bar
24 $ hg add bar
24 $ hg add bar
25 $ hg qrefresh -m 'patch 2'
25 $ hg qrefresh -m 'patch 2'
26 $ hg qnew --config 'mq.plain=true' -U bad-patch
26 $ hg qnew --config 'mq.plain=true' -U bad-patch
27 $ echo >> foo
27 $ echo >> foo
28 $ hg qrefresh
28 $ hg qrefresh
29 $ hg qpop -a
29 $ hg qpop -a
30 popping bad-patch
30 popping bad-patch
31 popping patch2
31 popping patch2
32 popping patch1
32 popping patch1
33 patch queue now empty
33 patch queue now empty
34 $ "$PYTHON" -c 'import sys; getattr(sys.stdout, "buffer", sys.stdout).write(b"\xe9\n")' > message
34 $ "$PYTHON" -c 'import sys; getattr(sys.stdout, "buffer", sys.stdout).write(b"\xe9\n")' > message
35 $ cat .hg/patches/bad-patch >> message
35 $ cat .hg/patches/bad-patch >> message
36 $ mv message .hg/patches/bad-patch
36 $ mv message .hg/patches/bad-patch
37 $ cat > $TESTTMP/wrapplayback.py <<EOF
37 $ cat > $TESTTMP/wrapplayback.py <<EOF
38 > import os
38 > import os
39 > from mercurial import extensions, transaction
39 > from mercurial import extensions, transaction
40 > def wrapplayback(orig,
40 > def wrapplayback(orig,
41 > journal, report, opener, vfsmap, entries, backupentries,
41 > journal, report, opener, vfsmap, entries, backupentries,
42 > unlink=True, checkambigfiles=None):
42 > unlink=True, checkambigfiles=None):
43 > orig(journal, report, opener, vfsmap, entries, backupentries, unlink,
43 > orig(journal, report, opener, vfsmap, entries, backupentries, unlink,
44 > checkambigfiles)
44 > checkambigfiles)
45 > # Touching files truncated at "transaction.abort" causes
45 > # Touching files truncated at "transaction.abort" causes
46 > # forcible re-loading invalidated filecache properties
46 > # forcible re-loading invalidated filecache properties
47 > # (including repo.changelog)
47 > # (including repo.changelog)
48 > for f, o, _ignore in entries:
48 > for f, o in entries:
49 > if o or not unlink:
49 > if o or not unlink:
50 > os.utime(opener.join(f), (0.0, 0.0))
50 > os.utime(opener.join(f), (0.0, 0.0))
51 > def extsetup(ui):
51 > def extsetup(ui):
52 > extensions.wrapfunction(transaction, '_playback', wrapplayback)
52 > extensions.wrapfunction(transaction, '_playback', wrapplayback)
53 > EOF
53 > EOF
54 $ hg qpush -a --config extensions.wrapplayback=$TESTTMP/wrapplayback.py && echo 'qpush succeeded?!'
54 $ hg qpush -a --config extensions.wrapplayback=$TESTTMP/wrapplayback.py && echo 'qpush succeeded?!'
55 applying patch1
55 applying patch1
56 applying patch2
56 applying patch2
57 applying bad-patch
57 applying bad-patch
58 transaction abort!
58 transaction abort!
59 rollback completed
59 rollback completed
60 cleaning up working directory...
60 cleaning up working directory...
61 reverting foo
61 reverting foo
62 done
62 done
63 abort: decoding near '\xe9': 'ascii' codec can't decode byte 0xe9 in position 0: ordinal not in range(128)! (esc)
63 abort: decoding near '\xe9': 'ascii' codec can't decode byte 0xe9 in position 0: ordinal not in range(128)! (esc)
64 [255]
64 [255]
65 $ hg parents
65 $ hg parents
66 changeset: 0:bbd179dfa0a7
66 changeset: 0:bbd179dfa0a7
67 tag: tip
67 tag: tip
68 user: test
68 user: test
69 date: Thu Jan 01 00:00:00 1970 +0000
69 date: Thu Jan 01 00:00:00 1970 +0000
70 summary: add foo
70 summary: add foo
71
71
72
72
73 test corrupt status file
73 test corrupt status file
74 $ hg qpush
74 $ hg qpush
75 applying patch1
75 applying patch1
76 now at: patch1
76 now at: patch1
77 $ cp .hg/patches/status .hg/patches/status.orig
77 $ cp .hg/patches/status .hg/patches/status.orig
78 $ hg qpop
78 $ hg qpop
79 popping patch1
79 popping patch1
80 patch queue now empty
80 patch queue now empty
81 $ cp .hg/patches/status.orig .hg/patches/status
81 $ cp .hg/patches/status.orig .hg/patches/status
82 $ hg qpush
82 $ hg qpush
83 abort: working directory revision is not qtip
83 abort: working directory revision is not qtip
84 [255]
84 [255]
85 $ rm .hg/patches/status .hg/patches/status.orig
85 $ rm .hg/patches/status .hg/patches/status.orig
86
86
87
87
88 bar should be gone; other unknown/ignored files should still be around
88 bar should be gone; other unknown/ignored files should still be around
89
89
90 $ hg status -A
90 $ hg status -A
91 ? untracked-file
91 ? untracked-file
92 I .hgignore
92 I .hgignore
93 C foo
93 C foo
94
94
95 preparing qpush of a missing patch
95 preparing qpush of a missing patch
96
96
97 $ hg qpop -a
97 $ hg qpop -a
98 no patches applied
98 no patches applied
99 $ hg qpush
99 $ hg qpush
100 applying patch1
100 applying patch1
101 now at: patch1
101 now at: patch1
102 $ rm .hg/patches/patch2
102 $ rm .hg/patches/patch2
103
103
104 now we expect the push to fail, but it should NOT complain about patch1
104 now we expect the push to fail, but it should NOT complain about patch1
105
105
106 $ hg qpush
106 $ hg qpush
107 applying patch2
107 applying patch2
108 unable to read patch2
108 unable to read patch2
109 now at: patch1
109 now at: patch1
110 [1]
110 [1]
111
111
112 preparing qpush of missing patch with no patch applied
112 preparing qpush of missing patch with no patch applied
113
113
114 $ hg qpop -a
114 $ hg qpop -a
115 popping patch1
115 popping patch1
116 patch queue now empty
116 patch queue now empty
117 $ rm .hg/patches/patch1
117 $ rm .hg/patches/patch1
118
118
119 qpush should fail the same way as below
119 qpush should fail the same way as below
120
120
121 $ hg qpush
121 $ hg qpush
122 applying patch1
122 applying patch1
123 unable to read patch1
123 unable to read patch1
124 [1]
124 [1]
125
125
126 Test qpush to a patch below the currently applied patch.
126 Test qpush to a patch below the currently applied patch.
127
127
128 $ hg qq -c guardedseriesorder
128 $ hg qq -c guardedseriesorder
129 $ hg qnew a
129 $ hg qnew a
130 $ hg qguard +block
130 $ hg qguard +block
131 $ hg qnew b
131 $ hg qnew b
132 $ hg qnew c
132 $ hg qnew c
133
133
134 $ hg qpop -a
134 $ hg qpop -a
135 popping c
135 popping c
136 popping b
136 popping b
137 popping a
137 popping a
138 patch queue now empty
138 patch queue now empty
139
139
140 try to push and pop while a is guarded
140 try to push and pop while a is guarded
141
141
142 $ hg qpush a
142 $ hg qpush a
143 cannot push 'a' - guarded by '+block'
143 cannot push 'a' - guarded by '+block'
144 [1]
144 [1]
145 $ hg qpush -a
145 $ hg qpush -a
146 applying b
146 applying b
147 patch b is empty
147 patch b is empty
148 applying c
148 applying c
149 patch c is empty
149 patch c is empty
150 now at: c
150 now at: c
151
151
152 now try it when a is unguarded, and we're at the top of the queue
152 now try it when a is unguarded, and we're at the top of the queue
153
153
154 $ hg qapplied -v
154 $ hg qapplied -v
155 0 G a
155 0 G a
156 1 A b
156 1 A b
157 2 A c
157 2 A c
158 $ hg qsel block
158 $ hg qsel block
159 $ hg qpush b
159 $ hg qpush b
160 abort: cannot push to a previous patch: b
160 abort: cannot push to a previous patch: b
161 [255]
161 [255]
162 $ hg qpush a
162 $ hg qpush a
163 abort: cannot push to a previous patch: a
163 abort: cannot push to a previous patch: a
164 [255]
164 [255]
165
165
166 and now we try it one more time with a unguarded, while we're not at the top of the queue
166 and now we try it one more time with a unguarded, while we're not at the top of the queue
167
167
168 $ hg qpop b
168 $ hg qpop b
169 popping c
169 popping c
170 now at: b
170 now at: b
171 $ hg qpush a
171 $ hg qpush a
172 abort: cannot push to a previous patch: a
172 abort: cannot push to a previous patch: a
173 [255]
173 [255]
174
174
175 test qpop --force and backup files
175 test qpop --force and backup files
176
176
177 $ hg qpop -a
177 $ hg qpop -a
178 popping b
178 popping b
179 patch queue now empty
179 patch queue now empty
180 $ hg qq --create force
180 $ hg qq --create force
181 $ echo a > a
181 $ echo a > a
182 $ echo b > b
182 $ echo b > b
183 $ echo c > c
183 $ echo c > c
184 $ hg ci -Am add a b c
184 $ hg ci -Am add a b c
185 $ echo a >> a
185 $ echo a >> a
186 $ hg rm b
186 $ hg rm b
187 $ hg rm c
187 $ hg rm c
188 $ hg qnew p1
188 $ hg qnew p1
189 $ echo a >> a
189 $ echo a >> a
190 $ echo bb > b
190 $ echo bb > b
191 $ hg add b
191 $ hg add b
192 $ echo cc > c
192 $ echo cc > c
193 $ hg add c
193 $ hg add c
194 $ hg qpop --force --verbose
194 $ hg qpop --force --verbose
195 saving current version of a as a.orig
195 saving current version of a as a.orig
196 saving current version of b as b.orig
196 saving current version of b as b.orig
197 saving current version of c as c.orig
197 saving current version of c as c.orig
198 popping p1
198 popping p1
199 patch queue now empty
199 patch queue now empty
200 $ hg st
200 $ hg st
201 ? a.orig
201 ? a.orig
202 ? b.orig
202 ? b.orig
203 ? c.orig
203 ? c.orig
204 ? untracked-file
204 ? untracked-file
205 $ cat a.orig
205 $ cat a.orig
206 a
206 a
207 a
207 a
208 a
208 a
209 $ cat b.orig
209 $ cat b.orig
210 bb
210 bb
211 $ cat c.orig
211 $ cat c.orig
212 cc
212 cc
213
213
214 test qpop --force --no-backup
214 test qpop --force --no-backup
215
215
216 $ hg qpush
216 $ hg qpush
217 applying p1
217 applying p1
218 now at: p1
218 now at: p1
219 $ rm a.orig
219 $ rm a.orig
220 $ echo a >> a
220 $ echo a >> a
221 $ hg qpop --force --no-backup --verbose
221 $ hg qpop --force --no-backup --verbose
222 popping p1
222 popping p1
223 patch queue now empty
223 patch queue now empty
224 $ test -f a.orig && echo 'error: backup with --no-backup'
224 $ test -f a.orig && echo 'error: backup with --no-backup'
225 [1]
225 [1]
226
226
227 test qpop --keep-changes
227 test qpop --keep-changes
228
228
229 $ hg qpush
229 $ hg qpush
230 applying p1
230 applying p1
231 now at: p1
231 now at: p1
232 $ hg qpop --keep-changes --force
232 $ hg qpop --keep-changes --force
233 abort: cannot use both --force and --keep-changes
233 abort: cannot use both --force and --keep-changes
234 [255]
234 [255]
235 $ echo a >> a
235 $ echo a >> a
236 $ hg qpop --keep-changes
236 $ hg qpop --keep-changes
237 abort: local changes found, qrefresh first
237 abort: local changes found, qrefresh first
238 [255]
238 [255]
239 $ hg revert -qa a
239 $ hg revert -qa a
240 $ rm a
240 $ rm a
241 $ hg qpop --keep-changes
241 $ hg qpop --keep-changes
242 abort: local changes found, qrefresh first
242 abort: local changes found, qrefresh first
243 [255]
243 [255]
244 $ hg rm -A a
244 $ hg rm -A a
245 $ hg qpop --keep-changes
245 $ hg qpop --keep-changes
246 abort: local changes found, qrefresh first
246 abort: local changes found, qrefresh first
247 [255]
247 [255]
248 $ hg revert -qa a
248 $ hg revert -qa a
249 $ echo b > b
249 $ echo b > b
250 $ hg add b
250 $ hg add b
251 $ hg qpop --keep-changes
251 $ hg qpop --keep-changes
252 abort: local changes found, qrefresh first
252 abort: local changes found, qrefresh first
253 [255]
253 [255]
254 $ hg forget b
254 $ hg forget b
255 $ echo d > d
255 $ echo d > d
256 $ hg add d
256 $ hg add d
257 $ hg qpop --keep-changes
257 $ hg qpop --keep-changes
258 popping p1
258 popping p1
259 patch queue now empty
259 patch queue now empty
260 $ hg forget d
260 $ hg forget d
261 $ rm d
261 $ rm d
262
262
263 test qpush --force and backup files
263 test qpush --force and backup files
264
264
265 $ echo a >> a
265 $ echo a >> a
266 $ hg qnew p2
266 $ hg qnew p2
267 $ echo b >> b
267 $ echo b >> b
268 $ echo d > d
268 $ echo d > d
269 $ echo e > e
269 $ echo e > e
270 $ hg add d e
270 $ hg add d e
271 $ hg rm c
271 $ hg rm c
272 $ hg qnew p3
272 $ hg qnew p3
273 $ hg qpop -a
273 $ hg qpop -a
274 popping p3
274 popping p3
275 popping p2
275 popping p2
276 patch queue now empty
276 patch queue now empty
277 $ echo a >> a
277 $ echo a >> a
278 $ echo b1 >> b
278 $ echo b1 >> b
279 $ echo d1 > d
279 $ echo d1 > d
280 $ hg add d
280 $ hg add d
281 $ echo e1 > e
281 $ echo e1 > e
282 $ hg qpush -a --force --verbose
282 $ hg qpush -a --force --verbose
283 applying p2
283 applying p2
284 saving current version of a as a.orig
284 saving current version of a as a.orig
285 patching file a
285 patching file a
286 committing files:
286 committing files:
287 a
287 a
288 committing manifest
288 committing manifest
289 committing changelog
289 committing changelog
290 applying p3
290 applying p3
291 saving current version of b as b.orig
291 saving current version of b as b.orig
292 saving current version of d as d.orig
292 saving current version of d as d.orig
293 patching file b
293 patching file b
294 patching file c
294 patching file c
295 patching file d
295 patching file d
296 file d already exists
296 file d already exists
297 1 out of 1 hunks FAILED -- saving rejects to file d.rej
297 1 out of 1 hunks FAILED -- saving rejects to file d.rej
298 patching file e
298 patching file e
299 file e already exists
299 file e already exists
300 1 out of 1 hunks FAILED -- saving rejects to file e.rej
300 1 out of 1 hunks FAILED -- saving rejects to file e.rej
301 patch failed to apply
301 patch failed to apply
302 committing files:
302 committing files:
303 b
303 b
304 committing manifest
304 committing manifest
305 committing changelog
305 committing changelog
306 patch failed, rejects left in working directory
306 patch failed, rejects left in working directory
307 errors during apply, please fix and qrefresh p3
307 errors during apply, please fix and qrefresh p3
308 [2]
308 [2]
309 $ cat a.orig
309 $ cat a.orig
310 a
310 a
311 a
311 a
312 $ cat b.orig
312 $ cat b.orig
313 b
313 b
314 b1
314 b1
315 $ cat d.orig
315 $ cat d.orig
316 d1
316 d1
317
317
318 test qpush --force --no-backup
318 test qpush --force --no-backup
319
319
320 $ hg revert -qa
320 $ hg revert -qa
321 $ hg qpop -a
321 $ hg qpop -a
322 popping p3
322 popping p3
323 popping p2
323 popping p2
324 patch queue now empty
324 patch queue now empty
325 $ echo a >> a
325 $ echo a >> a
326 $ rm a.orig
326 $ rm a.orig
327 $ hg qpush --force --no-backup --verbose
327 $ hg qpush --force --no-backup --verbose
328 applying p2
328 applying p2
329 patching file a
329 patching file a
330 committing files:
330 committing files:
331 a
331 a
332 committing manifest
332 committing manifest
333 committing changelog
333 committing changelog
334 now at: p2
334 now at: p2
335 $ test -f a.orig && echo 'error: backup with --no-backup'
335 $ test -f a.orig && echo 'error: backup with --no-backup'
336 [1]
336 [1]
337
337
338 test qgoto --force --no-backup
338 test qgoto --force --no-backup
339
339
340 $ hg qpop
340 $ hg qpop
341 popping p2
341 popping p2
342 patch queue now empty
342 patch queue now empty
343 $ echo a >> a
343 $ echo a >> a
344 $ hg qgoto --force --no-backup p2 --verbose
344 $ hg qgoto --force --no-backup p2 --verbose
345 applying p2
345 applying p2
346 patching file a
346 patching file a
347 committing files:
347 committing files:
348 a
348 a
349 committing manifest
349 committing manifest
350 committing changelog
350 committing changelog
351 now at: p2
351 now at: p2
352 $ test -f a.orig && echo 'error: backup with --no-backup'
352 $ test -f a.orig && echo 'error: backup with --no-backup'
353 [1]
353 [1]
354
354
355 test qpush --keep-changes
355 test qpush --keep-changes
356
356
357 $ hg qpush --keep-changes --force
357 $ hg qpush --keep-changes --force
358 abort: cannot use both --force and --keep-changes
358 abort: cannot use both --force and --keep-changes
359 [255]
359 [255]
360 $ hg qpush --keep-changes --exact
360 $ hg qpush --keep-changes --exact
361 abort: cannot use --exact and --keep-changes together
361 abort: cannot use --exact and --keep-changes together
362 [255]
362 [255]
363 $ echo b >> b
363 $ echo b >> b
364 $ hg qpush --keep-changes
364 $ hg qpush --keep-changes
365 applying p3
365 applying p3
366 abort: conflicting local changes found
366 abort: conflicting local changes found
367 (did you forget to qrefresh?)
367 (did you forget to qrefresh?)
368 [255]
368 [255]
369 $ rm b
369 $ rm b
370 $ hg qpush --keep-changes
370 $ hg qpush --keep-changes
371 applying p3
371 applying p3
372 abort: conflicting local changes found
372 abort: conflicting local changes found
373 (did you forget to qrefresh?)
373 (did you forget to qrefresh?)
374 [255]
374 [255]
375 $ hg rm -A b
375 $ hg rm -A b
376 $ hg qpush --keep-changes
376 $ hg qpush --keep-changes
377 applying p3
377 applying p3
378 abort: conflicting local changes found
378 abort: conflicting local changes found
379 (did you forget to qrefresh?)
379 (did you forget to qrefresh?)
380 [255]
380 [255]
381 $ hg revert -aq b
381 $ hg revert -aq b
382 $ echo d > d
382 $ echo d > d
383 $ hg add d
383 $ hg add d
384 $ hg qpush --keep-changes
384 $ hg qpush --keep-changes
385 applying p3
385 applying p3
386 abort: conflicting local changes found
386 abort: conflicting local changes found
387 (did you forget to qrefresh?)
387 (did you forget to qrefresh?)
388 [255]
388 [255]
389 $ hg forget d
389 $ hg forget d
390 $ rm d
390 $ rm d
391 $ hg qpop
391 $ hg qpop
392 popping p2
392 popping p2
393 patch queue now empty
393 patch queue now empty
394 $ echo b >> b
394 $ echo b >> b
395 $ hg qpush -a --keep-changes
395 $ hg qpush -a --keep-changes
396 applying p2
396 applying p2
397 applying p3
397 applying p3
398 abort: conflicting local changes found
398 abort: conflicting local changes found
399 (did you forget to qrefresh?)
399 (did you forget to qrefresh?)
400 [255]
400 [255]
401 $ hg qtop
401 $ hg qtop
402 p2
402 p2
403 $ hg parents --template "{rev} {desc}\n"
403 $ hg parents --template "{rev} {desc}\n"
404 2 imported patch p2
404 2 imported patch p2
405 $ hg st b
405 $ hg st b
406 M b
406 M b
407 $ cat b
407 $ cat b
408 b
408 b
409 b
409 b
410
410
411 test qgoto --keep-changes
411 test qgoto --keep-changes
412
412
413 $ hg revert -aq b
413 $ hg revert -aq b
414 $ rm e
414 $ rm e
415 $ hg qgoto --keep-changes --force p3
415 $ hg qgoto --keep-changes --force p3
416 abort: cannot use both --force and --keep-changes
416 abort: cannot use both --force and --keep-changes
417 [255]
417 [255]
418 $ echo a >> a
418 $ echo a >> a
419 $ hg qgoto --keep-changes p3
419 $ hg qgoto --keep-changes p3
420 applying p3
420 applying p3
421 now at: p3
421 now at: p3
422 $ hg st a
422 $ hg st a
423 M a
423 M a
424 $ hg qgoto --keep-changes p2
424 $ hg qgoto --keep-changes p2
425 popping p3
425 popping p3
426 now at: p2
426 now at: p2
427 $ hg st a
427 $ hg st a
428 M a
428 M a
429
429
430 test mq.keepchanges setting
430 test mq.keepchanges setting
431
431
432 $ hg --config mq.keepchanges=1 qpush
432 $ hg --config mq.keepchanges=1 qpush
433 applying p3
433 applying p3
434 now at: p3
434 now at: p3
435 $ hg st a
435 $ hg st a
436 M a
436 M a
437 $ hg --config mq.keepchanges=1 qpop
437 $ hg --config mq.keepchanges=1 qpop
438 popping p3
438 popping p3
439 now at: p2
439 now at: p2
440 $ hg st a
440 $ hg st a
441 M a
441 M a
442 $ hg --config mq.keepchanges=1 qgoto p3
442 $ hg --config mq.keepchanges=1 qgoto p3
443 applying p3
443 applying p3
444 now at: p3
444 now at: p3
445 $ hg st a
445 $ hg st a
446 M a
446 M a
447 $ echo b >> b
447 $ echo b >> b
448 $ hg --config mq.keepchanges=1 qpop --force --config 'ui.origbackuppath=.hg/origbackups'
448 $ hg --config mq.keepchanges=1 qpop --force --config 'ui.origbackuppath=.hg/origbackups'
449 popping p3
449 popping p3
450 now at: p2
450 now at: p2
451 $ hg st b
451 $ hg st b
452 $ hg --config mq.keepchanges=1 qpush --exact
452 $ hg --config mq.keepchanges=1 qpush --exact
453 abort: local changes found, qrefresh first
453 abort: local changes found, qrefresh first
454 [255]
454 [255]
455 $ hg revert -qa a
455 $ hg revert -qa a
456 $ hg qpop
456 $ hg qpop
457 popping p2
457 popping p2
458 patch queue now empty
458 patch queue now empty
459 $ echo a >> a
459 $ echo a >> a
460 $ hg --config mq.keepchanges=1 qpush --force
460 $ hg --config mq.keepchanges=1 qpush --force
461 applying p2
461 applying p2
462 now at: p2
462 now at: p2
463 $ hg st a
463 $ hg st a
464
464
465 test previous qpop (with --force and --config) saved .orig files to where user
465 test previous qpop (with --force and --config) saved .orig files to where user
466 wants them
466 wants them
467 $ ls .hg/origbackups
467 $ ls .hg/origbackups
468 b
468 b
469 $ rm -rf .hg/origbackups
469 $ rm -rf .hg/origbackups
470
470
471 $ cd ..
471 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now