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