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