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