##// END OF EJS Templates
transaction: rename find to findoffset and drop backup file support...
Joerg Sonnenberger -
r46482:a6f08085 default
parent child Browse files
Show More
@@ -1,3082 +1,3081 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 troffset = tr.findoffset(self.indexfile)
2004 if trinfo is None:
2004 if troffset 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 troffset = trinfo[1]
2009 trindex = 0
2008 trindex = 0
2010 tr.add(self.datafile, 0)
2009 tr.add(self.datafile, 0)
2011
2010
2012 if fp:
2011 if fp:
2013 fp.flush()
2012 fp.flush()
2014 fp.close()
2013 fp.close()
2015 # We can't use the cached file handle after close(). So prevent
2014 # We can't use the cached file handle after close(). So prevent
2016 # its usage.
2015 # its usage.
2017 self._writinghandles = None
2016 self._writinghandles = None
2018
2017
2019 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
2018 with self._indexfp(b'r') as ifh, self._datafp(b'w') as dfh:
2020 for r in self:
2019 for r in self:
2021 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
2020 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
2022 if troffset <= self.start(r):
2021 if troffset <= self.start(r):
2023 trindex = r
2022 trindex = r
2024
2023
2025 with self._indexfp(b'w') as fp:
2024 with self._indexfp(b'w') as fp:
2026 self.version &= ~FLAG_INLINE_DATA
2025 self.version &= ~FLAG_INLINE_DATA
2027 self._inline = False
2026 self._inline = False
2028 io = self._io
2027 io = self._io
2029 for i in self:
2028 for i in self:
2030 e = io.packentry(self.index[i], self.node, self.version, i)
2029 e = io.packentry(self.index[i], self.node, self.version, i)
2031 fp.write(e)
2030 fp.write(e)
2032
2031
2033 # the temp file replace the real index when we exit the context
2032 # the temp file replace the real index when we exit the context
2034 # manager
2033 # manager
2035
2034
2036 tr.replace(self.indexfile, trindex * self._io.size)
2035 tr.replace(self.indexfile, trindex * self._io.size)
2037 nodemaputil.setup_persistent_nodemap(tr, self)
2036 nodemaputil.setup_persistent_nodemap(tr, self)
2038 self._chunkclear()
2037 self._chunkclear()
2039
2038
2040 def _nodeduplicatecallback(self, transaction, node):
2039 def _nodeduplicatecallback(self, transaction, node):
2041 """called when trying to add a node already stored.
2040 """called when trying to add a node already stored.
2042 """
2041 """
2043
2042
2044 def addrevision(
2043 def addrevision(
2045 self,
2044 self,
2046 text,
2045 text,
2047 transaction,
2046 transaction,
2048 link,
2047 link,
2049 p1,
2048 p1,
2050 p2,
2049 p2,
2051 cachedelta=None,
2050 cachedelta=None,
2052 node=None,
2051 node=None,
2053 flags=REVIDX_DEFAULT_FLAGS,
2052 flags=REVIDX_DEFAULT_FLAGS,
2054 deltacomputer=None,
2053 deltacomputer=None,
2055 sidedata=None,
2054 sidedata=None,
2056 ):
2055 ):
2057 """add a revision to the log
2056 """add a revision to the log
2058
2057
2059 text - the revision data to add
2058 text - the revision data to add
2060 transaction - the transaction object used for rollback
2059 transaction - the transaction object used for rollback
2061 link - the linkrev data to add
2060 link - the linkrev data to add
2062 p1, p2 - the parent nodeids of the revision
2061 p1, p2 - the parent nodeids of the revision
2063 cachedelta - an optional precomputed delta
2062 cachedelta - an optional precomputed delta
2064 node - nodeid of revision; typically node is not specified, and it is
2063 node - nodeid of revision; typically node is not specified, and it is
2065 computed by default as hash(text, p1, p2), however subclasses might
2064 computed by default as hash(text, p1, p2), however subclasses might
2066 use different hashing method (and override checkhash() in such case)
2065 use different hashing method (and override checkhash() in such case)
2067 flags - the known flags to set on the revision
2066 flags - the known flags to set on the revision
2068 deltacomputer - an optional deltacomputer instance shared between
2067 deltacomputer - an optional deltacomputer instance shared between
2069 multiple calls
2068 multiple calls
2070 """
2069 """
2071 if link == nullrev:
2070 if link == nullrev:
2072 raise error.RevlogError(
2071 raise error.RevlogError(
2073 _(b"attempted to add linkrev -1 to %s") % self.indexfile
2072 _(b"attempted to add linkrev -1 to %s") % self.indexfile
2074 )
2073 )
2075
2074
2076 if sidedata is None:
2075 if sidedata is None:
2077 sidedata = {}
2076 sidedata = {}
2078 flags = flags & ~REVIDX_SIDEDATA
2077 flags = flags & ~REVIDX_SIDEDATA
2079 elif not self.hassidedata:
2078 elif not self.hassidedata:
2080 raise error.ProgrammingError(
2079 raise error.ProgrammingError(
2081 _(b"trying to add sidedata to a revlog who don't support them")
2080 _(b"trying to add sidedata to a revlog who don't support them")
2082 )
2081 )
2083 else:
2082 else:
2084 flags |= REVIDX_SIDEDATA
2083 flags |= REVIDX_SIDEDATA
2085
2084
2086 if flags:
2085 if flags:
2087 node = node or self.hash(text, p1, p2)
2086 node = node or self.hash(text, p1, p2)
2088
2087
2089 rawtext, validatehash = flagutil.processflagswrite(
2088 rawtext, validatehash = flagutil.processflagswrite(
2090 self, text, flags, sidedata=sidedata
2089 self, text, flags, sidedata=sidedata
2091 )
2090 )
2092
2091
2093 # If the flag processor modifies the revision data, ignore any provided
2092 # If the flag processor modifies the revision data, ignore any provided
2094 # cachedelta.
2093 # cachedelta.
2095 if rawtext != text:
2094 if rawtext != text:
2096 cachedelta = None
2095 cachedelta = None
2097
2096
2098 if len(rawtext) > _maxentrysize:
2097 if len(rawtext) > _maxentrysize:
2099 raise error.RevlogError(
2098 raise error.RevlogError(
2100 _(
2099 _(
2101 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2100 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2102 )
2101 )
2103 % (self.indexfile, len(rawtext))
2102 % (self.indexfile, len(rawtext))
2104 )
2103 )
2105
2104
2106 node = node or self.hash(rawtext, p1, p2)
2105 node = node or self.hash(rawtext, p1, p2)
2107 if self.index.has_node(node):
2106 if self.index.has_node(node):
2108 return node
2107 return node
2109
2108
2110 if validatehash:
2109 if validatehash:
2111 self.checkhash(rawtext, node, p1=p1, p2=p2)
2110 self.checkhash(rawtext, node, p1=p1, p2=p2)
2112
2111
2113 return self.addrawrevision(
2112 return self.addrawrevision(
2114 rawtext,
2113 rawtext,
2115 transaction,
2114 transaction,
2116 link,
2115 link,
2117 p1,
2116 p1,
2118 p2,
2117 p2,
2119 node,
2118 node,
2120 flags,
2119 flags,
2121 cachedelta=cachedelta,
2120 cachedelta=cachedelta,
2122 deltacomputer=deltacomputer,
2121 deltacomputer=deltacomputer,
2123 )
2122 )
2124
2123
2125 def addrawrevision(
2124 def addrawrevision(
2126 self,
2125 self,
2127 rawtext,
2126 rawtext,
2128 transaction,
2127 transaction,
2129 link,
2128 link,
2130 p1,
2129 p1,
2131 p2,
2130 p2,
2132 node,
2131 node,
2133 flags,
2132 flags,
2134 cachedelta=None,
2133 cachedelta=None,
2135 deltacomputer=None,
2134 deltacomputer=None,
2136 ):
2135 ):
2137 """add a raw revision with known flags, node and parents
2136 """add a raw revision with known flags, node and parents
2138 useful when reusing a revision not stored in this revlog (ex: received
2137 useful when reusing a revision not stored in this revlog (ex: received
2139 over wire, or read from an external bundle).
2138 over wire, or read from an external bundle).
2140 """
2139 """
2141 dfh = None
2140 dfh = None
2142 if not self._inline:
2141 if not self._inline:
2143 dfh = self._datafp(b"a+")
2142 dfh = self._datafp(b"a+")
2144 ifh = self._indexfp(b"a+")
2143 ifh = self._indexfp(b"a+")
2145 try:
2144 try:
2146 return self._addrevision(
2145 return self._addrevision(
2147 node,
2146 node,
2148 rawtext,
2147 rawtext,
2149 transaction,
2148 transaction,
2150 link,
2149 link,
2151 p1,
2150 p1,
2152 p2,
2151 p2,
2153 flags,
2152 flags,
2154 cachedelta,
2153 cachedelta,
2155 ifh,
2154 ifh,
2156 dfh,
2155 dfh,
2157 deltacomputer=deltacomputer,
2156 deltacomputer=deltacomputer,
2158 )
2157 )
2159 finally:
2158 finally:
2160 if dfh:
2159 if dfh:
2161 dfh.close()
2160 dfh.close()
2162 ifh.close()
2161 ifh.close()
2163
2162
2164 def compress(self, data):
2163 def compress(self, data):
2165 """Generate a possibly-compressed representation of data."""
2164 """Generate a possibly-compressed representation of data."""
2166 if not data:
2165 if not data:
2167 return b'', data
2166 return b'', data
2168
2167
2169 compressed = self._compressor.compress(data)
2168 compressed = self._compressor.compress(data)
2170
2169
2171 if compressed:
2170 if compressed:
2172 # The revlog compressor added the header in the returned data.
2171 # The revlog compressor added the header in the returned data.
2173 return b'', compressed
2172 return b'', compressed
2174
2173
2175 if data[0:1] == b'\0':
2174 if data[0:1] == b'\0':
2176 return b'', data
2175 return b'', data
2177 return b'u', data
2176 return b'u', data
2178
2177
2179 def decompress(self, data):
2178 def decompress(self, data):
2180 """Decompress a revlog chunk.
2179 """Decompress a revlog chunk.
2181
2180
2182 The chunk is expected to begin with a header identifying the
2181 The chunk is expected to begin with a header identifying the
2183 format type so it can be routed to an appropriate decompressor.
2182 format type so it can be routed to an appropriate decompressor.
2184 """
2183 """
2185 if not data:
2184 if not data:
2186 return data
2185 return data
2187
2186
2188 # Revlogs are read much more frequently than they are written and many
2187 # Revlogs are read much more frequently than they are written and many
2189 # chunks only take microseconds to decompress, so performance is
2188 # chunks only take microseconds to decompress, so performance is
2190 # important here.
2189 # important here.
2191 #
2190 #
2192 # We can make a few assumptions about revlogs:
2191 # We can make a few assumptions about revlogs:
2193 #
2192 #
2194 # 1) the majority of chunks will be compressed (as opposed to inline
2193 # 1) the majority of chunks will be compressed (as opposed to inline
2195 # raw data).
2194 # raw data).
2196 # 2) decompressing *any* data will likely by at least 10x slower than
2195 # 2) decompressing *any* data will likely by at least 10x slower than
2197 # returning raw inline data.
2196 # returning raw inline data.
2198 # 3) we want to prioritize common and officially supported compression
2197 # 3) we want to prioritize common and officially supported compression
2199 # engines
2198 # engines
2200 #
2199 #
2201 # It follows that we want to optimize for "decompress compressed data
2200 # It follows that we want to optimize for "decompress compressed data
2202 # when encoded with common and officially supported compression engines"
2201 # when encoded with common and officially supported compression engines"
2203 # case over "raw data" and "data encoded by less common or non-official
2202 # case over "raw data" and "data encoded by less common or non-official
2204 # compression engines." That is why we have the inline lookup first
2203 # compression engines." That is why we have the inline lookup first
2205 # followed by the compengines lookup.
2204 # followed by the compengines lookup.
2206 #
2205 #
2207 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2206 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2208 # compressed chunks. And this matters for changelog and manifest reads.
2207 # compressed chunks. And this matters for changelog and manifest reads.
2209 t = data[0:1]
2208 t = data[0:1]
2210
2209
2211 if t == b'x':
2210 if t == b'x':
2212 try:
2211 try:
2213 return _zlibdecompress(data)
2212 return _zlibdecompress(data)
2214 except zlib.error as e:
2213 except zlib.error as e:
2215 raise error.RevlogError(
2214 raise error.RevlogError(
2216 _(b'revlog decompress error: %s')
2215 _(b'revlog decompress error: %s')
2217 % stringutil.forcebytestr(e)
2216 % stringutil.forcebytestr(e)
2218 )
2217 )
2219 # '\0' is more common than 'u' so it goes first.
2218 # '\0' is more common than 'u' so it goes first.
2220 elif t == b'\0':
2219 elif t == b'\0':
2221 return data
2220 return data
2222 elif t == b'u':
2221 elif t == b'u':
2223 return util.buffer(data, 1)
2222 return util.buffer(data, 1)
2224
2223
2225 try:
2224 try:
2226 compressor = self._decompressors[t]
2225 compressor = self._decompressors[t]
2227 except KeyError:
2226 except KeyError:
2228 try:
2227 try:
2229 engine = util.compengines.forrevlogheader(t)
2228 engine = util.compengines.forrevlogheader(t)
2230 compressor = engine.revlogcompressor(self._compengineopts)
2229 compressor = engine.revlogcompressor(self._compengineopts)
2231 self._decompressors[t] = compressor
2230 self._decompressors[t] = compressor
2232 except KeyError:
2231 except KeyError:
2233 raise error.RevlogError(_(b'unknown compression type %r') % t)
2232 raise error.RevlogError(_(b'unknown compression type %r') % t)
2234
2233
2235 return compressor.decompress(data)
2234 return compressor.decompress(data)
2236
2235
2237 def _addrevision(
2236 def _addrevision(
2238 self,
2237 self,
2239 node,
2238 node,
2240 rawtext,
2239 rawtext,
2241 transaction,
2240 transaction,
2242 link,
2241 link,
2243 p1,
2242 p1,
2244 p2,
2243 p2,
2245 flags,
2244 flags,
2246 cachedelta,
2245 cachedelta,
2247 ifh,
2246 ifh,
2248 dfh,
2247 dfh,
2249 alwayscache=False,
2248 alwayscache=False,
2250 deltacomputer=None,
2249 deltacomputer=None,
2251 ):
2250 ):
2252 """internal function to add revisions to the log
2251 """internal function to add revisions to the log
2253
2252
2254 see addrevision for argument descriptions.
2253 see addrevision for argument descriptions.
2255
2254
2256 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2255 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2257
2256
2258 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2257 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2259 be used.
2258 be used.
2260
2259
2261 invariants:
2260 invariants:
2262 - rawtext is optional (can be None); if not set, cachedelta must be set.
2261 - rawtext is optional (can be None); if not set, cachedelta must be set.
2263 if both are set, they must correspond to each other.
2262 if both are set, they must correspond to each other.
2264 """
2263 """
2265 if node == nullid:
2264 if node == nullid:
2266 raise error.RevlogError(
2265 raise error.RevlogError(
2267 _(b"%s: attempt to add null revision") % self.indexfile
2266 _(b"%s: attempt to add null revision") % self.indexfile
2268 )
2267 )
2269 if node == wdirid or node in wdirfilenodeids:
2268 if node == wdirid or node in wdirfilenodeids:
2270 raise error.RevlogError(
2269 raise error.RevlogError(
2271 _(b"%s: attempt to add wdir revision") % self.indexfile
2270 _(b"%s: attempt to add wdir revision") % self.indexfile
2272 )
2271 )
2273
2272
2274 if self._inline:
2273 if self._inline:
2275 fh = ifh
2274 fh = ifh
2276 else:
2275 else:
2277 fh = dfh
2276 fh = dfh
2278
2277
2279 btext = [rawtext]
2278 btext = [rawtext]
2280
2279
2281 curr = len(self)
2280 curr = len(self)
2282 prev = curr - 1
2281 prev = curr - 1
2283 offset = self.end(prev)
2282 offset = self.end(prev)
2284 p1r, p2r = self.rev(p1), self.rev(p2)
2283 p1r, p2r = self.rev(p1), self.rev(p2)
2285
2284
2286 # full versions are inserted when the needed deltas
2285 # full versions are inserted when the needed deltas
2287 # become comparable to the uncompressed text
2286 # become comparable to the uncompressed text
2288 if rawtext is None:
2287 if rawtext is None:
2289 # need rawtext size, before changed by flag processors, which is
2288 # need rawtext size, before changed by flag processors, which is
2290 # the non-raw size. use revlog explicitly to avoid filelog's extra
2289 # the non-raw size. use revlog explicitly to avoid filelog's extra
2291 # logic that might remove metadata size.
2290 # logic that might remove metadata size.
2292 textlen = mdiff.patchedsize(
2291 textlen = mdiff.patchedsize(
2293 revlog.size(self, cachedelta[0]), cachedelta[1]
2292 revlog.size(self, cachedelta[0]), cachedelta[1]
2294 )
2293 )
2295 else:
2294 else:
2296 textlen = len(rawtext)
2295 textlen = len(rawtext)
2297
2296
2298 if deltacomputer is None:
2297 if deltacomputer is None:
2299 deltacomputer = deltautil.deltacomputer(self)
2298 deltacomputer = deltautil.deltacomputer(self)
2300
2299
2301 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2300 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2302
2301
2303 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2302 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2304
2303
2305 e = (
2304 e = (
2306 offset_type(offset, flags),
2305 offset_type(offset, flags),
2307 deltainfo.deltalen,
2306 deltainfo.deltalen,
2308 textlen,
2307 textlen,
2309 deltainfo.base,
2308 deltainfo.base,
2310 link,
2309 link,
2311 p1r,
2310 p1r,
2312 p2r,
2311 p2r,
2313 node,
2312 node,
2314 )
2313 )
2315 self.index.append(e)
2314 self.index.append(e)
2316
2315
2317 entry = self._io.packentry(e, self.node, self.version, curr)
2316 entry = self._io.packentry(e, self.node, self.version, curr)
2318 self._writeentry(
2317 self._writeentry(
2319 transaction, ifh, dfh, entry, deltainfo.data, link, offset
2318 transaction, ifh, dfh, entry, deltainfo.data, link, offset
2320 )
2319 )
2321
2320
2322 rawtext = btext[0]
2321 rawtext = btext[0]
2323
2322
2324 if alwayscache and rawtext is None:
2323 if alwayscache and rawtext is None:
2325 rawtext = deltacomputer.buildtext(revinfo, fh)
2324 rawtext = deltacomputer.buildtext(revinfo, fh)
2326
2325
2327 if type(rawtext) == bytes: # only accept immutable objects
2326 if type(rawtext) == bytes: # only accept immutable objects
2328 self._revisioncache = (node, curr, rawtext)
2327 self._revisioncache = (node, curr, rawtext)
2329 self._chainbasecache[curr] = deltainfo.chainbase
2328 self._chainbasecache[curr] = deltainfo.chainbase
2330 return node
2329 return node
2331
2330
2332 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2331 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2333 # Files opened in a+ mode have inconsistent behavior on various
2332 # Files opened in a+ mode have inconsistent behavior on various
2334 # platforms. Windows requires that a file positioning call be made
2333 # platforms. Windows requires that a file positioning call be made
2335 # when the file handle transitions between reads and writes. See
2334 # when the file handle transitions between reads and writes. See
2336 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2335 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2337 # platforms, Python or the platform itself can be buggy. Some versions
2336 # platforms, Python or the platform itself can be buggy. Some versions
2338 # of Solaris have been observed to not append at the end of the file
2337 # of Solaris have been observed to not append at the end of the file
2339 # if the file was seeked to before the end. See issue4943 for more.
2338 # if the file was seeked to before the end. See issue4943 for more.
2340 #
2339 #
2341 # We work around this issue by inserting a seek() before writing.
2340 # We work around this issue by inserting a seek() before writing.
2342 # Note: This is likely not necessary on Python 3. However, because
2341 # Note: This is likely not necessary on Python 3. However, because
2343 # the file handle is reused for reads and may be seeked there, we need
2342 # the file handle is reused for reads and may be seeked there, we need
2344 # to be careful before changing this.
2343 # to be careful before changing this.
2345 ifh.seek(0, os.SEEK_END)
2344 ifh.seek(0, os.SEEK_END)
2346 if dfh:
2345 if dfh:
2347 dfh.seek(0, os.SEEK_END)
2346 dfh.seek(0, os.SEEK_END)
2348
2347
2349 curr = len(self) - 1
2348 curr = len(self) - 1
2350 if not self._inline:
2349 if not self._inline:
2351 transaction.add(self.datafile, offset)
2350 transaction.add(self.datafile, offset)
2352 transaction.add(self.indexfile, curr * len(entry))
2351 transaction.add(self.indexfile, curr * len(entry))
2353 if data[0]:
2352 if data[0]:
2354 dfh.write(data[0])
2353 dfh.write(data[0])
2355 dfh.write(data[1])
2354 dfh.write(data[1])
2356 ifh.write(entry)
2355 ifh.write(entry)
2357 else:
2356 else:
2358 offset += curr * self._io.size
2357 offset += curr * self._io.size
2359 transaction.add(self.indexfile, offset)
2358 transaction.add(self.indexfile, offset)
2360 ifh.write(entry)
2359 ifh.write(entry)
2361 ifh.write(data[0])
2360 ifh.write(data[0])
2362 ifh.write(data[1])
2361 ifh.write(data[1])
2363 self._enforceinlinesize(transaction, ifh)
2362 self._enforceinlinesize(transaction, ifh)
2364 nodemaputil.setup_persistent_nodemap(transaction, self)
2363 nodemaputil.setup_persistent_nodemap(transaction, self)
2365
2364
2366 def addgroup(
2365 def addgroup(
2367 self,
2366 self,
2368 deltas,
2367 deltas,
2369 linkmapper,
2368 linkmapper,
2370 transaction,
2369 transaction,
2371 addrevisioncb=None,
2370 addrevisioncb=None,
2372 duplicaterevisioncb=None,
2371 duplicaterevisioncb=None,
2373 ):
2372 ):
2374 """
2373 """
2375 add a delta group
2374 add a delta group
2376
2375
2377 given a set of deltas, add them to the revision log. the
2376 given a set of deltas, add them to the revision log. the
2378 first delta is against its parent, which should be in our
2377 first delta is against its parent, which should be in our
2379 log, the rest are against the previous delta.
2378 log, the rest are against the previous delta.
2380
2379
2381 If ``addrevisioncb`` is defined, it will be called with arguments of
2380 If ``addrevisioncb`` is defined, it will be called with arguments of
2382 this revlog and the node that was added.
2381 this revlog and the node that was added.
2383 """
2382 """
2384
2383
2385 if self._writinghandles:
2384 if self._writinghandles:
2386 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2385 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2387
2386
2388 r = len(self)
2387 r = len(self)
2389 end = 0
2388 end = 0
2390 if r:
2389 if r:
2391 end = self.end(r - 1)
2390 end = self.end(r - 1)
2392 ifh = self._indexfp(b"a+")
2391 ifh = self._indexfp(b"a+")
2393 isize = r * self._io.size
2392 isize = r * self._io.size
2394 if self._inline:
2393 if self._inline:
2395 transaction.add(self.indexfile, end + isize)
2394 transaction.add(self.indexfile, end + isize)
2396 dfh = None
2395 dfh = None
2397 else:
2396 else:
2398 transaction.add(self.indexfile, isize)
2397 transaction.add(self.indexfile, isize)
2399 transaction.add(self.datafile, end)
2398 transaction.add(self.datafile, end)
2400 dfh = self._datafp(b"a+")
2399 dfh = self._datafp(b"a+")
2401
2400
2402 def flush():
2401 def flush():
2403 if dfh:
2402 if dfh:
2404 dfh.flush()
2403 dfh.flush()
2405 ifh.flush()
2404 ifh.flush()
2406
2405
2407 self._writinghandles = (ifh, dfh)
2406 self._writinghandles = (ifh, dfh)
2408 empty = True
2407 empty = True
2409
2408
2410 try:
2409 try:
2411 deltacomputer = deltautil.deltacomputer(self)
2410 deltacomputer = deltautil.deltacomputer(self)
2412 # loop through our set of deltas
2411 # loop through our set of deltas
2413 for data in deltas:
2412 for data in deltas:
2414 node, p1, p2, linknode, deltabase, delta, flags = data
2413 node, p1, p2, linknode, deltabase, delta, flags = data
2415 link = linkmapper(linknode)
2414 link = linkmapper(linknode)
2416 flags = flags or REVIDX_DEFAULT_FLAGS
2415 flags = flags or REVIDX_DEFAULT_FLAGS
2417
2416
2418 if self.index.has_node(node):
2417 if self.index.has_node(node):
2419 # this can happen if two branches make the same change
2418 # this can happen if two branches make the same change
2420 self._nodeduplicatecallback(transaction, node)
2419 self._nodeduplicatecallback(transaction, node)
2421 if duplicaterevisioncb:
2420 if duplicaterevisioncb:
2422 duplicaterevisioncb(self, node)
2421 duplicaterevisioncb(self, node)
2423 empty = False
2422 empty = False
2424 continue
2423 continue
2425
2424
2426 for p in (p1, p2):
2425 for p in (p1, p2):
2427 if not self.index.has_node(p):
2426 if not self.index.has_node(p):
2428 raise error.LookupError(
2427 raise error.LookupError(
2429 p, self.indexfile, _(b'unknown parent')
2428 p, self.indexfile, _(b'unknown parent')
2430 )
2429 )
2431
2430
2432 if not self.index.has_node(deltabase):
2431 if not self.index.has_node(deltabase):
2433 raise error.LookupError(
2432 raise error.LookupError(
2434 deltabase, self.indexfile, _(b'unknown delta base')
2433 deltabase, self.indexfile, _(b'unknown delta base')
2435 )
2434 )
2436
2435
2437 baserev = self.rev(deltabase)
2436 baserev = self.rev(deltabase)
2438
2437
2439 if baserev != nullrev and self.iscensored(baserev):
2438 if baserev != nullrev and self.iscensored(baserev):
2440 # if base is censored, delta must be full replacement in a
2439 # if base is censored, delta must be full replacement in a
2441 # single patch operation
2440 # single patch operation
2442 hlen = struct.calcsize(b">lll")
2441 hlen = struct.calcsize(b">lll")
2443 oldlen = self.rawsize(baserev)
2442 oldlen = self.rawsize(baserev)
2444 newlen = len(delta) - hlen
2443 newlen = len(delta) - hlen
2445 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2444 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2446 raise error.CensoredBaseError(
2445 raise error.CensoredBaseError(
2447 self.indexfile, self.node(baserev)
2446 self.indexfile, self.node(baserev)
2448 )
2447 )
2449
2448
2450 if not flags and self._peek_iscensored(baserev, delta, flush):
2449 if not flags and self._peek_iscensored(baserev, delta, flush):
2451 flags |= REVIDX_ISCENSORED
2450 flags |= REVIDX_ISCENSORED
2452
2451
2453 # We assume consumers of addrevisioncb will want to retrieve
2452 # We assume consumers of addrevisioncb will want to retrieve
2454 # the added revision, which will require a call to
2453 # the added revision, which will require a call to
2455 # revision(). revision() will fast path if there is a cache
2454 # revision(). revision() will fast path if there is a cache
2456 # hit. So, we tell _addrevision() to always cache in this case.
2455 # hit. So, we tell _addrevision() to always cache in this case.
2457 # We're only using addgroup() in the context of changegroup
2456 # We're only using addgroup() in the context of changegroup
2458 # generation so the revision data can always be handled as raw
2457 # generation so the revision data can always be handled as raw
2459 # by the flagprocessor.
2458 # by the flagprocessor.
2460 self._addrevision(
2459 self._addrevision(
2461 node,
2460 node,
2462 None,
2461 None,
2463 transaction,
2462 transaction,
2464 link,
2463 link,
2465 p1,
2464 p1,
2466 p2,
2465 p2,
2467 flags,
2466 flags,
2468 (baserev, delta),
2467 (baserev, delta),
2469 ifh,
2468 ifh,
2470 dfh,
2469 dfh,
2471 alwayscache=bool(addrevisioncb),
2470 alwayscache=bool(addrevisioncb),
2472 deltacomputer=deltacomputer,
2471 deltacomputer=deltacomputer,
2473 )
2472 )
2474
2473
2475 if addrevisioncb:
2474 if addrevisioncb:
2476 addrevisioncb(self, node)
2475 addrevisioncb(self, node)
2477 empty = False
2476 empty = False
2478
2477
2479 if not dfh and not self._inline:
2478 if not dfh and not self._inline:
2480 # addrevision switched from inline to conventional
2479 # addrevision switched from inline to conventional
2481 # reopen the index
2480 # reopen the index
2482 ifh.close()
2481 ifh.close()
2483 dfh = self._datafp(b"a+")
2482 dfh = self._datafp(b"a+")
2484 ifh = self._indexfp(b"a+")
2483 ifh = self._indexfp(b"a+")
2485 self._writinghandles = (ifh, dfh)
2484 self._writinghandles = (ifh, dfh)
2486 finally:
2485 finally:
2487 self._writinghandles = None
2486 self._writinghandles = None
2488
2487
2489 if dfh:
2488 if dfh:
2490 dfh.close()
2489 dfh.close()
2491 ifh.close()
2490 ifh.close()
2492 return not empty
2491 return not empty
2493
2492
2494 def iscensored(self, rev):
2493 def iscensored(self, rev):
2495 """Check if a file revision is censored."""
2494 """Check if a file revision is censored."""
2496 if not self._censorable:
2495 if not self._censorable:
2497 return False
2496 return False
2498
2497
2499 return self.flags(rev) & REVIDX_ISCENSORED
2498 return self.flags(rev) & REVIDX_ISCENSORED
2500
2499
2501 def _peek_iscensored(self, baserev, delta, flush):
2500 def _peek_iscensored(self, baserev, delta, flush):
2502 """Quickly check if a delta produces a censored revision."""
2501 """Quickly check if a delta produces a censored revision."""
2503 if not self._censorable:
2502 if not self._censorable:
2504 return False
2503 return False
2505
2504
2506 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2505 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2507
2506
2508 def getstrippoint(self, minlink):
2507 def getstrippoint(self, minlink):
2509 """find the minimum rev that must be stripped to strip the linkrev
2508 """find the minimum rev that must be stripped to strip the linkrev
2510
2509
2511 Returns a tuple containing the minimum rev and a set of all revs that
2510 Returns a tuple containing the minimum rev and a set of all revs that
2512 have linkrevs that will be broken by this strip.
2511 have linkrevs that will be broken by this strip.
2513 """
2512 """
2514 return storageutil.resolvestripinfo(
2513 return storageutil.resolvestripinfo(
2515 minlink,
2514 minlink,
2516 len(self) - 1,
2515 len(self) - 1,
2517 self.headrevs(),
2516 self.headrevs(),
2518 self.linkrev,
2517 self.linkrev,
2519 self.parentrevs,
2518 self.parentrevs,
2520 )
2519 )
2521
2520
2522 def strip(self, minlink, transaction):
2521 def strip(self, minlink, transaction):
2523 """truncate the revlog on the first revision with a linkrev >= minlink
2522 """truncate the revlog on the first revision with a linkrev >= minlink
2524
2523
2525 This function is called when we're stripping revision minlink and
2524 This function is called when we're stripping revision minlink and
2526 its descendants from the repository.
2525 its descendants from the repository.
2527
2526
2528 We have to remove all revisions with linkrev >= minlink, because
2527 We have to remove all revisions with linkrev >= minlink, because
2529 the equivalent changelog revisions will be renumbered after the
2528 the equivalent changelog revisions will be renumbered after the
2530 strip.
2529 strip.
2531
2530
2532 So we truncate the revlog on the first of these revisions, and
2531 So we truncate the revlog on the first of these revisions, and
2533 trust that the caller has saved the revisions that shouldn't be
2532 trust that the caller has saved the revisions that shouldn't be
2534 removed and that it'll re-add them after this truncation.
2533 removed and that it'll re-add them after this truncation.
2535 """
2534 """
2536 if len(self) == 0:
2535 if len(self) == 0:
2537 return
2536 return
2538
2537
2539 rev, _ = self.getstrippoint(minlink)
2538 rev, _ = self.getstrippoint(minlink)
2540 if rev == len(self):
2539 if rev == len(self):
2541 return
2540 return
2542
2541
2543 # first truncate the files on disk
2542 # first truncate the files on disk
2544 end = self.start(rev)
2543 end = self.start(rev)
2545 if not self._inline:
2544 if not self._inline:
2546 transaction.add(self.datafile, end)
2545 transaction.add(self.datafile, end)
2547 end = rev * self._io.size
2546 end = rev * self._io.size
2548 else:
2547 else:
2549 end += rev * self._io.size
2548 end += rev * self._io.size
2550
2549
2551 transaction.add(self.indexfile, end)
2550 transaction.add(self.indexfile, end)
2552
2551
2553 # then reset internal state in memory to forget those revisions
2552 # then reset internal state in memory to forget those revisions
2554 self._revisioncache = None
2553 self._revisioncache = None
2555 self._chaininfocache = util.lrucachedict(500)
2554 self._chaininfocache = util.lrucachedict(500)
2556 self._chunkclear()
2555 self._chunkclear()
2557
2556
2558 del self.index[rev:-1]
2557 del self.index[rev:-1]
2559
2558
2560 def checksize(self):
2559 def checksize(self):
2561 """Check size of index and data files
2560 """Check size of index and data files
2562
2561
2563 return a (dd, di) tuple.
2562 return a (dd, di) tuple.
2564 - dd: extra bytes for the "data" file
2563 - dd: extra bytes for the "data" file
2565 - di: extra bytes for the "index" file
2564 - di: extra bytes for the "index" file
2566
2565
2567 A healthy revlog will return (0, 0).
2566 A healthy revlog will return (0, 0).
2568 """
2567 """
2569 expected = 0
2568 expected = 0
2570 if len(self):
2569 if len(self):
2571 expected = max(0, self.end(len(self) - 1))
2570 expected = max(0, self.end(len(self) - 1))
2572
2571
2573 try:
2572 try:
2574 with self._datafp() as f:
2573 with self._datafp() as f:
2575 f.seek(0, io.SEEK_END)
2574 f.seek(0, io.SEEK_END)
2576 actual = f.tell()
2575 actual = f.tell()
2577 dd = actual - expected
2576 dd = actual - expected
2578 except IOError as inst:
2577 except IOError as inst:
2579 if inst.errno != errno.ENOENT:
2578 if inst.errno != errno.ENOENT:
2580 raise
2579 raise
2581 dd = 0
2580 dd = 0
2582
2581
2583 try:
2582 try:
2584 f = self.opener(self.indexfile)
2583 f = self.opener(self.indexfile)
2585 f.seek(0, io.SEEK_END)
2584 f.seek(0, io.SEEK_END)
2586 actual = f.tell()
2585 actual = f.tell()
2587 f.close()
2586 f.close()
2588 s = self._io.size
2587 s = self._io.size
2589 i = max(0, actual // s)
2588 i = max(0, actual // s)
2590 di = actual - (i * s)
2589 di = actual - (i * s)
2591 if self._inline:
2590 if self._inline:
2592 databytes = 0
2591 databytes = 0
2593 for r in self:
2592 for r in self:
2594 databytes += max(0, self.length(r))
2593 databytes += max(0, self.length(r))
2595 dd = 0
2594 dd = 0
2596 di = actual - len(self) * s - databytes
2595 di = actual - len(self) * s - databytes
2597 except IOError as inst:
2596 except IOError as inst:
2598 if inst.errno != errno.ENOENT:
2597 if inst.errno != errno.ENOENT:
2599 raise
2598 raise
2600 di = 0
2599 di = 0
2601
2600
2602 return (dd, di)
2601 return (dd, di)
2603
2602
2604 def files(self):
2603 def files(self):
2605 res = [self.indexfile]
2604 res = [self.indexfile]
2606 if not self._inline:
2605 if not self._inline:
2607 res.append(self.datafile)
2606 res.append(self.datafile)
2608 return res
2607 return res
2609
2608
2610 def emitrevisions(
2609 def emitrevisions(
2611 self,
2610 self,
2612 nodes,
2611 nodes,
2613 nodesorder=None,
2612 nodesorder=None,
2614 revisiondata=False,
2613 revisiondata=False,
2615 assumehaveparentrevisions=False,
2614 assumehaveparentrevisions=False,
2616 deltamode=repository.CG_DELTAMODE_STD,
2615 deltamode=repository.CG_DELTAMODE_STD,
2617 ):
2616 ):
2618 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2617 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2619 raise error.ProgrammingError(
2618 raise error.ProgrammingError(
2620 b'unhandled value for nodesorder: %s' % nodesorder
2619 b'unhandled value for nodesorder: %s' % nodesorder
2621 )
2620 )
2622
2621
2623 if nodesorder is None and not self._generaldelta:
2622 if nodesorder is None and not self._generaldelta:
2624 nodesorder = b'storage'
2623 nodesorder = b'storage'
2625
2624
2626 if (
2625 if (
2627 not self._storedeltachains
2626 not self._storedeltachains
2628 and deltamode != repository.CG_DELTAMODE_PREV
2627 and deltamode != repository.CG_DELTAMODE_PREV
2629 ):
2628 ):
2630 deltamode = repository.CG_DELTAMODE_FULL
2629 deltamode = repository.CG_DELTAMODE_FULL
2631
2630
2632 return storageutil.emitrevisions(
2631 return storageutil.emitrevisions(
2633 self,
2632 self,
2634 nodes,
2633 nodes,
2635 nodesorder,
2634 nodesorder,
2636 revlogrevisiondelta,
2635 revlogrevisiondelta,
2637 deltaparentfn=self.deltaparent,
2636 deltaparentfn=self.deltaparent,
2638 candeltafn=self.candelta,
2637 candeltafn=self.candelta,
2639 rawsizefn=self.rawsize,
2638 rawsizefn=self.rawsize,
2640 revdifffn=self.revdiff,
2639 revdifffn=self.revdiff,
2641 flagsfn=self.flags,
2640 flagsfn=self.flags,
2642 deltamode=deltamode,
2641 deltamode=deltamode,
2643 revisiondata=revisiondata,
2642 revisiondata=revisiondata,
2644 assumehaveparentrevisions=assumehaveparentrevisions,
2643 assumehaveparentrevisions=assumehaveparentrevisions,
2645 )
2644 )
2646
2645
2647 DELTAREUSEALWAYS = b'always'
2646 DELTAREUSEALWAYS = b'always'
2648 DELTAREUSESAMEREVS = b'samerevs'
2647 DELTAREUSESAMEREVS = b'samerevs'
2649 DELTAREUSENEVER = b'never'
2648 DELTAREUSENEVER = b'never'
2650
2649
2651 DELTAREUSEFULLADD = b'fulladd'
2650 DELTAREUSEFULLADD = b'fulladd'
2652
2651
2653 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2652 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2654
2653
2655 def clone(
2654 def clone(
2656 self,
2655 self,
2657 tr,
2656 tr,
2658 destrevlog,
2657 destrevlog,
2659 addrevisioncb=None,
2658 addrevisioncb=None,
2660 deltareuse=DELTAREUSESAMEREVS,
2659 deltareuse=DELTAREUSESAMEREVS,
2661 forcedeltabothparents=None,
2660 forcedeltabothparents=None,
2662 sidedatacompanion=None,
2661 sidedatacompanion=None,
2663 ):
2662 ):
2664 """Copy this revlog to another, possibly with format changes.
2663 """Copy this revlog to another, possibly with format changes.
2665
2664
2666 The destination revlog will contain the same revisions and nodes.
2665 The destination revlog will contain the same revisions and nodes.
2667 However, it may not be bit-for-bit identical due to e.g. delta encoding
2666 However, it may not be bit-for-bit identical due to e.g. delta encoding
2668 differences.
2667 differences.
2669
2668
2670 The ``deltareuse`` argument control how deltas from the existing revlog
2669 The ``deltareuse`` argument control how deltas from the existing revlog
2671 are preserved in the destination revlog. The argument can have the
2670 are preserved in the destination revlog. The argument can have the
2672 following values:
2671 following values:
2673
2672
2674 DELTAREUSEALWAYS
2673 DELTAREUSEALWAYS
2675 Deltas will always be reused (if possible), even if the destination
2674 Deltas will always be reused (if possible), even if the destination
2676 revlog would not select the same revisions for the delta. This is the
2675 revlog would not select the same revisions for the delta. This is the
2677 fastest mode of operation.
2676 fastest mode of operation.
2678 DELTAREUSESAMEREVS
2677 DELTAREUSESAMEREVS
2679 Deltas will be reused if the destination revlog would pick the same
2678 Deltas will be reused if the destination revlog would pick the same
2680 revisions for the delta. This mode strikes a balance between speed
2679 revisions for the delta. This mode strikes a balance between speed
2681 and optimization.
2680 and optimization.
2682 DELTAREUSENEVER
2681 DELTAREUSENEVER
2683 Deltas will never be reused. This is the slowest mode of execution.
2682 Deltas will never be reused. This is the slowest mode of execution.
2684 This mode can be used to recompute deltas (e.g. if the diff/delta
2683 This mode can be used to recompute deltas (e.g. if the diff/delta
2685 algorithm changes).
2684 algorithm changes).
2686 DELTAREUSEFULLADD
2685 DELTAREUSEFULLADD
2687 Revision will be re-added as if their were new content. This is
2686 Revision will be re-added as if their were new content. This is
2688 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2687 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2689 eg: large file detection and handling.
2688 eg: large file detection and handling.
2690
2689
2691 Delta computation can be slow, so the choice of delta reuse policy can
2690 Delta computation can be slow, so the choice of delta reuse policy can
2692 significantly affect run time.
2691 significantly affect run time.
2693
2692
2694 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2693 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2695 two extremes. Deltas will be reused if they are appropriate. But if the
2694 two extremes. Deltas will be reused if they are appropriate. But if the
2696 delta could choose a better revision, it will do so. This means if you
2695 delta could choose a better revision, it will do so. This means if you
2697 are converting a non-generaldelta revlog to a generaldelta revlog,
2696 are converting a non-generaldelta revlog to a generaldelta revlog,
2698 deltas will be recomputed if the delta's parent isn't a parent of the
2697 deltas will be recomputed if the delta's parent isn't a parent of the
2699 revision.
2698 revision.
2700
2699
2701 In addition to the delta policy, the ``forcedeltabothparents``
2700 In addition to the delta policy, the ``forcedeltabothparents``
2702 argument controls whether to force compute deltas against both parents
2701 argument controls whether to force compute deltas against both parents
2703 for merges. By default, the current default is used.
2702 for merges. By default, the current default is used.
2704
2703
2705 If not None, the `sidedatacompanion` is callable that accept two
2704 If not None, the `sidedatacompanion` is callable that accept two
2706 arguments:
2705 arguments:
2707
2706
2708 (srcrevlog, rev)
2707 (srcrevlog, rev)
2709
2708
2710 and return a quintet that control changes to sidedata content from the
2709 and return a quintet that control changes to sidedata content from the
2711 old revision to the new clone result:
2710 old revision to the new clone result:
2712
2711
2713 (dropall, filterout, update, new_flags, dropped_flags)
2712 (dropall, filterout, update, new_flags, dropped_flags)
2714
2713
2715 * if `dropall` is True, all sidedata should be dropped
2714 * if `dropall` is True, all sidedata should be dropped
2716 * `filterout` is a set of sidedata keys that should be dropped
2715 * `filterout` is a set of sidedata keys that should be dropped
2717 * `update` is a mapping of additionnal/new key -> value
2716 * `update` is a mapping of additionnal/new key -> value
2718 * new_flags is a bitfields of new flags that the revision should get
2717 * new_flags is a bitfields of new flags that the revision should get
2719 * dropped_flags is a bitfields of new flags that the revision shoudl not longer have
2718 * dropped_flags is a bitfields of new flags that the revision shoudl not longer have
2720 """
2719 """
2721 if deltareuse not in self.DELTAREUSEALL:
2720 if deltareuse not in self.DELTAREUSEALL:
2722 raise ValueError(
2721 raise ValueError(
2723 _(b'value for deltareuse invalid: %s') % deltareuse
2722 _(b'value for deltareuse invalid: %s') % deltareuse
2724 )
2723 )
2725
2724
2726 if len(destrevlog):
2725 if len(destrevlog):
2727 raise ValueError(_(b'destination revlog is not empty'))
2726 raise ValueError(_(b'destination revlog is not empty'))
2728
2727
2729 if getattr(self, 'filteredrevs', None):
2728 if getattr(self, 'filteredrevs', None):
2730 raise ValueError(_(b'source revlog has filtered revisions'))
2729 raise ValueError(_(b'source revlog has filtered revisions'))
2731 if getattr(destrevlog, 'filteredrevs', None):
2730 if getattr(destrevlog, 'filteredrevs', None):
2732 raise ValueError(_(b'destination revlog has filtered revisions'))
2731 raise ValueError(_(b'destination revlog has filtered revisions'))
2733
2732
2734 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2733 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2735 # if possible.
2734 # if possible.
2736 oldlazydelta = destrevlog._lazydelta
2735 oldlazydelta = destrevlog._lazydelta
2737 oldlazydeltabase = destrevlog._lazydeltabase
2736 oldlazydeltabase = destrevlog._lazydeltabase
2738 oldamd = destrevlog._deltabothparents
2737 oldamd = destrevlog._deltabothparents
2739
2738
2740 try:
2739 try:
2741 if deltareuse == self.DELTAREUSEALWAYS:
2740 if deltareuse == self.DELTAREUSEALWAYS:
2742 destrevlog._lazydeltabase = True
2741 destrevlog._lazydeltabase = True
2743 destrevlog._lazydelta = True
2742 destrevlog._lazydelta = True
2744 elif deltareuse == self.DELTAREUSESAMEREVS:
2743 elif deltareuse == self.DELTAREUSESAMEREVS:
2745 destrevlog._lazydeltabase = False
2744 destrevlog._lazydeltabase = False
2746 destrevlog._lazydelta = True
2745 destrevlog._lazydelta = True
2747 elif deltareuse == self.DELTAREUSENEVER:
2746 elif deltareuse == self.DELTAREUSENEVER:
2748 destrevlog._lazydeltabase = False
2747 destrevlog._lazydeltabase = False
2749 destrevlog._lazydelta = False
2748 destrevlog._lazydelta = False
2750
2749
2751 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2750 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2752
2751
2753 self._clone(
2752 self._clone(
2754 tr,
2753 tr,
2755 destrevlog,
2754 destrevlog,
2756 addrevisioncb,
2755 addrevisioncb,
2757 deltareuse,
2756 deltareuse,
2758 forcedeltabothparents,
2757 forcedeltabothparents,
2759 sidedatacompanion,
2758 sidedatacompanion,
2760 )
2759 )
2761
2760
2762 finally:
2761 finally:
2763 destrevlog._lazydelta = oldlazydelta
2762 destrevlog._lazydelta = oldlazydelta
2764 destrevlog._lazydeltabase = oldlazydeltabase
2763 destrevlog._lazydeltabase = oldlazydeltabase
2765 destrevlog._deltabothparents = oldamd
2764 destrevlog._deltabothparents = oldamd
2766
2765
2767 def _clone(
2766 def _clone(
2768 self,
2767 self,
2769 tr,
2768 tr,
2770 destrevlog,
2769 destrevlog,
2771 addrevisioncb,
2770 addrevisioncb,
2772 deltareuse,
2771 deltareuse,
2773 forcedeltabothparents,
2772 forcedeltabothparents,
2774 sidedatacompanion,
2773 sidedatacompanion,
2775 ):
2774 ):
2776 """perform the core duty of `revlog.clone` after parameter processing"""
2775 """perform the core duty of `revlog.clone` after parameter processing"""
2777 deltacomputer = deltautil.deltacomputer(destrevlog)
2776 deltacomputer = deltautil.deltacomputer(destrevlog)
2778 index = self.index
2777 index = self.index
2779 for rev in self:
2778 for rev in self:
2780 entry = index[rev]
2779 entry = index[rev]
2781
2780
2782 # Some classes override linkrev to take filtered revs into
2781 # Some classes override linkrev to take filtered revs into
2783 # account. Use raw entry from index.
2782 # account. Use raw entry from index.
2784 flags = entry[0] & 0xFFFF
2783 flags = entry[0] & 0xFFFF
2785 linkrev = entry[4]
2784 linkrev = entry[4]
2786 p1 = index[entry[5]][7]
2785 p1 = index[entry[5]][7]
2787 p2 = index[entry[6]][7]
2786 p2 = index[entry[6]][7]
2788 node = entry[7]
2787 node = entry[7]
2789
2788
2790 sidedataactions = (False, [], {}, 0, 0)
2789 sidedataactions = (False, [], {}, 0, 0)
2791 if sidedatacompanion is not None:
2790 if sidedatacompanion is not None:
2792 sidedataactions = sidedatacompanion(self, rev)
2791 sidedataactions = sidedatacompanion(self, rev)
2793
2792
2794 # (Possibly) reuse the delta from the revlog if allowed and
2793 # (Possibly) reuse the delta from the revlog if allowed and
2795 # the revlog chunk is a delta.
2794 # the revlog chunk is a delta.
2796 cachedelta = None
2795 cachedelta = None
2797 rawtext = None
2796 rawtext = None
2798 if any(sidedataactions) or deltareuse == self.DELTAREUSEFULLADD:
2797 if any(sidedataactions) or deltareuse == self.DELTAREUSEFULLADD:
2799 dropall = sidedataactions[0]
2798 dropall = sidedataactions[0]
2800 filterout = sidedataactions[1]
2799 filterout = sidedataactions[1]
2801 update = sidedataactions[2]
2800 update = sidedataactions[2]
2802 new_flags = sidedataactions[3]
2801 new_flags = sidedataactions[3]
2803 dropped_flags = sidedataactions[4]
2802 dropped_flags = sidedataactions[4]
2804 text, sidedata = self._revisiondata(rev)
2803 text, sidedata = self._revisiondata(rev)
2805 if dropall:
2804 if dropall:
2806 sidedata = {}
2805 sidedata = {}
2807 for key in filterout:
2806 for key in filterout:
2808 sidedata.pop(key, None)
2807 sidedata.pop(key, None)
2809 sidedata.update(update)
2808 sidedata.update(update)
2810 if not sidedata:
2809 if not sidedata:
2811 sidedata = None
2810 sidedata = None
2812
2811
2813 flags |= new_flags
2812 flags |= new_flags
2814 flags &= ~dropped_flags
2813 flags &= ~dropped_flags
2815
2814
2816 destrevlog.addrevision(
2815 destrevlog.addrevision(
2817 text,
2816 text,
2818 tr,
2817 tr,
2819 linkrev,
2818 linkrev,
2820 p1,
2819 p1,
2821 p2,
2820 p2,
2822 cachedelta=cachedelta,
2821 cachedelta=cachedelta,
2823 node=node,
2822 node=node,
2824 flags=flags,
2823 flags=flags,
2825 deltacomputer=deltacomputer,
2824 deltacomputer=deltacomputer,
2826 sidedata=sidedata,
2825 sidedata=sidedata,
2827 )
2826 )
2828 else:
2827 else:
2829 if destrevlog._lazydelta:
2828 if destrevlog._lazydelta:
2830 dp = self.deltaparent(rev)
2829 dp = self.deltaparent(rev)
2831 if dp != nullrev:
2830 if dp != nullrev:
2832 cachedelta = (dp, bytes(self._chunk(rev)))
2831 cachedelta = (dp, bytes(self._chunk(rev)))
2833
2832
2834 if not cachedelta:
2833 if not cachedelta:
2835 rawtext = self.rawdata(rev)
2834 rawtext = self.rawdata(rev)
2836
2835
2837 ifh = destrevlog.opener(
2836 ifh = destrevlog.opener(
2838 destrevlog.indexfile, b'a+', checkambig=False
2837 destrevlog.indexfile, b'a+', checkambig=False
2839 )
2838 )
2840 dfh = None
2839 dfh = None
2841 if not destrevlog._inline:
2840 if not destrevlog._inline:
2842 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2841 dfh = destrevlog.opener(destrevlog.datafile, b'a+')
2843 try:
2842 try:
2844 destrevlog._addrevision(
2843 destrevlog._addrevision(
2845 node,
2844 node,
2846 rawtext,
2845 rawtext,
2847 tr,
2846 tr,
2848 linkrev,
2847 linkrev,
2849 p1,
2848 p1,
2850 p2,
2849 p2,
2851 flags,
2850 flags,
2852 cachedelta,
2851 cachedelta,
2853 ifh,
2852 ifh,
2854 dfh,
2853 dfh,
2855 deltacomputer=deltacomputer,
2854 deltacomputer=deltacomputer,
2856 )
2855 )
2857 finally:
2856 finally:
2858 if dfh:
2857 if dfh:
2859 dfh.close()
2858 dfh.close()
2860 ifh.close()
2859 ifh.close()
2861
2860
2862 if addrevisioncb:
2861 if addrevisioncb:
2863 addrevisioncb(self, rev, node)
2862 addrevisioncb(self, rev, node)
2864
2863
2865 def censorrevision(self, tr, censornode, tombstone=b''):
2864 def censorrevision(self, tr, censornode, tombstone=b''):
2866 if (self.version & 0xFFFF) == REVLOGV0:
2865 if (self.version & 0xFFFF) == REVLOGV0:
2867 raise error.RevlogError(
2866 raise error.RevlogError(
2868 _(b'cannot censor with version %d revlogs') % self.version
2867 _(b'cannot censor with version %d revlogs') % self.version
2869 )
2868 )
2870
2869
2871 censorrev = self.rev(censornode)
2870 censorrev = self.rev(censornode)
2872 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2871 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2873
2872
2874 if len(tombstone) > self.rawsize(censorrev):
2873 if len(tombstone) > self.rawsize(censorrev):
2875 raise error.Abort(
2874 raise error.Abort(
2876 _(b'censor tombstone must be no longer than censored data')
2875 _(b'censor tombstone must be no longer than censored data')
2877 )
2876 )
2878
2877
2879 # Rewriting the revlog in place is hard. Our strategy for censoring is
2878 # Rewriting the revlog in place is hard. Our strategy for censoring is
2880 # to create a new revlog, copy all revisions to it, then replace the
2879 # to create a new revlog, copy all revisions to it, then replace the
2881 # revlogs on transaction close.
2880 # revlogs on transaction close.
2882
2881
2883 newindexfile = self.indexfile + b'.tmpcensored'
2882 newindexfile = self.indexfile + b'.tmpcensored'
2884 newdatafile = self.datafile + b'.tmpcensored'
2883 newdatafile = self.datafile + b'.tmpcensored'
2885
2884
2886 # This is a bit dangerous. We could easily have a mismatch of state.
2885 # This is a bit dangerous. We could easily have a mismatch of state.
2887 newrl = revlog(self.opener, newindexfile, newdatafile, censorable=True)
2886 newrl = revlog(self.opener, newindexfile, newdatafile, censorable=True)
2888 newrl.version = self.version
2887 newrl.version = self.version
2889 newrl._generaldelta = self._generaldelta
2888 newrl._generaldelta = self._generaldelta
2890 newrl._io = self._io
2889 newrl._io = self._io
2891
2890
2892 for rev in self.revs():
2891 for rev in self.revs():
2893 node = self.node(rev)
2892 node = self.node(rev)
2894 p1, p2 = self.parents(node)
2893 p1, p2 = self.parents(node)
2895
2894
2896 if rev == censorrev:
2895 if rev == censorrev:
2897 newrl.addrawrevision(
2896 newrl.addrawrevision(
2898 tombstone,
2897 tombstone,
2899 tr,
2898 tr,
2900 self.linkrev(censorrev),
2899 self.linkrev(censorrev),
2901 p1,
2900 p1,
2902 p2,
2901 p2,
2903 censornode,
2902 censornode,
2904 REVIDX_ISCENSORED,
2903 REVIDX_ISCENSORED,
2905 )
2904 )
2906
2905
2907 if newrl.deltaparent(rev) != nullrev:
2906 if newrl.deltaparent(rev) != nullrev:
2908 raise error.Abort(
2907 raise error.Abort(
2909 _(
2908 _(
2910 b'censored revision stored as delta; '
2909 b'censored revision stored as delta; '
2911 b'cannot censor'
2910 b'cannot censor'
2912 ),
2911 ),
2913 hint=_(
2912 hint=_(
2914 b'censoring of revlogs is not '
2913 b'censoring of revlogs is not '
2915 b'fully implemented; please report '
2914 b'fully implemented; please report '
2916 b'this bug'
2915 b'this bug'
2917 ),
2916 ),
2918 )
2917 )
2919 continue
2918 continue
2920
2919
2921 if self.iscensored(rev):
2920 if self.iscensored(rev):
2922 if self.deltaparent(rev) != nullrev:
2921 if self.deltaparent(rev) != nullrev:
2923 raise error.Abort(
2922 raise error.Abort(
2924 _(
2923 _(
2925 b'cannot censor due to censored '
2924 b'cannot censor due to censored '
2926 b'revision having delta stored'
2925 b'revision having delta stored'
2927 )
2926 )
2928 )
2927 )
2929 rawtext = self._chunk(rev)
2928 rawtext = self._chunk(rev)
2930 else:
2929 else:
2931 rawtext = self.rawdata(rev)
2930 rawtext = self.rawdata(rev)
2932
2931
2933 newrl.addrawrevision(
2932 newrl.addrawrevision(
2934 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2933 rawtext, tr, self.linkrev(rev), p1, p2, node, self.flags(rev)
2935 )
2934 )
2936
2935
2937 tr.addbackup(self.indexfile, location=b'store')
2936 tr.addbackup(self.indexfile, location=b'store')
2938 if not self._inline:
2937 if not self._inline:
2939 tr.addbackup(self.datafile, location=b'store')
2938 tr.addbackup(self.datafile, location=b'store')
2940
2939
2941 self.opener.rename(newrl.indexfile, self.indexfile)
2940 self.opener.rename(newrl.indexfile, self.indexfile)
2942 if not self._inline:
2941 if not self._inline:
2943 self.opener.rename(newrl.datafile, self.datafile)
2942 self.opener.rename(newrl.datafile, self.datafile)
2944
2943
2945 self.clearcaches()
2944 self.clearcaches()
2946 self._loadindex()
2945 self._loadindex()
2947
2946
2948 def verifyintegrity(self, state):
2947 def verifyintegrity(self, state):
2949 """Verifies the integrity of the revlog.
2948 """Verifies the integrity of the revlog.
2950
2949
2951 Yields ``revlogproblem`` instances describing problems that are
2950 Yields ``revlogproblem`` instances describing problems that are
2952 found.
2951 found.
2953 """
2952 """
2954 dd, di = self.checksize()
2953 dd, di = self.checksize()
2955 if dd:
2954 if dd:
2956 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2955 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
2957 if di:
2956 if di:
2958 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2957 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
2959
2958
2960 version = self.version & 0xFFFF
2959 version = self.version & 0xFFFF
2961
2960
2962 # The verifier tells us what version revlog we should be.
2961 # The verifier tells us what version revlog we should be.
2963 if version != state[b'expectedversion']:
2962 if version != state[b'expectedversion']:
2964 yield revlogproblem(
2963 yield revlogproblem(
2965 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2964 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
2966 % (self.indexfile, version, state[b'expectedversion'])
2965 % (self.indexfile, version, state[b'expectedversion'])
2967 )
2966 )
2968
2967
2969 state[b'skipread'] = set()
2968 state[b'skipread'] = set()
2970 state[b'safe_renamed'] = set()
2969 state[b'safe_renamed'] = set()
2971
2970
2972 for rev in self:
2971 for rev in self:
2973 node = self.node(rev)
2972 node = self.node(rev)
2974
2973
2975 # Verify contents. 4 cases to care about:
2974 # Verify contents. 4 cases to care about:
2976 #
2975 #
2977 # common: the most common case
2976 # common: the most common case
2978 # rename: with a rename
2977 # rename: with a rename
2979 # meta: file content starts with b'\1\n', the metadata
2978 # meta: file content starts with b'\1\n', the metadata
2980 # header defined in filelog.py, but without a rename
2979 # header defined in filelog.py, but without a rename
2981 # ext: content stored externally
2980 # ext: content stored externally
2982 #
2981 #
2983 # More formally, their differences are shown below:
2982 # More formally, their differences are shown below:
2984 #
2983 #
2985 # | common | rename | meta | ext
2984 # | common | rename | meta | ext
2986 # -------------------------------------------------------
2985 # -------------------------------------------------------
2987 # flags() | 0 | 0 | 0 | not 0
2986 # flags() | 0 | 0 | 0 | not 0
2988 # renamed() | False | True | False | ?
2987 # renamed() | False | True | False | ?
2989 # rawtext[0:2]=='\1\n'| False | True | True | ?
2988 # rawtext[0:2]=='\1\n'| False | True | True | ?
2990 #
2989 #
2991 # "rawtext" means the raw text stored in revlog data, which
2990 # "rawtext" means the raw text stored in revlog data, which
2992 # could be retrieved by "rawdata(rev)". "text"
2991 # could be retrieved by "rawdata(rev)". "text"
2993 # mentioned below is "revision(rev)".
2992 # mentioned below is "revision(rev)".
2994 #
2993 #
2995 # There are 3 different lengths stored physically:
2994 # There are 3 different lengths stored physically:
2996 # 1. L1: rawsize, stored in revlog index
2995 # 1. L1: rawsize, stored in revlog index
2997 # 2. L2: len(rawtext), stored in revlog data
2996 # 2. L2: len(rawtext), stored in revlog data
2998 # 3. L3: len(text), stored in revlog data if flags==0, or
2997 # 3. L3: len(text), stored in revlog data if flags==0, or
2999 # possibly somewhere else if flags!=0
2998 # possibly somewhere else if flags!=0
3000 #
2999 #
3001 # L1 should be equal to L2. L3 could be different from them.
3000 # L1 should be equal to L2. L3 could be different from them.
3002 # "text" may or may not affect commit hash depending on flag
3001 # "text" may or may not affect commit hash depending on flag
3003 # processors (see flagutil.addflagprocessor).
3002 # processors (see flagutil.addflagprocessor).
3004 #
3003 #
3005 # | common | rename | meta | ext
3004 # | common | rename | meta | ext
3006 # -------------------------------------------------
3005 # -------------------------------------------------
3007 # rawsize() | L1 | L1 | L1 | L1
3006 # rawsize() | L1 | L1 | L1 | L1
3008 # size() | L1 | L2-LM | L1(*) | L1 (?)
3007 # size() | L1 | L2-LM | L1(*) | L1 (?)
3009 # len(rawtext) | L2 | L2 | L2 | L2
3008 # len(rawtext) | L2 | L2 | L2 | L2
3010 # len(text) | L2 | L2 | L2 | L3
3009 # len(text) | L2 | L2 | L2 | L3
3011 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3010 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3012 #
3011 #
3013 # LM: length of metadata, depending on rawtext
3012 # LM: length of metadata, depending on rawtext
3014 # (*): not ideal, see comment in filelog.size
3013 # (*): not ideal, see comment in filelog.size
3015 # (?): could be "- len(meta)" if the resolved content has
3014 # (?): could be "- len(meta)" if the resolved content has
3016 # rename metadata
3015 # rename metadata
3017 #
3016 #
3018 # Checks needed to be done:
3017 # Checks needed to be done:
3019 # 1. length check: L1 == L2, in all cases.
3018 # 1. length check: L1 == L2, in all cases.
3020 # 2. hash check: depending on flag processor, we may need to
3019 # 2. hash check: depending on flag processor, we may need to
3021 # use either "text" (external), or "rawtext" (in revlog).
3020 # use either "text" (external), or "rawtext" (in revlog).
3022
3021
3023 try:
3022 try:
3024 skipflags = state.get(b'skipflags', 0)
3023 skipflags = state.get(b'skipflags', 0)
3025 if skipflags:
3024 if skipflags:
3026 skipflags &= self.flags(rev)
3025 skipflags &= self.flags(rev)
3027
3026
3028 _verify_revision(self, skipflags, state, node)
3027 _verify_revision(self, skipflags, state, node)
3029
3028
3030 l1 = self.rawsize(rev)
3029 l1 = self.rawsize(rev)
3031 l2 = len(self.rawdata(node))
3030 l2 = len(self.rawdata(node))
3032
3031
3033 if l1 != l2:
3032 if l1 != l2:
3034 yield revlogproblem(
3033 yield revlogproblem(
3035 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3034 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3036 node=node,
3035 node=node,
3037 )
3036 )
3038
3037
3039 except error.CensoredNodeError:
3038 except error.CensoredNodeError:
3040 if state[b'erroroncensored']:
3039 if state[b'erroroncensored']:
3041 yield revlogproblem(
3040 yield revlogproblem(
3042 error=_(b'censored file data'), node=node
3041 error=_(b'censored file data'), node=node
3043 )
3042 )
3044 state[b'skipread'].add(node)
3043 state[b'skipread'].add(node)
3045 except Exception as e:
3044 except Exception as e:
3046 yield revlogproblem(
3045 yield revlogproblem(
3047 error=_(b'unpacking %s: %s')
3046 error=_(b'unpacking %s: %s')
3048 % (short(node), stringutil.forcebytestr(e)),
3047 % (short(node), stringutil.forcebytestr(e)),
3049 node=node,
3048 node=node,
3050 )
3049 )
3051 state[b'skipread'].add(node)
3050 state[b'skipread'].add(node)
3052
3051
3053 def storageinfo(
3052 def storageinfo(
3054 self,
3053 self,
3055 exclusivefiles=False,
3054 exclusivefiles=False,
3056 sharedfiles=False,
3055 sharedfiles=False,
3057 revisionscount=False,
3056 revisionscount=False,
3058 trackedsize=False,
3057 trackedsize=False,
3059 storedsize=False,
3058 storedsize=False,
3060 ):
3059 ):
3061 d = {}
3060 d = {}
3062
3061
3063 if exclusivefiles:
3062 if exclusivefiles:
3064 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
3063 d[b'exclusivefiles'] = [(self.opener, self.indexfile)]
3065 if not self._inline:
3064 if not self._inline:
3066 d[b'exclusivefiles'].append((self.opener, self.datafile))
3065 d[b'exclusivefiles'].append((self.opener, self.datafile))
3067
3066
3068 if sharedfiles:
3067 if sharedfiles:
3069 d[b'sharedfiles'] = []
3068 d[b'sharedfiles'] = []
3070
3069
3071 if revisionscount:
3070 if revisionscount:
3072 d[b'revisionscount'] = len(self)
3071 d[b'revisionscount'] = len(self)
3073
3072
3074 if trackedsize:
3073 if trackedsize:
3075 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3074 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3076
3075
3077 if storedsize:
3076 if storedsize:
3078 d[b'storedsize'] = sum(
3077 d[b'storedsize'] = sum(
3079 self.opener.stat(path).st_size for path in self.files()
3078 self.opener.stat(path).st_size for path in self.files()
3080 )
3079 )
3081
3080
3082 return d
3081 return d
@@ -1,734 +1,732 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 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 in q:
246 for f, o in q:
247 self._addentry(f, o)
247 self._addentry(f, o)
248
248
249 @active
249 @active
250 def add(self, file, offset):
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))
255 self._queue[-1].append((file, offset))
256 return
256 return
257
257
258 self._addentry(file, offset)
258 self._addentry(file, offset)
259
259
260 def _addentry(self, file, offset):
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))
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 findoffset(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]][1]
401 if file in self._backupmap:
402 return self._backupentries[self._backupmap[file]]
403 return None
401 return None
404
402
405 @active
403 @active
406 def replace(self, file, offset):
404 def replace(self, file, offset):
407 '''
405 '''
408 replace can only replace already committed entries
406 replace can only replace already committed entries
409 that are not pending in the queue
407 that are not pending in the queue
410 '''
408 '''
411
409
412 if file not in self._map:
410 if file not in self._map:
413 raise KeyError(file)
411 raise KeyError(file)
414 index = self._map[file]
412 index = self._map[file]
415 self._entries[index] = (file, offset)
413 self._entries[index] = (file, offset)
416 self._file.write(b"%s\0%d\n" % (file, offset))
414 self._file.write(b"%s\0%d\n" % (file, offset))
417 self._file.flush()
415 self._file.flush()
418
416
419 @active
417 @active
420 def nest(self, name='<unnamed>'):
418 def nest(self, name='<unnamed>'):
421 self._count += 1
419 self._count += 1
422 self._usages += 1
420 self._usages += 1
423 self._names.append(name)
421 self._names.append(name)
424 return self
422 return self
425
423
426 def release(self):
424 def release(self):
427 if self._count > 0:
425 if self._count > 0:
428 self._usages -= 1
426 self._usages -= 1
429 if self._names:
427 if self._names:
430 self._names.pop()
428 self._names.pop()
431 # if the transaction scopes are left without being closed, fail
429 # if the transaction scopes are left without being closed, fail
432 if self._count > 0 and self._usages == 0:
430 if self._count > 0 and self._usages == 0:
433 self._abort()
431 self._abort()
434
432
435 def running(self):
433 def running(self):
436 return self._count > 0
434 return self._count > 0
437
435
438 def addpending(self, category, callback):
436 def addpending(self, category, callback):
439 """add a callback to be called when the transaction is pending
437 """add a callback to be called when the transaction is pending
440
438
441 The transaction will be given as callback's first argument.
439 The transaction will be given as callback's first argument.
442
440
443 Category is a unique identifier to allow overwriting an old callback
441 Category is a unique identifier to allow overwriting an old callback
444 with a newer callback.
442 with a newer callback.
445 """
443 """
446 self._pendingcallback[category] = callback
444 self._pendingcallback[category] = callback
447
445
448 @active
446 @active
449 def writepending(self):
447 def writepending(self):
450 '''write pending file to temporary version
448 '''write pending file to temporary version
451
449
452 This is used to allow hooks to view a transaction before commit'''
450 This is used to allow hooks to view a transaction before commit'''
453 categories = sorted(self._pendingcallback)
451 categories = sorted(self._pendingcallback)
454 for cat in categories:
452 for cat in categories:
455 # remove callback since the data will have been flushed
453 # remove callback since the data will have been flushed
456 any = self._pendingcallback.pop(cat)(self)
454 any = self._pendingcallback.pop(cat)(self)
457 self._anypending = self._anypending or any
455 self._anypending = self._anypending or any
458 self._anypending |= self._generatefiles(suffix=b'.pending')
456 self._anypending |= self._generatefiles(suffix=b'.pending')
459 return self._anypending
457 return self._anypending
460
458
461 @active
459 @active
462 def hasfinalize(self, category):
460 def hasfinalize(self, category):
463 """check is a callback already exist for a category
461 """check is a callback already exist for a category
464 """
462 """
465 return category in self._finalizecallback
463 return category in self._finalizecallback
466
464
467 @active
465 @active
468 def addfinalize(self, category, callback):
466 def addfinalize(self, category, callback):
469 """add a callback to be called when the transaction is closed
467 """add a callback to be called when the transaction is closed
470
468
471 The transaction will be given as callback's first argument.
469 The transaction will be given as callback's first argument.
472
470
473 Category is a unique identifier to allow overwriting old callbacks with
471 Category is a unique identifier to allow overwriting old callbacks with
474 newer callbacks.
472 newer callbacks.
475 """
473 """
476 self._finalizecallback[category] = callback
474 self._finalizecallback[category] = callback
477
475
478 @active
476 @active
479 def addpostclose(self, category, callback):
477 def addpostclose(self, category, callback):
480 """add or replace a callback to be called after the transaction closed
478 """add or replace a callback to be called after the transaction closed
481
479
482 The transaction will be given as callback's first argument.
480 The transaction will be given as callback's first argument.
483
481
484 Category is a unique identifier to allow overwriting an old callback
482 Category is a unique identifier to allow overwriting an old callback
485 with a newer callback.
483 with a newer callback.
486 """
484 """
487 self._postclosecallback[category] = callback
485 self._postclosecallback[category] = callback
488
486
489 @active
487 @active
490 def getpostclose(self, category):
488 def getpostclose(self, category):
491 """return a postclose callback added before, or None"""
489 """return a postclose callback added before, or None"""
492 return self._postclosecallback.get(category, None)
490 return self._postclosecallback.get(category, None)
493
491
494 @active
492 @active
495 def addabort(self, category, callback):
493 def addabort(self, category, callback):
496 """add a callback to be called when the transaction is aborted.
494 """add a callback to be called when the transaction is aborted.
497
495
498 The transaction will be given as the first argument to the callback.
496 The transaction will be given as the first argument to the callback.
499
497
500 Category is a unique identifier to allow overwriting an old callback
498 Category is a unique identifier to allow overwriting an old callback
501 with a newer callback.
499 with a newer callback.
502 """
500 """
503 self._abortcallback[category] = callback
501 self._abortcallback[category] = callback
504
502
505 @active
503 @active
506 def addvalidator(self, category, callback):
504 def addvalidator(self, category, callback):
507 """ adds a callback to be called when validating the transaction.
505 """ adds a callback to be called when validating the transaction.
508
506
509 The transaction will be given as the first argument to the callback.
507 The transaction will be given as the first argument to the callback.
510
508
511 callback should raise exception if to abort transaction """
509 callback should raise exception if to abort transaction """
512 self._validatecallback[category] = callback
510 self._validatecallback[category] = callback
513
511
514 @active
512 @active
515 def close(self):
513 def close(self):
516 '''commit the transaction'''
514 '''commit the transaction'''
517 if self._count == 1:
515 if self._count == 1:
518 for category in sorted(self._validatecallback):
516 for category in sorted(self._validatecallback):
519 self._validatecallback[category](self)
517 self._validatecallback[category](self)
520 self._validatecallback = None # Help prevent cycles.
518 self._validatecallback = None # Help prevent cycles.
521 self._generatefiles(group=GEN_GROUP_PRE_FINALIZE)
519 self._generatefiles(group=GEN_GROUP_PRE_FINALIZE)
522 while self._finalizecallback:
520 while self._finalizecallback:
523 callbacks = self._finalizecallback
521 callbacks = self._finalizecallback
524 self._finalizecallback = {}
522 self._finalizecallback = {}
525 categories = sorted(callbacks)
523 categories = sorted(callbacks)
526 for cat in categories:
524 for cat in categories:
527 callbacks[cat](self)
525 callbacks[cat](self)
528 # Prevent double usage and help clear cycles.
526 # Prevent double usage and help clear cycles.
529 self._finalizecallback = None
527 self._finalizecallback = None
530 self._generatefiles(group=GEN_GROUP_POST_FINALIZE)
528 self._generatefiles(group=GEN_GROUP_POST_FINALIZE)
531
529
532 self._count -= 1
530 self._count -= 1
533 if self._count != 0:
531 if self._count != 0:
534 return
532 return
535 self._file.close()
533 self._file.close()
536 self._backupsfile.close()
534 self._backupsfile.close()
537 # cleanup temporary files
535 # cleanup temporary files
538 for l, f, b, c in self._backupentries:
536 for l, f, b, c in self._backupentries:
539 if l not in self._vfsmap and c:
537 if l not in self._vfsmap and c:
540 self._report(
538 self._report(
541 b"couldn't remove %s: unknown cache location %s\n" % (b, l)
539 b"couldn't remove %s: unknown cache location %s\n" % (b, l)
542 )
540 )
543 continue
541 continue
544 vfs = self._vfsmap[l]
542 vfs = self._vfsmap[l]
545 if not f and b and vfs.exists(b):
543 if not f and b and vfs.exists(b):
546 try:
544 try:
547 vfs.unlink(b)
545 vfs.unlink(b)
548 except (IOError, OSError, error.Abort) as inst:
546 except (IOError, OSError, error.Abort) as inst:
549 if not c:
547 if not c:
550 raise
548 raise
551 # Abort may be raise by read only opener
549 # Abort may be raise by read only opener
552 self._report(
550 self._report(
553 b"couldn't remove %s: %s\n" % (vfs.join(b), inst)
551 b"couldn't remove %s: %s\n" % (vfs.join(b), inst)
554 )
552 )
555 self._entries = []
553 self._entries = []
556 self._writeundo()
554 self._writeundo()
557 if self._after:
555 if self._after:
558 self._after()
556 self._after()
559 self._after = None # Help prevent cycles.
557 self._after = None # Help prevent cycles.
560 if self._opener.isfile(self._backupjournal):
558 if self._opener.isfile(self._backupjournal):
561 self._opener.unlink(self._backupjournal)
559 self._opener.unlink(self._backupjournal)
562 if self._opener.isfile(self._journal):
560 if self._opener.isfile(self._journal):
563 self._opener.unlink(self._journal)
561 self._opener.unlink(self._journal)
564 for l, _f, b, c in self._backupentries:
562 for l, _f, b, c in self._backupentries:
565 if l not in self._vfsmap and c:
563 if l not in self._vfsmap and c:
566 self._report(
564 self._report(
567 b"couldn't remove %s: unknown cache location"
565 b"couldn't remove %s: unknown cache location"
568 b"%s\n" % (b, l)
566 b"%s\n" % (b, l)
569 )
567 )
570 continue
568 continue
571 vfs = self._vfsmap[l]
569 vfs = self._vfsmap[l]
572 if b and vfs.exists(b):
570 if b and vfs.exists(b):
573 try:
571 try:
574 vfs.unlink(b)
572 vfs.unlink(b)
575 except (IOError, OSError, error.Abort) as inst:
573 except (IOError, OSError, error.Abort) as inst:
576 if not c:
574 if not c:
577 raise
575 raise
578 # Abort may be raise by read only opener
576 # Abort may be raise by read only opener
579 self._report(
577 self._report(
580 b"couldn't remove %s: %s\n" % (vfs.join(b), inst)
578 b"couldn't remove %s: %s\n" % (vfs.join(b), inst)
581 )
579 )
582 self._backupentries = []
580 self._backupentries = []
583 self._journal = None
581 self._journal = None
584
582
585 self._releasefn(self, True) # notify success of closing transaction
583 self._releasefn(self, True) # notify success of closing transaction
586 self._releasefn = None # Help prevent cycles.
584 self._releasefn = None # Help prevent cycles.
587
585
588 # run post close action
586 # run post close action
589 categories = sorted(self._postclosecallback)
587 categories = sorted(self._postclosecallback)
590 for cat in categories:
588 for cat in categories:
591 self._postclosecallback[cat](self)
589 self._postclosecallback[cat](self)
592 # Prevent double usage and help clear cycles.
590 # Prevent double usage and help clear cycles.
593 self._postclosecallback = None
591 self._postclosecallback = None
594
592
595 @active
593 @active
596 def abort(self):
594 def abort(self):
597 '''abort the transaction (generally called on error, or when the
595 '''abort the transaction (generally called on error, or when the
598 transaction is not explicitly committed before going out of
596 transaction is not explicitly committed before going out of
599 scope)'''
597 scope)'''
600 self._abort()
598 self._abort()
601
599
602 def _writeundo(self):
600 def _writeundo(self):
603 """write transaction data for possible future undo call"""
601 """write transaction data for possible future undo call"""
604 if self._undoname is None:
602 if self._undoname is None:
605 return
603 return
606 undobackupfile = self._opener.open(
604 undobackupfile = self._opener.open(
607 b"%s.backupfiles" % self._undoname, b'w'
605 b"%s.backupfiles" % self._undoname, b'w'
608 )
606 )
609 undobackupfile.write(b'%d\n' % version)
607 undobackupfile.write(b'%d\n' % version)
610 for l, f, b, c in self._backupentries:
608 for l, f, b, c in self._backupentries:
611 if not f: # temporary file
609 if not f: # temporary file
612 continue
610 continue
613 if not b:
611 if not b:
614 u = b''
612 u = b''
615 else:
613 else:
616 if l not in self._vfsmap and c:
614 if l not in self._vfsmap and c:
617 self._report(
615 self._report(
618 b"couldn't remove %s: unknown cache location"
616 b"couldn't remove %s: unknown cache location"
619 b"%s\n" % (b, l)
617 b"%s\n" % (b, l)
620 )
618 )
621 continue
619 continue
622 vfs = self._vfsmap[l]
620 vfs = self._vfsmap[l]
623 base, name = vfs.split(b)
621 base, name = vfs.split(b)
624 assert name.startswith(self._journal), name
622 assert name.startswith(self._journal), name
625 uname = name.replace(self._journal, self._undoname, 1)
623 uname = name.replace(self._journal, self._undoname, 1)
626 u = vfs.reljoin(base, uname)
624 u = vfs.reljoin(base, uname)
627 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True)
625 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))
626 undobackupfile.write(b"%s\0%s\0%s\0%d\n" % (l, f, u, c))
629 undobackupfile.close()
627 undobackupfile.close()
630
628
631 def _abort(self):
629 def _abort(self):
632 self._count = 0
630 self._count = 0
633 self._usages = 0
631 self._usages = 0
634 self._file.close()
632 self._file.close()
635 self._backupsfile.close()
633 self._backupsfile.close()
636
634
637 try:
635 try:
638 if not self._entries and not self._backupentries:
636 if not self._entries and not self._backupentries:
639 if self._backupjournal:
637 if self._backupjournal:
640 self._opener.unlink(self._backupjournal)
638 self._opener.unlink(self._backupjournal)
641 if self._journal:
639 if self._journal:
642 self._opener.unlink(self._journal)
640 self._opener.unlink(self._journal)
643 return
641 return
644
642
645 self._report(_(b"transaction abort!\n"))
643 self._report(_(b"transaction abort!\n"))
646
644
647 try:
645 try:
648 for cat in sorted(self._abortcallback):
646 for cat in sorted(self._abortcallback):
649 self._abortcallback[cat](self)
647 self._abortcallback[cat](self)
650 # Prevent double usage and help clear cycles.
648 # Prevent double usage and help clear cycles.
651 self._abortcallback = None
649 self._abortcallback = None
652 _playback(
650 _playback(
653 self._journal,
651 self._journal,
654 self._report,
652 self._report,
655 self._opener,
653 self._opener,
656 self._vfsmap,
654 self._vfsmap,
657 self._entries,
655 self._entries,
658 self._backupentries,
656 self._backupentries,
659 False,
657 False,
660 checkambigfiles=self._checkambigfiles,
658 checkambigfiles=self._checkambigfiles,
661 )
659 )
662 self._report(_(b"rollback completed\n"))
660 self._report(_(b"rollback completed\n"))
663 except BaseException as exc:
661 except BaseException as exc:
664 self._report(_(b"rollback failed - please run hg recover\n"))
662 self._report(_(b"rollback failed - please run hg recover\n"))
665 self._report(
663 self._report(
666 _(b"(failure reason: %s)\n") % stringutil.forcebytestr(exc)
664 _(b"(failure reason: %s)\n") % stringutil.forcebytestr(exc)
667 )
665 )
668 finally:
666 finally:
669 self._journal = None
667 self._journal = None
670 self._releasefn(self, False) # notify failure of transaction
668 self._releasefn(self, False) # notify failure of transaction
671 self._releasefn = None # Help prevent cycles.
669 self._releasefn = None # Help prevent cycles.
672
670
673
671
674 def rollback(opener, vfsmap, file, report, checkambigfiles=None):
672 def rollback(opener, vfsmap, file, report, checkambigfiles=None):
675 """Rolls back the transaction contained in the given file
673 """Rolls back the transaction contained in the given file
676
674
677 Reads the entries in the specified file, and the corresponding
675 Reads the entries in the specified file, and the corresponding
678 '*.backupfiles' file, to recover from an incomplete transaction.
676 '*.backupfiles' file, to recover from an incomplete transaction.
679
677
680 * `file`: a file containing a list of entries, specifying where
678 * `file`: a file containing a list of entries, specifying where
681 to truncate each file. The file should contain a list of
679 to truncate each file. The file should contain a list of
682 file\0offset pairs, delimited by newlines. The corresponding
680 file\0offset pairs, delimited by newlines. The corresponding
683 '*.backupfiles' file should contain a list of file\0backupfile
681 '*.backupfiles' file should contain a list of file\0backupfile
684 pairs, delimited by \0.
682 pairs, delimited by \0.
685
683
686 `checkambigfiles` is a set of (path, vfs-location) tuples,
684 `checkambigfiles` is a set of (path, vfs-location) tuples,
687 which determine whether file stat ambiguity should be avoided at
685 which determine whether file stat ambiguity should be avoided at
688 restoring corresponded files.
686 restoring corresponded files.
689 """
687 """
690 entries = []
688 entries = []
691 backupentries = []
689 backupentries = []
692
690
693 fp = opener.open(file)
691 fp = opener.open(file)
694 lines = fp.readlines()
692 lines = fp.readlines()
695 fp.close()
693 fp.close()
696 for l in lines:
694 for l in lines:
697 try:
695 try:
698 f, o = l.split(b'\0')
696 f, o = l.split(b'\0')
699 entries.append((f, int(o)))
697 entries.append((f, int(o)))
700 except ValueError:
698 except ValueError:
701 report(
699 report(
702 _(b"couldn't read journal entry %r!\n") % pycompat.bytestr(l)
700 _(b"couldn't read journal entry %r!\n") % pycompat.bytestr(l)
703 )
701 )
704
702
705 backupjournal = b"%s.backupfiles" % file
703 backupjournal = b"%s.backupfiles" % file
706 if opener.exists(backupjournal):
704 if opener.exists(backupjournal):
707 fp = opener.open(backupjournal)
705 fp = opener.open(backupjournal)
708 lines = fp.readlines()
706 lines = fp.readlines()
709 if lines:
707 if lines:
710 ver = lines[0][:-1]
708 ver = lines[0][:-1]
711 if ver == (b'%d' % version):
709 if ver == (b'%d' % version):
712 for line in lines[1:]:
710 for line in lines[1:]:
713 if line:
711 if line:
714 # Shave off the trailing newline
712 # Shave off the trailing newline
715 line = line[:-1]
713 line = line[:-1]
716 l, f, b, c = line.split(b'\0')
714 l, f, b, c = line.split(b'\0')
717 backupentries.append((l, f, b, bool(c)))
715 backupentries.append((l, f, b, bool(c)))
718 else:
716 else:
719 report(
717 report(
720 _(
718 _(
721 b"journal was created by a different version of "
719 b"journal was created by a different version of "
722 b"Mercurial\n"
720 b"Mercurial\n"
723 )
721 )
724 )
722 )
725
723
726 _playback(
724 _playback(
727 file,
725 file,
728 report,
726 report,
729 opener,
727 opener,
730 vfsmap,
728 vfsmap,
731 entries,
729 entries,
732 backupentries,
730 backupentries,
733 checkambigfiles=checkambigfiles,
731 checkambigfiles=checkambigfiles,
734 )
732 )
General Comments 0
You need to be logged in to leave comments. Login now