##// END OF EJS Templates
flagutil: move insertflagprocessor to the new module (API)
marmoute -
r42957:5109217a default
parent child Browse files
Show More
@@ -1,2698 +1,2686 b''
1 1 # revlog.py - storage back-end for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """Storage back-end for Mercurial.
9 9
10 10 This provides efficient delta storage with O(1) retrieve and append
11 11 and O(changes) merge between branches.
12 12 """
13 13
14 14 from __future__ import absolute_import
15 15
16 16 import collections
17 17 import contextlib
18 18 import errno
19 19 import io
20 20 import os
21 21 import struct
22 22 import zlib
23 23
24 24 # import stuff from node for others to import from revlog
25 25 from .node import (
26 26 bin,
27 27 hex,
28 28 nullhex,
29 29 nullid,
30 30 nullrev,
31 31 short,
32 32 wdirfilenodeids,
33 33 wdirhex,
34 34 wdirid,
35 35 wdirrev,
36 36 )
37 37 from .i18n import _
38 38 from .revlogutils.constants import (
39 39 FLAG_GENERALDELTA,
40 40 FLAG_INLINE_DATA,
41 41 REVLOGV0,
42 42 REVLOGV1,
43 43 REVLOGV1_FLAGS,
44 44 REVLOGV2,
45 45 REVLOGV2_FLAGS,
46 46 REVLOG_DEFAULT_FLAGS,
47 47 REVLOG_DEFAULT_FORMAT,
48 48 REVLOG_DEFAULT_VERSION,
49 49 )
50 50 from .revlogutils.flagutil import (
51 51 REVIDX_DEFAULT_FLAGS,
52 52 REVIDX_ELLIPSIS,
53 53 REVIDX_EXTSTORED,
54 54 REVIDX_FLAGS_ORDER,
55 55 REVIDX_ISCENSORED,
56 56 REVIDX_RAWTEXT_CHANGING_FLAGS,
57 57 )
58 58 from .thirdparty import (
59 59 attr,
60 60 )
61 61 from . import (
62 62 ancestor,
63 63 dagop,
64 64 error,
65 65 mdiff,
66 66 policy,
67 67 pycompat,
68 68 repository,
69 69 templatefilters,
70 70 util,
71 71 )
72 72 from .revlogutils import (
73 73 deltas as deltautil,
74 74 flagutil,
75 75 )
76 76 from .utils import (
77 77 interfaceutil,
78 78 storageutil,
79 79 stringutil,
80 80 )
81 81
82 82 # blanked usage of all the name to prevent pyflakes constraints
83 83 # We need these name available in the module for extensions.
84 84 REVLOGV0
85 85 REVLOGV1
86 86 REVLOGV2
87 87 FLAG_INLINE_DATA
88 88 FLAG_GENERALDELTA
89 89 REVLOG_DEFAULT_FLAGS
90 90 REVLOG_DEFAULT_FORMAT
91 91 REVLOG_DEFAULT_VERSION
92 92 REVLOGV1_FLAGS
93 93 REVLOGV2_FLAGS
94 94 REVIDX_ISCENSORED
95 95 REVIDX_ELLIPSIS
96 96 REVIDX_EXTSTORED
97 97 REVIDX_DEFAULT_FLAGS
98 98 REVIDX_FLAGS_ORDER
99 99 REVIDX_RAWTEXT_CHANGING_FLAGS
100 100
101 101 parsers = policy.importmod(r'parsers')
102 102 rustancestor = policy.importrust(r'ancestor')
103 103 rustdagop = policy.importrust(r'dagop')
104 104
105 105 # Aliased for performance.
106 106 _zlibdecompress = zlib.decompress
107 107
108 108 # max size of revlog with inline data
109 109 _maxinline = 131072
110 110 _chunksize = 1048576
111 111
112 112 # Flag processors for REVIDX_ELLIPSIS.
113 113 def ellipsisreadprocessor(rl, text):
114 114 return text, False
115 115
116 116 def ellipsiswriteprocessor(rl, text):
117 117 return text, False
118 118
119 119 def ellipsisrawprocessor(rl, text):
120 120 return False
121 121
122 122 ellipsisprocessor = (
123 123 ellipsisreadprocessor,
124 124 ellipsiswriteprocessor,
125 125 ellipsisrawprocessor,
126 126 )
127 127
128 128 def addflagprocessor(flag, processor):
129 129 """Register a flag processor on a revision data flag.
130 130
131 131 Invariant:
132 132 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
133 133 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
134 134 - Only one flag processor can be registered on a specific flag.
135 135 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
136 136 following signatures:
137 137 - (read) f(self, rawtext) -> text, bool
138 138 - (write) f(self, text) -> rawtext, bool
139 139 - (raw) f(self, rawtext) -> bool
140 140 "text" is presented to the user. "rawtext" is stored in revlog data, not
141 141 directly visible to the user.
142 142 The boolean returned by these transforms is used to determine whether
143 143 the returned text can be used for hash integrity checking. For example,
144 144 if "write" returns False, then "text" is used to generate hash. If
145 145 "write" returns True, that basically means "rawtext" returned by "write"
146 146 should be used to generate hash. Usually, "write" and "read" return
147 147 different booleans. And "raw" returns a same boolean as "write".
148 148
149 149 Note: The 'raw' transform is used for changegroup generation and in some
150 150 debug commands. In this case the transform only indicates whether the
151 151 contents can be used for hash integrity checks.
152 152 """
153 _insertflagprocessor(flag, processor, flagutil.flagprocessors)
154
155 def _insertflagprocessor(flag, processor, flagprocessors):
156 if not flag & flagutil.REVIDX_KNOWN_FLAGS:
157 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
158 raise error.ProgrammingError(msg)
159 if flag not in REVIDX_FLAGS_ORDER:
160 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
161 raise error.ProgrammingError(msg)
162 if flag in flagprocessors:
163 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
164 raise error.Abort(msg)
165 flagprocessors[flag] = processor
153 flagutil.insertflagprocessor(flag, processor, flagutil.flagprocessors)
166 154
167 155 def getoffset(q):
168 156 return int(q >> 16)
169 157
170 158 def gettype(q):
171 159 return int(q & 0xFFFF)
172 160
173 161 def offset_type(offset, type):
174 162 if (type & ~flagutil.REVIDX_KNOWN_FLAGS) != 0:
175 163 raise ValueError('unknown revlog index flags')
176 164 return int(int(offset) << 16 | type)
177 165
178 166 @attr.s(slots=True, frozen=True)
179 167 class _revisioninfo(object):
180 168 """Information about a revision that allows building its fulltext
181 169 node: expected hash of the revision
182 170 p1, p2: parent revs of the revision
183 171 btext: built text cache consisting of a one-element list
184 172 cachedelta: (baserev, uncompressed_delta) or None
185 173 flags: flags associated to the revision storage
186 174
187 175 One of btext[0] or cachedelta must be set.
188 176 """
189 177 node = attr.ib()
190 178 p1 = attr.ib()
191 179 p2 = attr.ib()
192 180 btext = attr.ib()
193 181 textlen = attr.ib()
194 182 cachedelta = attr.ib()
195 183 flags = attr.ib()
196 184
197 185 @interfaceutil.implementer(repository.irevisiondelta)
198 186 @attr.s(slots=True)
199 187 class revlogrevisiondelta(object):
200 188 node = attr.ib()
201 189 p1node = attr.ib()
202 190 p2node = attr.ib()
203 191 basenode = attr.ib()
204 192 flags = attr.ib()
205 193 baserevisionsize = attr.ib()
206 194 revision = attr.ib()
207 195 delta = attr.ib()
208 196 linknode = attr.ib(default=None)
209 197
210 198 @interfaceutil.implementer(repository.iverifyproblem)
211 199 @attr.s(frozen=True)
212 200 class revlogproblem(object):
213 201 warning = attr.ib(default=None)
214 202 error = attr.ib(default=None)
215 203 node = attr.ib(default=None)
216 204
217 205 # index v0:
218 206 # 4 bytes: offset
219 207 # 4 bytes: compressed length
220 208 # 4 bytes: base rev
221 209 # 4 bytes: link rev
222 210 # 20 bytes: parent 1 nodeid
223 211 # 20 bytes: parent 2 nodeid
224 212 # 20 bytes: nodeid
225 213 indexformatv0 = struct.Struct(">4l20s20s20s")
226 214 indexformatv0_pack = indexformatv0.pack
227 215 indexformatv0_unpack = indexformatv0.unpack
228 216
229 217 class revlogoldindex(list):
230 218 def __getitem__(self, i):
231 219 if i == -1:
232 220 return (0, 0, 0, -1, -1, -1, -1, nullid)
233 221 return list.__getitem__(self, i)
234 222
235 223 class revlogoldio(object):
236 224 def __init__(self):
237 225 self.size = indexformatv0.size
238 226
239 227 def parseindex(self, data, inline):
240 228 s = self.size
241 229 index = []
242 230 nodemap = {nullid: nullrev}
243 231 n = off = 0
244 232 l = len(data)
245 233 while off + s <= l:
246 234 cur = data[off:off + s]
247 235 off += s
248 236 e = indexformatv0_unpack(cur)
249 237 # transform to revlogv1 format
250 238 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
251 239 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
252 240 index.append(e2)
253 241 nodemap[e[6]] = n
254 242 n += 1
255 243
256 244 return revlogoldindex(index), nodemap, None
257 245
258 246 def packentry(self, entry, node, version, rev):
259 247 if gettype(entry[0]):
260 248 raise error.RevlogError(_('index entry flags need revlog '
261 249 'version 1'))
262 250 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
263 251 node(entry[5]), node(entry[6]), entry[7])
264 252 return indexformatv0_pack(*e2)
265 253
266 254 # index ng:
267 255 # 6 bytes: offset
268 256 # 2 bytes: flags
269 257 # 4 bytes: compressed length
270 258 # 4 bytes: uncompressed length
271 259 # 4 bytes: base rev
272 260 # 4 bytes: link rev
273 261 # 4 bytes: parent 1 rev
274 262 # 4 bytes: parent 2 rev
275 263 # 32 bytes: nodeid
276 264 indexformatng = struct.Struct(">Qiiiiii20s12x")
277 265 indexformatng_pack = indexformatng.pack
278 266 versionformat = struct.Struct(">I")
279 267 versionformat_pack = versionformat.pack
280 268 versionformat_unpack = versionformat.unpack
281 269
282 270 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
283 271 # signed integer)
284 272 _maxentrysize = 0x7fffffff
285 273
286 274 class revlogio(object):
287 275 def __init__(self):
288 276 self.size = indexformatng.size
289 277
290 278 def parseindex(self, data, inline):
291 279 # call the C implementation to parse the index data
292 280 index, cache = parsers.parse_index2(data, inline)
293 281 return index, getattr(index, 'nodemap', None), cache
294 282
295 283 def packentry(self, entry, node, version, rev):
296 284 p = indexformatng_pack(*entry)
297 285 if rev == 0:
298 286 p = versionformat_pack(version) + p[4:]
299 287 return p
300 288
301 289 class revlog(object):
302 290 """
303 291 the underlying revision storage object
304 292
305 293 A revlog consists of two parts, an index and the revision data.
306 294
307 295 The index is a file with a fixed record size containing
308 296 information on each revision, including its nodeid (hash), the
309 297 nodeids of its parents, the position and offset of its data within
310 298 the data file, and the revision it's based on. Finally, each entry
311 299 contains a linkrev entry that can serve as a pointer to external
312 300 data.
313 301
314 302 The revision data itself is a linear collection of data chunks.
315 303 Each chunk represents a revision and is usually represented as a
316 304 delta against the previous chunk. To bound lookup time, runs of
317 305 deltas are limited to about 2 times the length of the original
318 306 version data. This makes retrieval of a version proportional to
319 307 its size, or O(1) relative to the number of revisions.
320 308
321 309 Both pieces of the revlog are written to in an append-only
322 310 fashion, which means we never need to rewrite a file to insert or
323 311 remove data, and can use some simple techniques to avoid the need
324 312 for locking while reading.
325 313
326 314 If checkambig, indexfile is opened with checkambig=True at
327 315 writing, to avoid file stat ambiguity.
328 316
329 317 If mmaplargeindex is True, and an mmapindexthreshold is set, the
330 318 index will be mmapped rather than read if it is larger than the
331 319 configured threshold.
332 320
333 321 If censorable is True, the revlog can have censored revisions.
334 322
335 323 If `upperboundcomp` is not None, this is the expected maximal gain from
336 324 compression for the data content.
337 325 """
338 326 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
339 327 mmaplargeindex=False, censorable=False,
340 328 upperboundcomp=None):
341 329 """
342 330 create a revlog object
343 331
344 332 opener is a function that abstracts the file opening operation
345 333 and can be used to implement COW semantics or the like.
346 334
347 335 """
348 336 self.upperboundcomp = upperboundcomp
349 337 self.indexfile = indexfile
350 338 self.datafile = datafile or (indexfile[:-2] + ".d")
351 339 self.opener = opener
352 340 # When True, indexfile is opened with checkambig=True at writing, to
353 341 # avoid file stat ambiguity.
354 342 self._checkambig = checkambig
355 343 self._mmaplargeindex = mmaplargeindex
356 344 self._censorable = censorable
357 345 # 3-tuple of (node, rev, text) for a raw revision.
358 346 self._revisioncache = None
359 347 # Maps rev to chain base rev.
360 348 self._chainbasecache = util.lrucachedict(100)
361 349 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
362 350 self._chunkcache = (0, '')
363 351 # How much data to read and cache into the raw revlog data cache.
364 352 self._chunkcachesize = 65536
365 353 self._maxchainlen = None
366 354 self._deltabothparents = True
367 355 self.index = []
368 356 # Mapping of partial identifiers to full nodes.
369 357 self._pcache = {}
370 358 # Mapping of revision integer to full node.
371 359 self._nodecache = {nullid: nullrev}
372 360 self._nodepos = None
373 361 self._compengine = 'zlib'
374 362 self._compengineopts = {}
375 363 self._maxdeltachainspan = -1
376 364 self._withsparseread = False
377 365 self._sparserevlog = False
378 366 self._srdensitythreshold = 0.50
379 367 self._srmingapsize = 262144
380 368
381 369 # Make copy of flag processors so each revlog instance can support
382 370 # custom flags.
383 371 self._flagprocessors = dict(flagutil.flagprocessors)
384 372
385 373 # 2-tuple of file handles being used for active writing.
386 374 self._writinghandles = None
387 375
388 376 self._loadindex()
389 377
390 378 def _loadindex(self):
391 379 mmapindexthreshold = None
392 380 opts = getattr(self.opener, 'options', {}) or {}
393 381
394 382 if 'revlogv2' in opts:
395 383 newversionflags = REVLOGV2 | FLAG_INLINE_DATA
396 384 elif 'revlogv1' in opts:
397 385 newversionflags = REVLOGV1 | FLAG_INLINE_DATA
398 386 if 'generaldelta' in opts:
399 387 newversionflags |= FLAG_GENERALDELTA
400 388 elif getattr(self.opener, 'options', None) is not None:
401 389 # If options provided but no 'revlog*' found, the repository
402 390 # would have no 'requires' file in it, which means we have to
403 391 # stick to the old format.
404 392 newversionflags = REVLOGV0
405 393 else:
406 394 newversionflags = REVLOG_DEFAULT_VERSION
407 395
408 396 if 'chunkcachesize' in opts:
409 397 self._chunkcachesize = opts['chunkcachesize']
410 398 if 'maxchainlen' in opts:
411 399 self._maxchainlen = opts['maxchainlen']
412 400 if 'deltabothparents' in opts:
413 401 self._deltabothparents = opts['deltabothparents']
414 402 self._lazydelta = bool(opts.get('lazydelta', True))
415 403 self._lazydeltabase = False
416 404 if self._lazydelta:
417 405 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
418 406 if 'compengine' in opts:
419 407 self._compengine = opts['compengine']
420 408 if 'zlib.level' in opts:
421 409 self._compengineopts['zlib.level'] = opts['zlib.level']
422 410 if 'zstd.level' in opts:
423 411 self._compengineopts['zstd.level'] = opts['zstd.level']
424 412 if 'maxdeltachainspan' in opts:
425 413 self._maxdeltachainspan = opts['maxdeltachainspan']
426 414 if self._mmaplargeindex and 'mmapindexthreshold' in opts:
427 415 mmapindexthreshold = opts['mmapindexthreshold']
428 416 self._sparserevlog = bool(opts.get('sparse-revlog', False))
429 417 withsparseread = bool(opts.get('with-sparse-read', False))
430 418 # sparse-revlog forces sparse-read
431 419 self._withsparseread = self._sparserevlog or withsparseread
432 420 if 'sparse-read-density-threshold' in opts:
433 421 self._srdensitythreshold = opts['sparse-read-density-threshold']
434 422 if 'sparse-read-min-gap-size' in opts:
435 423 self._srmingapsize = opts['sparse-read-min-gap-size']
436 424 if opts.get('enableellipsis'):
437 425 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
438 426
439 427 # revlog v0 doesn't have flag processors
440 428 for flag, processor in opts.get(b'flagprocessors', {}).iteritems():
441 _insertflagprocessor(flag, processor, self._flagprocessors)
429 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
442 430
443 431 if self._chunkcachesize <= 0:
444 432 raise error.RevlogError(_('revlog chunk cache size %r is not '
445 433 'greater than 0') % self._chunkcachesize)
446 434 elif self._chunkcachesize & (self._chunkcachesize - 1):
447 435 raise error.RevlogError(_('revlog chunk cache size %r is not a '
448 436 'power of 2') % self._chunkcachesize)
449 437
450 438 indexdata = ''
451 439 self._initempty = True
452 440 try:
453 441 with self._indexfp() as f:
454 442 if (mmapindexthreshold is not None and
455 443 self.opener.fstat(f).st_size >= mmapindexthreshold):
456 444 # TODO: should .close() to release resources without
457 445 # relying on Python GC
458 446 indexdata = util.buffer(util.mmapread(f))
459 447 else:
460 448 indexdata = f.read()
461 449 if len(indexdata) > 0:
462 450 versionflags = versionformat_unpack(indexdata[:4])[0]
463 451 self._initempty = False
464 452 else:
465 453 versionflags = newversionflags
466 454 except IOError as inst:
467 455 if inst.errno != errno.ENOENT:
468 456 raise
469 457
470 458 versionflags = newversionflags
471 459
472 460 self.version = versionflags
473 461
474 462 flags = versionflags & ~0xFFFF
475 463 fmt = versionflags & 0xFFFF
476 464
477 465 if fmt == REVLOGV0:
478 466 if flags:
479 467 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
480 468 'revlog %s') %
481 469 (flags >> 16, fmt, self.indexfile))
482 470
483 471 self._inline = False
484 472 self._generaldelta = False
485 473
486 474 elif fmt == REVLOGV1:
487 475 if flags & ~REVLOGV1_FLAGS:
488 476 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
489 477 'revlog %s') %
490 478 (flags >> 16, fmt, self.indexfile))
491 479
492 480 self._inline = versionflags & FLAG_INLINE_DATA
493 481 self._generaldelta = versionflags & FLAG_GENERALDELTA
494 482
495 483 elif fmt == REVLOGV2:
496 484 if flags & ~REVLOGV2_FLAGS:
497 485 raise error.RevlogError(_('unknown flags (%#04x) in version %d '
498 486 'revlog %s') %
499 487 (flags >> 16, fmt, self.indexfile))
500 488
501 489 self._inline = versionflags & FLAG_INLINE_DATA
502 490 # generaldelta implied by version 2 revlogs.
503 491 self._generaldelta = True
504 492
505 493 else:
506 494 raise error.RevlogError(_('unknown version (%d) in revlog %s') %
507 495 (fmt, self.indexfile))
508 496 # sparse-revlog can't be on without general-delta (issue6056)
509 497 if not self._generaldelta:
510 498 self._sparserevlog = False
511 499
512 500 self._storedeltachains = True
513 501
514 502 self._io = revlogio()
515 503 if self.version == REVLOGV0:
516 504 self._io = revlogoldio()
517 505 try:
518 506 d = self._io.parseindex(indexdata, self._inline)
519 507 except (ValueError, IndexError):
520 508 raise error.RevlogError(_("index %s is corrupted") %
521 509 self.indexfile)
522 510 self.index, nodemap, self._chunkcache = d
523 511 if nodemap is not None:
524 512 self.nodemap = self._nodecache = nodemap
525 513 if not self._chunkcache:
526 514 self._chunkclear()
527 515 # revnum -> (chain-length, sum-delta-length)
528 516 self._chaininfocache = {}
529 517 # revlog header -> revlog compressor
530 518 self._decompressors = {}
531 519
532 520 @util.propertycache
533 521 def _compressor(self):
534 522 engine = util.compengines[self._compengine]
535 523 return engine.revlogcompressor(self._compengineopts)
536 524
537 525 def _indexfp(self, mode='r'):
538 526 """file object for the revlog's index file"""
539 527 args = {r'mode': mode}
540 528 if mode != 'r':
541 529 args[r'checkambig'] = self._checkambig
542 530 if mode == 'w':
543 531 args[r'atomictemp'] = True
544 532 return self.opener(self.indexfile, **args)
545 533
546 534 def _datafp(self, mode='r'):
547 535 """file object for the revlog's data file"""
548 536 return self.opener(self.datafile, mode=mode)
549 537
550 538 @contextlib.contextmanager
551 539 def _datareadfp(self, existingfp=None):
552 540 """file object suitable to read data"""
553 541 # Use explicit file handle, if given.
554 542 if existingfp is not None:
555 543 yield existingfp
556 544
557 545 # Use a file handle being actively used for writes, if available.
558 546 # There is some danger to doing this because reads will seek the
559 547 # file. However, _writeentry() performs a SEEK_END before all writes,
560 548 # so we should be safe.
561 549 elif self._writinghandles:
562 550 if self._inline:
563 551 yield self._writinghandles[0]
564 552 else:
565 553 yield self._writinghandles[1]
566 554
567 555 # Otherwise open a new file handle.
568 556 else:
569 557 if self._inline:
570 558 func = self._indexfp
571 559 else:
572 560 func = self._datafp
573 561 with func() as fp:
574 562 yield fp
575 563
576 564 def tip(self):
577 565 return self.node(len(self.index) - 1)
578 566 def __contains__(self, rev):
579 567 return 0 <= rev < len(self)
580 568 def __len__(self):
581 569 return len(self.index)
582 570 def __iter__(self):
583 571 return iter(pycompat.xrange(len(self)))
584 572 def revs(self, start=0, stop=None):
585 573 """iterate over all rev in this revlog (from start to stop)"""
586 574 return storageutil.iterrevs(len(self), start=start, stop=stop)
587 575
588 576 @util.propertycache
589 577 def nodemap(self):
590 578 if self.index:
591 579 # populate mapping down to the initial node
592 580 node0 = self.index[0][7] # get around changelog filtering
593 581 self.rev(node0)
594 582 return self._nodecache
595 583
596 584 def hasnode(self, node):
597 585 try:
598 586 self.rev(node)
599 587 return True
600 588 except KeyError:
601 589 return False
602 590
603 591 def candelta(self, baserev, rev):
604 592 """whether two revisions (baserev, rev) can be delta-ed or not"""
605 593 # Disable delta if either rev requires a content-changing flag
606 594 # processor (ex. LFS). This is because such flag processor can alter
607 595 # the rawtext content that the delta will be based on, and two clients
608 596 # could have a same revlog node with different flags (i.e. different
609 597 # rawtext contents) and the delta could be incompatible.
610 598 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
611 599 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
612 600 return False
613 601 return True
614 602
615 603 def clearcaches(self):
616 604 self._revisioncache = None
617 605 self._chainbasecache.clear()
618 606 self._chunkcache = (0, '')
619 607 self._pcache = {}
620 608
621 609 try:
622 610 # If we are using the native C version, you are in a fun case
623 611 # where self.index, self.nodemap and self._nodecaches is the same
624 612 # object.
625 613 self._nodecache.clearcaches()
626 614 except AttributeError:
627 615 self._nodecache = {nullid: nullrev}
628 616 self._nodepos = None
629 617
630 618 def rev(self, node):
631 619 try:
632 620 return self._nodecache[node]
633 621 except TypeError:
634 622 raise
635 623 except error.RevlogError:
636 624 # parsers.c radix tree lookup failed
637 625 if node == wdirid or node in wdirfilenodeids:
638 626 raise error.WdirUnsupported
639 627 raise error.LookupError(node, self.indexfile, _('no node'))
640 628 except KeyError:
641 629 # pure python cache lookup failed
642 630 n = self._nodecache
643 631 i = self.index
644 632 p = self._nodepos
645 633 if p is None:
646 634 p = len(i) - 1
647 635 else:
648 636 assert p < len(i)
649 637 for r in pycompat.xrange(p, -1, -1):
650 638 v = i[r][7]
651 639 n[v] = r
652 640 if v == node:
653 641 self._nodepos = r - 1
654 642 return r
655 643 if node == wdirid or node in wdirfilenodeids:
656 644 raise error.WdirUnsupported
657 645 raise error.LookupError(node, self.indexfile, _('no node'))
658 646
659 647 # Accessors for index entries.
660 648
661 649 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
662 650 # are flags.
663 651 def start(self, rev):
664 652 return int(self.index[rev][0] >> 16)
665 653
666 654 def flags(self, rev):
667 655 return self.index[rev][0] & 0xFFFF
668 656
669 657 def length(self, rev):
670 658 return self.index[rev][1]
671 659
672 660 def rawsize(self, rev):
673 661 """return the length of the uncompressed text for a given revision"""
674 662 l = self.index[rev][2]
675 663 if l >= 0:
676 664 return l
677 665
678 666 t = self.revision(rev, raw=True)
679 667 return len(t)
680 668
681 669 def size(self, rev):
682 670 """length of non-raw text (processed by a "read" flag processor)"""
683 671 # fast path: if no "read" flag processor could change the content,
684 672 # size is rawsize. note: ELLIPSIS is known to not change the content.
685 673 flags = self.flags(rev)
686 674 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
687 675 return self.rawsize(rev)
688 676
689 677 return len(self.revision(rev, raw=False))
690 678
691 679 def chainbase(self, rev):
692 680 base = self._chainbasecache.get(rev)
693 681 if base is not None:
694 682 return base
695 683
696 684 index = self.index
697 685 iterrev = rev
698 686 base = index[iterrev][3]
699 687 while base != iterrev:
700 688 iterrev = base
701 689 base = index[iterrev][3]
702 690
703 691 self._chainbasecache[rev] = base
704 692 return base
705 693
706 694 def linkrev(self, rev):
707 695 return self.index[rev][4]
708 696
709 697 def parentrevs(self, rev):
710 698 try:
711 699 entry = self.index[rev]
712 700 except IndexError:
713 701 if rev == wdirrev:
714 702 raise error.WdirUnsupported
715 703 raise
716 704
717 705 return entry[5], entry[6]
718 706
719 707 # fast parentrevs(rev) where rev isn't filtered
720 708 _uncheckedparentrevs = parentrevs
721 709
722 710 def node(self, rev):
723 711 try:
724 712 return self.index[rev][7]
725 713 except IndexError:
726 714 if rev == wdirrev:
727 715 raise error.WdirUnsupported
728 716 raise
729 717
730 718 # Derived from index values.
731 719
732 720 def end(self, rev):
733 721 return self.start(rev) + self.length(rev)
734 722
735 723 def parents(self, node):
736 724 i = self.index
737 725 d = i[self.rev(node)]
738 726 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
739 727
740 728 def chainlen(self, rev):
741 729 return self._chaininfo(rev)[0]
742 730
743 731 def _chaininfo(self, rev):
744 732 chaininfocache = self._chaininfocache
745 733 if rev in chaininfocache:
746 734 return chaininfocache[rev]
747 735 index = self.index
748 736 generaldelta = self._generaldelta
749 737 iterrev = rev
750 738 e = index[iterrev]
751 739 clen = 0
752 740 compresseddeltalen = 0
753 741 while iterrev != e[3]:
754 742 clen += 1
755 743 compresseddeltalen += e[1]
756 744 if generaldelta:
757 745 iterrev = e[3]
758 746 else:
759 747 iterrev -= 1
760 748 if iterrev in chaininfocache:
761 749 t = chaininfocache[iterrev]
762 750 clen += t[0]
763 751 compresseddeltalen += t[1]
764 752 break
765 753 e = index[iterrev]
766 754 else:
767 755 # Add text length of base since decompressing that also takes
768 756 # work. For cache hits the length is already included.
769 757 compresseddeltalen += e[1]
770 758 r = (clen, compresseddeltalen)
771 759 chaininfocache[rev] = r
772 760 return r
773 761
774 762 def _deltachain(self, rev, stoprev=None):
775 763 """Obtain the delta chain for a revision.
776 764
777 765 ``stoprev`` specifies a revision to stop at. If not specified, we
778 766 stop at the base of the chain.
779 767
780 768 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
781 769 revs in ascending order and ``stopped`` is a bool indicating whether
782 770 ``stoprev`` was hit.
783 771 """
784 772 # Try C implementation.
785 773 try:
786 774 return self.index.deltachain(rev, stoprev, self._generaldelta)
787 775 except AttributeError:
788 776 pass
789 777
790 778 chain = []
791 779
792 780 # Alias to prevent attribute lookup in tight loop.
793 781 index = self.index
794 782 generaldelta = self._generaldelta
795 783
796 784 iterrev = rev
797 785 e = index[iterrev]
798 786 while iterrev != e[3] and iterrev != stoprev:
799 787 chain.append(iterrev)
800 788 if generaldelta:
801 789 iterrev = e[3]
802 790 else:
803 791 iterrev -= 1
804 792 e = index[iterrev]
805 793
806 794 if iterrev == stoprev:
807 795 stopped = True
808 796 else:
809 797 chain.append(iterrev)
810 798 stopped = False
811 799
812 800 chain.reverse()
813 801 return chain, stopped
814 802
815 803 def ancestors(self, revs, stoprev=0, inclusive=False):
816 804 """Generate the ancestors of 'revs' in reverse revision order.
817 805 Does not generate revs lower than stoprev.
818 806
819 807 See the documentation for ancestor.lazyancestors for more details."""
820 808
821 809 # first, make sure start revisions aren't filtered
822 810 revs = list(revs)
823 811 checkrev = self.node
824 812 for r in revs:
825 813 checkrev(r)
826 814 # and we're sure ancestors aren't filtered as well
827 815
828 816 if rustancestor is not None:
829 817 lazyancestors = rustancestor.LazyAncestors
830 818 arg = self.index
831 819 elif util.safehasattr(parsers, 'rustlazyancestors'):
832 820 lazyancestors = ancestor.rustlazyancestors
833 821 arg = self.index
834 822 else:
835 823 lazyancestors = ancestor.lazyancestors
836 824 arg = self._uncheckedparentrevs
837 825 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
838 826
839 827 def descendants(self, revs):
840 828 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
841 829
842 830 def findcommonmissing(self, common=None, heads=None):
843 831 """Return a tuple of the ancestors of common and the ancestors of heads
844 832 that are not ancestors of common. In revset terminology, we return the
845 833 tuple:
846 834
847 835 ::common, (::heads) - (::common)
848 836
849 837 The list is sorted by revision number, meaning it is
850 838 topologically sorted.
851 839
852 840 'heads' and 'common' are both lists of node IDs. If heads is
853 841 not supplied, uses all of the revlog's heads. If common is not
854 842 supplied, uses nullid."""
855 843 if common is None:
856 844 common = [nullid]
857 845 if heads is None:
858 846 heads = self.heads()
859 847
860 848 common = [self.rev(n) for n in common]
861 849 heads = [self.rev(n) for n in heads]
862 850
863 851 # we want the ancestors, but inclusive
864 852 class lazyset(object):
865 853 def __init__(self, lazyvalues):
866 854 self.addedvalues = set()
867 855 self.lazyvalues = lazyvalues
868 856
869 857 def __contains__(self, value):
870 858 return value in self.addedvalues or value in self.lazyvalues
871 859
872 860 def __iter__(self):
873 861 added = self.addedvalues
874 862 for r in added:
875 863 yield r
876 864 for r in self.lazyvalues:
877 865 if not r in added:
878 866 yield r
879 867
880 868 def add(self, value):
881 869 self.addedvalues.add(value)
882 870
883 871 def update(self, values):
884 872 self.addedvalues.update(values)
885 873
886 874 has = lazyset(self.ancestors(common))
887 875 has.add(nullrev)
888 876 has.update(common)
889 877
890 878 # take all ancestors from heads that aren't in has
891 879 missing = set()
892 880 visit = collections.deque(r for r in heads if r not in has)
893 881 while visit:
894 882 r = visit.popleft()
895 883 if r in missing:
896 884 continue
897 885 else:
898 886 missing.add(r)
899 887 for p in self.parentrevs(r):
900 888 if p not in has:
901 889 visit.append(p)
902 890 missing = list(missing)
903 891 missing.sort()
904 892 return has, [self.node(miss) for miss in missing]
905 893
906 894 def incrementalmissingrevs(self, common=None):
907 895 """Return an object that can be used to incrementally compute the
908 896 revision numbers of the ancestors of arbitrary sets that are not
909 897 ancestors of common. This is an ancestor.incrementalmissingancestors
910 898 object.
911 899
912 900 'common' is a list of revision numbers. If common is not supplied, uses
913 901 nullrev.
914 902 """
915 903 if common is None:
916 904 common = [nullrev]
917 905
918 906 if rustancestor is not None:
919 907 return rustancestor.MissingAncestors(self.index, common)
920 908 return ancestor.incrementalmissingancestors(self.parentrevs, common)
921 909
922 910 def findmissingrevs(self, common=None, heads=None):
923 911 """Return the revision numbers of the ancestors of heads that
924 912 are not ancestors of common.
925 913
926 914 More specifically, return a list of revision numbers corresponding to
927 915 nodes N such that every N satisfies the following constraints:
928 916
929 917 1. N is an ancestor of some node in 'heads'
930 918 2. N is not an ancestor of any node in 'common'
931 919
932 920 The list is sorted by revision number, meaning it is
933 921 topologically sorted.
934 922
935 923 'heads' and 'common' are both lists of revision numbers. If heads is
936 924 not supplied, uses all of the revlog's heads. If common is not
937 925 supplied, uses nullid."""
938 926 if common is None:
939 927 common = [nullrev]
940 928 if heads is None:
941 929 heads = self.headrevs()
942 930
943 931 inc = self.incrementalmissingrevs(common=common)
944 932 return inc.missingancestors(heads)
945 933
946 934 def findmissing(self, common=None, heads=None):
947 935 """Return the ancestors of heads that are not ancestors of common.
948 936
949 937 More specifically, return a list of nodes N such that every N
950 938 satisfies the following constraints:
951 939
952 940 1. N is an ancestor of some node in 'heads'
953 941 2. N is not an ancestor of any node in 'common'
954 942
955 943 The list is sorted by revision number, meaning it is
956 944 topologically sorted.
957 945
958 946 'heads' and 'common' are both lists of node IDs. If heads is
959 947 not supplied, uses all of the revlog's heads. If common is not
960 948 supplied, uses nullid."""
961 949 if common is None:
962 950 common = [nullid]
963 951 if heads is None:
964 952 heads = self.heads()
965 953
966 954 common = [self.rev(n) for n in common]
967 955 heads = [self.rev(n) for n in heads]
968 956
969 957 inc = self.incrementalmissingrevs(common=common)
970 958 return [self.node(r) for r in inc.missingancestors(heads)]
971 959
972 960 def nodesbetween(self, roots=None, heads=None):
973 961 """Return a topological path from 'roots' to 'heads'.
974 962
975 963 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
976 964 topologically sorted list of all nodes N that satisfy both of
977 965 these constraints:
978 966
979 967 1. N is a descendant of some node in 'roots'
980 968 2. N is an ancestor of some node in 'heads'
981 969
982 970 Every node is considered to be both a descendant and an ancestor
983 971 of itself, so every reachable node in 'roots' and 'heads' will be
984 972 included in 'nodes'.
985 973
986 974 'outroots' is the list of reachable nodes in 'roots', i.e., the
987 975 subset of 'roots' that is returned in 'nodes'. Likewise,
988 976 'outheads' is the subset of 'heads' that is also in 'nodes'.
989 977
990 978 'roots' and 'heads' are both lists of node IDs. If 'roots' is
991 979 unspecified, uses nullid as the only root. If 'heads' is
992 980 unspecified, uses list of all of the revlog's heads."""
993 981 nonodes = ([], [], [])
994 982 if roots is not None:
995 983 roots = list(roots)
996 984 if not roots:
997 985 return nonodes
998 986 lowestrev = min([self.rev(n) for n in roots])
999 987 else:
1000 988 roots = [nullid] # Everybody's a descendant of nullid
1001 989 lowestrev = nullrev
1002 990 if (lowestrev == nullrev) and (heads is None):
1003 991 # We want _all_ the nodes!
1004 992 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1005 993 if heads is None:
1006 994 # All nodes are ancestors, so the latest ancestor is the last
1007 995 # node.
1008 996 highestrev = len(self) - 1
1009 997 # Set ancestors to None to signal that every node is an ancestor.
1010 998 ancestors = None
1011 999 # Set heads to an empty dictionary for later discovery of heads
1012 1000 heads = {}
1013 1001 else:
1014 1002 heads = list(heads)
1015 1003 if not heads:
1016 1004 return nonodes
1017 1005 ancestors = set()
1018 1006 # Turn heads into a dictionary so we can remove 'fake' heads.
1019 1007 # Also, later we will be using it to filter out the heads we can't
1020 1008 # find from roots.
1021 1009 heads = dict.fromkeys(heads, False)
1022 1010 # Start at the top and keep marking parents until we're done.
1023 1011 nodestotag = set(heads)
1024 1012 # Remember where the top was so we can use it as a limit later.
1025 1013 highestrev = max([self.rev(n) for n in nodestotag])
1026 1014 while nodestotag:
1027 1015 # grab a node to tag
1028 1016 n = nodestotag.pop()
1029 1017 # Never tag nullid
1030 1018 if n == nullid:
1031 1019 continue
1032 1020 # A node's revision number represents its place in a
1033 1021 # topologically sorted list of nodes.
1034 1022 r = self.rev(n)
1035 1023 if r >= lowestrev:
1036 1024 if n not in ancestors:
1037 1025 # If we are possibly a descendant of one of the roots
1038 1026 # and we haven't already been marked as an ancestor
1039 1027 ancestors.add(n) # Mark as ancestor
1040 1028 # Add non-nullid parents to list of nodes to tag.
1041 1029 nodestotag.update([p for p in self.parents(n) if
1042 1030 p != nullid])
1043 1031 elif n in heads: # We've seen it before, is it a fake head?
1044 1032 # So it is, real heads should not be the ancestors of
1045 1033 # any other heads.
1046 1034 heads.pop(n)
1047 1035 if not ancestors:
1048 1036 return nonodes
1049 1037 # Now that we have our set of ancestors, we want to remove any
1050 1038 # roots that are not ancestors.
1051 1039
1052 1040 # If one of the roots was nullid, everything is included anyway.
1053 1041 if lowestrev > nullrev:
1054 1042 # But, since we weren't, let's recompute the lowest rev to not
1055 1043 # include roots that aren't ancestors.
1056 1044
1057 1045 # Filter out roots that aren't ancestors of heads
1058 1046 roots = [root for root in roots if root in ancestors]
1059 1047 # Recompute the lowest revision
1060 1048 if roots:
1061 1049 lowestrev = min([self.rev(root) for root in roots])
1062 1050 else:
1063 1051 # No more roots? Return empty list
1064 1052 return nonodes
1065 1053 else:
1066 1054 # We are descending from nullid, and don't need to care about
1067 1055 # any other roots.
1068 1056 lowestrev = nullrev
1069 1057 roots = [nullid]
1070 1058 # Transform our roots list into a set.
1071 1059 descendants = set(roots)
1072 1060 # Also, keep the original roots so we can filter out roots that aren't
1073 1061 # 'real' roots (i.e. are descended from other roots).
1074 1062 roots = descendants.copy()
1075 1063 # Our topologically sorted list of output nodes.
1076 1064 orderedout = []
1077 1065 # Don't start at nullid since we don't want nullid in our output list,
1078 1066 # and if nullid shows up in descendants, empty parents will look like
1079 1067 # they're descendants.
1080 1068 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1081 1069 n = self.node(r)
1082 1070 isdescendant = False
1083 1071 if lowestrev == nullrev: # Everybody is a descendant of nullid
1084 1072 isdescendant = True
1085 1073 elif n in descendants:
1086 1074 # n is already a descendant
1087 1075 isdescendant = True
1088 1076 # This check only needs to be done here because all the roots
1089 1077 # will start being marked is descendants before the loop.
1090 1078 if n in roots:
1091 1079 # If n was a root, check if it's a 'real' root.
1092 1080 p = tuple(self.parents(n))
1093 1081 # If any of its parents are descendants, it's not a root.
1094 1082 if (p[0] in descendants) or (p[1] in descendants):
1095 1083 roots.remove(n)
1096 1084 else:
1097 1085 p = tuple(self.parents(n))
1098 1086 # A node is a descendant if either of its parents are
1099 1087 # descendants. (We seeded the dependents list with the roots
1100 1088 # up there, remember?)
1101 1089 if (p[0] in descendants) or (p[1] in descendants):
1102 1090 descendants.add(n)
1103 1091 isdescendant = True
1104 1092 if isdescendant and ((ancestors is None) or (n in ancestors)):
1105 1093 # Only include nodes that are both descendants and ancestors.
1106 1094 orderedout.append(n)
1107 1095 if (ancestors is not None) and (n in heads):
1108 1096 # We're trying to figure out which heads are reachable
1109 1097 # from roots.
1110 1098 # Mark this head as having been reached
1111 1099 heads[n] = True
1112 1100 elif ancestors is None:
1113 1101 # Otherwise, we're trying to discover the heads.
1114 1102 # Assume this is a head because if it isn't, the next step
1115 1103 # will eventually remove it.
1116 1104 heads[n] = True
1117 1105 # But, obviously its parents aren't.
1118 1106 for p in self.parents(n):
1119 1107 heads.pop(p, None)
1120 1108 heads = [head for head, flag in heads.iteritems() if flag]
1121 1109 roots = list(roots)
1122 1110 assert orderedout
1123 1111 assert roots
1124 1112 assert heads
1125 1113 return (orderedout, roots, heads)
1126 1114
1127 1115 def headrevs(self, revs=None):
1128 1116 if revs is None:
1129 1117 try:
1130 1118 return self.index.headrevs()
1131 1119 except AttributeError:
1132 1120 return self._headrevs()
1133 1121 if rustdagop is not None:
1134 1122 return rustdagop.headrevs(self.index, revs)
1135 1123 return dagop.headrevs(revs, self._uncheckedparentrevs)
1136 1124
1137 1125 def computephases(self, roots):
1138 1126 return self.index.computephasesmapsets(roots)
1139 1127
1140 1128 def _headrevs(self):
1141 1129 count = len(self)
1142 1130 if not count:
1143 1131 return [nullrev]
1144 1132 # we won't iter over filtered rev so nobody is a head at start
1145 1133 ishead = [0] * (count + 1)
1146 1134 index = self.index
1147 1135 for r in self:
1148 1136 ishead[r] = 1 # I may be an head
1149 1137 e = index[r]
1150 1138 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1151 1139 return [r for r, val in enumerate(ishead) if val]
1152 1140
1153 1141 def heads(self, start=None, stop=None):
1154 1142 """return the list of all nodes that have no children
1155 1143
1156 1144 if start is specified, only heads that are descendants of
1157 1145 start will be returned
1158 1146 if stop is specified, it will consider all the revs from stop
1159 1147 as if they had no children
1160 1148 """
1161 1149 if start is None and stop is None:
1162 1150 if not len(self):
1163 1151 return [nullid]
1164 1152 return [self.node(r) for r in self.headrevs()]
1165 1153
1166 1154 if start is None:
1167 1155 start = nullrev
1168 1156 else:
1169 1157 start = self.rev(start)
1170 1158
1171 1159 stoprevs = set(self.rev(n) for n in stop or [])
1172 1160
1173 1161 revs = dagop.headrevssubset(self.revs, self.parentrevs, startrev=start,
1174 1162 stoprevs=stoprevs)
1175 1163
1176 1164 return [self.node(rev) for rev in revs]
1177 1165
1178 1166 def children(self, node):
1179 1167 """find the children of a given node"""
1180 1168 c = []
1181 1169 p = self.rev(node)
1182 1170 for r in self.revs(start=p + 1):
1183 1171 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1184 1172 if prevs:
1185 1173 for pr in prevs:
1186 1174 if pr == p:
1187 1175 c.append(self.node(r))
1188 1176 elif p == nullrev:
1189 1177 c.append(self.node(r))
1190 1178 return c
1191 1179
1192 1180 def commonancestorsheads(self, a, b):
1193 1181 """calculate all the heads of the common ancestors of nodes a and b"""
1194 1182 a, b = self.rev(a), self.rev(b)
1195 1183 ancs = self._commonancestorsheads(a, b)
1196 1184 return pycompat.maplist(self.node, ancs)
1197 1185
1198 1186 def _commonancestorsheads(self, *revs):
1199 1187 """calculate all the heads of the common ancestors of revs"""
1200 1188 try:
1201 1189 ancs = self.index.commonancestorsheads(*revs)
1202 1190 except (AttributeError, OverflowError): # C implementation failed
1203 1191 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1204 1192 return ancs
1205 1193
1206 1194 def isancestor(self, a, b):
1207 1195 """return True if node a is an ancestor of node b
1208 1196
1209 1197 A revision is considered an ancestor of itself."""
1210 1198 a, b = self.rev(a), self.rev(b)
1211 1199 return self.isancestorrev(a, b)
1212 1200
1213 1201 def isancestorrev(self, a, b):
1214 1202 """return True if revision a is an ancestor of revision b
1215 1203
1216 1204 A revision is considered an ancestor of itself.
1217 1205
1218 1206 The implementation of this is trivial but the use of
1219 1207 reachableroots is not."""
1220 1208 if a == nullrev:
1221 1209 return True
1222 1210 elif a == b:
1223 1211 return True
1224 1212 elif a > b:
1225 1213 return False
1226 1214 return bool(self.reachableroots(a, [b], [a], includepath=False))
1227 1215
1228 1216 def reachableroots(self, minroot, heads, roots, includepath=False):
1229 1217 """return (heads(::<roots> and <roots>::<heads>))
1230 1218
1231 1219 If includepath is True, return (<roots>::<heads>)."""
1232 1220 try:
1233 1221 return self.index.reachableroots2(minroot, heads, roots,
1234 1222 includepath)
1235 1223 except AttributeError:
1236 1224 return dagop._reachablerootspure(self.parentrevs,
1237 1225 minroot, roots, heads, includepath)
1238 1226
1239 1227 def ancestor(self, a, b):
1240 1228 """calculate the "best" common ancestor of nodes a and b"""
1241 1229
1242 1230 a, b = self.rev(a), self.rev(b)
1243 1231 try:
1244 1232 ancs = self.index.ancestors(a, b)
1245 1233 except (AttributeError, OverflowError):
1246 1234 ancs = ancestor.ancestors(self.parentrevs, a, b)
1247 1235 if ancs:
1248 1236 # choose a consistent winner when there's a tie
1249 1237 return min(map(self.node, ancs))
1250 1238 return nullid
1251 1239
1252 1240 def _match(self, id):
1253 1241 if isinstance(id, int):
1254 1242 # rev
1255 1243 return self.node(id)
1256 1244 if len(id) == 20:
1257 1245 # possibly a binary node
1258 1246 # odds of a binary node being all hex in ASCII are 1 in 10**25
1259 1247 try:
1260 1248 node = id
1261 1249 self.rev(node) # quick search the index
1262 1250 return node
1263 1251 except error.LookupError:
1264 1252 pass # may be partial hex id
1265 1253 try:
1266 1254 # str(rev)
1267 1255 rev = int(id)
1268 1256 if "%d" % rev != id:
1269 1257 raise ValueError
1270 1258 if rev < 0:
1271 1259 rev = len(self) + rev
1272 1260 if rev < 0 or rev >= len(self):
1273 1261 raise ValueError
1274 1262 return self.node(rev)
1275 1263 except (ValueError, OverflowError):
1276 1264 pass
1277 1265 if len(id) == 40:
1278 1266 try:
1279 1267 # a full hex nodeid?
1280 1268 node = bin(id)
1281 1269 self.rev(node)
1282 1270 return node
1283 1271 except (TypeError, error.LookupError):
1284 1272 pass
1285 1273
1286 1274 def _partialmatch(self, id):
1287 1275 # we don't care wdirfilenodeids as they should be always full hash
1288 1276 maybewdir = wdirhex.startswith(id)
1289 1277 try:
1290 1278 partial = self.index.partialmatch(id)
1291 1279 if partial and self.hasnode(partial):
1292 1280 if maybewdir:
1293 1281 # single 'ff...' match in radix tree, ambiguous with wdir
1294 1282 raise error.RevlogError
1295 1283 return partial
1296 1284 if maybewdir:
1297 1285 # no 'ff...' match in radix tree, wdir identified
1298 1286 raise error.WdirUnsupported
1299 1287 return None
1300 1288 except error.RevlogError:
1301 1289 # parsers.c radix tree lookup gave multiple matches
1302 1290 # fast path: for unfiltered changelog, radix tree is accurate
1303 1291 if not getattr(self, 'filteredrevs', None):
1304 1292 raise error.AmbiguousPrefixLookupError(
1305 1293 id, self.indexfile, _('ambiguous identifier'))
1306 1294 # fall through to slow path that filters hidden revisions
1307 1295 except (AttributeError, ValueError):
1308 1296 # we are pure python, or key was too short to search radix tree
1309 1297 pass
1310 1298
1311 1299 if id in self._pcache:
1312 1300 return self._pcache[id]
1313 1301
1314 1302 if len(id) <= 40:
1315 1303 try:
1316 1304 # hex(node)[:...]
1317 1305 l = len(id) // 2 # grab an even number of digits
1318 1306 prefix = bin(id[:l * 2])
1319 1307 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1320 1308 nl = [n for n in nl if hex(n).startswith(id) and
1321 1309 self.hasnode(n)]
1322 1310 if nullhex.startswith(id):
1323 1311 nl.append(nullid)
1324 1312 if len(nl) > 0:
1325 1313 if len(nl) == 1 and not maybewdir:
1326 1314 self._pcache[id] = nl[0]
1327 1315 return nl[0]
1328 1316 raise error.AmbiguousPrefixLookupError(
1329 1317 id, self.indexfile, _('ambiguous identifier'))
1330 1318 if maybewdir:
1331 1319 raise error.WdirUnsupported
1332 1320 return None
1333 1321 except TypeError:
1334 1322 pass
1335 1323
1336 1324 def lookup(self, id):
1337 1325 """locate a node based on:
1338 1326 - revision number or str(revision number)
1339 1327 - nodeid or subset of hex nodeid
1340 1328 """
1341 1329 n = self._match(id)
1342 1330 if n is not None:
1343 1331 return n
1344 1332 n = self._partialmatch(id)
1345 1333 if n:
1346 1334 return n
1347 1335
1348 1336 raise error.LookupError(id, self.indexfile, _('no match found'))
1349 1337
1350 1338 def shortest(self, node, minlength=1):
1351 1339 """Find the shortest unambiguous prefix that matches node."""
1352 1340 def isvalid(prefix):
1353 1341 try:
1354 1342 matchednode = self._partialmatch(prefix)
1355 1343 except error.AmbiguousPrefixLookupError:
1356 1344 return False
1357 1345 except error.WdirUnsupported:
1358 1346 # single 'ff...' match
1359 1347 return True
1360 1348 if matchednode is None:
1361 1349 raise error.LookupError(node, self.indexfile, _('no node'))
1362 1350 return True
1363 1351
1364 1352 def maybewdir(prefix):
1365 1353 return all(c == 'f' for c in pycompat.iterbytestr(prefix))
1366 1354
1367 1355 hexnode = hex(node)
1368 1356
1369 1357 def disambiguate(hexnode, minlength):
1370 1358 """Disambiguate against wdirid."""
1371 1359 for length in range(minlength, 41):
1372 1360 prefix = hexnode[:length]
1373 1361 if not maybewdir(prefix):
1374 1362 return prefix
1375 1363
1376 1364 if not getattr(self, 'filteredrevs', None):
1377 1365 try:
1378 1366 length = max(self.index.shortest(node), minlength)
1379 1367 return disambiguate(hexnode, length)
1380 1368 except error.RevlogError:
1381 1369 if node != wdirid:
1382 1370 raise error.LookupError(node, self.indexfile, _('no node'))
1383 1371 except AttributeError:
1384 1372 # Fall through to pure code
1385 1373 pass
1386 1374
1387 1375 if node == wdirid:
1388 1376 for length in range(minlength, 41):
1389 1377 prefix = hexnode[:length]
1390 1378 if isvalid(prefix):
1391 1379 return prefix
1392 1380
1393 1381 for length in range(minlength, 41):
1394 1382 prefix = hexnode[:length]
1395 1383 if isvalid(prefix):
1396 1384 return disambiguate(hexnode, length)
1397 1385
1398 1386 def cmp(self, node, text):
1399 1387 """compare text with a given file revision
1400 1388
1401 1389 returns True if text is different than what is stored.
1402 1390 """
1403 1391 p1, p2 = self.parents(node)
1404 1392 return storageutil.hashrevisionsha1(text, p1, p2) != node
1405 1393
1406 1394 def _cachesegment(self, offset, data):
1407 1395 """Add a segment to the revlog cache.
1408 1396
1409 1397 Accepts an absolute offset and the data that is at that location.
1410 1398 """
1411 1399 o, d = self._chunkcache
1412 1400 # try to add to existing cache
1413 1401 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1414 1402 self._chunkcache = o, d + data
1415 1403 else:
1416 1404 self._chunkcache = offset, data
1417 1405
1418 1406 def _readsegment(self, offset, length, df=None):
1419 1407 """Load a segment of raw data from the revlog.
1420 1408
1421 1409 Accepts an absolute offset, length to read, and an optional existing
1422 1410 file handle to read from.
1423 1411
1424 1412 If an existing file handle is passed, it will be seeked and the
1425 1413 original seek position will NOT be restored.
1426 1414
1427 1415 Returns a str or buffer of raw byte data.
1428 1416
1429 1417 Raises if the requested number of bytes could not be read.
1430 1418 """
1431 1419 # Cache data both forward and backward around the requested
1432 1420 # data, in a fixed size window. This helps speed up operations
1433 1421 # involving reading the revlog backwards.
1434 1422 cachesize = self._chunkcachesize
1435 1423 realoffset = offset & ~(cachesize - 1)
1436 1424 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1437 1425 - realoffset)
1438 1426 with self._datareadfp(df) as df:
1439 1427 df.seek(realoffset)
1440 1428 d = df.read(reallength)
1441 1429
1442 1430 self._cachesegment(realoffset, d)
1443 1431 if offset != realoffset or reallength != length:
1444 1432 startoffset = offset - realoffset
1445 1433 if len(d) - startoffset < length:
1446 1434 raise error.RevlogError(
1447 1435 _('partial read of revlog %s; expected %d bytes from '
1448 1436 'offset %d, got %d') %
1449 1437 (self.indexfile if self._inline else self.datafile,
1450 1438 length, realoffset, len(d) - startoffset))
1451 1439
1452 1440 return util.buffer(d, startoffset, length)
1453 1441
1454 1442 if len(d) < length:
1455 1443 raise error.RevlogError(
1456 1444 _('partial read of revlog %s; expected %d bytes from offset '
1457 1445 '%d, got %d') %
1458 1446 (self.indexfile if self._inline else self.datafile,
1459 1447 length, offset, len(d)))
1460 1448
1461 1449 return d
1462 1450
1463 1451 def _getsegment(self, offset, length, df=None):
1464 1452 """Obtain a segment of raw data from the revlog.
1465 1453
1466 1454 Accepts an absolute offset, length of bytes to obtain, and an
1467 1455 optional file handle to the already-opened revlog. If the file
1468 1456 handle is used, it's original seek position will not be preserved.
1469 1457
1470 1458 Requests for data may be returned from a cache.
1471 1459
1472 1460 Returns a str or a buffer instance of raw byte data.
1473 1461 """
1474 1462 o, d = self._chunkcache
1475 1463 l = len(d)
1476 1464
1477 1465 # is it in the cache?
1478 1466 cachestart = offset - o
1479 1467 cacheend = cachestart + length
1480 1468 if cachestart >= 0 and cacheend <= l:
1481 1469 if cachestart == 0 and cacheend == l:
1482 1470 return d # avoid a copy
1483 1471 return util.buffer(d, cachestart, cacheend - cachestart)
1484 1472
1485 1473 return self._readsegment(offset, length, df=df)
1486 1474
1487 1475 def _getsegmentforrevs(self, startrev, endrev, df=None):
1488 1476 """Obtain a segment of raw data corresponding to a range of revisions.
1489 1477
1490 1478 Accepts the start and end revisions and an optional already-open
1491 1479 file handle to be used for reading. If the file handle is read, its
1492 1480 seek position will not be preserved.
1493 1481
1494 1482 Requests for data may be satisfied by a cache.
1495 1483
1496 1484 Returns a 2-tuple of (offset, data) for the requested range of
1497 1485 revisions. Offset is the integer offset from the beginning of the
1498 1486 revlog and data is a str or buffer of the raw byte data.
1499 1487
1500 1488 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1501 1489 to determine where each revision's data begins and ends.
1502 1490 """
1503 1491 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1504 1492 # (functions are expensive).
1505 1493 index = self.index
1506 1494 istart = index[startrev]
1507 1495 start = int(istart[0] >> 16)
1508 1496 if startrev == endrev:
1509 1497 end = start + istart[1]
1510 1498 else:
1511 1499 iend = index[endrev]
1512 1500 end = int(iend[0] >> 16) + iend[1]
1513 1501
1514 1502 if self._inline:
1515 1503 start += (startrev + 1) * self._io.size
1516 1504 end += (endrev + 1) * self._io.size
1517 1505 length = end - start
1518 1506
1519 1507 return start, self._getsegment(start, length, df=df)
1520 1508
1521 1509 def _chunk(self, rev, df=None):
1522 1510 """Obtain a single decompressed chunk for a revision.
1523 1511
1524 1512 Accepts an integer revision and an optional already-open file handle
1525 1513 to be used for reading. If used, the seek position of the file will not
1526 1514 be preserved.
1527 1515
1528 1516 Returns a str holding uncompressed data for the requested revision.
1529 1517 """
1530 1518 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1531 1519
1532 1520 def _chunks(self, revs, df=None, targetsize=None):
1533 1521 """Obtain decompressed chunks for the specified revisions.
1534 1522
1535 1523 Accepts an iterable of numeric revisions that are assumed to be in
1536 1524 ascending order. Also accepts an optional already-open file handle
1537 1525 to be used for reading. If used, the seek position of the file will
1538 1526 not be preserved.
1539 1527
1540 1528 This function is similar to calling ``self._chunk()`` multiple times,
1541 1529 but is faster.
1542 1530
1543 1531 Returns a list with decompressed data for each requested revision.
1544 1532 """
1545 1533 if not revs:
1546 1534 return []
1547 1535 start = self.start
1548 1536 length = self.length
1549 1537 inline = self._inline
1550 1538 iosize = self._io.size
1551 1539 buffer = util.buffer
1552 1540
1553 1541 l = []
1554 1542 ladd = l.append
1555 1543
1556 1544 if not self._withsparseread:
1557 1545 slicedchunks = (revs,)
1558 1546 else:
1559 1547 slicedchunks = deltautil.slicechunk(self, revs,
1560 1548 targetsize=targetsize)
1561 1549
1562 1550 for revschunk in slicedchunks:
1563 1551 firstrev = revschunk[0]
1564 1552 # Skip trailing revisions with empty diff
1565 1553 for lastrev in revschunk[::-1]:
1566 1554 if length(lastrev) != 0:
1567 1555 break
1568 1556
1569 1557 try:
1570 1558 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1571 1559 except OverflowError:
1572 1560 # issue4215 - we can't cache a run of chunks greater than
1573 1561 # 2G on Windows
1574 1562 return [self._chunk(rev, df=df) for rev in revschunk]
1575 1563
1576 1564 decomp = self.decompress
1577 1565 for rev in revschunk:
1578 1566 chunkstart = start(rev)
1579 1567 if inline:
1580 1568 chunkstart += (rev + 1) * iosize
1581 1569 chunklength = length(rev)
1582 1570 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
1583 1571
1584 1572 return l
1585 1573
1586 1574 def _chunkclear(self):
1587 1575 """Clear the raw chunk cache."""
1588 1576 self._chunkcache = (0, '')
1589 1577
1590 1578 def deltaparent(self, rev):
1591 1579 """return deltaparent of the given revision"""
1592 1580 base = self.index[rev][3]
1593 1581 if base == rev:
1594 1582 return nullrev
1595 1583 elif self._generaldelta:
1596 1584 return base
1597 1585 else:
1598 1586 return rev - 1
1599 1587
1600 1588 def issnapshot(self, rev):
1601 1589 """tells whether rev is a snapshot
1602 1590 """
1603 1591 if not self._sparserevlog:
1604 1592 return self.deltaparent(rev) == nullrev
1605 1593 elif util.safehasattr(self.index, 'issnapshot'):
1606 1594 # directly assign the method to cache the testing and access
1607 1595 self.issnapshot = self.index.issnapshot
1608 1596 return self.issnapshot(rev)
1609 1597 if rev == nullrev:
1610 1598 return True
1611 1599 entry = self.index[rev]
1612 1600 base = entry[3]
1613 1601 if base == rev:
1614 1602 return True
1615 1603 if base == nullrev:
1616 1604 return True
1617 1605 p1 = entry[5]
1618 1606 p2 = entry[6]
1619 1607 if base == p1 or base == p2:
1620 1608 return False
1621 1609 return self.issnapshot(base)
1622 1610
1623 1611 def snapshotdepth(self, rev):
1624 1612 """number of snapshot in the chain before this one"""
1625 1613 if not self.issnapshot(rev):
1626 1614 raise error.ProgrammingError('revision %d not a snapshot')
1627 1615 return len(self._deltachain(rev)[0]) - 1
1628 1616
1629 1617 def revdiff(self, rev1, rev2):
1630 1618 """return or calculate a delta between two revisions
1631 1619
1632 1620 The delta calculated is in binary form and is intended to be written to
1633 1621 revlog data directly. So this function needs raw revision data.
1634 1622 """
1635 1623 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1636 1624 return bytes(self._chunk(rev2))
1637 1625
1638 1626 return mdiff.textdiff(self.revision(rev1, raw=True),
1639 1627 self.revision(rev2, raw=True))
1640 1628
1641 1629 def revision(self, nodeorrev, _df=None, raw=False):
1642 1630 """return an uncompressed revision of a given node or revision
1643 1631 number.
1644 1632
1645 1633 _df - an existing file handle to read from. (internal-only)
1646 1634 raw - an optional argument specifying if the revision data is to be
1647 1635 treated as raw data when applying flag transforms. 'raw' should be set
1648 1636 to True when generating changegroups or in debug commands.
1649 1637 """
1650 1638 return self._revisiondata(nodeorrev, _df, raw=raw)
1651 1639
1652 1640 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1653 1641 if isinstance(nodeorrev, int):
1654 1642 rev = nodeorrev
1655 1643 node = self.node(rev)
1656 1644 else:
1657 1645 node = nodeorrev
1658 1646 rev = None
1659 1647
1660 1648 cachedrev = None
1661 1649 flags = None
1662 1650 rawtext = None
1663 1651 if node == nullid:
1664 1652 return ""
1665 1653 if self._revisioncache:
1666 1654 if self._revisioncache[0] == node:
1667 1655 # _cache only stores rawtext
1668 1656 if raw:
1669 1657 return self._revisioncache[2]
1670 1658 # duplicated, but good for perf
1671 1659 if rev is None:
1672 1660 rev = self.rev(node)
1673 1661 if flags is None:
1674 1662 flags = self.flags(rev)
1675 1663 # no extra flags set, no flag processor runs, text = rawtext
1676 1664 if flags == REVIDX_DEFAULT_FLAGS:
1677 1665 return self._revisioncache[2]
1678 1666 # rawtext is reusable. need to run flag processor
1679 1667 rawtext = self._revisioncache[2]
1680 1668
1681 1669 cachedrev = self._revisioncache[1]
1682 1670
1683 1671 # look up what we need to read
1684 1672 if rawtext is None:
1685 1673 if rev is None:
1686 1674 rev = self.rev(node)
1687 1675
1688 1676 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1689 1677 if stopped:
1690 1678 rawtext = self._revisioncache[2]
1691 1679
1692 1680 # drop cache to save memory
1693 1681 self._revisioncache = None
1694 1682
1695 1683 targetsize = None
1696 1684 rawsize = self.index[rev][2]
1697 1685 if 0 <= rawsize:
1698 1686 targetsize = 4 * rawsize
1699 1687
1700 1688 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1701 1689 if rawtext is None:
1702 1690 rawtext = bytes(bins[0])
1703 1691 bins = bins[1:]
1704 1692
1705 1693 rawtext = mdiff.patches(rawtext, bins)
1706 1694 self._revisioncache = (node, rev, rawtext)
1707 1695
1708 1696 if flags is None:
1709 1697 if rev is None:
1710 1698 rev = self.rev(node)
1711 1699 flags = self.flags(rev)
1712 1700
1713 1701 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
1714 1702 if validatehash:
1715 1703 self.checkhash(text, node, rev=rev)
1716 1704
1717 1705 return text
1718 1706
1719 1707 def rawdata(self, nodeorrev, _df=None, raw=False):
1720 1708 """return an uncompressed raw data of a given node or revision number.
1721 1709
1722 1710 _df - an existing file handle to read from. (internal-only)
1723 1711 """
1724 1712 return self._revisiondata(nodeorrev, _df, raw=True)
1725 1713
1726 1714 def hash(self, text, p1, p2):
1727 1715 """Compute a node hash.
1728 1716
1729 1717 Available as a function so that subclasses can replace the hash
1730 1718 as needed.
1731 1719 """
1732 1720 return storageutil.hashrevisionsha1(text, p1, p2)
1733 1721
1734 1722 def _processflags(self, text, flags, operation, raw=False):
1735 1723 """Inspect revision data flags and applies transforms defined by
1736 1724 registered flag processors.
1737 1725
1738 1726 ``text`` - the revision data to process
1739 1727 ``flags`` - the revision flags
1740 1728 ``operation`` - the operation being performed (read or write)
1741 1729 ``raw`` - an optional argument describing if the raw transform should be
1742 1730 applied.
1743 1731
1744 1732 This method processes the flags in the order (or reverse order if
1745 1733 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
1746 1734 flag processors registered for present flags. The order of flags defined
1747 1735 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
1748 1736
1749 1737 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
1750 1738 processed text and ``validatehash`` is a bool indicating whether the
1751 1739 returned text should be checked for hash integrity.
1752 1740
1753 1741 Note: If the ``raw`` argument is set, it has precedence over the
1754 1742 operation and will only update the value of ``validatehash``.
1755 1743 """
1756 1744 # fast path: no flag processors will run
1757 1745 if flags == 0:
1758 1746 return text, True
1759 1747 if not operation in ('read', 'write'):
1760 1748 raise error.ProgrammingError(_("invalid '%s' operation") %
1761 1749 operation)
1762 1750 # Check all flags are known.
1763 1751 if flags & ~flagutil.REVIDX_KNOWN_FLAGS:
1764 1752 raise error.RevlogError(_("incompatible revision flag '%#x'") %
1765 1753 (flags & ~flagutil.REVIDX_KNOWN_FLAGS))
1766 1754 validatehash = True
1767 1755 # Depending on the operation (read or write), the order might be
1768 1756 # reversed due to non-commutative transforms.
1769 1757 orderedflags = REVIDX_FLAGS_ORDER
1770 1758 if operation == 'write':
1771 1759 orderedflags = reversed(orderedflags)
1772 1760
1773 1761 for flag in orderedflags:
1774 1762 # If a flagprocessor has been registered for a known flag, apply the
1775 1763 # related operation transform and update result tuple.
1776 1764 if flag & flags:
1777 1765 vhash = True
1778 1766
1779 1767 if flag not in self._flagprocessors:
1780 1768 message = _("missing processor for flag '%#x'") % (flag)
1781 1769 raise error.RevlogError(message)
1782 1770
1783 1771 processor = self._flagprocessors[flag]
1784 1772 if processor is not None:
1785 1773 readtransform, writetransform, rawtransform = processor
1786 1774
1787 1775 if raw:
1788 1776 vhash = rawtransform(self, text)
1789 1777 elif operation == 'read':
1790 1778 text, vhash = readtransform(self, text)
1791 1779 else: # write operation
1792 1780 text, vhash = writetransform(self, text)
1793 1781 validatehash = validatehash and vhash
1794 1782
1795 1783 return text, validatehash
1796 1784
1797 1785 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1798 1786 """Check node hash integrity.
1799 1787
1800 1788 Available as a function so that subclasses can extend hash mismatch
1801 1789 behaviors as needed.
1802 1790 """
1803 1791 try:
1804 1792 if p1 is None and p2 is None:
1805 1793 p1, p2 = self.parents(node)
1806 1794 if node != self.hash(text, p1, p2):
1807 1795 # Clear the revision cache on hash failure. The revision cache
1808 1796 # only stores the raw revision and clearing the cache does have
1809 1797 # the side-effect that we won't have a cache hit when the raw
1810 1798 # revision data is accessed. But this case should be rare and
1811 1799 # it is extra work to teach the cache about the hash
1812 1800 # verification state.
1813 1801 if self._revisioncache and self._revisioncache[0] == node:
1814 1802 self._revisioncache = None
1815 1803
1816 1804 revornode = rev
1817 1805 if revornode is None:
1818 1806 revornode = templatefilters.short(hex(node))
1819 1807 raise error.RevlogError(_("integrity check failed on %s:%s")
1820 1808 % (self.indexfile, pycompat.bytestr(revornode)))
1821 1809 except error.RevlogError:
1822 1810 if self._censorable and storageutil.iscensoredtext(text):
1823 1811 raise error.CensoredNodeError(self.indexfile, node, text)
1824 1812 raise
1825 1813
1826 1814 def _enforceinlinesize(self, tr, fp=None):
1827 1815 """Check if the revlog is too big for inline and convert if so.
1828 1816
1829 1817 This should be called after revisions are added to the revlog. If the
1830 1818 revlog has grown too large to be an inline revlog, it will convert it
1831 1819 to use multiple index and data files.
1832 1820 """
1833 1821 tiprev = len(self) - 1
1834 1822 if (not self._inline or
1835 1823 (self.start(tiprev) + self.length(tiprev)) < _maxinline):
1836 1824 return
1837 1825
1838 1826 trinfo = tr.find(self.indexfile)
1839 1827 if trinfo is None:
1840 1828 raise error.RevlogError(_("%s not found in the transaction")
1841 1829 % self.indexfile)
1842 1830
1843 1831 trindex = trinfo[2]
1844 1832 if trindex is not None:
1845 1833 dataoff = self.start(trindex)
1846 1834 else:
1847 1835 # revlog was stripped at start of transaction, use all leftover data
1848 1836 trindex = len(self) - 1
1849 1837 dataoff = self.end(tiprev)
1850 1838
1851 1839 tr.add(self.datafile, dataoff)
1852 1840
1853 1841 if fp:
1854 1842 fp.flush()
1855 1843 fp.close()
1856 1844 # We can't use the cached file handle after close(). So prevent
1857 1845 # its usage.
1858 1846 self._writinghandles = None
1859 1847
1860 1848 with self._indexfp('r') as ifh, self._datafp('w') as dfh:
1861 1849 for r in self:
1862 1850 dfh.write(self._getsegmentforrevs(r, r, df=ifh)[1])
1863 1851
1864 1852 with self._indexfp('w') as fp:
1865 1853 self.version &= ~FLAG_INLINE_DATA
1866 1854 self._inline = False
1867 1855 io = self._io
1868 1856 for i in self:
1869 1857 e = io.packentry(self.index[i], self.node, self.version, i)
1870 1858 fp.write(e)
1871 1859
1872 1860 # the temp file replace the real index when we exit the context
1873 1861 # manager
1874 1862
1875 1863 tr.replace(self.indexfile, trindex * self._io.size)
1876 1864 self._chunkclear()
1877 1865
1878 1866 def _nodeduplicatecallback(self, transaction, node):
1879 1867 """called when trying to add a node already stored.
1880 1868 """
1881 1869
1882 1870 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1883 1871 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
1884 1872 """add a revision to the log
1885 1873
1886 1874 text - the revision data to add
1887 1875 transaction - the transaction object used for rollback
1888 1876 link - the linkrev data to add
1889 1877 p1, p2 - the parent nodeids of the revision
1890 1878 cachedelta - an optional precomputed delta
1891 1879 node - nodeid of revision; typically node is not specified, and it is
1892 1880 computed by default as hash(text, p1, p2), however subclasses might
1893 1881 use different hashing method (and override checkhash() in such case)
1894 1882 flags - the known flags to set on the revision
1895 1883 deltacomputer - an optional deltacomputer instance shared between
1896 1884 multiple calls
1897 1885 """
1898 1886 if link == nullrev:
1899 1887 raise error.RevlogError(_("attempted to add linkrev -1 to %s")
1900 1888 % self.indexfile)
1901 1889
1902 1890 if flags:
1903 1891 node = node or self.hash(text, p1, p2)
1904 1892
1905 1893 rawtext, validatehash = self._processflags(text, flags, 'write')
1906 1894
1907 1895 # If the flag processor modifies the revision data, ignore any provided
1908 1896 # cachedelta.
1909 1897 if rawtext != text:
1910 1898 cachedelta = None
1911 1899
1912 1900 if len(rawtext) > _maxentrysize:
1913 1901 raise error.RevlogError(
1914 1902 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1915 1903 % (self.indexfile, len(rawtext)))
1916 1904
1917 1905 node = node or self.hash(rawtext, p1, p2)
1918 1906 if node in self.nodemap:
1919 1907 return node
1920 1908
1921 1909 if validatehash:
1922 1910 self.checkhash(rawtext, node, p1=p1, p2=p2)
1923 1911
1924 1912 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
1925 1913 flags, cachedelta=cachedelta,
1926 1914 deltacomputer=deltacomputer)
1927 1915
1928 1916 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
1929 1917 cachedelta=None, deltacomputer=None):
1930 1918 """add a raw revision with known flags, node and parents
1931 1919 useful when reusing a revision not stored in this revlog (ex: received
1932 1920 over wire, or read from an external bundle).
1933 1921 """
1934 1922 dfh = None
1935 1923 if not self._inline:
1936 1924 dfh = self._datafp("a+")
1937 1925 ifh = self._indexfp("a+")
1938 1926 try:
1939 1927 return self._addrevision(node, rawtext, transaction, link, p1, p2,
1940 1928 flags, cachedelta, ifh, dfh,
1941 1929 deltacomputer=deltacomputer)
1942 1930 finally:
1943 1931 if dfh:
1944 1932 dfh.close()
1945 1933 ifh.close()
1946 1934
1947 1935 def compress(self, data):
1948 1936 """Generate a possibly-compressed representation of data."""
1949 1937 if not data:
1950 1938 return '', data
1951 1939
1952 1940 compressed = self._compressor.compress(data)
1953 1941
1954 1942 if compressed:
1955 1943 # The revlog compressor added the header in the returned data.
1956 1944 return '', compressed
1957 1945
1958 1946 if data[0:1] == '\0':
1959 1947 return '', data
1960 1948 return 'u', data
1961 1949
1962 1950 def decompress(self, data):
1963 1951 """Decompress a revlog chunk.
1964 1952
1965 1953 The chunk is expected to begin with a header identifying the
1966 1954 format type so it can be routed to an appropriate decompressor.
1967 1955 """
1968 1956 if not data:
1969 1957 return data
1970 1958
1971 1959 # Revlogs are read much more frequently than they are written and many
1972 1960 # chunks only take microseconds to decompress, so performance is
1973 1961 # important here.
1974 1962 #
1975 1963 # We can make a few assumptions about revlogs:
1976 1964 #
1977 1965 # 1) the majority of chunks will be compressed (as opposed to inline
1978 1966 # raw data).
1979 1967 # 2) decompressing *any* data will likely by at least 10x slower than
1980 1968 # returning raw inline data.
1981 1969 # 3) we want to prioritize common and officially supported compression
1982 1970 # engines
1983 1971 #
1984 1972 # It follows that we want to optimize for "decompress compressed data
1985 1973 # when encoded with common and officially supported compression engines"
1986 1974 # case over "raw data" and "data encoded by less common or non-official
1987 1975 # compression engines." That is why we have the inline lookup first
1988 1976 # followed by the compengines lookup.
1989 1977 #
1990 1978 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
1991 1979 # compressed chunks. And this matters for changelog and manifest reads.
1992 1980 t = data[0:1]
1993 1981
1994 1982 if t == 'x':
1995 1983 try:
1996 1984 return _zlibdecompress(data)
1997 1985 except zlib.error as e:
1998 1986 raise error.RevlogError(_('revlog decompress error: %s') %
1999 1987 stringutil.forcebytestr(e))
2000 1988 # '\0' is more common than 'u' so it goes first.
2001 1989 elif t == '\0':
2002 1990 return data
2003 1991 elif t == 'u':
2004 1992 return util.buffer(data, 1)
2005 1993
2006 1994 try:
2007 1995 compressor = self._decompressors[t]
2008 1996 except KeyError:
2009 1997 try:
2010 1998 engine = util.compengines.forrevlogheader(t)
2011 1999 compressor = engine.revlogcompressor(self._compengineopts)
2012 2000 self._decompressors[t] = compressor
2013 2001 except KeyError:
2014 2002 raise error.RevlogError(_('unknown compression type %r') % t)
2015 2003
2016 2004 return compressor.decompress(data)
2017 2005
2018 2006 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
2019 2007 cachedelta, ifh, dfh, alwayscache=False,
2020 2008 deltacomputer=None):
2021 2009 """internal function to add revisions to the log
2022 2010
2023 2011 see addrevision for argument descriptions.
2024 2012
2025 2013 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2026 2014
2027 2015 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2028 2016 be used.
2029 2017
2030 2018 invariants:
2031 2019 - rawtext is optional (can be None); if not set, cachedelta must be set.
2032 2020 if both are set, they must correspond to each other.
2033 2021 """
2034 2022 if node == nullid:
2035 2023 raise error.RevlogError(_("%s: attempt to add null revision") %
2036 2024 self.indexfile)
2037 2025 if node == wdirid or node in wdirfilenodeids:
2038 2026 raise error.RevlogError(_("%s: attempt to add wdir revision") %
2039 2027 self.indexfile)
2040 2028
2041 2029 if self._inline:
2042 2030 fh = ifh
2043 2031 else:
2044 2032 fh = dfh
2045 2033
2046 2034 btext = [rawtext]
2047 2035
2048 2036 curr = len(self)
2049 2037 prev = curr - 1
2050 2038 offset = self.end(prev)
2051 2039 p1r, p2r = self.rev(p1), self.rev(p2)
2052 2040
2053 2041 # full versions are inserted when the needed deltas
2054 2042 # become comparable to the uncompressed text
2055 2043 if rawtext is None:
2056 2044 # need rawtext size, before changed by flag processors, which is
2057 2045 # the non-raw size. use revlog explicitly to avoid filelog's extra
2058 2046 # logic that might remove metadata size.
2059 2047 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
2060 2048 cachedelta[1])
2061 2049 else:
2062 2050 textlen = len(rawtext)
2063 2051
2064 2052 if deltacomputer is None:
2065 2053 deltacomputer = deltautil.deltacomputer(self)
2066 2054
2067 2055 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2068 2056
2069 2057 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2070 2058
2071 2059 e = (offset_type(offset, flags), deltainfo.deltalen, textlen,
2072 2060 deltainfo.base, link, p1r, p2r, node)
2073 2061 self.index.append(e)
2074 2062 self.nodemap[node] = curr
2075 2063
2076 2064 # Reset the pure node cache start lookup offset to account for new
2077 2065 # revision.
2078 2066 if self._nodepos is not None:
2079 2067 self._nodepos = curr
2080 2068
2081 2069 entry = self._io.packentry(e, self.node, self.version, curr)
2082 2070 self._writeentry(transaction, ifh, dfh, entry, deltainfo.data,
2083 2071 link, offset)
2084 2072
2085 2073 rawtext = btext[0]
2086 2074
2087 2075 if alwayscache and rawtext is None:
2088 2076 rawtext = deltacomputer.buildtext(revinfo, fh)
2089 2077
2090 2078 if type(rawtext) == bytes: # only accept immutable objects
2091 2079 self._revisioncache = (node, curr, rawtext)
2092 2080 self._chainbasecache[curr] = deltainfo.chainbase
2093 2081 return node
2094 2082
2095 2083 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2096 2084 # Files opened in a+ mode have inconsistent behavior on various
2097 2085 # platforms. Windows requires that a file positioning call be made
2098 2086 # when the file handle transitions between reads and writes. See
2099 2087 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2100 2088 # platforms, Python or the platform itself can be buggy. Some versions
2101 2089 # of Solaris have been observed to not append at the end of the file
2102 2090 # if the file was seeked to before the end. See issue4943 for more.
2103 2091 #
2104 2092 # We work around this issue by inserting a seek() before writing.
2105 2093 # Note: This is likely not necessary on Python 3. However, because
2106 2094 # the file handle is reused for reads and may be seeked there, we need
2107 2095 # to be careful before changing this.
2108 2096 ifh.seek(0, os.SEEK_END)
2109 2097 if dfh:
2110 2098 dfh.seek(0, os.SEEK_END)
2111 2099
2112 2100 curr = len(self) - 1
2113 2101 if not self._inline:
2114 2102 transaction.add(self.datafile, offset)
2115 2103 transaction.add(self.indexfile, curr * len(entry))
2116 2104 if data[0]:
2117 2105 dfh.write(data[0])
2118 2106 dfh.write(data[1])
2119 2107 ifh.write(entry)
2120 2108 else:
2121 2109 offset += curr * self._io.size
2122 2110 transaction.add(self.indexfile, offset, curr)
2123 2111 ifh.write(entry)
2124 2112 ifh.write(data[0])
2125 2113 ifh.write(data[1])
2126 2114 self._enforceinlinesize(transaction, ifh)
2127 2115
2128 2116 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2129 2117 """
2130 2118 add a delta group
2131 2119
2132 2120 given a set of deltas, add them to the revision log. the
2133 2121 first delta is against its parent, which should be in our
2134 2122 log, the rest are against the previous delta.
2135 2123
2136 2124 If ``addrevisioncb`` is defined, it will be called with arguments of
2137 2125 this revlog and the node that was added.
2138 2126 """
2139 2127
2140 2128 if self._writinghandles:
2141 2129 raise error.ProgrammingError('cannot nest addgroup() calls')
2142 2130
2143 2131 nodes = []
2144 2132
2145 2133 r = len(self)
2146 2134 end = 0
2147 2135 if r:
2148 2136 end = self.end(r - 1)
2149 2137 ifh = self._indexfp("a+")
2150 2138 isize = r * self._io.size
2151 2139 if self._inline:
2152 2140 transaction.add(self.indexfile, end + isize, r)
2153 2141 dfh = None
2154 2142 else:
2155 2143 transaction.add(self.indexfile, isize, r)
2156 2144 transaction.add(self.datafile, end)
2157 2145 dfh = self._datafp("a+")
2158 2146 def flush():
2159 2147 if dfh:
2160 2148 dfh.flush()
2161 2149 ifh.flush()
2162 2150
2163 2151 self._writinghandles = (ifh, dfh)
2164 2152
2165 2153 try:
2166 2154 deltacomputer = deltautil.deltacomputer(self)
2167 2155 # loop through our set of deltas
2168 2156 for data in deltas:
2169 2157 node, p1, p2, linknode, deltabase, delta, flags = data
2170 2158 link = linkmapper(linknode)
2171 2159 flags = flags or REVIDX_DEFAULT_FLAGS
2172 2160
2173 2161 nodes.append(node)
2174 2162
2175 2163 if node in self.nodemap:
2176 2164 self._nodeduplicatecallback(transaction, node)
2177 2165 # this can happen if two branches make the same change
2178 2166 continue
2179 2167
2180 2168 for p in (p1, p2):
2181 2169 if p not in self.nodemap:
2182 2170 raise error.LookupError(p, self.indexfile,
2183 2171 _('unknown parent'))
2184 2172
2185 2173 if deltabase not in self.nodemap:
2186 2174 raise error.LookupError(deltabase, self.indexfile,
2187 2175 _('unknown delta base'))
2188 2176
2189 2177 baserev = self.rev(deltabase)
2190 2178
2191 2179 if baserev != nullrev and self.iscensored(baserev):
2192 2180 # if base is censored, delta must be full replacement in a
2193 2181 # single patch operation
2194 2182 hlen = struct.calcsize(">lll")
2195 2183 oldlen = self.rawsize(baserev)
2196 2184 newlen = len(delta) - hlen
2197 2185 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2198 2186 raise error.CensoredBaseError(self.indexfile,
2199 2187 self.node(baserev))
2200 2188
2201 2189 if not flags and self._peek_iscensored(baserev, delta, flush):
2202 2190 flags |= REVIDX_ISCENSORED
2203 2191
2204 2192 # We assume consumers of addrevisioncb will want to retrieve
2205 2193 # the added revision, which will require a call to
2206 2194 # revision(). revision() will fast path if there is a cache
2207 2195 # hit. So, we tell _addrevision() to always cache in this case.
2208 2196 # We're only using addgroup() in the context of changegroup
2209 2197 # generation so the revision data can always be handled as raw
2210 2198 # by the flagprocessor.
2211 2199 self._addrevision(node, None, transaction, link,
2212 2200 p1, p2, flags, (baserev, delta),
2213 2201 ifh, dfh,
2214 2202 alwayscache=bool(addrevisioncb),
2215 2203 deltacomputer=deltacomputer)
2216 2204
2217 2205 if addrevisioncb:
2218 2206 addrevisioncb(self, node)
2219 2207
2220 2208 if not dfh and not self._inline:
2221 2209 # addrevision switched from inline to conventional
2222 2210 # reopen the index
2223 2211 ifh.close()
2224 2212 dfh = self._datafp("a+")
2225 2213 ifh = self._indexfp("a+")
2226 2214 self._writinghandles = (ifh, dfh)
2227 2215 finally:
2228 2216 self._writinghandles = None
2229 2217
2230 2218 if dfh:
2231 2219 dfh.close()
2232 2220 ifh.close()
2233 2221
2234 2222 return nodes
2235 2223
2236 2224 def iscensored(self, rev):
2237 2225 """Check if a file revision is censored."""
2238 2226 if not self._censorable:
2239 2227 return False
2240 2228
2241 2229 return self.flags(rev) & REVIDX_ISCENSORED
2242 2230
2243 2231 def _peek_iscensored(self, baserev, delta, flush):
2244 2232 """Quickly check if a delta produces a censored revision."""
2245 2233 if not self._censorable:
2246 2234 return False
2247 2235
2248 2236 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2249 2237
2250 2238 def getstrippoint(self, minlink):
2251 2239 """find the minimum rev that must be stripped to strip the linkrev
2252 2240
2253 2241 Returns a tuple containing the minimum rev and a set of all revs that
2254 2242 have linkrevs that will be broken by this strip.
2255 2243 """
2256 2244 return storageutil.resolvestripinfo(minlink, len(self) - 1,
2257 2245 self.headrevs(),
2258 2246 self.linkrev, self.parentrevs)
2259 2247
2260 2248 def strip(self, minlink, transaction):
2261 2249 """truncate the revlog on the first revision with a linkrev >= minlink
2262 2250
2263 2251 This function is called when we're stripping revision minlink and
2264 2252 its descendants from the repository.
2265 2253
2266 2254 We have to remove all revisions with linkrev >= minlink, because
2267 2255 the equivalent changelog revisions will be renumbered after the
2268 2256 strip.
2269 2257
2270 2258 So we truncate the revlog on the first of these revisions, and
2271 2259 trust that the caller has saved the revisions that shouldn't be
2272 2260 removed and that it'll re-add them after this truncation.
2273 2261 """
2274 2262 if len(self) == 0:
2275 2263 return
2276 2264
2277 2265 rev, _ = self.getstrippoint(minlink)
2278 2266 if rev == len(self):
2279 2267 return
2280 2268
2281 2269 # first truncate the files on disk
2282 2270 end = self.start(rev)
2283 2271 if not self._inline:
2284 2272 transaction.add(self.datafile, end)
2285 2273 end = rev * self._io.size
2286 2274 else:
2287 2275 end += rev * self._io.size
2288 2276
2289 2277 transaction.add(self.indexfile, end)
2290 2278
2291 2279 # then reset internal state in memory to forget those revisions
2292 2280 self._revisioncache = None
2293 2281 self._chaininfocache = {}
2294 2282 self._chunkclear()
2295 2283 for x in pycompat.xrange(rev, len(self)):
2296 2284 del self.nodemap[self.node(x)]
2297 2285
2298 2286 del self.index[rev:-1]
2299 2287 self._nodepos = None
2300 2288
2301 2289 def checksize(self):
2302 2290 """Check size of index and data files
2303 2291
2304 2292 return a (dd, di) tuple.
2305 2293 - dd: extra bytes for the "data" file
2306 2294 - di: extra bytes for the "index" file
2307 2295
2308 2296 A healthy revlog will return (0, 0).
2309 2297 """
2310 2298 expected = 0
2311 2299 if len(self):
2312 2300 expected = max(0, self.end(len(self) - 1))
2313 2301
2314 2302 try:
2315 2303 with self._datafp() as f:
2316 2304 f.seek(0, io.SEEK_END)
2317 2305 actual = f.tell()
2318 2306 dd = actual - expected
2319 2307 except IOError as inst:
2320 2308 if inst.errno != errno.ENOENT:
2321 2309 raise
2322 2310 dd = 0
2323 2311
2324 2312 try:
2325 2313 f = self.opener(self.indexfile)
2326 2314 f.seek(0, io.SEEK_END)
2327 2315 actual = f.tell()
2328 2316 f.close()
2329 2317 s = self._io.size
2330 2318 i = max(0, actual // s)
2331 2319 di = actual - (i * s)
2332 2320 if self._inline:
2333 2321 databytes = 0
2334 2322 for r in self:
2335 2323 databytes += max(0, self.length(r))
2336 2324 dd = 0
2337 2325 di = actual - len(self) * s - databytes
2338 2326 except IOError as inst:
2339 2327 if inst.errno != errno.ENOENT:
2340 2328 raise
2341 2329 di = 0
2342 2330
2343 2331 return (dd, di)
2344 2332
2345 2333 def files(self):
2346 2334 res = [self.indexfile]
2347 2335 if not self._inline:
2348 2336 res.append(self.datafile)
2349 2337 return res
2350 2338
2351 2339 def emitrevisions(self, nodes, nodesorder=None, revisiondata=False,
2352 2340 assumehaveparentrevisions=False,
2353 2341 deltamode=repository.CG_DELTAMODE_STD):
2354 2342 if nodesorder not in ('nodes', 'storage', 'linear', None):
2355 2343 raise error.ProgrammingError('unhandled value for nodesorder: %s' %
2356 2344 nodesorder)
2357 2345
2358 2346 if nodesorder is None and not self._generaldelta:
2359 2347 nodesorder = 'storage'
2360 2348
2361 2349 if (not self._storedeltachains and
2362 2350 deltamode != repository.CG_DELTAMODE_PREV):
2363 2351 deltamode = repository.CG_DELTAMODE_FULL
2364 2352
2365 2353 return storageutil.emitrevisions(
2366 2354 self, nodes, nodesorder, revlogrevisiondelta,
2367 2355 deltaparentfn=self.deltaparent,
2368 2356 candeltafn=self.candelta,
2369 2357 rawsizefn=self.rawsize,
2370 2358 revdifffn=self.revdiff,
2371 2359 flagsfn=self.flags,
2372 2360 deltamode=deltamode,
2373 2361 revisiondata=revisiondata,
2374 2362 assumehaveparentrevisions=assumehaveparentrevisions)
2375 2363
2376 2364 DELTAREUSEALWAYS = 'always'
2377 2365 DELTAREUSESAMEREVS = 'samerevs'
2378 2366 DELTAREUSENEVER = 'never'
2379 2367
2380 2368 DELTAREUSEFULLADD = 'fulladd'
2381 2369
2382 2370 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2383 2371
2384 2372 def clone(self, tr, destrevlog, addrevisioncb=None,
2385 2373 deltareuse=DELTAREUSESAMEREVS, forcedeltabothparents=None):
2386 2374 """Copy this revlog to another, possibly with format changes.
2387 2375
2388 2376 The destination revlog will contain the same revisions and nodes.
2389 2377 However, it may not be bit-for-bit identical due to e.g. delta encoding
2390 2378 differences.
2391 2379
2392 2380 The ``deltareuse`` argument control how deltas from the existing revlog
2393 2381 are preserved in the destination revlog. The argument can have the
2394 2382 following values:
2395 2383
2396 2384 DELTAREUSEALWAYS
2397 2385 Deltas will always be reused (if possible), even if the destination
2398 2386 revlog would not select the same revisions for the delta. This is the
2399 2387 fastest mode of operation.
2400 2388 DELTAREUSESAMEREVS
2401 2389 Deltas will be reused if the destination revlog would pick the same
2402 2390 revisions for the delta. This mode strikes a balance between speed
2403 2391 and optimization.
2404 2392 DELTAREUSENEVER
2405 2393 Deltas will never be reused. This is the slowest mode of execution.
2406 2394 This mode can be used to recompute deltas (e.g. if the diff/delta
2407 2395 algorithm changes).
2408 2396
2409 2397 Delta computation can be slow, so the choice of delta reuse policy can
2410 2398 significantly affect run time.
2411 2399
2412 2400 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2413 2401 two extremes. Deltas will be reused if they are appropriate. But if the
2414 2402 delta could choose a better revision, it will do so. This means if you
2415 2403 are converting a non-generaldelta revlog to a generaldelta revlog,
2416 2404 deltas will be recomputed if the delta's parent isn't a parent of the
2417 2405 revision.
2418 2406
2419 2407 In addition to the delta policy, the ``forcedeltabothparents``
2420 2408 argument controls whether to force compute deltas against both parents
2421 2409 for merges. By default, the current default is used.
2422 2410 """
2423 2411 if deltareuse not in self.DELTAREUSEALL:
2424 2412 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2425 2413
2426 2414 if len(destrevlog):
2427 2415 raise ValueError(_('destination revlog is not empty'))
2428 2416
2429 2417 if getattr(self, 'filteredrevs', None):
2430 2418 raise ValueError(_('source revlog has filtered revisions'))
2431 2419 if getattr(destrevlog, 'filteredrevs', None):
2432 2420 raise ValueError(_('destination revlog has filtered revisions'))
2433 2421
2434 2422 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2435 2423 # if possible.
2436 2424 oldlazydelta = destrevlog._lazydelta
2437 2425 oldlazydeltabase = destrevlog._lazydeltabase
2438 2426 oldamd = destrevlog._deltabothparents
2439 2427
2440 2428 try:
2441 2429 if deltareuse == self.DELTAREUSEALWAYS:
2442 2430 destrevlog._lazydeltabase = True
2443 2431 destrevlog._lazydelta = True
2444 2432 elif deltareuse == self.DELTAREUSESAMEREVS:
2445 2433 destrevlog._lazydeltabase = False
2446 2434 destrevlog._lazydelta = True
2447 2435 elif deltareuse == self.DELTAREUSENEVER:
2448 2436 destrevlog._lazydeltabase = False
2449 2437 destrevlog._lazydelta = False
2450 2438
2451 2439 destrevlog._deltabothparents = forcedeltabothparents or oldamd
2452 2440
2453 2441 deltacomputer = deltautil.deltacomputer(destrevlog)
2454 2442 index = self.index
2455 2443 for rev in self:
2456 2444 entry = index[rev]
2457 2445
2458 2446 # Some classes override linkrev to take filtered revs into
2459 2447 # account. Use raw entry from index.
2460 2448 flags = entry[0] & 0xffff
2461 2449 linkrev = entry[4]
2462 2450 p1 = index[entry[5]][7]
2463 2451 p2 = index[entry[6]][7]
2464 2452 node = entry[7]
2465 2453
2466 2454 # (Possibly) reuse the delta from the revlog if allowed and
2467 2455 # the revlog chunk is a delta.
2468 2456 cachedelta = None
2469 2457 rawtext = None
2470 2458 if (deltareuse != self.DELTAREUSEFULLADD
2471 2459 and destrevlog._lazydelta):
2472 2460 dp = self.deltaparent(rev)
2473 2461 if dp != nullrev:
2474 2462 cachedelta = (dp, bytes(self._chunk(rev)))
2475 2463
2476 2464 if not cachedelta:
2477 2465 rawtext = self.revision(rev, raw=True)
2478 2466
2479 2467
2480 2468 if deltareuse == self.DELTAREUSEFULLADD:
2481 2469 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2482 2470 cachedelta=cachedelta,
2483 2471 node=node, flags=flags,
2484 2472 deltacomputer=deltacomputer)
2485 2473 else:
2486 2474 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2487 2475 checkambig=False)
2488 2476 dfh = None
2489 2477 if not destrevlog._inline:
2490 2478 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2491 2479 try:
2492 2480 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2493 2481 p2, flags, cachedelta, ifh, dfh,
2494 2482 deltacomputer=deltacomputer)
2495 2483 finally:
2496 2484 if dfh:
2497 2485 dfh.close()
2498 2486 ifh.close()
2499 2487
2500 2488 if addrevisioncb:
2501 2489 addrevisioncb(self, rev, node)
2502 2490 finally:
2503 2491 destrevlog._lazydelta = oldlazydelta
2504 2492 destrevlog._lazydeltabase = oldlazydeltabase
2505 2493 destrevlog._deltabothparents = oldamd
2506 2494
2507 2495 def censorrevision(self, tr, censornode, tombstone=b''):
2508 2496 if (self.version & 0xFFFF) == REVLOGV0:
2509 2497 raise error.RevlogError(_('cannot censor with version %d revlogs') %
2510 2498 self.version)
2511 2499
2512 2500 censorrev = self.rev(censornode)
2513 2501 tombstone = storageutil.packmeta({b'censored': tombstone}, b'')
2514 2502
2515 2503 if len(tombstone) > self.rawsize(censorrev):
2516 2504 raise error.Abort(_('censor tombstone must be no longer than '
2517 2505 'censored data'))
2518 2506
2519 2507 # Rewriting the revlog in place is hard. Our strategy for censoring is
2520 2508 # to create a new revlog, copy all revisions to it, then replace the
2521 2509 # revlogs on transaction close.
2522 2510
2523 2511 newindexfile = self.indexfile + b'.tmpcensored'
2524 2512 newdatafile = self.datafile + b'.tmpcensored'
2525 2513
2526 2514 # This is a bit dangerous. We could easily have a mismatch of state.
2527 2515 newrl = revlog(self.opener, newindexfile, newdatafile,
2528 2516 censorable=True)
2529 2517 newrl.version = self.version
2530 2518 newrl._generaldelta = self._generaldelta
2531 2519 newrl._io = self._io
2532 2520
2533 2521 for rev in self.revs():
2534 2522 node = self.node(rev)
2535 2523 p1, p2 = self.parents(node)
2536 2524
2537 2525 if rev == censorrev:
2538 2526 newrl.addrawrevision(tombstone, tr, self.linkrev(censorrev),
2539 2527 p1, p2, censornode, REVIDX_ISCENSORED)
2540 2528
2541 2529 if newrl.deltaparent(rev) != nullrev:
2542 2530 raise error.Abort(_('censored revision stored as delta; '
2543 2531 'cannot censor'),
2544 2532 hint=_('censoring of revlogs is not '
2545 2533 'fully implemented; please report '
2546 2534 'this bug'))
2547 2535 continue
2548 2536
2549 2537 if self.iscensored(rev):
2550 2538 if self.deltaparent(rev) != nullrev:
2551 2539 raise error.Abort(_('cannot censor due to censored '
2552 2540 'revision having delta stored'))
2553 2541 rawtext = self._chunk(rev)
2554 2542 else:
2555 2543 rawtext = self.revision(rev, raw=True)
2556 2544
2557 2545 newrl.addrawrevision(rawtext, tr, self.linkrev(rev), p1, p2, node,
2558 2546 self.flags(rev))
2559 2547
2560 2548 tr.addbackup(self.indexfile, location='store')
2561 2549 if not self._inline:
2562 2550 tr.addbackup(self.datafile, location='store')
2563 2551
2564 2552 self.opener.rename(newrl.indexfile, self.indexfile)
2565 2553 if not self._inline:
2566 2554 self.opener.rename(newrl.datafile, self.datafile)
2567 2555
2568 2556 self.clearcaches()
2569 2557 self._loadindex()
2570 2558
2571 2559 def verifyintegrity(self, state):
2572 2560 """Verifies the integrity of the revlog.
2573 2561
2574 2562 Yields ``revlogproblem`` instances describing problems that are
2575 2563 found.
2576 2564 """
2577 2565 dd, di = self.checksize()
2578 2566 if dd:
2579 2567 yield revlogproblem(error=_('data length off by %d bytes') % dd)
2580 2568 if di:
2581 2569 yield revlogproblem(error=_('index contains %d extra bytes') % di)
2582 2570
2583 2571 version = self.version & 0xFFFF
2584 2572
2585 2573 # The verifier tells us what version revlog we should be.
2586 2574 if version != state['expectedversion']:
2587 2575 yield revlogproblem(
2588 2576 warning=_("warning: '%s' uses revlog format %d; expected %d") %
2589 2577 (self.indexfile, version, state['expectedversion']))
2590 2578
2591 2579 state['skipread'] = set()
2592 2580
2593 2581 for rev in self:
2594 2582 node = self.node(rev)
2595 2583
2596 2584 # Verify contents. 4 cases to care about:
2597 2585 #
2598 2586 # common: the most common case
2599 2587 # rename: with a rename
2600 2588 # meta: file content starts with b'\1\n', the metadata
2601 2589 # header defined in filelog.py, but without a rename
2602 2590 # ext: content stored externally
2603 2591 #
2604 2592 # More formally, their differences are shown below:
2605 2593 #
2606 2594 # | common | rename | meta | ext
2607 2595 # -------------------------------------------------------
2608 2596 # flags() | 0 | 0 | 0 | not 0
2609 2597 # renamed() | False | True | False | ?
2610 2598 # rawtext[0:2]=='\1\n'| False | True | True | ?
2611 2599 #
2612 2600 # "rawtext" means the raw text stored in revlog data, which
2613 2601 # could be retrieved by "revision(rev, raw=True)". "text"
2614 2602 # mentioned below is "revision(rev, raw=False)".
2615 2603 #
2616 2604 # There are 3 different lengths stored physically:
2617 2605 # 1. L1: rawsize, stored in revlog index
2618 2606 # 2. L2: len(rawtext), stored in revlog data
2619 2607 # 3. L3: len(text), stored in revlog data if flags==0, or
2620 2608 # possibly somewhere else if flags!=0
2621 2609 #
2622 2610 # L1 should be equal to L2. L3 could be different from them.
2623 2611 # "text" may or may not affect commit hash depending on flag
2624 2612 # processors (see revlog.addflagprocessor).
2625 2613 #
2626 2614 # | common | rename | meta | ext
2627 2615 # -------------------------------------------------
2628 2616 # rawsize() | L1 | L1 | L1 | L1
2629 2617 # size() | L1 | L2-LM | L1(*) | L1 (?)
2630 2618 # len(rawtext) | L2 | L2 | L2 | L2
2631 2619 # len(text) | L2 | L2 | L2 | L3
2632 2620 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
2633 2621 #
2634 2622 # LM: length of metadata, depending on rawtext
2635 2623 # (*): not ideal, see comment in filelog.size
2636 2624 # (?): could be "- len(meta)" if the resolved content has
2637 2625 # rename metadata
2638 2626 #
2639 2627 # Checks needed to be done:
2640 2628 # 1. length check: L1 == L2, in all cases.
2641 2629 # 2. hash check: depending on flag processor, we may need to
2642 2630 # use either "text" (external), or "rawtext" (in revlog).
2643 2631
2644 2632 try:
2645 2633 skipflags = state.get('skipflags', 0)
2646 2634 if skipflags:
2647 2635 skipflags &= self.flags(rev)
2648 2636
2649 2637 if skipflags:
2650 2638 state['skipread'].add(node)
2651 2639 else:
2652 2640 # Side-effect: read content and verify hash.
2653 2641 self.revision(node)
2654 2642
2655 2643 l1 = self.rawsize(rev)
2656 2644 l2 = len(self.revision(node, raw=True))
2657 2645
2658 2646 if l1 != l2:
2659 2647 yield revlogproblem(
2660 2648 error=_('unpacked size is %d, %d expected') % (l2, l1),
2661 2649 node=node)
2662 2650
2663 2651 except error.CensoredNodeError:
2664 2652 if state['erroroncensored']:
2665 2653 yield revlogproblem(error=_('censored file data'),
2666 2654 node=node)
2667 2655 state['skipread'].add(node)
2668 2656 except Exception as e:
2669 2657 yield revlogproblem(
2670 2658 error=_('unpacking %s: %s') % (short(node),
2671 2659 stringutil.forcebytestr(e)),
2672 2660 node=node)
2673 2661 state['skipread'].add(node)
2674 2662
2675 2663 def storageinfo(self, exclusivefiles=False, sharedfiles=False,
2676 2664 revisionscount=False, trackedsize=False,
2677 2665 storedsize=False):
2678 2666 d = {}
2679 2667
2680 2668 if exclusivefiles:
2681 2669 d['exclusivefiles'] = [(self.opener, self.indexfile)]
2682 2670 if not self._inline:
2683 2671 d['exclusivefiles'].append((self.opener, self.datafile))
2684 2672
2685 2673 if sharedfiles:
2686 2674 d['sharedfiles'] = []
2687 2675
2688 2676 if revisionscount:
2689 2677 d['revisionscount'] = len(self)
2690 2678
2691 2679 if trackedsize:
2692 2680 d['trackedsize'] = sum(map(self.rawsize, iter(self)))
2693 2681
2694 2682 if storedsize:
2695 2683 d['storedsize'] = sum(self.opener.stat(path).st_size
2696 2684 for path in self.files())
2697 2685
2698 2686 return d
@@ -1,39 +1,53 b''
1 1 # flagutils.py - code to deal with revlog flags and their processors
2 2 #
3 3 # Copyright 2016 Remi Chaintron <remi@fb.com>
4 4 # Copyright 2016-2019 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 from ..i18n import _
12
11 13 from .constants import (
12 14 REVIDX_DEFAULT_FLAGS,
13 15 REVIDX_ELLIPSIS,
14 16 REVIDX_EXTSTORED,
15 17 REVIDX_FLAGS_ORDER,
16 18 REVIDX_ISCENSORED,
17 19 REVIDX_RAWTEXT_CHANGING_FLAGS,
18 20 )
19 21
20 22 from .. import (
23 error,
21 24 util
22 25 )
23 26
24 27 # blanked usage of all the name to prevent pyflakes constraints
25 28 # We need these name available in the module for extensions.
26 29 REVIDX_ISCENSORED
27 30 REVIDX_ELLIPSIS
28 31 REVIDX_EXTSTORED
29 32 REVIDX_DEFAULT_FLAGS
30 33 REVIDX_FLAGS_ORDER
31 34 REVIDX_RAWTEXT_CHANGING_FLAGS
32 35
33 36 REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
34 37
35 38 # Store flag processors (cf. 'addflagprocessor()' to register)
36 39 flagprocessors = {
37 40 REVIDX_ISCENSORED: None,
38 41 }
39 42
43 def insertflagprocessor(flag, processor, flagprocessors):
44 if not flag & REVIDX_KNOWN_FLAGS:
45 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
46 raise error.ProgrammingError(msg)
47 if flag not in REVIDX_FLAGS_ORDER:
48 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
49 raise error.ProgrammingError(msg)
50 if flag in flagprocessors:
51 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
52 raise error.Abort(msg)
53 flagprocessors[flag] = processor
@@ -1,304 +1,304 b''
1 1 # Create server
2 2 $ hg init server
3 3 $ cd server
4 4 $ cat >> .hg/hgrc << EOF
5 5 > [extensions]
6 6 > extension=$TESTDIR/flagprocessorext.py
7 7 > EOF
8 8 $ cd ../
9 9
10 10 # Clone server and enable extensions
11 11 $ hg clone -q server client
12 12 $ cd client
13 13 $ cat >> .hg/hgrc << EOF
14 14 > [extensions]
15 15 > extension=$TESTDIR/flagprocessorext.py
16 16 > EOF
17 17
18 18 # Commit file that will trigger the noop extension
19 19 $ echo '[NOOP]' > noop
20 20 $ hg commit -Aqm "noop"
21 21
22 22 # Commit file that will trigger the base64 extension
23 23 $ echo '[BASE64]' > base64
24 24 $ hg commit -Aqm 'base64'
25 25
26 26 # Commit file that will trigger the gzip extension
27 27 $ echo '[GZIP]' > gzip
28 28 $ hg commit -Aqm 'gzip'
29 29
30 30 # Commit file that will trigger noop and base64
31 31 $ echo '[NOOP][BASE64]' > noop-base64
32 32 $ hg commit -Aqm 'noop+base64'
33 33
34 34 # Commit file that will trigger noop and gzip
35 35 $ echo '[NOOP][GZIP]' > noop-gzip
36 36 $ hg commit -Aqm 'noop+gzip'
37 37
38 38 # Commit file that will trigger base64 and gzip
39 39 $ echo '[BASE64][GZIP]' > base64-gzip
40 40 $ hg commit -Aqm 'base64+gzip'
41 41
42 42 # Commit file that will trigger base64, gzip and noop
43 43 $ echo '[BASE64][GZIP][NOOP]' > base64-gzip-noop
44 44 $ hg commit -Aqm 'base64+gzip+noop'
45 45
46 46 # TEST: ensure the revision data is consistent
47 47 $ hg cat noop
48 48 [NOOP]
49 49 $ hg debugdata noop 0
50 50 [NOOP]
51 51
52 52 $ hg cat -r . base64
53 53 [BASE64]
54 54 $ hg debugdata base64 0
55 55 W0JBU0U2NF0K (no-eol)
56 56
57 57 $ hg cat -r . gzip
58 58 [GZIP]
59 59 $ hg debugdata gzip 0
60 60 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
61 61
62 62 $ hg cat -r . noop-base64
63 63 [NOOP][BASE64]
64 64 $ hg debugdata noop-base64 0
65 65 W05PT1BdW0JBU0U2NF0K (no-eol)
66 66
67 67 $ hg cat -r . noop-gzip
68 68 [NOOP][GZIP]
69 69 $ hg debugdata noop-gzip 0
70 70 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
71 71
72 72 $ hg cat -r . base64-gzip
73 73 [BASE64][GZIP]
74 74 $ hg debugdata base64-gzip 0
75 75 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
76 76
77 77 $ hg cat -r . base64-gzip-noop
78 78 [BASE64][GZIP][NOOP]
79 79 $ hg debugdata base64-gzip-noop 0
80 80 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
81 81
82 82 # Push to the server
83 83 $ hg push
84 84 pushing to $TESTTMP/server
85 85 searching for changes
86 86 adding changesets
87 87 adding manifests
88 88 adding file changes
89 89 added 7 changesets with 7 changes to 7 files
90 90
91 91 Ensure the data got to the server OK
92 92
93 93 $ cd ../server
94 94 $ hg cat -r 6e48f4215d24 noop
95 95 [NOOP]
96 96 $ hg debugdata noop 0
97 97 [NOOP]
98 98
99 99 $ hg cat -r 6e48f4215d24 base64
100 100 [BASE64]
101 101 $ hg debugdata base64 0
102 102 W0JBU0U2NF0K (no-eol)
103 103
104 104 $ hg cat -r 6e48f4215d24 gzip
105 105 [GZIP]
106 106 $ hg debugdata gzip 0
107 107 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
108 108
109 109 $ hg cat -r 6e48f4215d24 noop-base64
110 110 [NOOP][BASE64]
111 111 $ hg debugdata noop-base64 0
112 112 W05PT1BdW0JBU0U2NF0K (no-eol)
113 113
114 114 $ hg cat -r 6e48f4215d24 noop-gzip
115 115 [NOOP][GZIP]
116 116 $ hg debugdata noop-gzip 0
117 117 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
118 118
119 119 $ hg cat -r 6e48f4215d24 base64-gzip
120 120 [BASE64][GZIP]
121 121 $ hg debugdata base64-gzip 0
122 122 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
123 123
124 124 $ hg cat -r 6e48f4215d24 base64-gzip-noop
125 125 [BASE64][GZIP][NOOP]
126 126 $ hg debugdata base64-gzip-noop 0
127 127 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
128 128
129 129 # Initialize new client (not cloning) and setup extension
130 130 $ cd ..
131 131 $ hg init client2
132 132 $ cd client2
133 133 $ cat >> .hg/hgrc << EOF
134 134 > [paths]
135 135 > default = $TESTTMP/server
136 136 > [extensions]
137 137 > extension=$TESTDIR/flagprocessorext.py
138 138 > EOF
139 139
140 140 # Pull from server and update to latest revision
141 141 $ hg pull default
142 142 pulling from $TESTTMP/server
143 143 requesting all changes
144 144 adding changesets
145 145 adding manifests
146 146 adding file changes
147 147 added 7 changesets with 7 changes to 7 files
148 148 new changesets 07b1b9442c5b:6e48f4215d24
149 149 (run 'hg update' to get a working copy)
150 150 $ hg update
151 151 7 files updated, 0 files merged, 0 files removed, 0 files unresolved
152 152
153 153 # TEST: ensure the revision data is consistent
154 154 $ hg cat noop
155 155 [NOOP]
156 156 $ hg debugdata noop 0
157 157 [NOOP]
158 158
159 159 $ hg cat -r . base64
160 160 [BASE64]
161 161 $ hg debugdata base64 0
162 162 W0JBU0U2NF0K (no-eol)
163 163
164 164 $ hg cat -r . gzip
165 165 [GZIP]
166 166 $ hg debugdata gzip 0
167 167 x\x9c\x8bv\x8f\xf2\x0c\x88\xe5\x02\x00\x08\xc8\x01\xfd (no-eol) (esc)
168 168
169 169 $ hg cat -r . noop-base64
170 170 [NOOP][BASE64]
171 171 $ hg debugdata noop-base64 0
172 172 W05PT1BdW0JBU0U2NF0K (no-eol)
173 173
174 174 $ hg cat -r . noop-gzip
175 175 [NOOP][GZIP]
176 176 $ hg debugdata noop-gzip 0
177 177 x\x9c\x8b\xf6\xf3\xf7\x0f\x88\x8dv\x8f\xf2\x0c\x88\xe5\x02\x00\x1dH\x03\xf1 (no-eol) (esc)
178 178
179 179 $ hg cat -r . base64-gzip
180 180 [BASE64][GZIP]
181 181 $ hg debugdata base64-gzip 0
182 182 eJyLdnIMdjUziY12j/IMiOUCACLBBDo= (no-eol)
183 183
184 184 $ hg cat -r . base64-gzip-noop
185 185 [BASE64][GZIP][NOOP]
186 186 $ hg debugdata base64-gzip-noop 0
187 187 eJyLdnIMdjUziY12j/IMiI328/cPiOUCAESjBi4= (no-eol)
188 188
189 189 # TEST: ensure a missing processor is handled
190 190 $ echo '[FAIL][BASE64][GZIP][NOOP]' > fail-base64-gzip-noop
191 191 $ hg commit -Aqm 'fail+base64+gzip+noop'
192 192 abort: missing processor for flag '0x1'!
193 193 [255]
194 194 $ rm fail-base64-gzip-noop
195 195
196 196 # TEST: ensure we cannot register several flag processors on the same flag
197 197 $ cat >> .hg/hgrc << EOF
198 198 > [extensions]
199 199 > extension=$TESTDIR/flagprocessorext.py
200 200 > duplicate=$TESTDIR/flagprocessorext.py
201 201 > EOF
202 202 $ hg debugrebuilddirstate
203 203 Traceback (most recent call last):
204 204 File "*/mercurial/extensions.py", line *, in _runextsetup (glob)
205 205 extsetup(ui)
206 206 File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
207 207 validatehash,
208 208 File "*/mercurial/revlog.py", line *, in addflagprocessor (glob)
209 _insertflagprocessor(flag, processor, flagutil.flagprocessors)
210 File "*/mercurial/revlog.py", line *, in _insertflagprocessor (glob)
209 flagutil.insertflagprocessor(flag, processor, flagutil.flagprocessors)
210 File "*/mercurial/revlogutils/flagutil.py", line *, in insertflagprocessor (glob)
211 211 raise error.Abort(msg)
212 212 mercurial.error.Abort: b"cannot register multiple processors on flag '0x8'." (py3 !)
213 213 Abort: cannot register multiple processors on flag '0x8'. (no-py3 !)
214 214 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
215 215 $ hg st 2>&1 | egrep 'cannot register multiple processors|flagprocessorext'
216 216 File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
217 217 mercurial.error.Abort: b"cannot register multiple processors on flag '0x8'." (py3 !)
218 218 Abort: cannot register multiple processors on flag '0x8'. (no-py3 !)
219 219 *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
220 220 File "*/tests/flagprocessorext.py", line *, in b64decode (glob)
221 221
222 222 $ cd ..
223 223
224 224 # TEST: bundle repo
225 225 $ hg init bundletest
226 226 $ cd bundletest
227 227
228 228 $ cat >> .hg/hgrc << EOF
229 229 > [extensions]
230 230 > flagprocessor=$TESTDIR/flagprocessorext.py
231 231 > EOF
232 232
233 233 $ for i in 0 single two three 4; do
234 234 > echo '[BASE64]a-bit-longer-'$i > base64
235 235 > hg commit -m base64-$i -A base64
236 236 > done
237 237
238 238 $ hg update 2 -q
239 239 $ echo '[BASE64]a-bit-longer-branching' > base64
240 240 $ hg commit -q -m branching
241 241
242 242 #if repobundlerepo
243 243 $ hg bundle --base 1 bundle.hg
244 244 4 changesets found
245 245 $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
246 246 $ hg -R bundle.hg log --stat -T '{rev} {desc}\n' base64
247 247 5 branching
248 248 base64 | 2 +-
249 249 1 files changed, 1 insertions(+), 1 deletions(-)
250 250
251 251 4 base64-4
252 252 base64 | 2 +-
253 253 1 files changed, 1 insertions(+), 1 deletions(-)
254 254
255 255 3 base64-three
256 256 base64 | 2 +-
257 257 1 files changed, 1 insertions(+), 1 deletions(-)
258 258
259 259 2 base64-two
260 260 base64 | 2 +-
261 261 1 files changed, 1 insertions(+), 1 deletions(-)
262 262
263 263 1 base64-single
264 264 base64 | 2 +-
265 265 1 files changed, 1 insertions(+), 1 deletions(-)
266 266
267 267 0 base64-0
268 268 base64 | 1 +
269 269 1 files changed, 1 insertions(+), 0 deletions(-)
270 270
271 271
272 272 $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
273 273 $ hg -R bundle-again.hg log --stat -T '{rev} {desc}\n' base64
274 274 5 branching
275 275 base64 | 2 +-
276 276 1 files changed, 1 insertions(+), 1 deletions(-)
277 277
278 278 4 base64-4
279 279 base64 | 2 +-
280 280 1 files changed, 1 insertions(+), 1 deletions(-)
281 281
282 282 3 base64-three
283 283 base64 | 2 +-
284 284 1 files changed, 1 insertions(+), 1 deletions(-)
285 285
286 286 2 base64-two
287 287 base64 | 2 +-
288 288 1 files changed, 1 insertions(+), 1 deletions(-)
289 289
290 290 1 base64-single
291 291 base64 | 2 +-
292 292 1 files changed, 1 insertions(+), 1 deletions(-)
293 293
294 294 0 base64-0
295 295 base64 | 1 +
296 296 1 files changed, 1 insertions(+), 0 deletions(-)
297 297
298 298 $ rm bundle.hg bundle-again.hg
299 299 #endif
300 300
301 301 # TEST: hg status
302 302
303 303 $ hg status
304 304 $ hg diff
General Comments 0
You need to be logged in to leave comments. Login now