##// END OF EJS Templates
addrevision: use general delta when the incoming base delta is bad...
Pierre-Yves David -
r27191:20a9226b default
parent child Browse files
Show More
@@ -1,1732 +1,1731
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 # import stuff from node for others to import from revlog
15 15 import collections
16 16 from node import bin, hex, nullid, nullrev
17 17 from i18n import _
18 18 import ancestor, mdiff, parsers, error, util, templatefilters
19 19 import struct, zlib, errno
20 20
21 21 _pack = struct.pack
22 22 _unpack = struct.unpack
23 23 _compress = zlib.compress
24 24 _decompress = zlib.decompress
25 25 _sha = util.sha1
26 26
27 27 # revlog header flags
28 28 REVLOGV0 = 0
29 29 REVLOGNG = 1
30 30 REVLOGNGINLINEDATA = (1 << 16)
31 31 REVLOGGENERALDELTA = (1 << 17)
32 32 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
33 33 REVLOG_DEFAULT_FORMAT = REVLOGNG
34 34 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
35 35 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
36 36
37 37 # revlog index flags
38 38 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
39 39 REVIDX_DEFAULT_FLAGS = 0
40 40 REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED
41 41
42 42 # max size of revlog with inline data
43 43 _maxinline = 131072
44 44 _chunksize = 1048576
45 45
46 46 RevlogError = error.RevlogError
47 47 LookupError = error.LookupError
48 48 CensoredNodeError = error.CensoredNodeError
49 49
50 50 def getoffset(q):
51 51 return int(q >> 16)
52 52
53 53 def gettype(q):
54 54 return int(q & 0xFFFF)
55 55
56 56 def offset_type(offset, type):
57 57 return long(long(offset) << 16 | type)
58 58
59 59 _nullhash = _sha(nullid)
60 60
61 61 def hash(text, p1, p2):
62 62 """generate a hash from the given text and its parent hashes
63 63
64 64 This hash combines both the current file contents and its history
65 65 in a manner that makes it easy to distinguish nodes with the same
66 66 content in the revision graph.
67 67 """
68 68 # As of now, if one of the parent node is null, p2 is null
69 69 if p2 == nullid:
70 70 # deep copy of a hash is faster than creating one
71 71 s = _nullhash.copy()
72 72 s.update(p1)
73 73 else:
74 74 # none of the parent nodes are nullid
75 75 l = [p1, p2]
76 76 l.sort()
77 77 s = _sha(l[0])
78 78 s.update(l[1])
79 79 s.update(text)
80 80 return s.digest()
81 81
82 82 def decompress(bin):
83 83 """ decompress the given input """
84 84 if not bin:
85 85 return bin
86 86 t = bin[0]
87 87 if t == '\0':
88 88 return bin
89 89 if t == 'x':
90 90 try:
91 91 return _decompress(bin)
92 92 except zlib.error as e:
93 93 raise RevlogError(_("revlog decompress error: %s") % str(e))
94 94 if t == 'u':
95 95 return bin[1:]
96 96 raise RevlogError(_("unknown compression type %r") % t)
97 97
98 98 # index v0:
99 99 # 4 bytes: offset
100 100 # 4 bytes: compressed length
101 101 # 4 bytes: base rev
102 102 # 4 bytes: link rev
103 103 # 20 bytes: parent 1 nodeid
104 104 # 20 bytes: parent 2 nodeid
105 105 # 20 bytes: nodeid
106 106 indexformatv0 = ">4l20s20s20s"
107 107
108 108 class revlogoldio(object):
109 109 def __init__(self):
110 110 self.size = struct.calcsize(indexformatv0)
111 111
112 112 def parseindex(self, data, inline):
113 113 s = self.size
114 114 index = []
115 115 nodemap = {nullid: nullrev}
116 116 n = off = 0
117 117 l = len(data)
118 118 while off + s <= l:
119 119 cur = data[off:off + s]
120 120 off += s
121 121 e = _unpack(indexformatv0, cur)
122 122 # transform to revlogv1 format
123 123 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
124 124 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
125 125 index.append(e2)
126 126 nodemap[e[6]] = n
127 127 n += 1
128 128
129 129 # add the magic null revision at -1
130 130 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
131 131
132 132 return index, nodemap, None
133 133
134 134 def packentry(self, entry, node, version, rev):
135 135 if gettype(entry[0]):
136 136 raise RevlogError(_("index entry flags need RevlogNG"))
137 137 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
138 138 node(entry[5]), node(entry[6]), entry[7])
139 139 return _pack(indexformatv0, *e2)
140 140
141 141 # index ng:
142 142 # 6 bytes: offset
143 143 # 2 bytes: flags
144 144 # 4 bytes: compressed length
145 145 # 4 bytes: uncompressed length
146 146 # 4 bytes: base rev
147 147 # 4 bytes: link rev
148 148 # 4 bytes: parent 1 rev
149 149 # 4 bytes: parent 2 rev
150 150 # 32 bytes: nodeid
151 151 indexformatng = ">Qiiiiii20s12x"
152 152 versionformat = ">I"
153 153
154 154 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
155 155 # signed integer)
156 156 _maxentrysize = 0x7fffffff
157 157
158 158 class revlogio(object):
159 159 def __init__(self):
160 160 self.size = struct.calcsize(indexformatng)
161 161
162 162 def parseindex(self, data, inline):
163 163 # call the C implementation to parse the index data
164 164 index, cache = parsers.parse_index2(data, inline)
165 165 return index, getattr(index, 'nodemap', None), cache
166 166
167 167 def packentry(self, entry, node, version, rev):
168 168 p = _pack(indexformatng, *entry)
169 169 if rev == 0:
170 170 p = _pack(versionformat, version) + p[4:]
171 171 return p
172 172
173 173 class revlog(object):
174 174 """
175 175 the underlying revision storage object
176 176
177 177 A revlog consists of two parts, an index and the revision data.
178 178
179 179 The index is a file with a fixed record size containing
180 180 information on each revision, including its nodeid (hash), the
181 181 nodeids of its parents, the position and offset of its data within
182 182 the data file, and the revision it's based on. Finally, each entry
183 183 contains a linkrev entry that can serve as a pointer to external
184 184 data.
185 185
186 186 The revision data itself is a linear collection of data chunks.
187 187 Each chunk represents a revision and is usually represented as a
188 188 delta against the previous chunk. To bound lookup time, runs of
189 189 deltas are limited to about 2 times the length of the original
190 190 version data. This makes retrieval of a version proportional to
191 191 its size, or O(1) relative to the number of revisions.
192 192
193 193 Both pieces of the revlog are written to in an append-only
194 194 fashion, which means we never need to rewrite a file to insert or
195 195 remove data, and can use some simple techniques to avoid the need
196 196 for locking while reading.
197 197 """
198 198 def __init__(self, opener, indexfile):
199 199 """
200 200 create a revlog object
201 201
202 202 opener is a function that abstracts the file opening operation
203 203 and can be used to implement COW semantics or the like.
204 204 """
205 205 self.indexfile = indexfile
206 206 self.datafile = indexfile[:-2] + ".d"
207 207 self.opener = opener
208 208 # 3-tuple of (node, rev, text) for a raw revision.
209 209 self._cache = None
210 210 # 2-tuple of (rev, baserev) defining the base revision the delta chain
211 211 # begins at for a revision.
212 212 self._basecache = None
213 213 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
214 214 self._chunkcache = (0, '')
215 215 # How much data to read and cache into the raw revlog data cache.
216 216 self._chunkcachesize = 65536
217 217 self._maxchainlen = None
218 218 self._aggressivemergedeltas = False
219 219 self.index = []
220 220 # Mapping of partial identifiers to full nodes.
221 221 self._pcache = {}
222 222 # Mapping of revision integer to full node.
223 223 self._nodecache = {nullid: nullrev}
224 224 self._nodepos = None
225 225
226 226 v = REVLOG_DEFAULT_VERSION
227 227 opts = getattr(opener, 'options', None)
228 228 if opts is not None:
229 229 if 'revlogv1' in opts:
230 230 if 'generaldelta' in opts:
231 231 v |= REVLOGGENERALDELTA
232 232 else:
233 233 v = 0
234 234 if 'chunkcachesize' in opts:
235 235 self._chunkcachesize = opts['chunkcachesize']
236 236 if 'maxchainlen' in opts:
237 237 self._maxchainlen = opts['maxchainlen']
238 238 if 'aggressivemergedeltas' in opts:
239 239 self._aggressivemergedeltas = opts['aggressivemergedeltas']
240 240 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
241 241
242 242 if self._chunkcachesize <= 0:
243 243 raise RevlogError(_('revlog chunk cache size %r is not greater '
244 244 'than 0') % self._chunkcachesize)
245 245 elif self._chunkcachesize & (self._chunkcachesize - 1):
246 246 raise RevlogError(_('revlog chunk cache size %r is not a power '
247 247 'of 2') % self._chunkcachesize)
248 248
249 249 indexdata = ''
250 250 self._initempty = True
251 251 try:
252 252 f = self.opener(self.indexfile)
253 253 indexdata = f.read()
254 254 f.close()
255 255 if len(indexdata) > 0:
256 256 v = struct.unpack(versionformat, indexdata[:4])[0]
257 257 self._initempty = False
258 258 except IOError as inst:
259 259 if inst.errno != errno.ENOENT:
260 260 raise
261 261
262 262 self.version = v
263 263 self._inline = v & REVLOGNGINLINEDATA
264 264 self._generaldelta = v & REVLOGGENERALDELTA
265 265 flags = v & ~0xFFFF
266 266 fmt = v & 0xFFFF
267 267 if fmt == REVLOGV0 and flags:
268 268 raise RevlogError(_("index %s unknown flags %#04x for format v0")
269 269 % (self.indexfile, flags >> 16))
270 270 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
271 271 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
272 272 % (self.indexfile, flags >> 16))
273 273 elif fmt > REVLOGNG:
274 274 raise RevlogError(_("index %s unknown format %d")
275 275 % (self.indexfile, fmt))
276 276
277 277 self._io = revlogio()
278 278 if self.version == REVLOGV0:
279 279 self._io = revlogoldio()
280 280 try:
281 281 d = self._io.parseindex(indexdata, self._inline)
282 282 except (ValueError, IndexError):
283 283 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
284 284 self.index, nodemap, self._chunkcache = d
285 285 if nodemap is not None:
286 286 self.nodemap = self._nodecache = nodemap
287 287 if not self._chunkcache:
288 288 self._chunkclear()
289 289 # revnum -> (chain-length, sum-delta-length)
290 290 self._chaininfocache = {}
291 291
292 292 def tip(self):
293 293 return self.node(len(self.index) - 2)
294 294 def __contains__(self, rev):
295 295 return 0 <= rev < len(self)
296 296 def __len__(self):
297 297 return len(self.index) - 1
298 298 def __iter__(self):
299 299 return iter(xrange(len(self)))
300 300 def revs(self, start=0, stop=None):
301 301 """iterate over all rev in this revlog (from start to stop)"""
302 302 step = 1
303 303 if stop is not None:
304 304 if start > stop:
305 305 step = -1
306 306 stop += step
307 307 else:
308 308 stop = len(self)
309 309 return xrange(start, stop, step)
310 310
311 311 @util.propertycache
312 312 def nodemap(self):
313 313 self.rev(self.node(0))
314 314 return self._nodecache
315 315
316 316 def hasnode(self, node):
317 317 try:
318 318 self.rev(node)
319 319 return True
320 320 except KeyError:
321 321 return False
322 322
323 323 def clearcaches(self):
324 324 try:
325 325 self._nodecache.clearcaches()
326 326 except AttributeError:
327 327 self._nodecache = {nullid: nullrev}
328 328 self._nodepos = None
329 329
330 330 def rev(self, node):
331 331 try:
332 332 return self._nodecache[node]
333 333 except TypeError:
334 334 raise
335 335 except RevlogError:
336 336 # parsers.c radix tree lookup failed
337 337 raise LookupError(node, self.indexfile, _('no node'))
338 338 except KeyError:
339 339 # pure python cache lookup failed
340 340 n = self._nodecache
341 341 i = self.index
342 342 p = self._nodepos
343 343 if p is None:
344 344 p = len(i) - 2
345 345 for r in xrange(p, -1, -1):
346 346 v = i[r][7]
347 347 n[v] = r
348 348 if v == node:
349 349 self._nodepos = r - 1
350 350 return r
351 351 raise LookupError(node, self.indexfile, _('no node'))
352 352
353 353 def node(self, rev):
354 354 return self.index[rev][7]
355 355 def linkrev(self, rev):
356 356 return self.index[rev][4]
357 357 def parents(self, node):
358 358 i = self.index
359 359 d = i[self.rev(node)]
360 360 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
361 361 def parentrevs(self, rev):
362 362 return self.index[rev][5:7]
363 363 def start(self, rev):
364 364 return int(self.index[rev][0] >> 16)
365 365 def end(self, rev):
366 366 return self.start(rev) + self.length(rev)
367 367 def length(self, rev):
368 368 return self.index[rev][1]
369 369 def chainbase(self, rev):
370 370 index = self.index
371 371 base = index[rev][3]
372 372 while base != rev:
373 373 rev = base
374 374 base = index[rev][3]
375 375 return base
376 376 def chainlen(self, rev):
377 377 return self._chaininfo(rev)[0]
378 378
379 379 def _chaininfo(self, rev):
380 380 chaininfocache = self._chaininfocache
381 381 if rev in chaininfocache:
382 382 return chaininfocache[rev]
383 383 index = self.index
384 384 generaldelta = self._generaldelta
385 385 iterrev = rev
386 386 e = index[iterrev]
387 387 clen = 0
388 388 compresseddeltalen = 0
389 389 while iterrev != e[3]:
390 390 clen += 1
391 391 compresseddeltalen += e[1]
392 392 if generaldelta:
393 393 iterrev = e[3]
394 394 else:
395 395 iterrev -= 1
396 396 if iterrev in chaininfocache:
397 397 t = chaininfocache[iterrev]
398 398 clen += t[0]
399 399 compresseddeltalen += t[1]
400 400 break
401 401 e = index[iterrev]
402 402 else:
403 403 # Add text length of base since decompressing that also takes
404 404 # work. For cache hits the length is already included.
405 405 compresseddeltalen += e[1]
406 406 r = (clen, compresseddeltalen)
407 407 chaininfocache[rev] = r
408 408 return r
409 409
410 410 def flags(self, rev):
411 411 return self.index[rev][0] & 0xFFFF
412 412 def rawsize(self, rev):
413 413 """return the length of the uncompressed text for a given revision"""
414 414 l = self.index[rev][2]
415 415 if l >= 0:
416 416 return l
417 417
418 418 t = self.revision(self.node(rev))
419 419 return len(t)
420 420 size = rawsize
421 421
422 422 def ancestors(self, revs, stoprev=0, inclusive=False):
423 423 """Generate the ancestors of 'revs' in reverse topological order.
424 424 Does not generate revs lower than stoprev.
425 425
426 426 See the documentation for ancestor.lazyancestors for more details."""
427 427
428 428 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
429 429 inclusive=inclusive)
430 430
431 431 def descendants(self, revs):
432 432 """Generate the descendants of 'revs' in revision order.
433 433
434 434 Yield a sequence of revision numbers starting with a child of
435 435 some rev in revs, i.e., each revision is *not* considered a
436 436 descendant of itself. Results are ordered by revision number (a
437 437 topological sort)."""
438 438 first = min(revs)
439 439 if first == nullrev:
440 440 for i in self:
441 441 yield i
442 442 return
443 443
444 444 seen = set(revs)
445 445 for i in self.revs(start=first + 1):
446 446 for x in self.parentrevs(i):
447 447 if x != nullrev and x in seen:
448 448 seen.add(i)
449 449 yield i
450 450 break
451 451
452 452 def findcommonmissing(self, common=None, heads=None):
453 453 """Return a tuple of the ancestors of common and the ancestors of heads
454 454 that are not ancestors of common. In revset terminology, we return the
455 455 tuple:
456 456
457 457 ::common, (::heads) - (::common)
458 458
459 459 The list is sorted by revision number, meaning it is
460 460 topologically sorted.
461 461
462 462 'heads' and 'common' are both lists of node IDs. If heads is
463 463 not supplied, uses all of the revlog's heads. If common is not
464 464 supplied, uses nullid."""
465 465 if common is None:
466 466 common = [nullid]
467 467 if heads is None:
468 468 heads = self.heads()
469 469
470 470 common = [self.rev(n) for n in common]
471 471 heads = [self.rev(n) for n in heads]
472 472
473 473 # we want the ancestors, but inclusive
474 474 class lazyset(object):
475 475 def __init__(self, lazyvalues):
476 476 self.addedvalues = set()
477 477 self.lazyvalues = lazyvalues
478 478
479 479 def __contains__(self, value):
480 480 return value in self.addedvalues or value in self.lazyvalues
481 481
482 482 def __iter__(self):
483 483 added = self.addedvalues
484 484 for r in added:
485 485 yield r
486 486 for r in self.lazyvalues:
487 487 if not r in added:
488 488 yield r
489 489
490 490 def add(self, value):
491 491 self.addedvalues.add(value)
492 492
493 493 def update(self, values):
494 494 self.addedvalues.update(values)
495 495
496 496 has = lazyset(self.ancestors(common))
497 497 has.add(nullrev)
498 498 has.update(common)
499 499
500 500 # take all ancestors from heads that aren't in has
501 501 missing = set()
502 502 visit = collections.deque(r for r in heads if r not in has)
503 503 while visit:
504 504 r = visit.popleft()
505 505 if r in missing:
506 506 continue
507 507 else:
508 508 missing.add(r)
509 509 for p in self.parentrevs(r):
510 510 if p not in has:
511 511 visit.append(p)
512 512 missing = list(missing)
513 513 missing.sort()
514 514 return has, [self.node(r) for r in missing]
515 515
516 516 def incrementalmissingrevs(self, common=None):
517 517 """Return an object that can be used to incrementally compute the
518 518 revision numbers of the ancestors of arbitrary sets that are not
519 519 ancestors of common. This is an ancestor.incrementalmissingancestors
520 520 object.
521 521
522 522 'common' is a list of revision numbers. If common is not supplied, uses
523 523 nullrev.
524 524 """
525 525 if common is None:
526 526 common = [nullrev]
527 527
528 528 return ancestor.incrementalmissingancestors(self.parentrevs, common)
529 529
530 530 def findmissingrevs(self, common=None, heads=None):
531 531 """Return the revision numbers of the ancestors of heads that
532 532 are not ancestors of common.
533 533
534 534 More specifically, return a list of revision numbers corresponding to
535 535 nodes N such that every N satisfies the following constraints:
536 536
537 537 1. N is an ancestor of some node in 'heads'
538 538 2. N is not an ancestor of any node in 'common'
539 539
540 540 The list is sorted by revision number, meaning it is
541 541 topologically sorted.
542 542
543 543 'heads' and 'common' are both lists of revision numbers. If heads is
544 544 not supplied, uses all of the revlog's heads. If common is not
545 545 supplied, uses nullid."""
546 546 if common is None:
547 547 common = [nullrev]
548 548 if heads is None:
549 549 heads = self.headrevs()
550 550
551 551 inc = self.incrementalmissingrevs(common=common)
552 552 return inc.missingancestors(heads)
553 553
554 554 def findmissing(self, common=None, heads=None):
555 555 """Return the ancestors of heads that are not ancestors of common.
556 556
557 557 More specifically, return a list of nodes N such that every N
558 558 satisfies the following constraints:
559 559
560 560 1. N is an ancestor of some node in 'heads'
561 561 2. N is not an ancestor of any node in 'common'
562 562
563 563 The list is sorted by revision number, meaning it is
564 564 topologically sorted.
565 565
566 566 'heads' and 'common' are both lists of node IDs. If heads is
567 567 not supplied, uses all of the revlog's heads. If common is not
568 568 supplied, uses nullid."""
569 569 if common is None:
570 570 common = [nullid]
571 571 if heads is None:
572 572 heads = self.heads()
573 573
574 574 common = [self.rev(n) for n in common]
575 575 heads = [self.rev(n) for n in heads]
576 576
577 577 inc = self.incrementalmissingrevs(common=common)
578 578 return [self.node(r) for r in inc.missingancestors(heads)]
579 579
580 580 def nodesbetween(self, roots=None, heads=None):
581 581 """Return a topological path from 'roots' to 'heads'.
582 582
583 583 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
584 584 topologically sorted list of all nodes N that satisfy both of
585 585 these constraints:
586 586
587 587 1. N is a descendant of some node in 'roots'
588 588 2. N is an ancestor of some node in 'heads'
589 589
590 590 Every node is considered to be both a descendant and an ancestor
591 591 of itself, so every reachable node in 'roots' and 'heads' will be
592 592 included in 'nodes'.
593 593
594 594 'outroots' is the list of reachable nodes in 'roots', i.e., the
595 595 subset of 'roots' that is returned in 'nodes'. Likewise,
596 596 'outheads' is the subset of 'heads' that is also in 'nodes'.
597 597
598 598 'roots' and 'heads' are both lists of node IDs. If 'roots' is
599 599 unspecified, uses nullid as the only root. If 'heads' is
600 600 unspecified, uses list of all of the revlog's heads."""
601 601 nonodes = ([], [], [])
602 602 if roots is not None:
603 603 roots = list(roots)
604 604 if not roots:
605 605 return nonodes
606 606 lowestrev = min([self.rev(n) for n in roots])
607 607 else:
608 608 roots = [nullid] # Everybody's a descendant of nullid
609 609 lowestrev = nullrev
610 610 if (lowestrev == nullrev) and (heads is None):
611 611 # We want _all_ the nodes!
612 612 return ([self.node(r) for r in self], [nullid], list(self.heads()))
613 613 if heads is None:
614 614 # All nodes are ancestors, so the latest ancestor is the last
615 615 # node.
616 616 highestrev = len(self) - 1
617 617 # Set ancestors to None to signal that every node is an ancestor.
618 618 ancestors = None
619 619 # Set heads to an empty dictionary for later discovery of heads
620 620 heads = {}
621 621 else:
622 622 heads = list(heads)
623 623 if not heads:
624 624 return nonodes
625 625 ancestors = set()
626 626 # Turn heads into a dictionary so we can remove 'fake' heads.
627 627 # Also, later we will be using it to filter out the heads we can't
628 628 # find from roots.
629 629 heads = dict.fromkeys(heads, False)
630 630 # Start at the top and keep marking parents until we're done.
631 631 nodestotag = set(heads)
632 632 # Remember where the top was so we can use it as a limit later.
633 633 highestrev = max([self.rev(n) for n in nodestotag])
634 634 while nodestotag:
635 635 # grab a node to tag
636 636 n = nodestotag.pop()
637 637 # Never tag nullid
638 638 if n == nullid:
639 639 continue
640 640 # A node's revision number represents its place in a
641 641 # topologically sorted list of nodes.
642 642 r = self.rev(n)
643 643 if r >= lowestrev:
644 644 if n not in ancestors:
645 645 # If we are possibly a descendant of one of the roots
646 646 # and we haven't already been marked as an ancestor
647 647 ancestors.add(n) # Mark as ancestor
648 648 # Add non-nullid parents to list of nodes to tag.
649 649 nodestotag.update([p for p in self.parents(n) if
650 650 p != nullid])
651 651 elif n in heads: # We've seen it before, is it a fake head?
652 652 # So it is, real heads should not be the ancestors of
653 653 # any other heads.
654 654 heads.pop(n)
655 655 if not ancestors:
656 656 return nonodes
657 657 # Now that we have our set of ancestors, we want to remove any
658 658 # roots that are not ancestors.
659 659
660 660 # If one of the roots was nullid, everything is included anyway.
661 661 if lowestrev > nullrev:
662 662 # But, since we weren't, let's recompute the lowest rev to not
663 663 # include roots that aren't ancestors.
664 664
665 665 # Filter out roots that aren't ancestors of heads
666 666 roots = [n for n in roots if n in ancestors]
667 667 # Recompute the lowest revision
668 668 if roots:
669 669 lowestrev = min([self.rev(n) for n in roots])
670 670 else:
671 671 # No more roots? Return empty list
672 672 return nonodes
673 673 else:
674 674 # We are descending from nullid, and don't need to care about
675 675 # any other roots.
676 676 lowestrev = nullrev
677 677 roots = [nullid]
678 678 # Transform our roots list into a set.
679 679 descendants = set(roots)
680 680 # Also, keep the original roots so we can filter out roots that aren't
681 681 # 'real' roots (i.e. are descended from other roots).
682 682 roots = descendants.copy()
683 683 # Our topologically sorted list of output nodes.
684 684 orderedout = []
685 685 # Don't start at nullid since we don't want nullid in our output list,
686 686 # and if nullid shows up in descendants, empty parents will look like
687 687 # they're descendants.
688 688 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
689 689 n = self.node(r)
690 690 isdescendant = False
691 691 if lowestrev == nullrev: # Everybody is a descendant of nullid
692 692 isdescendant = True
693 693 elif n in descendants:
694 694 # n is already a descendant
695 695 isdescendant = True
696 696 # This check only needs to be done here because all the roots
697 697 # will start being marked is descendants before the loop.
698 698 if n in roots:
699 699 # If n was a root, check if it's a 'real' root.
700 700 p = tuple(self.parents(n))
701 701 # If any of its parents are descendants, it's not a root.
702 702 if (p[0] in descendants) or (p[1] in descendants):
703 703 roots.remove(n)
704 704 else:
705 705 p = tuple(self.parents(n))
706 706 # A node is a descendant if either of its parents are
707 707 # descendants. (We seeded the dependents list with the roots
708 708 # up there, remember?)
709 709 if (p[0] in descendants) or (p[1] in descendants):
710 710 descendants.add(n)
711 711 isdescendant = True
712 712 if isdescendant and ((ancestors is None) or (n in ancestors)):
713 713 # Only include nodes that are both descendants and ancestors.
714 714 orderedout.append(n)
715 715 if (ancestors is not None) and (n in heads):
716 716 # We're trying to figure out which heads are reachable
717 717 # from roots.
718 718 # Mark this head as having been reached
719 719 heads[n] = True
720 720 elif ancestors is None:
721 721 # Otherwise, we're trying to discover the heads.
722 722 # Assume this is a head because if it isn't, the next step
723 723 # will eventually remove it.
724 724 heads[n] = True
725 725 # But, obviously its parents aren't.
726 726 for p in self.parents(n):
727 727 heads.pop(p, None)
728 728 heads = [n for n, flag in heads.iteritems() if flag]
729 729 roots = list(roots)
730 730 assert orderedout
731 731 assert roots
732 732 assert heads
733 733 return (orderedout, roots, heads)
734 734
735 735 def headrevs(self):
736 736 try:
737 737 return self.index.headrevs()
738 738 except AttributeError:
739 739 return self._headrevs()
740 740
741 741 def computephases(self, roots):
742 742 return self.index.computephasesmapsets(roots)
743 743
744 744 def _headrevs(self):
745 745 count = len(self)
746 746 if not count:
747 747 return [nullrev]
748 748 # we won't iter over filtered rev so nobody is a head at start
749 749 ishead = [0] * (count + 1)
750 750 index = self.index
751 751 for r in self:
752 752 ishead[r] = 1 # I may be an head
753 753 e = index[r]
754 754 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
755 755 return [r for r, val in enumerate(ishead) if val]
756 756
757 757 def heads(self, start=None, stop=None):
758 758 """return the list of all nodes that have no children
759 759
760 760 if start is specified, only heads that are descendants of
761 761 start will be returned
762 762 if stop is specified, it will consider all the revs from stop
763 763 as if they had no children
764 764 """
765 765 if start is None and stop is None:
766 766 if not len(self):
767 767 return [nullid]
768 768 return [self.node(r) for r in self.headrevs()]
769 769
770 770 if start is None:
771 771 start = nullid
772 772 if stop is None:
773 773 stop = []
774 774 stoprevs = set([self.rev(n) for n in stop])
775 775 startrev = self.rev(start)
776 776 reachable = set((startrev,))
777 777 heads = set((startrev,))
778 778
779 779 parentrevs = self.parentrevs
780 780 for r in self.revs(start=startrev + 1):
781 781 for p in parentrevs(r):
782 782 if p in reachable:
783 783 if r not in stoprevs:
784 784 reachable.add(r)
785 785 heads.add(r)
786 786 if p in heads and p not in stoprevs:
787 787 heads.remove(p)
788 788
789 789 return [self.node(r) for r in heads]
790 790
791 791 def children(self, node):
792 792 """find the children of a given node"""
793 793 c = []
794 794 p = self.rev(node)
795 795 for r in self.revs(start=p + 1):
796 796 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
797 797 if prevs:
798 798 for pr in prevs:
799 799 if pr == p:
800 800 c.append(self.node(r))
801 801 elif p == nullrev:
802 802 c.append(self.node(r))
803 803 return c
804 804
805 805 def descendant(self, start, end):
806 806 if start == nullrev:
807 807 return True
808 808 for i in self.descendants([start]):
809 809 if i == end:
810 810 return True
811 811 elif i > end:
812 812 break
813 813 return False
814 814
815 815 def commonancestorsheads(self, a, b):
816 816 """calculate all the heads of the common ancestors of nodes a and b"""
817 817 a, b = self.rev(a), self.rev(b)
818 818 try:
819 819 ancs = self.index.commonancestorsheads(a, b)
820 820 except (AttributeError, OverflowError): # C implementation failed
821 821 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
822 822 return map(self.node, ancs)
823 823
824 824 def isancestor(self, a, b):
825 825 """return True if node a is an ancestor of node b
826 826
827 827 The implementation of this is trivial but the use of
828 828 commonancestorsheads is not."""
829 829 return a in self.commonancestorsheads(a, b)
830 830
831 831 def ancestor(self, a, b):
832 832 """calculate the "best" common ancestor of nodes a and b"""
833 833
834 834 a, b = self.rev(a), self.rev(b)
835 835 try:
836 836 ancs = self.index.ancestors(a, b)
837 837 except (AttributeError, OverflowError):
838 838 ancs = ancestor.ancestors(self.parentrevs, a, b)
839 839 if ancs:
840 840 # choose a consistent winner when there's a tie
841 841 return min(map(self.node, ancs))
842 842 return nullid
843 843
844 844 def _match(self, id):
845 845 if isinstance(id, int):
846 846 # rev
847 847 return self.node(id)
848 848 if len(id) == 20:
849 849 # possibly a binary node
850 850 # odds of a binary node being all hex in ASCII are 1 in 10**25
851 851 try:
852 852 node = id
853 853 self.rev(node) # quick search the index
854 854 return node
855 855 except LookupError:
856 856 pass # may be partial hex id
857 857 try:
858 858 # str(rev)
859 859 rev = int(id)
860 860 if str(rev) != id:
861 861 raise ValueError
862 862 if rev < 0:
863 863 rev = len(self) + rev
864 864 if rev < 0 or rev >= len(self):
865 865 raise ValueError
866 866 return self.node(rev)
867 867 except (ValueError, OverflowError):
868 868 pass
869 869 if len(id) == 40:
870 870 try:
871 871 # a full hex nodeid?
872 872 node = bin(id)
873 873 self.rev(node)
874 874 return node
875 875 except (TypeError, LookupError):
876 876 pass
877 877
878 878 def _partialmatch(self, id):
879 879 try:
880 880 n = self.index.partialmatch(id)
881 881 if n and self.hasnode(n):
882 882 return n
883 883 return None
884 884 except RevlogError:
885 885 # parsers.c radix tree lookup gave multiple matches
886 886 # fall through to slow path that filters hidden revisions
887 887 pass
888 888 except (AttributeError, ValueError):
889 889 # we are pure python, or key was too short to search radix tree
890 890 pass
891 891
892 892 if id in self._pcache:
893 893 return self._pcache[id]
894 894
895 895 if len(id) < 40:
896 896 try:
897 897 # hex(node)[:...]
898 898 l = len(id) // 2 # grab an even number of digits
899 899 prefix = bin(id[:l * 2])
900 900 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
901 901 nl = [n for n in nl if hex(n).startswith(id) and
902 902 self.hasnode(n)]
903 903 if len(nl) > 0:
904 904 if len(nl) == 1:
905 905 self._pcache[id] = nl[0]
906 906 return nl[0]
907 907 raise LookupError(id, self.indexfile,
908 908 _('ambiguous identifier'))
909 909 return None
910 910 except TypeError:
911 911 pass
912 912
913 913 def lookup(self, id):
914 914 """locate a node based on:
915 915 - revision number or str(revision number)
916 916 - nodeid or subset of hex nodeid
917 917 """
918 918 n = self._match(id)
919 919 if n is not None:
920 920 return n
921 921 n = self._partialmatch(id)
922 922 if n:
923 923 return n
924 924
925 925 raise LookupError(id, self.indexfile, _('no match found'))
926 926
927 927 def cmp(self, node, text):
928 928 """compare text with a given file revision
929 929
930 930 returns True if text is different than what is stored.
931 931 """
932 932 p1, p2 = self.parents(node)
933 933 return hash(text, p1, p2) != node
934 934
935 935 def _addchunk(self, offset, data):
936 936 """Add a segment to the revlog cache.
937 937
938 938 Accepts an absolute offset and the data that is at that location.
939 939 """
940 940 o, d = self._chunkcache
941 941 # try to add to existing cache
942 942 if o + len(d) == offset and len(d) + len(data) < _chunksize:
943 943 self._chunkcache = o, d + data
944 944 else:
945 945 self._chunkcache = offset, data
946 946
947 947 def _loadchunk(self, offset, length, df=None):
948 948 """Load a segment of raw data from the revlog.
949 949
950 950 Accepts an absolute offset, length to read, and an optional existing
951 951 file handle to read from.
952 952
953 953 If an existing file handle is passed, it will be seeked and the
954 954 original seek position will NOT be restored.
955 955
956 956 Returns a str or buffer of raw byte data.
957 957 """
958 958 if df is not None:
959 959 closehandle = False
960 960 else:
961 961 if self._inline:
962 962 df = self.opener(self.indexfile)
963 963 else:
964 964 df = self.opener(self.datafile)
965 965 closehandle = True
966 966
967 967 # Cache data both forward and backward around the requested
968 968 # data, in a fixed size window. This helps speed up operations
969 969 # involving reading the revlog backwards.
970 970 cachesize = self._chunkcachesize
971 971 realoffset = offset & ~(cachesize - 1)
972 972 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
973 973 - realoffset)
974 974 df.seek(realoffset)
975 975 d = df.read(reallength)
976 976 if closehandle:
977 977 df.close()
978 978 self._addchunk(realoffset, d)
979 979 if offset != realoffset or reallength != length:
980 980 return util.buffer(d, offset - realoffset, length)
981 981 return d
982 982
983 983 def _getchunk(self, offset, length, df=None):
984 984 """Obtain a segment of raw data from the revlog.
985 985
986 986 Accepts an absolute offset, length of bytes to obtain, and an
987 987 optional file handle to the already-opened revlog. If the file
988 988 handle is used, it's original seek position will not be preserved.
989 989
990 990 Requests for data may be returned from a cache.
991 991
992 992 Returns a str or a buffer instance of raw byte data.
993 993 """
994 994 o, d = self._chunkcache
995 995 l = len(d)
996 996
997 997 # is it in the cache?
998 998 cachestart = offset - o
999 999 cacheend = cachestart + length
1000 1000 if cachestart >= 0 and cacheend <= l:
1001 1001 if cachestart == 0 and cacheend == l:
1002 1002 return d # avoid a copy
1003 1003 return util.buffer(d, cachestart, cacheend - cachestart)
1004 1004
1005 1005 return self._loadchunk(offset, length, df=df)
1006 1006
1007 1007 def _chunkraw(self, startrev, endrev, df=None):
1008 1008 """Obtain a segment of raw data corresponding to a range of revisions.
1009 1009
1010 1010 Accepts the start and end revisions and an optional already-open
1011 1011 file handle to be used for reading. If the file handle is read, its
1012 1012 seek position will not be preserved.
1013 1013
1014 1014 Requests for data may be satisfied by a cache.
1015 1015
1016 1016 Returns a str or a buffer instance of raw byte data. Callers will
1017 1017 need to call ``self.start(rev)`` and ``self.length()`` to determine
1018 1018 where each revision's data begins and ends.
1019 1019 """
1020 1020 start = self.start(startrev)
1021 1021 end = self.end(endrev)
1022 1022 if self._inline:
1023 1023 start += (startrev + 1) * self._io.size
1024 1024 end += (endrev + 1) * self._io.size
1025 1025 length = end - start
1026 1026 return self._getchunk(start, length, df=df)
1027 1027
1028 1028 def _chunk(self, rev, df=None):
1029 1029 """Obtain a single decompressed chunk for a revision.
1030 1030
1031 1031 Accepts an integer revision and an optional already-open file handle
1032 1032 to be used for reading. If used, the seek position of the file will not
1033 1033 be preserved.
1034 1034
1035 1035 Returns a str holding uncompressed data for the requested revision.
1036 1036 """
1037 1037 return decompress(self._chunkraw(rev, rev, df=df))
1038 1038
1039 1039 def _chunks(self, revs, df=None):
1040 1040 """Obtain decompressed chunks for the specified revisions.
1041 1041
1042 1042 Accepts an iterable of numeric revisions that are assumed to be in
1043 1043 ascending order. Also accepts an optional already-open file handle
1044 1044 to be used for reading. If used, the seek position of the file will
1045 1045 not be preserved.
1046 1046
1047 1047 This function is similar to calling ``self._chunk()`` multiple times,
1048 1048 but is faster.
1049 1049
1050 1050 Returns a list with decompressed data for each requested revision.
1051 1051 """
1052 1052 if not revs:
1053 1053 return []
1054 1054 start = self.start
1055 1055 length = self.length
1056 1056 inline = self._inline
1057 1057 iosize = self._io.size
1058 1058 buffer = util.buffer
1059 1059
1060 1060 l = []
1061 1061 ladd = l.append
1062 1062
1063 1063 # preload the cache
1064 1064 try:
1065 1065 while True:
1066 1066 # ensure that the cache doesn't change out from under us
1067 1067 _cache = self._chunkcache
1068 1068 self._chunkraw(revs[0], revs[-1], df=df)
1069 1069 if _cache == self._chunkcache:
1070 1070 break
1071 1071 offset, data = _cache
1072 1072 except OverflowError:
1073 1073 # issue4215 - we can't cache a run of chunks greater than
1074 1074 # 2G on Windows
1075 1075 return [self._chunk(rev, df=df) for rev in revs]
1076 1076
1077 1077 for rev in revs:
1078 1078 chunkstart = start(rev)
1079 1079 if inline:
1080 1080 chunkstart += (rev + 1) * iosize
1081 1081 chunklength = length(rev)
1082 1082 ladd(decompress(buffer(data, chunkstart - offset, chunklength)))
1083 1083
1084 1084 return l
1085 1085
1086 1086 def _chunkclear(self):
1087 1087 """Clear the raw chunk cache."""
1088 1088 self._chunkcache = (0, '')
1089 1089
1090 1090 def deltaparent(self, rev):
1091 1091 """return deltaparent of the given revision"""
1092 1092 base = self.index[rev][3]
1093 1093 if base == rev:
1094 1094 return nullrev
1095 1095 elif self._generaldelta:
1096 1096 return base
1097 1097 else:
1098 1098 return rev - 1
1099 1099
1100 1100 def revdiff(self, rev1, rev2):
1101 1101 """return or calculate a delta between two revisions"""
1102 1102 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1103 1103 return str(self._chunk(rev2))
1104 1104
1105 1105 return mdiff.textdiff(self.revision(rev1),
1106 1106 self.revision(rev2))
1107 1107
1108 1108 def revision(self, nodeorrev, _df=None):
1109 1109 """return an uncompressed revision of a given node or revision
1110 1110 number.
1111 1111
1112 1112 _df is an existing file handle to read from. It is meant to only be
1113 1113 used internally.
1114 1114 """
1115 1115 if isinstance(nodeorrev, int):
1116 1116 rev = nodeorrev
1117 1117 node = self.node(rev)
1118 1118 else:
1119 1119 node = nodeorrev
1120 1120 rev = None
1121 1121
1122 1122 cachedrev = None
1123 1123 if node == nullid:
1124 1124 return ""
1125 1125 if self._cache:
1126 1126 if self._cache[0] == node:
1127 1127 return self._cache[2]
1128 1128 cachedrev = self._cache[1]
1129 1129
1130 1130 # look up what we need to read
1131 1131 text = None
1132 1132 if rev is None:
1133 1133 rev = self.rev(node)
1134 1134
1135 1135 # check rev flags
1136 1136 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
1137 1137 raise RevlogError(_('incompatible revision flag %x') %
1138 1138 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
1139 1139
1140 1140 # build delta chain
1141 1141 chain = []
1142 1142 index = self.index # for performance
1143 1143 generaldelta = self._generaldelta
1144 1144 iterrev = rev
1145 1145 e = index[iterrev]
1146 1146 while iterrev != e[3] and iterrev != cachedrev:
1147 1147 chain.append(iterrev)
1148 1148 if generaldelta:
1149 1149 iterrev = e[3]
1150 1150 else:
1151 1151 iterrev -= 1
1152 1152 e = index[iterrev]
1153 1153
1154 1154 if iterrev == cachedrev:
1155 1155 # cache hit
1156 1156 text = self._cache[2]
1157 1157 else:
1158 1158 chain.append(iterrev)
1159 1159 chain.reverse()
1160 1160
1161 1161 # drop cache to save memory
1162 1162 self._cache = None
1163 1163
1164 1164 bins = self._chunks(chain, df=_df)
1165 1165 if text is None:
1166 1166 text = str(bins[0])
1167 1167 bins = bins[1:]
1168 1168
1169 1169 text = mdiff.patches(text, bins)
1170 1170
1171 1171 text = self._checkhash(text, node, rev)
1172 1172
1173 1173 self._cache = (node, rev, text)
1174 1174 return text
1175 1175
1176 1176 def hash(self, text, p1, p2):
1177 1177 """Compute a node hash.
1178 1178
1179 1179 Available as a function so that subclasses can replace the hash
1180 1180 as needed.
1181 1181 """
1182 1182 return hash(text, p1, p2)
1183 1183
1184 1184 def _checkhash(self, text, node, rev):
1185 1185 p1, p2 = self.parents(node)
1186 1186 self.checkhash(text, p1, p2, node, rev)
1187 1187 return text
1188 1188
1189 1189 def checkhash(self, text, p1, p2, node, rev=None):
1190 1190 if node != self.hash(text, p1, p2):
1191 1191 revornode = rev
1192 1192 if revornode is None:
1193 1193 revornode = templatefilters.short(hex(node))
1194 1194 raise RevlogError(_("integrity check failed on %s:%s")
1195 1195 % (self.indexfile, revornode))
1196 1196
1197 1197 def checkinlinesize(self, tr, fp=None):
1198 1198 """Check if the revlog is too big for inline and convert if so.
1199 1199
1200 1200 This should be called after revisions are added to the revlog. If the
1201 1201 revlog has grown too large to be an inline revlog, it will convert it
1202 1202 to use multiple index and data files.
1203 1203 """
1204 1204 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1205 1205 return
1206 1206
1207 1207 trinfo = tr.find(self.indexfile)
1208 1208 if trinfo is None:
1209 1209 raise RevlogError(_("%s not found in the transaction")
1210 1210 % self.indexfile)
1211 1211
1212 1212 trindex = trinfo[2]
1213 1213 if trindex is not None:
1214 1214 dataoff = self.start(trindex)
1215 1215 else:
1216 1216 # revlog was stripped at start of transaction, use all leftover data
1217 1217 trindex = len(self) - 1
1218 1218 dataoff = self.end(-2)
1219 1219
1220 1220 tr.add(self.datafile, dataoff)
1221 1221
1222 1222 if fp:
1223 1223 fp.flush()
1224 1224 fp.close()
1225 1225
1226 1226 df = self.opener(self.datafile, 'w')
1227 1227 try:
1228 1228 for r in self:
1229 1229 df.write(self._chunkraw(r, r))
1230 1230 finally:
1231 1231 df.close()
1232 1232
1233 1233 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1234 1234 self.version &= ~(REVLOGNGINLINEDATA)
1235 1235 self._inline = False
1236 1236 for i in self:
1237 1237 e = self._io.packentry(self.index[i], self.node, self.version, i)
1238 1238 fp.write(e)
1239 1239
1240 1240 # if we don't call close, the temp file will never replace the
1241 1241 # real index
1242 1242 fp.close()
1243 1243
1244 1244 tr.replace(self.indexfile, trindex * self._io.size)
1245 1245 self._chunkclear()
1246 1246
1247 1247 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1248 1248 node=None):
1249 1249 """add a revision to the log
1250 1250
1251 1251 text - the revision data to add
1252 1252 transaction - the transaction object used for rollback
1253 1253 link - the linkrev data to add
1254 1254 p1, p2 - the parent nodeids of the revision
1255 1255 cachedelta - an optional precomputed delta
1256 1256 node - nodeid of revision; typically node is not specified, and it is
1257 1257 computed by default as hash(text, p1, p2), however subclasses might
1258 1258 use different hashing method (and override checkhash() in such case)
1259 1259 """
1260 1260 if link == nullrev:
1261 1261 raise RevlogError(_("attempted to add linkrev -1 to %s")
1262 1262 % self.indexfile)
1263 1263
1264 1264 if len(text) > _maxentrysize:
1265 1265 raise RevlogError(
1266 1266 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1267 1267 % (self.indexfile, len(text)))
1268 1268
1269 1269 node = node or self.hash(text, p1, p2)
1270 1270 if node in self.nodemap:
1271 1271 return node
1272 1272
1273 1273 dfh = None
1274 1274 if not self._inline:
1275 1275 dfh = self.opener(self.datafile, "a+")
1276 1276 ifh = self.opener(self.indexfile, "a+")
1277 1277 try:
1278 1278 return self._addrevision(node, text, transaction, link, p1, p2,
1279 1279 REVIDX_DEFAULT_FLAGS, cachedelta, ifh, dfh)
1280 1280 finally:
1281 1281 if dfh:
1282 1282 dfh.close()
1283 1283 ifh.close()
1284 1284
1285 1285 def compress(self, text):
1286 1286 """ generate a possibly-compressed representation of text """
1287 1287 if not text:
1288 1288 return ("", text)
1289 1289 l = len(text)
1290 1290 bin = None
1291 1291 if l < 44:
1292 1292 pass
1293 1293 elif l > 1000000:
1294 1294 # zlib makes an internal copy, thus doubling memory usage for
1295 1295 # large files, so lets do this in pieces
1296 1296 z = zlib.compressobj()
1297 1297 p = []
1298 1298 pos = 0
1299 1299 while pos < l:
1300 1300 pos2 = pos + 2**20
1301 1301 p.append(z.compress(text[pos:pos2]))
1302 1302 pos = pos2
1303 1303 p.append(z.flush())
1304 1304 if sum(map(len, p)) < l:
1305 1305 bin = "".join(p)
1306 1306 else:
1307 1307 bin = _compress(text)
1308 1308 if bin is None or len(bin) > l:
1309 1309 if text[0] == '\0':
1310 1310 return ("", text)
1311 1311 return ('u', text)
1312 1312 return ("", bin)
1313 1313
1314 1314 def _isgooddelta(self, d, textlen):
1315 1315 """Returns True if the given delta is good. Good means that it is within
1316 1316 the disk span, disk size, and chain length bounds that we know to be
1317 1317 performant."""
1318 1318 if d is None:
1319 1319 return False
1320 1320
1321 1321 # - 'dist' is the distance from the base revision -- bounding it limits
1322 1322 # the amount of I/O we need to do.
1323 1323 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1324 1324 # to apply -- bounding it limits the amount of CPU we consume.
1325 1325 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1326 1326 if (dist > textlen * 4 or l > textlen or
1327 1327 compresseddeltalen > textlen * 2 or
1328 1328 (self._maxchainlen and chainlen > self._maxchainlen)):
1329 1329 return False
1330 1330
1331 1331 return True
1332 1332
1333 1333 def _addrevision(self, node, text, transaction, link, p1, p2, flags,
1334 1334 cachedelta, ifh, dfh, alwayscache=False):
1335 1335 """internal function to add revisions to the log
1336 1336
1337 1337 see addrevision for argument descriptions.
1338 1338 invariants:
1339 1339 - text is optional (can be None); if not set, cachedelta must be set.
1340 1340 if both are set, they must correspond to each other.
1341 1341 """
1342 1342 btext = [text]
1343 1343 def buildtext():
1344 1344 if btext[0] is not None:
1345 1345 return btext[0]
1346 1346 baserev = cachedelta[0]
1347 1347 delta = cachedelta[1]
1348 1348 # special case deltas which replace entire base; no need to decode
1349 1349 # base revision. this neatly avoids censored bases, which throw when
1350 1350 # they're decoded.
1351 1351 hlen = struct.calcsize(">lll")
1352 1352 if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
1353 1353 len(delta) - hlen):
1354 1354 btext[0] = delta[hlen:]
1355 1355 else:
1356 1356 if self._inline:
1357 1357 fh = ifh
1358 1358 else:
1359 1359 fh = dfh
1360 1360 basetext = self.revision(self.node(baserev), _df=fh)
1361 1361 btext[0] = mdiff.patch(basetext, delta)
1362 1362 try:
1363 1363 self.checkhash(btext[0], p1, p2, node)
1364 1364 if flags & REVIDX_ISCENSORED:
1365 1365 raise RevlogError(_('node %s is not censored') % node)
1366 1366 except CensoredNodeError:
1367 1367 # must pass the censored index flag to add censored revisions
1368 1368 if not flags & REVIDX_ISCENSORED:
1369 1369 raise
1370 1370 return btext[0]
1371 1371
1372 1372 def builddelta(rev):
1373 1373 # can we use the cached delta?
1374 1374 if cachedelta and cachedelta[0] == rev:
1375 1375 delta = cachedelta[1]
1376 1376 else:
1377 1377 t = buildtext()
1378 1378 if self.iscensored(rev):
1379 1379 # deltas based on a censored revision must replace the
1380 1380 # full content in one patch, so delta works everywhere
1381 1381 header = mdiff.replacediffheader(self.rawsize(rev), len(t))
1382 1382 delta = header + t
1383 1383 else:
1384 1384 if self._inline:
1385 1385 fh = ifh
1386 1386 else:
1387 1387 fh = dfh
1388 1388 ptext = self.revision(self.node(rev), _df=fh)
1389 1389 delta = mdiff.textdiff(ptext, t)
1390 1390 data = self.compress(delta)
1391 1391 l = len(data[1]) + len(data[0])
1392 1392 if basecache[0] == rev:
1393 1393 chainbase = basecache[1]
1394 1394 else:
1395 1395 chainbase = self.chainbase(rev)
1396 1396 dist = l + offset - self.start(chainbase)
1397 1397 if self._generaldelta:
1398 1398 base = rev
1399 1399 else:
1400 1400 base = chainbase
1401 1401 chainlen, compresseddeltalen = self._chaininfo(rev)
1402 1402 chainlen += 1
1403 1403 compresseddeltalen += l
1404 1404 return dist, l, data, base, chainbase, chainlen, compresseddeltalen
1405 1405
1406 1406 curr = len(self)
1407 1407 prev = curr - 1
1408 1408 base = chainbase = curr
1409 1409 chainlen = None
1410 1410 offset = self.end(prev)
1411 1411 delta = None
1412 1412 if self._basecache is None:
1413 1413 self._basecache = (prev, self.chainbase(prev))
1414 1414 basecache = self._basecache
1415 1415 p1r, p2r = self.rev(p1), self.rev(p2)
1416 1416
1417 1417 # full versions are inserted when the needed deltas
1418 1418 # become comparable to the uncompressed text
1419 1419 if text is None:
1420 1420 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1421 1421 cachedelta[1])
1422 1422 else:
1423 1423 textlen = len(text)
1424 1424
1425 1425 # should we try to build a delta?
1426 1426 if prev != nullrev:
1427 tested = set()
1427 1428 if cachedelta and self._generaldelta and self._lazydeltabase:
1428 1429 # Assume what we received from the server is a good choice
1429 1430 # build delta will reuse the cache
1430 1431 candidatedelta = builddelta(cachedelta[0])
1432 tested.add(candidatedelta[3])
1431 1433 if self._isgooddelta(candidatedelta, textlen):
1432 1434 delta = candidatedelta
1433 elif prev != candidatedelta[3]:
1434 # Try against prev to hopefully save us a fulltext.
1435 delta = builddelta(prev)
1436 elif self._generaldelta:
1435 if delta is None and self._generaldelta:
1437 1436 parents = [p1r, p2r]
1438 if not self._aggressivemergedeltas:
1437 # exclude already lazy tested base if any
1438 parents = [p for p in parents if p not in tested]
1439 if parents and not self._aggressivemergedeltas:
1439 1440 # Pick whichever parent is closer to us (to minimize the
1440 # chance of having to build a fulltext). Since
1441 # nullrev == -1, any non-merge commit will always pick p1r.
1441 # chance of having to build a fulltext).
1442 1442 parents = [max(parents)]
1443 tested.update(parents)
1443 1444 pdeltas = []
1444 1445 for p in parents:
1445 1446 pd = builddelta(p)
1446 1447 if self._isgooddelta(pd, textlen):
1447 1448 pdeltas.append(pd)
1448 1449 if pdeltas:
1449 1450 delta = min(pdeltas, key=lambda x: x[1])
1450 elif prev not in parents:
1451 # Neither is good, try against prev to hopefully save us
1452 # a fulltext.
1453 delta = builddelta(prev)
1454 else:
1451 if delta is None and prev not in tested:
1452 # other approach failed try against prev to hopefully save us a
1453 # fulltext.
1455 1454 delta = builddelta(prev)
1456 1455 if delta is not None:
1457 1456 dist, l, data, base, chainbase, chainlen, compresseddeltalen = delta
1458 1457
1459 1458 if not self._isgooddelta(delta, textlen):
1460 1459 text = buildtext()
1461 1460 data = self.compress(text)
1462 1461 l = len(data[1]) + len(data[0])
1463 1462 base = chainbase = curr
1464 1463
1465 1464 e = (offset_type(offset, flags), l, textlen,
1466 1465 base, link, p1r, p2r, node)
1467 1466 self.index.insert(-1, e)
1468 1467 self.nodemap[node] = curr
1469 1468
1470 1469 entry = self._io.packentry(e, self.node, self.version, curr)
1471 1470 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1472 1471
1473 1472 if alwayscache and text is None:
1474 1473 text = buildtext()
1475 1474
1476 1475 if type(text) == str: # only accept immutable objects
1477 1476 self._cache = (node, curr, text)
1478 1477 self._basecache = (curr, chainbase)
1479 1478 return node
1480 1479
1481 1480 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1482 1481 curr = len(self) - 1
1483 1482 if not self._inline:
1484 1483 transaction.add(self.datafile, offset)
1485 1484 transaction.add(self.indexfile, curr * len(entry))
1486 1485 if data[0]:
1487 1486 dfh.write(data[0])
1488 1487 dfh.write(data[1])
1489 1488 ifh.write(entry)
1490 1489 else:
1491 1490 offset += curr * self._io.size
1492 1491 transaction.add(self.indexfile, offset, curr)
1493 1492 ifh.write(entry)
1494 1493 ifh.write(data[0])
1495 1494 ifh.write(data[1])
1496 1495 self.checkinlinesize(transaction, ifh)
1497 1496
1498 1497 def addgroup(self, cg, linkmapper, transaction, addrevisioncb=None):
1499 1498 """
1500 1499 add a delta group
1501 1500
1502 1501 given a set of deltas, add them to the revision log. the
1503 1502 first delta is against its parent, which should be in our
1504 1503 log, the rest are against the previous delta.
1505 1504
1506 1505 If ``addrevisioncb`` is defined, it will be called with arguments of
1507 1506 this revlog and the node that was added.
1508 1507 """
1509 1508
1510 1509 # track the base of the current delta log
1511 1510 content = []
1512 1511 node = None
1513 1512
1514 1513 r = len(self)
1515 1514 end = 0
1516 1515 if r:
1517 1516 end = self.end(r - 1)
1518 1517 ifh = self.opener(self.indexfile, "a+")
1519 1518 isize = r * self._io.size
1520 1519 if self._inline:
1521 1520 transaction.add(self.indexfile, end + isize, r)
1522 1521 dfh = None
1523 1522 else:
1524 1523 transaction.add(self.indexfile, isize, r)
1525 1524 transaction.add(self.datafile, end)
1526 1525 dfh = self.opener(self.datafile, "a+")
1527 1526 def flush():
1528 1527 if dfh:
1529 1528 dfh.flush()
1530 1529 ifh.flush()
1531 1530 try:
1532 1531 # loop through our set of deltas
1533 1532 chain = None
1534 1533 while True:
1535 1534 chunkdata = cg.deltachunk(chain)
1536 1535 if not chunkdata:
1537 1536 break
1538 1537 node = chunkdata['node']
1539 1538 p1 = chunkdata['p1']
1540 1539 p2 = chunkdata['p2']
1541 1540 cs = chunkdata['cs']
1542 1541 deltabase = chunkdata['deltabase']
1543 1542 delta = chunkdata['delta']
1544 1543
1545 1544 content.append(node)
1546 1545
1547 1546 link = linkmapper(cs)
1548 1547 if node in self.nodemap:
1549 1548 # this can happen if two branches make the same change
1550 1549 chain = node
1551 1550 continue
1552 1551
1553 1552 for p in (p1, p2):
1554 1553 if p not in self.nodemap:
1555 1554 raise LookupError(p, self.indexfile,
1556 1555 _('unknown parent'))
1557 1556
1558 1557 if deltabase not in self.nodemap:
1559 1558 raise LookupError(deltabase, self.indexfile,
1560 1559 _('unknown delta base'))
1561 1560
1562 1561 baserev = self.rev(deltabase)
1563 1562
1564 1563 if baserev != nullrev and self.iscensored(baserev):
1565 1564 # if base is censored, delta must be full replacement in a
1566 1565 # single patch operation
1567 1566 hlen = struct.calcsize(">lll")
1568 1567 oldlen = self.rawsize(baserev)
1569 1568 newlen = len(delta) - hlen
1570 1569 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
1571 1570 raise error.CensoredBaseError(self.indexfile,
1572 1571 self.node(baserev))
1573 1572
1574 1573 flags = REVIDX_DEFAULT_FLAGS
1575 1574 if self._peek_iscensored(baserev, delta, flush):
1576 1575 flags |= REVIDX_ISCENSORED
1577 1576
1578 1577 # We assume consumers of addrevisioncb will want to retrieve
1579 1578 # the added revision, which will require a call to
1580 1579 # revision(). revision() will fast path if there is a cache
1581 1580 # hit. So, we tell _addrevision() to always cache in this case.
1582 1581 chain = self._addrevision(node, None, transaction, link,
1583 1582 p1, p2, flags, (baserev, delta),
1584 1583 ifh, dfh,
1585 1584 alwayscache=bool(addrevisioncb))
1586 1585
1587 1586 if addrevisioncb:
1588 1587 addrevisioncb(self, chain)
1589 1588
1590 1589 if not dfh and not self._inline:
1591 1590 # addrevision switched from inline to conventional
1592 1591 # reopen the index
1593 1592 ifh.close()
1594 1593 dfh = self.opener(self.datafile, "a+")
1595 1594 ifh = self.opener(self.indexfile, "a+")
1596 1595 finally:
1597 1596 if dfh:
1598 1597 dfh.close()
1599 1598 ifh.close()
1600 1599
1601 1600 return content
1602 1601
1603 1602 def iscensored(self, rev):
1604 1603 """Check if a file revision is censored."""
1605 1604 return False
1606 1605
1607 1606 def _peek_iscensored(self, baserev, delta, flush):
1608 1607 """Quickly check if a delta produces a censored revision."""
1609 1608 return False
1610 1609
1611 1610 def getstrippoint(self, minlink):
1612 1611 """find the minimum rev that must be stripped to strip the linkrev
1613 1612
1614 1613 Returns a tuple containing the minimum rev and a set of all revs that
1615 1614 have linkrevs that will be broken by this strip.
1616 1615 """
1617 1616 brokenrevs = set()
1618 1617 strippoint = len(self)
1619 1618
1620 1619 heads = {}
1621 1620 futurelargelinkrevs = set()
1622 1621 for head in self.headrevs():
1623 1622 headlinkrev = self.linkrev(head)
1624 1623 heads[head] = headlinkrev
1625 1624 if headlinkrev >= minlink:
1626 1625 futurelargelinkrevs.add(headlinkrev)
1627 1626
1628 1627 # This algorithm involves walking down the rev graph, starting at the
1629 1628 # heads. Since the revs are topologically sorted according to linkrev,
1630 1629 # once all head linkrevs are below the minlink, we know there are
1631 1630 # no more revs that could have a linkrev greater than minlink.
1632 1631 # So we can stop walking.
1633 1632 while futurelargelinkrevs:
1634 1633 strippoint -= 1
1635 1634 linkrev = heads.pop(strippoint)
1636 1635
1637 1636 if linkrev < minlink:
1638 1637 brokenrevs.add(strippoint)
1639 1638 else:
1640 1639 futurelargelinkrevs.remove(linkrev)
1641 1640
1642 1641 for p in self.parentrevs(strippoint):
1643 1642 if p != nullrev:
1644 1643 plinkrev = self.linkrev(p)
1645 1644 heads[p] = plinkrev
1646 1645 if plinkrev >= minlink:
1647 1646 futurelargelinkrevs.add(plinkrev)
1648 1647
1649 1648 return strippoint, brokenrevs
1650 1649
1651 1650 def strip(self, minlink, transaction):
1652 1651 """truncate the revlog on the first revision with a linkrev >= minlink
1653 1652
1654 1653 This function is called when we're stripping revision minlink and
1655 1654 its descendants from the repository.
1656 1655
1657 1656 We have to remove all revisions with linkrev >= minlink, because
1658 1657 the equivalent changelog revisions will be renumbered after the
1659 1658 strip.
1660 1659
1661 1660 So we truncate the revlog on the first of these revisions, and
1662 1661 trust that the caller has saved the revisions that shouldn't be
1663 1662 removed and that it'll re-add them after this truncation.
1664 1663 """
1665 1664 if len(self) == 0:
1666 1665 return
1667 1666
1668 1667 rev, _ = self.getstrippoint(minlink)
1669 1668 if rev == len(self):
1670 1669 return
1671 1670
1672 1671 # first truncate the files on disk
1673 1672 end = self.start(rev)
1674 1673 if not self._inline:
1675 1674 transaction.add(self.datafile, end)
1676 1675 end = rev * self._io.size
1677 1676 else:
1678 1677 end += rev * self._io.size
1679 1678
1680 1679 transaction.add(self.indexfile, end)
1681 1680
1682 1681 # then reset internal state in memory to forget those revisions
1683 1682 self._cache = None
1684 1683 self._chaininfocache = {}
1685 1684 self._chunkclear()
1686 1685 for x in xrange(rev, len(self)):
1687 1686 del self.nodemap[self.node(x)]
1688 1687
1689 1688 del self.index[rev:-1]
1690 1689
1691 1690 def checksize(self):
1692 1691 expected = 0
1693 1692 if len(self):
1694 1693 expected = max(0, self.end(len(self) - 1))
1695 1694
1696 1695 try:
1697 1696 f = self.opener(self.datafile)
1698 1697 f.seek(0, 2)
1699 1698 actual = f.tell()
1700 1699 f.close()
1701 1700 dd = actual - expected
1702 1701 except IOError as inst:
1703 1702 if inst.errno != errno.ENOENT:
1704 1703 raise
1705 1704 dd = 0
1706 1705
1707 1706 try:
1708 1707 f = self.opener(self.indexfile)
1709 1708 f.seek(0, 2)
1710 1709 actual = f.tell()
1711 1710 f.close()
1712 1711 s = self._io.size
1713 1712 i = max(0, actual // s)
1714 1713 di = actual - (i * s)
1715 1714 if self._inline:
1716 1715 databytes = 0
1717 1716 for r in self:
1718 1717 databytes += max(0, self.length(r))
1719 1718 dd = 0
1720 1719 di = actual - len(self) * s - databytes
1721 1720 except IOError as inst:
1722 1721 if inst.errno != errno.ENOENT:
1723 1722 raise
1724 1723 di = 0
1725 1724
1726 1725 return (dd, di)
1727 1726
1728 1727 def files(self):
1729 1728 res = [self.indexfile]
1730 1729 if not self._inline:
1731 1730 res.append(self.datafile)
1732 1731 return res
@@ -1,725 +1,725
1 1
2 2 $ cat << EOF >> $HGRCPATH
3 3 > [format]
4 4 > usegeneraldelta=yes
5 5 > EOF
6 6
7 7 Setting up test
8 8
9 9 $ hg init test
10 10 $ cd test
11 11 $ echo 0 > afile
12 12 $ hg add afile
13 13 $ hg commit -m "0.0"
14 14 $ echo 1 >> afile
15 15 $ hg commit -m "0.1"
16 16 $ echo 2 >> afile
17 17 $ hg commit -m "0.2"
18 18 $ echo 3 >> afile
19 19 $ hg commit -m "0.3"
20 20 $ hg update -C 0
21 21 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
22 22 $ echo 1 >> afile
23 23 $ hg commit -m "1.1"
24 24 created new head
25 25 $ echo 2 >> afile
26 26 $ hg commit -m "1.2"
27 27 $ echo "a line" > fred
28 28 $ echo 3 >> afile
29 29 $ hg add fred
30 30 $ hg commit -m "1.3"
31 31 $ hg mv afile adifferentfile
32 32 $ hg commit -m "1.3m"
33 33 $ hg update -C 3
34 34 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
35 35 $ hg mv afile anotherfile
36 36 $ hg commit -m "0.3m"
37 37 $ hg verify
38 38 checking changesets
39 39 checking manifests
40 40 crosschecking files in changesets and manifests
41 41 checking files
42 42 4 files, 9 changesets, 7 total revisions
43 43 $ cd ..
44 44 $ hg init empty
45 45
46 46 Bundle and phase
47 47
48 48 $ hg -R test phase --force --secret 0
49 49 $ hg -R test bundle phase.hg empty
50 50 searching for changes
51 51 no changes found (ignored 9 secret changesets)
52 52 [1]
53 53 $ hg -R test phase --draft -r 'head()'
54 54
55 55 Bundle --all
56 56
57 57 $ hg -R test bundle --all all.hg
58 58 9 changesets found
59 59
60 60 Bundle test to full.hg
61 61
62 62 $ hg -R test bundle full.hg empty
63 63 searching for changes
64 64 9 changesets found
65 65
66 66 Unbundle full.hg in test
67 67
68 68 $ hg -R test unbundle full.hg
69 69 adding changesets
70 70 adding manifests
71 71 adding file changes
72 72 added 0 changesets with 0 changes to 4 files
73 73 (run 'hg update' to get a working copy)
74 74
75 75 Verify empty
76 76
77 77 $ hg -R empty heads
78 78 [1]
79 79 $ hg -R empty verify
80 80 checking changesets
81 81 checking manifests
82 82 crosschecking files in changesets and manifests
83 83 checking files
84 84 0 files, 0 changesets, 0 total revisions
85 85
86 86 Pull full.hg into test (using --cwd)
87 87
88 88 $ hg --cwd test pull ../full.hg
89 89 pulling from ../full.hg
90 90 searching for changes
91 91 no changes found
92 92
93 93 Verify that there are no leaked temporary files after pull (issue2797)
94 94
95 95 $ ls test/.hg | grep .hg10un
96 96 [1]
97 97
98 98 Pull full.hg into empty (using --cwd)
99 99
100 100 $ hg --cwd empty pull ../full.hg
101 101 pulling from ../full.hg
102 102 requesting all changes
103 103 adding changesets
104 104 adding manifests
105 105 adding file changes
106 106 added 9 changesets with 7 changes to 4 files (+1 heads)
107 107 (run 'hg heads' to see heads, 'hg merge' to merge)
108 108
109 109 Rollback empty
110 110
111 111 $ hg -R empty rollback
112 112 repository tip rolled back to revision -1 (undo pull)
113 113
114 114 Pull full.hg into empty again (using --cwd)
115 115
116 116 $ hg --cwd empty pull ../full.hg
117 117 pulling from ../full.hg
118 118 requesting all changes
119 119 adding changesets
120 120 adding manifests
121 121 adding file changes
122 122 added 9 changesets with 7 changes to 4 files (+1 heads)
123 123 (run 'hg heads' to see heads, 'hg merge' to merge)
124 124
125 125 Pull full.hg into test (using -R)
126 126
127 127 $ hg -R test pull full.hg
128 128 pulling from full.hg
129 129 searching for changes
130 130 no changes found
131 131
132 132 Pull full.hg into empty (using -R)
133 133
134 134 $ hg -R empty pull full.hg
135 135 pulling from full.hg
136 136 searching for changes
137 137 no changes found
138 138
139 139 Rollback empty
140 140
141 141 $ hg -R empty rollback
142 142 repository tip rolled back to revision -1 (undo pull)
143 143
144 144 Pull full.hg into empty again (using -R)
145 145
146 146 $ hg -R empty pull full.hg
147 147 pulling from full.hg
148 148 requesting all changes
149 149 adding changesets
150 150 adding manifests
151 151 adding file changes
152 152 added 9 changesets with 7 changes to 4 files (+1 heads)
153 153 (run 'hg heads' to see heads, 'hg merge' to merge)
154 154
155 155 Log -R full.hg in fresh empty
156 156
157 157 $ rm -r empty
158 158 $ hg init empty
159 159 $ cd empty
160 160 $ hg -R bundle://../full.hg log
161 161 changeset: 8:aa35859c02ea
162 162 tag: tip
163 163 parent: 3:eebf5a27f8ca
164 164 user: test
165 165 date: Thu Jan 01 00:00:00 1970 +0000
166 166 summary: 0.3m
167 167
168 168 changeset: 7:a6a34bfa0076
169 169 user: test
170 170 date: Thu Jan 01 00:00:00 1970 +0000
171 171 summary: 1.3m
172 172
173 173 changeset: 6:7373c1169842
174 174 user: test
175 175 date: Thu Jan 01 00:00:00 1970 +0000
176 176 summary: 1.3
177 177
178 178 changeset: 5:1bb50a9436a7
179 179 user: test
180 180 date: Thu Jan 01 00:00:00 1970 +0000
181 181 summary: 1.2
182 182
183 183 changeset: 4:095197eb4973
184 184 parent: 0:f9ee2f85a263
185 185 user: test
186 186 date: Thu Jan 01 00:00:00 1970 +0000
187 187 summary: 1.1
188 188
189 189 changeset: 3:eebf5a27f8ca
190 190 user: test
191 191 date: Thu Jan 01 00:00:00 1970 +0000
192 192 summary: 0.3
193 193
194 194 changeset: 2:e38ba6f5b7e0
195 195 user: test
196 196 date: Thu Jan 01 00:00:00 1970 +0000
197 197 summary: 0.2
198 198
199 199 changeset: 1:34c2bf6b0626
200 200 user: test
201 201 date: Thu Jan 01 00:00:00 1970 +0000
202 202 summary: 0.1
203 203
204 204 changeset: 0:f9ee2f85a263
205 205 user: test
206 206 date: Thu Jan 01 00:00:00 1970 +0000
207 207 summary: 0.0
208 208
209 209 Make sure bundlerepo doesn't leak tempfiles (issue2491)
210 210
211 211 $ ls .hg
212 212 00changelog.i
213 213 cache
214 214 requires
215 215 store
216 216
217 217 Pull ../full.hg into empty (with hook)
218 218
219 219 $ echo "[hooks]" >> .hg/hgrc
220 220 $ echo "changegroup = printenv.py changegroup" >> .hg/hgrc
221 221
222 222 doesn't work (yet ?)
223 223
224 224 hg -R bundle://../full.hg verify
225 225
226 226 $ hg pull bundle://../full.hg
227 227 pulling from bundle:../full.hg
228 228 requesting all changes
229 229 adding changesets
230 230 adding manifests
231 231 adding file changes
232 232 added 9 changesets with 7 changes to 4 files (+1 heads)
233 233 changegroup hook: HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=bundle:../full.hg (glob)
234 234 (run 'hg heads' to see heads, 'hg merge' to merge)
235 235
236 236 Rollback empty
237 237
238 238 $ hg rollback
239 239 repository tip rolled back to revision -1 (undo pull)
240 240 $ cd ..
241 241
242 242 Log -R bundle:empty+full.hg
243 243
244 244 $ hg -R bundle:empty+full.hg log --template="{rev} "; echo ""
245 245 8 7 6 5 4 3 2 1 0
246 246
247 247 Pull full.hg into empty again (using -R; with hook)
248 248
249 249 $ hg -R empty pull full.hg
250 250 pulling from full.hg
251 251 requesting all changes
252 252 adding changesets
253 253 adding manifests
254 254 adding file changes
255 255 added 9 changesets with 7 changes to 4 files (+1 heads)
256 256 changegroup hook: HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=bundle:empty+full.hg (glob)
257 257 (run 'hg heads' to see heads, 'hg merge' to merge)
258 258
259 259 Cannot produce streaming clone bundles with "hg bundle"
260 260
261 261 $ hg -R test bundle -t packed1 packed.hg
262 262 abort: packed bundles cannot be produced by "hg bundle"
263 263 (use "hg debugcreatestreamclonebundle")
264 264 [255]
265 265
266 266 packed1 is produced properly
267 267
268 268 $ hg -R test debugcreatestreamclonebundle packed.hg
269 writing 2663 bytes for 6 files
269 writing 2667 bytes for 6 files
270 270 bundle requirements: generaldelta, revlogv1
271 271
272 272 $ f -B 64 --size --sha1 --hexdump packed.hg
273 packed.hg: size=2826, sha1=e139f97692a142b19cdcff64a69697d5307ce6d4
273 packed.hg: size=2830, sha1=c28255110a88ffa52ddc44985cad295b1ab349bc
274 274 0000: 48 47 53 31 55 4e 00 00 00 00 00 00 00 06 00 00 |HGS1UN..........|
275 0010: 00 00 00 00 0a 67 00 16 67 65 6e 65 72 61 6c 64 |.....g..generald|
275 0010: 00 00 00 00 0a 6b 00 16 67 65 6e 65 72 61 6c 64 |.....k..generald|
276 276 0020: 65 6c 74 61 2c 72 65 76 6c 6f 67 76 31 00 64 61 |elta,revlogv1.da|
277 277 0030: 74 61 2f 61 64 69 66 66 65 72 65 6e 74 66 69 6c |ta/adifferentfil|
278 278
279 279 generaldelta requirement is listed in stream clone bundles
280 280
281 281 $ hg --config format.generaldelta=true init testgd
282 282 $ cd testgd
283 283 $ touch foo
284 284 $ hg -q commit -A -m initial
285 285 $ cd ..
286 286 $ hg -R testgd debugcreatestreamclonebundle packedgd.hg
287 287 writing 301 bytes for 3 files
288 288 bundle requirements: generaldelta, revlogv1
289 289
290 290 $ f -B 64 --size --sha1 --hexdump packedgd.hg
291 291 packedgd.hg: size=396, sha1=981f9e589799335304a5a9a44caa3623a48d2a9f
292 292 0000: 48 47 53 31 55 4e 00 00 00 00 00 00 00 03 00 00 |HGS1UN..........|
293 293 0010: 00 00 00 00 01 2d 00 16 67 65 6e 65 72 61 6c 64 |.....-..generald|
294 294 0020: 65 6c 74 61 2c 72 65 76 6c 6f 67 76 31 00 64 61 |elta,revlogv1.da|
295 295 0030: 74 61 2f 66 6f 6f 2e 69 00 36 34 0a 00 03 00 01 |ta/foo.i.64.....|
296 296
297 297 Unpacking packed1 bundles with "hg unbundle" isn't allowed
298 298
299 299 $ hg init packed
300 300 $ hg -R packed unbundle packed.hg
301 301 abort: packed bundles cannot be applied with "hg unbundle"
302 302 (use "hg debugapplystreamclonebundle")
303 303 [255]
304 304
305 305 packed1 can be consumed from debug command
306 306
307 307 $ hg -R packed debugapplystreamclonebundle packed.hg
308 308 6 files to transfer, 2.60 KB of data
309 309 transferred 2.60 KB in *.* seconds (* */sec) (glob)
310 310
311 311 Does not work on non-empty repo
312 312
313 313 $ hg -R packed debugapplystreamclonebundle packed.hg
314 314 abort: cannot apply stream clone bundle on non-empty repo
315 315 [255]
316 316
317 317 Create partial clones
318 318
319 319 $ rm -r empty
320 320 $ hg init empty
321 321 $ hg clone -r 3 test partial
322 322 adding changesets
323 323 adding manifests
324 324 adding file changes
325 325 added 4 changesets with 4 changes to 1 files
326 326 updating to branch default
327 327 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
328 328 $ hg clone partial partial2
329 329 updating to branch default
330 330 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
331 331 $ cd partial
332 332
333 333 Log -R full.hg in partial
334 334
335 335 $ hg -R bundle://../full.hg log -T phases
336 336 changeset: 8:aa35859c02ea
337 337 tag: tip
338 338 phase: draft
339 339 parent: 3:eebf5a27f8ca
340 340 user: test
341 341 date: Thu Jan 01 00:00:00 1970 +0000
342 342 summary: 0.3m
343 343
344 344 changeset: 7:a6a34bfa0076
345 345 phase: draft
346 346 user: test
347 347 date: Thu Jan 01 00:00:00 1970 +0000
348 348 summary: 1.3m
349 349
350 350 changeset: 6:7373c1169842
351 351 phase: draft
352 352 user: test
353 353 date: Thu Jan 01 00:00:00 1970 +0000
354 354 summary: 1.3
355 355
356 356 changeset: 5:1bb50a9436a7
357 357 phase: draft
358 358 user: test
359 359 date: Thu Jan 01 00:00:00 1970 +0000
360 360 summary: 1.2
361 361
362 362 changeset: 4:095197eb4973
363 363 phase: draft
364 364 parent: 0:f9ee2f85a263
365 365 user: test
366 366 date: Thu Jan 01 00:00:00 1970 +0000
367 367 summary: 1.1
368 368
369 369 changeset: 3:eebf5a27f8ca
370 370 phase: public
371 371 user: test
372 372 date: Thu Jan 01 00:00:00 1970 +0000
373 373 summary: 0.3
374 374
375 375 changeset: 2:e38ba6f5b7e0
376 376 phase: public
377 377 user: test
378 378 date: Thu Jan 01 00:00:00 1970 +0000
379 379 summary: 0.2
380 380
381 381 changeset: 1:34c2bf6b0626
382 382 phase: public
383 383 user: test
384 384 date: Thu Jan 01 00:00:00 1970 +0000
385 385 summary: 0.1
386 386
387 387 changeset: 0:f9ee2f85a263
388 388 phase: public
389 389 user: test
390 390 date: Thu Jan 01 00:00:00 1970 +0000
391 391 summary: 0.0
392 392
393 393
394 394 Incoming full.hg in partial
395 395
396 396 $ hg incoming bundle://../full.hg
397 397 comparing with bundle:../full.hg
398 398 searching for changes
399 399 changeset: 4:095197eb4973
400 400 parent: 0:f9ee2f85a263
401 401 user: test
402 402 date: Thu Jan 01 00:00:00 1970 +0000
403 403 summary: 1.1
404 404
405 405 changeset: 5:1bb50a9436a7
406 406 user: test
407 407 date: Thu Jan 01 00:00:00 1970 +0000
408 408 summary: 1.2
409 409
410 410 changeset: 6:7373c1169842
411 411 user: test
412 412 date: Thu Jan 01 00:00:00 1970 +0000
413 413 summary: 1.3
414 414
415 415 changeset: 7:a6a34bfa0076
416 416 user: test
417 417 date: Thu Jan 01 00:00:00 1970 +0000
418 418 summary: 1.3m
419 419
420 420 changeset: 8:aa35859c02ea
421 421 tag: tip
422 422 parent: 3:eebf5a27f8ca
423 423 user: test
424 424 date: Thu Jan 01 00:00:00 1970 +0000
425 425 summary: 0.3m
426 426
427 427
428 428 Outgoing -R full.hg vs partial2 in partial
429 429
430 430 $ hg -R bundle://../full.hg outgoing ../partial2
431 431 comparing with ../partial2
432 432 searching for changes
433 433 changeset: 4:095197eb4973
434 434 parent: 0:f9ee2f85a263
435 435 user: test
436 436 date: Thu Jan 01 00:00:00 1970 +0000
437 437 summary: 1.1
438 438
439 439 changeset: 5:1bb50a9436a7
440 440 user: test
441 441 date: Thu Jan 01 00:00:00 1970 +0000
442 442 summary: 1.2
443 443
444 444 changeset: 6:7373c1169842
445 445 user: test
446 446 date: Thu Jan 01 00:00:00 1970 +0000
447 447 summary: 1.3
448 448
449 449 changeset: 7:a6a34bfa0076
450 450 user: test
451 451 date: Thu Jan 01 00:00:00 1970 +0000
452 452 summary: 1.3m
453 453
454 454 changeset: 8:aa35859c02ea
455 455 tag: tip
456 456 parent: 3:eebf5a27f8ca
457 457 user: test
458 458 date: Thu Jan 01 00:00:00 1970 +0000
459 459 summary: 0.3m
460 460
461 461
462 462 Outgoing -R does-not-exist.hg vs partial2 in partial
463 463
464 464 $ hg -R bundle://../does-not-exist.hg outgoing ../partial2
465 465 abort: *../does-not-exist.hg* (glob)
466 466 [255]
467 467 $ cd ..
468 468
469 469 hide outer repo
470 470 $ hg init
471 471
472 472 Direct clone from bundle (all-history)
473 473
474 474 $ hg clone full.hg full-clone
475 475 requesting all changes
476 476 adding changesets
477 477 adding manifests
478 478 adding file changes
479 479 added 9 changesets with 7 changes to 4 files (+1 heads)
480 480 updating to branch default
481 481 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
482 482 $ hg -R full-clone heads
483 483 changeset: 8:aa35859c02ea
484 484 tag: tip
485 485 parent: 3:eebf5a27f8ca
486 486 user: test
487 487 date: Thu Jan 01 00:00:00 1970 +0000
488 488 summary: 0.3m
489 489
490 490 changeset: 7:a6a34bfa0076
491 491 user: test
492 492 date: Thu Jan 01 00:00:00 1970 +0000
493 493 summary: 1.3m
494 494
495 495 $ rm -r full-clone
496 496
497 497 When cloning from a non-copiable repository into '', do not
498 498 recurse infinitely (issue2528)
499 499
500 500 $ hg clone full.hg ''
501 501 abort: empty destination path is not valid
502 502 [255]
503 503
504 504 test for https://bz.mercurial-scm.org/216
505 505
506 506 Unbundle incremental bundles into fresh empty in one go
507 507
508 508 $ rm -r empty
509 509 $ hg init empty
510 510 $ hg -R test bundle --base null -r 0 ../0.hg
511 511 1 changesets found
512 512 $ hg -R test bundle --base 0 -r 1 ../1.hg
513 513 1 changesets found
514 514 $ hg -R empty unbundle -u ../0.hg ../1.hg
515 515 adding changesets
516 516 adding manifests
517 517 adding file changes
518 518 added 1 changesets with 1 changes to 1 files
519 519 adding changesets
520 520 adding manifests
521 521 adding file changes
522 522 added 1 changesets with 1 changes to 1 files
523 523 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
524 524
525 525 View full contents of the bundle
526 526 $ hg -R test bundle --base null -r 3 ../partial.hg
527 527 4 changesets found
528 528 $ cd test
529 529 $ hg -R ../../partial.hg log -r "bundle()"
530 530 changeset: 0:f9ee2f85a263
531 531 user: test
532 532 date: Thu Jan 01 00:00:00 1970 +0000
533 533 summary: 0.0
534 534
535 535 changeset: 1:34c2bf6b0626
536 536 user: test
537 537 date: Thu Jan 01 00:00:00 1970 +0000
538 538 summary: 0.1
539 539
540 540 changeset: 2:e38ba6f5b7e0
541 541 user: test
542 542 date: Thu Jan 01 00:00:00 1970 +0000
543 543 summary: 0.2
544 544
545 545 changeset: 3:eebf5a27f8ca
546 546 user: test
547 547 date: Thu Jan 01 00:00:00 1970 +0000
548 548 summary: 0.3
549 549
550 550 $ cd ..
551 551
552 552 test for 540d1059c802
553 553
554 554 test for 540d1059c802
555 555
556 556 $ hg init orig
557 557 $ cd orig
558 558 $ echo foo > foo
559 559 $ hg add foo
560 560 $ hg ci -m 'add foo'
561 561
562 562 $ hg clone . ../copy
563 563 updating to branch default
564 564 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
565 565 $ hg tag foo
566 566
567 567 $ cd ../copy
568 568 $ echo >> foo
569 569 $ hg ci -m 'change foo'
570 570 $ hg bundle ../bundle.hg ../orig
571 571 searching for changes
572 572 1 changesets found
573 573
574 574 $ cd ../orig
575 575 $ hg incoming ../bundle.hg
576 576 comparing with ../bundle.hg
577 577 searching for changes
578 578 changeset: 2:ed1b79f46b9a
579 579 tag: tip
580 580 parent: 0:bbd179dfa0a7
581 581 user: test
582 582 date: Thu Jan 01 00:00:00 1970 +0000
583 583 summary: change foo
584 584
585 585 $ cd ..
586 586
587 587 test bundle with # in the filename (issue2154):
588 588
589 589 $ cp bundle.hg 'test#bundle.hg'
590 590 $ cd orig
591 591 $ hg incoming '../test#bundle.hg'
592 592 comparing with ../test
593 593 abort: unknown revision 'bundle.hg'!
594 594 [255]
595 595
596 596 note that percent encoding is not handled:
597 597
598 598 $ hg incoming ../test%23bundle.hg
599 599 abort: repository ../test%23bundle.hg not found!
600 600 [255]
601 601 $ cd ..
602 602
603 603 test to bundle revisions on the newly created branch (issue3828):
604 604
605 605 $ hg -q clone -U test test-clone
606 606 $ cd test
607 607
608 608 $ hg -q branch foo
609 609 $ hg commit -m "create foo branch"
610 610 $ hg -q outgoing ../test-clone
611 611 9:b4f5acb1ee27
612 612 $ hg -q bundle --branch foo foo.hg ../test-clone
613 613 $ hg -R foo.hg -q log -r "bundle()"
614 614 9:b4f5acb1ee27
615 615
616 616 $ cd ..
617 617
618 618 test for https://bz.mercurial-scm.org/1144
619 619
620 620 test that verify bundle does not traceback
621 621
622 622 partial history bundle, fails w/ unknown parent
623 623
624 624 $ hg -R bundle.hg verify
625 625 abort: 00changelog.i@bbd179dfa0a7: unknown parent!
626 626 [255]
627 627
628 628 full history bundle, refuses to verify non-local repo
629 629
630 630 $ hg -R all.hg verify
631 631 abort: cannot verify bundle or remote repos
632 632 [255]
633 633
634 634 but, regular verify must continue to work
635 635
636 636 $ hg -R orig verify
637 637 checking changesets
638 638 checking manifests
639 639 crosschecking files in changesets and manifests
640 640 checking files
641 641 2 files, 2 changesets, 2 total revisions
642 642
643 643 diff against bundle
644 644
645 645 $ hg init b
646 646 $ cd b
647 647 $ hg -R ../all.hg diff -r tip
648 648 diff -r aa35859c02ea anotherfile
649 649 --- a/anotherfile Thu Jan 01 00:00:00 1970 +0000
650 650 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
651 651 @@ -1,4 +0,0 @@
652 652 -0
653 653 -1
654 654 -2
655 655 -3
656 656 $ cd ..
657 657
658 658 bundle single branch
659 659
660 660 $ hg init branchy
661 661 $ cd branchy
662 662 $ echo a >a
663 663 $ echo x >x
664 664 $ hg ci -Ama
665 665 adding a
666 666 adding x
667 667 $ echo c >c
668 668 $ echo xx >x
669 669 $ hg ci -Amc
670 670 adding c
671 671 $ echo c1 >c1
672 672 $ hg ci -Amc1
673 673 adding c1
674 674 $ hg up 0
675 675 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
676 676 $ echo b >b
677 677 $ hg ci -Amb
678 678 adding b
679 679 created new head
680 680 $ echo b1 >b1
681 681 $ echo xx >x
682 682 $ hg ci -Amb1
683 683 adding b1
684 684 $ hg clone -q -r2 . part
685 685
686 686 == bundling via incoming
687 687
688 688 $ hg in -R part --bundle incoming.hg --template "{node}\n" .
689 689 comparing with .
690 690 searching for changes
691 691 1a38c1b849e8b70c756d2d80b0b9a3ac0b7ea11a
692 692 057f4db07f61970e1c11e83be79e9d08adc4dc31
693 693
694 694 == bundling
695 695
696 696 $ hg bundle bundle.hg part --debug --config progress.debug=true
697 697 query 1; heads
698 698 searching for changes
699 699 all remote heads known locally
700 700 2 changesets found
701 701 list of changesets:
702 702 1a38c1b849e8b70c756d2d80b0b9a3ac0b7ea11a
703 703 057f4db07f61970e1c11e83be79e9d08adc4dc31
704 704 bundle2-output-bundle: "HG20", (1 params) 1 parts total
705 705 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
706 706 bundling: 1/2 changesets (50.00%)
707 707 bundling: 2/2 changesets (100.00%)
708 708 bundling: 1/2 manifests (50.00%)
709 709 bundling: 2/2 manifests (100.00%)
710 710 bundling: b 1/3 files (33.33%)
711 711 bundling: b1 2/3 files (66.67%)
712 712 bundling: x 3/3 files (100.00%)
713 713
714 714 == Test for issue3441
715 715
716 716 $ hg clone -q -r0 . part2
717 717 $ hg -q -R part2 pull bundle.hg
718 718 $ hg -R part2 verify
719 719 checking changesets
720 720 checking manifests
721 721 crosschecking files in changesets and manifests
722 722 checking files
723 723 4 files, 3 changesets, 5 total revisions
724 724
725 725 $ cd ..
@@ -1,160 +1,160
1 1 Check whether size of generaldelta revlog is not bigger than its
2 2 regular equivalent. Test would fail if generaldelta was naive
3 3 implementation of parentdelta: third manifest revision would be fully
4 4 inserted due to big distance from its paren revision (zero).
5 5
6 6 $ hg init repo --config format.generaldelta=no --config format.usegeneraldelta=no
7 7 $ cd repo
8 8 $ echo foo > foo
9 9 $ echo bar > bar
10 10 $ echo baz > baz
11 11 $ hg commit -q -Am boo
12 12 $ hg clone --pull . ../gdrepo -q --config format.generaldelta=yes
13 13 $ for r in 1 2 3; do
14 14 > echo $r > foo
15 15 > hg commit -q -m $r
16 16 > hg up -q -r 0
17 17 > hg pull . -q -r $r -R ../gdrepo
18 18 > done
19 19
20 20 $ cd ..
21 21 >>> import os
22 22 >>> regsize = os.stat("repo/.hg/store/00manifest.i").st_size
23 23 >>> gdsize = os.stat("gdrepo/.hg/store/00manifest.i").st_size
24 24 >>> if regsize < gdsize:
25 25 ... print 'generaldata increased size of manifest'
26 26
27 27 Verify rev reordering doesnt create invalid bundles (issue4462)
28 28 This requires a commit tree that when pulled will reorder manifest revs such
29 29 that the second manifest to create a file rev will be ordered before the first
30 30 manifest to create that file rev. We also need to do a partial pull to ensure
31 31 reordering happens. At the end we verify the linkrev points at the earliest
32 32 commit.
33 33
34 34 $ hg init server --config format.generaldelta=True
35 35 $ cd server
36 36 $ touch a
37 37 $ hg commit -Aqm a
38 38 $ echo x > x
39 39 $ echo y > y
40 40 $ hg commit -Aqm xy
41 41 $ hg up -q '.^'
42 42 $ echo x > x
43 43 $ echo z > z
44 44 $ hg commit -Aqm xz
45 45 $ hg up -q 1
46 46 $ echo b > b
47 47 $ hg commit -Aqm b
48 48 $ hg merge -q 2
49 49 $ hg commit -Aqm merge
50 50 $ echo c > c
51 51 $ hg commit -Aqm c
52 52 $ hg log -G -T '{rev} {shortest(node)} {desc}'
53 53 @ 5 ebb8 c
54 54 |
55 55 o 4 baf7 merge
56 56 |\
57 57 | o 3 a129 b
58 58 | |
59 59 o | 2 958c xz
60 60 | |
61 61 | o 1 f00c xy
62 62 |/
63 63 o 0 3903 a
64 64
65 65 $ cd ..
66 66 $ hg init client --config format.generaldelta=false --config format.usegeneraldelta=false
67 67 $ cd client
68 68 $ hg pull -q ../server -r 4
69 69 $ hg debugindex x
70 70 rev offset length base linkrev nodeid p1 p2
71 71 0 0 3 0 1 1406e7411862 000000000000 000000000000
72 72
73 73 $ cd ..
74 74
75 75 Test "usegeneraldelta" config
76 76 (repo are general delta, but incoming bundle are not re-deltified)
77 77
78 78 delta coming from the server base delta server are not recompressed.
79 79 (also include the aggressive version for comparison)
80 80
81 81 $ hg clone repo --pull --config format.usegeneraldelta=1 usegd
82 82 requesting all changes
83 83 adding changesets
84 84 adding manifests
85 85 adding file changes
86 86 added 4 changesets with 6 changes to 3 files (+2 heads)
87 87 updating to branch default
88 88 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
89 89 $ hg clone repo --pull --config format.generaldelta=1 full
90 90 requesting all changes
91 91 adding changesets
92 92 adding manifests
93 93 adding file changes
94 94 added 4 changesets with 6 changes to 3 files (+2 heads)
95 95 updating to branch default
96 96 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
97 97 $ hg -R repo debugindex -m
98 98 rev offset length base linkrev nodeid p1 p2
99 99 0 0 104 0 0 cef96823c800 000000000000 000000000000
100 100 1 104 57 0 1 58ab9a8d541d cef96823c800 000000000000
101 101 2 161 57 0 2 134fdc6fd680 cef96823c800 000000000000
102 102 3 218 104 3 3 723508934dad cef96823c800 000000000000
103 103 $ hg -R usegd debugindex -m
104 104 rev offset length delta linkrev nodeid p1 p2
105 105 0 0 104 -1 0 cef96823c800 000000000000 000000000000
106 106 1 104 57 0 1 58ab9a8d541d cef96823c800 000000000000
107 107 2 161 57 1 2 134fdc6fd680 cef96823c800 000000000000
108 3 218 104 -1 3 723508934dad cef96823c800 000000000000
108 3 218 57 0 3 723508934dad cef96823c800 000000000000
109 109 $ hg -R full debugindex -m
110 110 rev offset length delta linkrev nodeid p1 p2
111 111 0 0 104 -1 0 cef96823c800 000000000000 000000000000
112 112 1 104 57 0 1 58ab9a8d541d cef96823c800 000000000000
113 113 2 161 57 0 2 134fdc6fd680 cef96823c800 000000000000
114 114 3 218 57 0 3 723508934dad cef96823c800 000000000000
115 115
116 116 Test format.aggressivemergedeltas
117 117
118 118 $ hg init --config format.generaldelta=1 aggressive
119 119 $ cd aggressive
120 120 $ cat << EOF >> .hg/hgrc
121 121 > [format]
122 122 > generaldelta = 1
123 123 > EOF
124 124 $ touch a b c d e
125 125 $ hg commit -Aqm side1
126 126 $ hg up -q null
127 127 $ touch x y
128 128 $ hg commit -Aqm side2
129 129
130 130 - Verify non-aggressive merge uses p1 (commit 1) as delta parent
131 131 $ hg merge -q 0
132 132 $ hg commit -q -m merge
133 133 $ hg debugindex -m
134 134 rev offset length delta linkrev nodeid p1 p2
135 135 0 0 59 -1 0 8dde941edb6e 000000000000 000000000000
136 136 1 59 59 -1 1 315c023f341d 000000000000 000000000000
137 137 2 118 65 1 2 2ab389a983eb 315c023f341d 8dde941edb6e
138 138
139 139 $ hg strip -q -r . --config extensions.strip=
140 140
141 141 - Verify aggressive merge uses p2 (commit 0) as delta parent
142 142 $ hg up -q -C 1
143 143 $ hg merge -q 0
144 144 $ hg commit -q -m merge --config format.aggressivemergedeltas=True
145 145 $ hg debugindex -m
146 146 rev offset length delta linkrev nodeid p1 p2
147 147 0 0 59 -1 0 8dde941edb6e 000000000000 000000000000
148 148 1 59 59 -1 1 315c023f341d 000000000000 000000000000
149 149 2 118 62 0 2 2ab389a983eb 315c023f341d 8dde941edb6e
150 150
151 151 Test that strip bundle use bundle2
152 152 $ hg --config extensions.strip= strip .
153 153 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
154 154 saved backup bundle to $TESTTMP/aggressive/.hg/strip-backup/1c5d4dc9a8b8-6c68e60c-backup.hg (glob)
155 155 $ hg debugbundle .hg/strip-backup/*
156 156 Stream params: {'Compression': 'BZ'}
157 157 changegroup -- "{'version': '02'}"
158 158 1c5d4dc9a8b8d6e1750966d343e94db665e7a1e9
159 159
160 160 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now