##// END OF EJS Templates
revlog: use the user facing filename as the display_id for filelogs...
Matt Harbison -
r50429:92892dff default
parent child Browse files
Show More
@@ -1,3346 +1,3350 b''
1 1 # revlog.py - storage back-end for mercurial
2 2 # coding: utf8
3 3 #
4 4 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 """Storage back-end for Mercurial.
10 10
11 11 This provides efficient delta storage with O(1) retrieve and append
12 12 and O(changes) merge between branches.
13 13 """
14 14
15 15
16 16 import binascii
17 17 import collections
18 18 import contextlib
19 19 import io
20 20 import os
21 21 import struct
22 22 import zlib
23 23
24 24 # import stuff from node for others to import from revlog
25 25 from .node import (
26 26 bin,
27 27 hex,
28 28 nullrev,
29 29 sha1nodeconstants,
30 30 short,
31 31 wdirrev,
32 32 )
33 33 from .i18n import _
34 34 from .pycompat import getattr
35 35 from .revlogutils.constants import (
36 36 ALL_KINDS,
37 37 CHANGELOGV2,
38 38 COMP_MODE_DEFAULT,
39 39 COMP_MODE_INLINE,
40 40 COMP_MODE_PLAIN,
41 41 ENTRY_RANK,
42 42 FEATURES_BY_VERSION,
43 43 FLAG_GENERALDELTA,
44 44 FLAG_INLINE_DATA,
45 45 INDEX_HEADER,
46 46 KIND_CHANGELOG,
47 KIND_FILELOG,
47 48 RANK_UNKNOWN,
48 49 REVLOGV0,
49 50 REVLOGV1,
50 51 REVLOGV1_FLAGS,
51 52 REVLOGV2,
52 53 REVLOGV2_FLAGS,
53 54 REVLOG_DEFAULT_FLAGS,
54 55 REVLOG_DEFAULT_FORMAT,
55 56 REVLOG_DEFAULT_VERSION,
56 57 SUPPORTED_FLAGS,
57 58 )
58 59 from .revlogutils.flagutil import (
59 60 REVIDX_DEFAULT_FLAGS,
60 61 REVIDX_ELLIPSIS,
61 62 REVIDX_EXTSTORED,
62 63 REVIDX_FLAGS_ORDER,
63 64 REVIDX_HASCOPIESINFO,
64 65 REVIDX_ISCENSORED,
65 66 REVIDX_RAWTEXT_CHANGING_FLAGS,
66 67 )
67 68 from .thirdparty import attr
68 69 from . import (
69 70 ancestor,
70 71 dagop,
71 72 error,
72 73 mdiff,
73 74 policy,
74 75 pycompat,
75 76 revlogutils,
76 77 templatefilters,
77 78 util,
78 79 )
79 80 from .interfaces import (
80 81 repository,
81 82 util as interfaceutil,
82 83 )
83 84 from .revlogutils import (
84 85 deltas as deltautil,
85 86 docket as docketutil,
86 87 flagutil,
87 88 nodemap as nodemaputil,
88 89 randomaccessfile,
89 90 revlogv0,
90 91 rewrite,
91 92 sidedata as sidedatautil,
92 93 )
93 94 from .utils import (
94 95 storageutil,
95 96 stringutil,
96 97 )
97 98
98 99 # blanked usage of all the name to prevent pyflakes constraints
99 100 # We need these name available in the module for extensions.
100 101
101 102 REVLOGV0
102 103 REVLOGV1
103 104 REVLOGV2
104 105 CHANGELOGV2
105 106 FLAG_INLINE_DATA
106 107 FLAG_GENERALDELTA
107 108 REVLOG_DEFAULT_FLAGS
108 109 REVLOG_DEFAULT_FORMAT
109 110 REVLOG_DEFAULT_VERSION
110 111 REVLOGV1_FLAGS
111 112 REVLOGV2_FLAGS
112 113 REVIDX_ISCENSORED
113 114 REVIDX_ELLIPSIS
114 115 REVIDX_HASCOPIESINFO
115 116 REVIDX_EXTSTORED
116 117 REVIDX_DEFAULT_FLAGS
117 118 REVIDX_FLAGS_ORDER
118 119 REVIDX_RAWTEXT_CHANGING_FLAGS
119 120
120 121 parsers = policy.importmod('parsers')
121 122 rustancestor = policy.importrust('ancestor')
122 123 rustdagop = policy.importrust('dagop')
123 124 rustrevlog = policy.importrust('revlog')
124 125
125 126 # Aliased for performance.
126 127 _zlibdecompress = zlib.decompress
127 128
128 129 # max size of revlog with inline data
129 130 _maxinline = 131072
130 131
131 132 # Flag processors for REVIDX_ELLIPSIS.
132 133 def ellipsisreadprocessor(rl, text):
133 134 return text, False
134 135
135 136
136 137 def ellipsiswriteprocessor(rl, text):
137 138 return text, False
138 139
139 140
140 141 def ellipsisrawprocessor(rl, text):
141 142 return False
142 143
143 144
144 145 ellipsisprocessor = (
145 146 ellipsisreadprocessor,
146 147 ellipsiswriteprocessor,
147 148 ellipsisrawprocessor,
148 149 )
149 150
150 151
151 152 def _verify_revision(rl, skipflags, state, node):
152 153 """Verify the integrity of the given revlog ``node`` while providing a hook
153 154 point for extensions to influence the operation."""
154 155 if skipflags:
155 156 state[b'skipread'].add(node)
156 157 else:
157 158 # Side-effect: read content and verify hash.
158 159 rl.revision(node)
159 160
160 161
161 162 # True if a fast implementation for persistent-nodemap is available
162 163 #
163 164 # We also consider we have a "fast" implementation in "pure" python because
164 165 # people using pure don't really have performance consideration (and a
165 166 # wheelbarrow of other slowness source)
166 167 HAS_FAST_PERSISTENT_NODEMAP = rustrevlog is not None or util.safehasattr(
167 168 parsers, 'BaseIndexObject'
168 169 )
169 170
170 171
171 172 @interfaceutil.implementer(repository.irevisiondelta)
172 173 @attr.s(slots=True)
173 174 class revlogrevisiondelta:
174 175 node = attr.ib()
175 176 p1node = attr.ib()
176 177 p2node = attr.ib()
177 178 basenode = attr.ib()
178 179 flags = attr.ib()
179 180 baserevisionsize = attr.ib()
180 181 revision = attr.ib()
181 182 delta = attr.ib()
182 183 sidedata = attr.ib()
183 184 protocol_flags = attr.ib()
184 185 linknode = attr.ib(default=None)
185 186
186 187
187 188 @interfaceutil.implementer(repository.iverifyproblem)
188 189 @attr.s(frozen=True)
189 190 class revlogproblem:
190 191 warning = attr.ib(default=None)
191 192 error = attr.ib(default=None)
192 193 node = attr.ib(default=None)
193 194
194 195
195 196 def parse_index_v1(data, inline):
196 197 # call the C implementation to parse the index data
197 198 index, cache = parsers.parse_index2(data, inline)
198 199 return index, cache
199 200
200 201
201 202 def parse_index_v2(data, inline):
202 203 # call the C implementation to parse the index data
203 204 index, cache = parsers.parse_index2(data, inline, format=REVLOGV2)
204 205 return index, cache
205 206
206 207
207 208 def parse_index_cl_v2(data, inline):
208 209 # call the C implementation to parse the index data
209 210 index, cache = parsers.parse_index2(data, inline, format=CHANGELOGV2)
210 211 return index, cache
211 212
212 213
213 214 if util.safehasattr(parsers, 'parse_index_devel_nodemap'):
214 215
215 216 def parse_index_v1_nodemap(data, inline):
216 217 index, cache = parsers.parse_index_devel_nodemap(data, inline)
217 218 return index, cache
218 219
219 220
220 221 else:
221 222 parse_index_v1_nodemap = None
222 223
223 224
224 225 def parse_index_v1_mixed(data, inline):
225 226 index, cache = parse_index_v1(data, inline)
226 227 return rustrevlog.MixedIndex(index), cache
227 228
228 229
229 230 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
230 231 # signed integer)
231 232 _maxentrysize = 0x7FFFFFFF
232 233
233 234 FILE_TOO_SHORT_MSG = _(
234 235 b'cannot read from revlog %s;'
235 236 b' expected %d bytes from offset %d, data size is %d'
236 237 )
237 238
238 239 hexdigits = b'0123456789abcdefABCDEF'
239 240
240 241
241 242 class revlog:
242 243 """
243 244 the underlying revision storage object
244 245
245 246 A revlog consists of two parts, an index and the revision data.
246 247
247 248 The index is a file with a fixed record size containing
248 249 information on each revision, including its nodeid (hash), the
249 250 nodeids of its parents, the position and offset of its data within
250 251 the data file, and the revision it's based on. Finally, each entry
251 252 contains a linkrev entry that can serve as a pointer to external
252 253 data.
253 254
254 255 The revision data itself is a linear collection of data chunks.
255 256 Each chunk represents a revision and is usually represented as a
256 257 delta against the previous chunk. To bound lookup time, runs of
257 258 deltas are limited to about 2 times the length of the original
258 259 version data. This makes retrieval of a version proportional to
259 260 its size, or O(1) relative to the number of revisions.
260 261
261 262 Both pieces of the revlog are written to in an append-only
262 263 fashion, which means we never need to rewrite a file to insert or
263 264 remove data, and can use some simple techniques to avoid the need
264 265 for locking while reading.
265 266
266 267 If checkambig, indexfile is opened with checkambig=True at
267 268 writing, to avoid file stat ambiguity.
268 269
269 270 If mmaplargeindex is True, and an mmapindexthreshold is set, the
270 271 index will be mmapped rather than read if it is larger than the
271 272 configured threshold.
272 273
273 274 If censorable is True, the revlog can have censored revisions.
274 275
275 276 If `upperboundcomp` is not None, this is the expected maximal gain from
276 277 compression for the data content.
277 278
278 279 `concurrencychecker` is an optional function that receives 3 arguments: a
279 280 file handle, a filename, and an expected position. It should check whether
280 281 the current position in the file handle is valid, and log/warn/fail (by
281 282 raising).
282 283
283 284 See mercurial/revlogutils/contants.py for details about the content of an
284 285 index entry.
285 286 """
286 287
287 288 _flagserrorclass = error.RevlogError
288 289
289 290 def __init__(
290 291 self,
291 292 opener,
292 293 target,
293 294 radix,
294 295 postfix=None, # only exist for `tmpcensored` now
295 296 checkambig=False,
296 297 mmaplargeindex=False,
297 298 censorable=False,
298 299 upperboundcomp=None,
299 300 persistentnodemap=False,
300 301 concurrencychecker=None,
301 302 trypending=False,
302 303 canonical_parent_order=True,
303 304 ):
304 305 """
305 306 create a revlog object
306 307
307 308 opener is a function that abstracts the file opening operation
308 309 and can be used to implement COW semantics or the like.
309 310
310 311 `target`: a (KIND, ID) tuple that identify the content stored in
311 312 this revlog. It help the rest of the code to understand what the revlog
312 313 is about without having to resort to heuristic and index filename
313 314 analysis. Note: that this must be reliably be set by normal code, but
314 315 that test, debug, or performance measurement code might not set this to
315 316 accurate value.
316 317 """
317 318 self.upperboundcomp = upperboundcomp
318 319
319 320 self.radix = radix
320 321
321 322 self._docket_file = None
322 323 self._indexfile = None
323 324 self._datafile = None
324 325 self._sidedatafile = None
325 326 self._nodemap_file = None
326 327 self.postfix = postfix
327 328 self._trypending = trypending
328 329 self.opener = opener
329 330 if persistentnodemap:
330 331 self._nodemap_file = nodemaputil.get_nodemap_file(self)
331 332
332 333 assert target[0] in ALL_KINDS
333 334 assert len(target) == 2
334 335 self.target = target
335 336 # When True, indexfile is opened with checkambig=True at writing, to
336 337 # avoid file stat ambiguity.
337 338 self._checkambig = checkambig
338 339 self._mmaplargeindex = mmaplargeindex
339 340 self._censorable = censorable
340 341 # 3-tuple of (node, rev, text) for a raw revision.
341 342 self._revisioncache = None
342 343 # Maps rev to chain base rev.
343 344 self._chainbasecache = util.lrucachedict(100)
344 345 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
345 346 self._chunkcache = (0, b'')
346 347 # How much data to read and cache into the raw revlog data cache.
347 348 self._chunkcachesize = 65536
348 349 self._maxchainlen = None
349 350 self._deltabothparents = True
350 351 self._debug_delta = False
351 352 self.index = None
352 353 self._docket = None
353 354 self._nodemap_docket = None
354 355 # Mapping of partial identifiers to full nodes.
355 356 self._pcache = {}
356 357 # Mapping of revision integer to full node.
357 358 self._compengine = b'zlib'
358 359 self._compengineopts = {}
359 360 self._maxdeltachainspan = -1
360 361 self._withsparseread = False
361 362 self._sparserevlog = False
362 363 self.hassidedata = False
363 364 self._srdensitythreshold = 0.50
364 365 self._srmingapsize = 262144
365 366
366 367 # Make copy of flag processors so each revlog instance can support
367 368 # custom flags.
368 369 self._flagprocessors = dict(flagutil.flagprocessors)
369 370
370 371 # 3-tuple of file handles being used for active writing.
371 372 self._writinghandles = None
372 373 # prevent nesting of addgroup
373 374 self._adding_group = None
374 375
375 376 self._loadindex()
376 377
377 378 self._concurrencychecker = concurrencychecker
378 379
379 380 # parent order is supposed to be semantically irrelevant, so we
380 381 # normally resort parents to ensure that the first parent is non-null,
381 382 # if there is a non-null parent at all.
382 383 # filelog abuses the parent order as flag to mark some instances of
383 384 # meta-encoded files, so allow it to disable this behavior.
384 385 self.canonical_parent_order = canonical_parent_order
385 386
386 387 def _init_opts(self):
387 388 """process options (from above/config) to setup associated default revlog mode
388 389
389 390 These values might be affected when actually reading on disk information.
390 391
391 392 The relevant values are returned for use in _loadindex().
392 393
393 394 * newversionflags:
394 395 version header to use if we need to create a new revlog
395 396
396 397 * mmapindexthreshold:
397 398 minimal index size for start to use mmap
398 399
399 400 * force_nodemap:
400 401 force the usage of a "development" version of the nodemap code
401 402 """
402 403 mmapindexthreshold = None
403 404 opts = self.opener.options
404 405
405 406 if b'changelogv2' in opts and self.revlog_kind == KIND_CHANGELOG:
406 407 new_header = CHANGELOGV2
407 408 elif b'revlogv2' in opts:
408 409 new_header = REVLOGV2
409 410 elif b'revlogv1' in opts:
410 411 new_header = REVLOGV1 | FLAG_INLINE_DATA
411 412 if b'generaldelta' in opts:
412 413 new_header |= FLAG_GENERALDELTA
413 414 elif b'revlogv0' in self.opener.options:
414 415 new_header = REVLOGV0
415 416 else:
416 417 new_header = REVLOG_DEFAULT_VERSION
417 418
418 419 if b'chunkcachesize' in opts:
419 420 self._chunkcachesize = opts[b'chunkcachesize']
420 421 if b'maxchainlen' in opts:
421 422 self._maxchainlen = opts[b'maxchainlen']
422 423 if b'deltabothparents' in opts:
423 424 self._deltabothparents = opts[b'deltabothparents']
424 425 self._lazydelta = bool(opts.get(b'lazydelta', True))
425 426 self._lazydeltabase = False
426 427 if self._lazydelta:
427 428 self._lazydeltabase = bool(opts.get(b'lazydeltabase', False))
428 429 if b'debug-delta' in opts:
429 430 self._debug_delta = opts[b'debug-delta']
430 431 if b'compengine' in opts:
431 432 self._compengine = opts[b'compengine']
432 433 if b'zlib.level' in opts:
433 434 self._compengineopts[b'zlib.level'] = opts[b'zlib.level']
434 435 if b'zstd.level' in opts:
435 436 self._compengineopts[b'zstd.level'] = opts[b'zstd.level']
436 437 if b'maxdeltachainspan' in opts:
437 438 self._maxdeltachainspan = opts[b'maxdeltachainspan']
438 439 if self._mmaplargeindex and b'mmapindexthreshold' in opts:
439 440 mmapindexthreshold = opts[b'mmapindexthreshold']
440 441 self._sparserevlog = bool(opts.get(b'sparse-revlog', False))
441 442 withsparseread = bool(opts.get(b'with-sparse-read', False))
442 443 # sparse-revlog forces sparse-read
443 444 self._withsparseread = self._sparserevlog or withsparseread
444 445 if b'sparse-read-density-threshold' in opts:
445 446 self._srdensitythreshold = opts[b'sparse-read-density-threshold']
446 447 if b'sparse-read-min-gap-size' in opts:
447 448 self._srmingapsize = opts[b'sparse-read-min-gap-size']
448 449 if opts.get(b'enableellipsis'):
449 450 self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
450 451
451 452 # revlog v0 doesn't have flag processors
452 453 for flag, processor in opts.get(b'flagprocessors', {}).items():
453 454 flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
454 455
455 456 if self._chunkcachesize <= 0:
456 457 raise error.RevlogError(
457 458 _(b'revlog chunk cache size %r is not greater than 0')
458 459 % self._chunkcachesize
459 460 )
460 461 elif self._chunkcachesize & (self._chunkcachesize - 1):
461 462 raise error.RevlogError(
462 463 _(b'revlog chunk cache size %r is not a power of 2')
463 464 % self._chunkcachesize
464 465 )
465 466 force_nodemap = opts.get(b'devel-force-nodemap', False)
466 467 return new_header, mmapindexthreshold, force_nodemap
467 468
468 469 def _get_data(self, filepath, mmap_threshold, size=None):
469 470 """return a file content with or without mmap
470 471
471 472 If the file is missing return the empty string"""
472 473 try:
473 474 with self.opener(filepath) as fp:
474 475 if mmap_threshold is not None:
475 476 file_size = self.opener.fstat(fp).st_size
476 477 if file_size >= mmap_threshold:
477 478 if size is not None:
478 479 # avoid potentiel mmap crash
479 480 size = min(file_size, size)
480 481 # TODO: should .close() to release resources without
481 482 # relying on Python GC
482 483 if size is None:
483 484 return util.buffer(util.mmapread(fp))
484 485 else:
485 486 return util.buffer(util.mmapread(fp, size))
486 487 if size is None:
487 488 return fp.read()
488 489 else:
489 490 return fp.read(size)
490 491 except FileNotFoundError:
491 492 return b''
492 493
493 494 def _loadindex(self, docket=None):
494 495
495 496 new_header, mmapindexthreshold, force_nodemap = self._init_opts()
496 497
497 498 if self.postfix is not None:
498 499 entry_point = b'%s.i.%s' % (self.radix, self.postfix)
499 500 elif self._trypending and self.opener.exists(b'%s.i.a' % self.radix):
500 501 entry_point = b'%s.i.a' % self.radix
501 502 else:
502 503 entry_point = b'%s.i' % self.radix
503 504
504 505 if docket is not None:
505 506 self._docket = docket
506 507 self._docket_file = entry_point
507 508 else:
508 509 self._initempty = True
509 510 entry_data = self._get_data(entry_point, mmapindexthreshold)
510 511 if len(entry_data) > 0:
511 512 header = INDEX_HEADER.unpack(entry_data[:4])[0]
512 513 self._initempty = False
513 514 else:
514 515 header = new_header
515 516
516 517 self._format_flags = header & ~0xFFFF
517 518 self._format_version = header & 0xFFFF
518 519
519 520 supported_flags = SUPPORTED_FLAGS.get(self._format_version)
520 521 if supported_flags is None:
521 522 msg = _(b'unknown version (%d) in revlog %s')
522 523 msg %= (self._format_version, self.display_id)
523 524 raise error.RevlogError(msg)
524 525 elif self._format_flags & ~supported_flags:
525 526 msg = _(b'unknown flags (%#04x) in version %d revlog %s')
526 527 display_flag = self._format_flags >> 16
527 528 msg %= (display_flag, self._format_version, self.display_id)
528 529 raise error.RevlogError(msg)
529 530
530 531 features = FEATURES_BY_VERSION[self._format_version]
531 532 self._inline = features[b'inline'](self._format_flags)
532 533 self._generaldelta = features[b'generaldelta'](self._format_flags)
533 534 self.hassidedata = features[b'sidedata']
534 535
535 536 if not features[b'docket']:
536 537 self._indexfile = entry_point
537 538 index_data = entry_data
538 539 else:
539 540 self._docket_file = entry_point
540 541 if self._initempty:
541 542 self._docket = docketutil.default_docket(self, header)
542 543 else:
543 544 self._docket = docketutil.parse_docket(
544 545 self, entry_data, use_pending=self._trypending
545 546 )
546 547
547 548 if self._docket is not None:
548 549 self._indexfile = self._docket.index_filepath()
549 550 index_data = b''
550 551 index_size = self._docket.index_end
551 552 if index_size > 0:
552 553 index_data = self._get_data(
553 554 self._indexfile, mmapindexthreshold, size=index_size
554 555 )
555 556 if len(index_data) < index_size:
556 557 msg = _(b'too few index data for %s: got %d, expected %d')
557 558 msg %= (self.display_id, len(index_data), index_size)
558 559 raise error.RevlogError(msg)
559 560
560 561 self._inline = False
561 562 # generaldelta implied by version 2 revlogs.
562 563 self._generaldelta = True
563 564 # the logic for persistent nodemap will be dealt with within the
564 565 # main docket, so disable it for now.
565 566 self._nodemap_file = None
566 567
567 568 if self._docket is not None:
568 569 self._datafile = self._docket.data_filepath()
569 570 self._sidedatafile = self._docket.sidedata_filepath()
570 571 elif self.postfix is None:
571 572 self._datafile = b'%s.d' % self.radix
572 573 else:
573 574 self._datafile = b'%s.d.%s' % (self.radix, self.postfix)
574 575
575 576 self.nodeconstants = sha1nodeconstants
576 577 self.nullid = self.nodeconstants.nullid
577 578
578 579 # sparse-revlog can't be on without general-delta (issue6056)
579 580 if not self._generaldelta:
580 581 self._sparserevlog = False
581 582
582 583 self._storedeltachains = True
583 584
584 585 devel_nodemap = (
585 586 self._nodemap_file
586 587 and force_nodemap
587 588 and parse_index_v1_nodemap is not None
588 589 )
589 590
590 591 use_rust_index = False
591 592 if rustrevlog is not None:
592 593 if self._nodemap_file is not None:
593 594 use_rust_index = True
594 595 else:
595 596 use_rust_index = self.opener.options.get(b'rust.index')
596 597
597 598 self._parse_index = parse_index_v1
598 599 if self._format_version == REVLOGV0:
599 600 self._parse_index = revlogv0.parse_index_v0
600 601 elif self._format_version == REVLOGV2:
601 602 self._parse_index = parse_index_v2
602 603 elif self._format_version == CHANGELOGV2:
603 604 self._parse_index = parse_index_cl_v2
604 605 elif devel_nodemap:
605 606 self._parse_index = parse_index_v1_nodemap
606 607 elif use_rust_index:
607 608 self._parse_index = parse_index_v1_mixed
608 609 try:
609 610 d = self._parse_index(index_data, self._inline)
610 611 index, chunkcache = d
611 612 use_nodemap = (
612 613 not self._inline
613 614 and self._nodemap_file is not None
614 615 and util.safehasattr(index, 'update_nodemap_data')
615 616 )
616 617 if use_nodemap:
617 618 nodemap_data = nodemaputil.persisted_data(self)
618 619 if nodemap_data is not None:
619 620 docket = nodemap_data[0]
620 621 if (
621 622 len(d[0]) > docket.tip_rev
622 623 and d[0][docket.tip_rev][7] == docket.tip_node
623 624 ):
624 625 # no changelog tampering
625 626 self._nodemap_docket = docket
626 627 index.update_nodemap_data(*nodemap_data)
627 628 except (ValueError, IndexError):
628 629 raise error.RevlogError(
629 630 _(b"index %s is corrupted") % self.display_id
630 631 )
631 632 self.index = index
632 633 self._segmentfile = randomaccessfile.randomaccessfile(
633 634 self.opener,
634 635 (self._indexfile if self._inline else self._datafile),
635 636 self._chunkcachesize,
636 637 chunkcache,
637 638 )
638 639 self._segmentfile_sidedata = randomaccessfile.randomaccessfile(
639 640 self.opener,
640 641 self._sidedatafile,
641 642 self._chunkcachesize,
642 643 )
643 644 # revnum -> (chain-length, sum-delta-length)
644 645 self._chaininfocache = util.lrucachedict(500)
645 646 # revlog header -> revlog compressor
646 647 self._decompressors = {}
647 648
648 649 @util.propertycache
649 650 def revlog_kind(self):
650 651 return self.target[0]
651 652
652 653 @util.propertycache
653 654 def display_id(self):
654 655 """The public facing "ID" of the revlog that we use in message"""
655 # Maybe we should build a user facing representation of
656 # revlog.target instead of using `self.radix`
657 return self.radix
656 if self.revlog_kind == KIND_FILELOG:
657 # Reference the file without the "data/" prefix, so it is familiar
658 # to the user.
659 return self.target[1]
660 else:
661 return self.radix
658 662
659 663 def _get_decompressor(self, t):
660 664 try:
661 665 compressor = self._decompressors[t]
662 666 except KeyError:
663 667 try:
664 668 engine = util.compengines.forrevlogheader(t)
665 669 compressor = engine.revlogcompressor(self._compengineopts)
666 670 self._decompressors[t] = compressor
667 671 except KeyError:
668 672 raise error.RevlogError(
669 673 _(b'unknown compression type %s') % binascii.hexlify(t)
670 674 )
671 675 return compressor
672 676
673 677 @util.propertycache
674 678 def _compressor(self):
675 679 engine = util.compengines[self._compengine]
676 680 return engine.revlogcompressor(self._compengineopts)
677 681
678 682 @util.propertycache
679 683 def _decompressor(self):
680 684 """the default decompressor"""
681 685 if self._docket is None:
682 686 return None
683 687 t = self._docket.default_compression_header
684 688 c = self._get_decompressor(t)
685 689 return c.decompress
686 690
687 691 def _indexfp(self):
688 692 """file object for the revlog's index file"""
689 693 return self.opener(self._indexfile, mode=b"r")
690 694
691 695 def __index_write_fp(self):
692 696 # You should not use this directly and use `_writing` instead
693 697 try:
694 698 f = self.opener(
695 699 self._indexfile, mode=b"r+", checkambig=self._checkambig
696 700 )
697 701 if self._docket is None:
698 702 f.seek(0, os.SEEK_END)
699 703 else:
700 704 f.seek(self._docket.index_end, os.SEEK_SET)
701 705 return f
702 706 except FileNotFoundError:
703 707 return self.opener(
704 708 self._indexfile, mode=b"w+", checkambig=self._checkambig
705 709 )
706 710
707 711 def __index_new_fp(self):
708 712 # You should not use this unless you are upgrading from inline revlog
709 713 return self.opener(
710 714 self._indexfile,
711 715 mode=b"w",
712 716 checkambig=self._checkambig,
713 717 atomictemp=True,
714 718 )
715 719
716 720 def _datafp(self, mode=b'r'):
717 721 """file object for the revlog's data file"""
718 722 return self.opener(self._datafile, mode=mode)
719 723
720 724 @contextlib.contextmanager
721 725 def _sidedatareadfp(self):
722 726 """file object suitable to read sidedata"""
723 727 if self._writinghandles:
724 728 yield self._writinghandles[2]
725 729 else:
726 730 with self.opener(self._sidedatafile) as fp:
727 731 yield fp
728 732
729 733 def tiprev(self):
730 734 return len(self.index) - 1
731 735
732 736 def tip(self):
733 737 return self.node(self.tiprev())
734 738
735 739 def __contains__(self, rev):
736 740 return 0 <= rev < len(self)
737 741
738 742 def __len__(self):
739 743 return len(self.index)
740 744
741 745 def __iter__(self):
742 746 return iter(range(len(self)))
743 747
744 748 def revs(self, start=0, stop=None):
745 749 """iterate over all rev in this revlog (from start to stop)"""
746 750 return storageutil.iterrevs(len(self), start=start, stop=stop)
747 751
748 752 def hasnode(self, node):
749 753 try:
750 754 self.rev(node)
751 755 return True
752 756 except KeyError:
753 757 return False
754 758
755 759 def candelta(self, baserev, rev):
756 760 """whether two revisions (baserev, rev) can be delta-ed or not"""
757 761 # Disable delta if either rev requires a content-changing flag
758 762 # processor (ex. LFS). This is because such flag processor can alter
759 763 # the rawtext content that the delta will be based on, and two clients
760 764 # could have a same revlog node with different flags (i.e. different
761 765 # rawtext contents) and the delta could be incompatible.
762 766 if (self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS) or (
763 767 self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS
764 768 ):
765 769 return False
766 770 return True
767 771
768 772 def update_caches(self, transaction):
769 773 if self._nodemap_file is not None:
770 774 if transaction is None:
771 775 nodemaputil.update_persistent_nodemap(self)
772 776 else:
773 777 nodemaputil.setup_persistent_nodemap(transaction, self)
774 778
775 779 def clearcaches(self):
776 780 self._revisioncache = None
777 781 self._chainbasecache.clear()
778 782 self._segmentfile.clear_cache()
779 783 self._segmentfile_sidedata.clear_cache()
780 784 self._pcache = {}
781 785 self._nodemap_docket = None
782 786 self.index.clearcaches()
783 787 # The python code is the one responsible for validating the docket, we
784 788 # end up having to refresh it here.
785 789 use_nodemap = (
786 790 not self._inline
787 791 and self._nodemap_file is not None
788 792 and util.safehasattr(self.index, 'update_nodemap_data')
789 793 )
790 794 if use_nodemap:
791 795 nodemap_data = nodemaputil.persisted_data(self)
792 796 if nodemap_data is not None:
793 797 self._nodemap_docket = nodemap_data[0]
794 798 self.index.update_nodemap_data(*nodemap_data)
795 799
796 800 def rev(self, node):
797 801 try:
798 802 return self.index.rev(node)
799 803 except TypeError:
800 804 raise
801 805 except error.RevlogError:
802 806 # parsers.c radix tree lookup failed
803 807 if (
804 808 node == self.nodeconstants.wdirid
805 809 or node in self.nodeconstants.wdirfilenodeids
806 810 ):
807 811 raise error.WdirUnsupported
808 812 raise error.LookupError(node, self.display_id, _(b'no node'))
809 813
810 814 # Accessors for index entries.
811 815
812 816 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
813 817 # are flags.
814 818 def start(self, rev):
815 819 return int(self.index[rev][0] >> 16)
816 820
817 821 def sidedata_cut_off(self, rev):
818 822 sd_cut_off = self.index[rev][8]
819 823 if sd_cut_off != 0:
820 824 return sd_cut_off
821 825 # This is some annoying dance, because entries without sidedata
822 826 # currently use 0 as their ofsset. (instead of previous-offset +
823 827 # previous-size)
824 828 #
825 829 # We should reconsider this sidedata β†’ 0 sidata_offset policy.
826 830 # In the meantime, we need this.
827 831 while 0 <= rev:
828 832 e = self.index[rev]
829 833 if e[9] != 0:
830 834 return e[8] + e[9]
831 835 rev -= 1
832 836 return 0
833 837
834 838 def flags(self, rev):
835 839 return self.index[rev][0] & 0xFFFF
836 840
837 841 def length(self, rev):
838 842 return self.index[rev][1]
839 843
840 844 def sidedata_length(self, rev):
841 845 if not self.hassidedata:
842 846 return 0
843 847 return self.index[rev][9]
844 848
845 849 def rawsize(self, rev):
846 850 """return the length of the uncompressed text for a given revision"""
847 851 l = self.index[rev][2]
848 852 if l >= 0:
849 853 return l
850 854
851 855 t = self.rawdata(rev)
852 856 return len(t)
853 857
854 858 def size(self, rev):
855 859 """length of non-raw text (processed by a "read" flag processor)"""
856 860 # fast path: if no "read" flag processor could change the content,
857 861 # size is rawsize. note: ELLIPSIS is known to not change the content.
858 862 flags = self.flags(rev)
859 863 if flags & (flagutil.REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
860 864 return self.rawsize(rev)
861 865
862 866 return len(self.revision(rev))
863 867
864 868 def fast_rank(self, rev):
865 869 """Return the rank of a revision if already known, or None otherwise.
866 870
867 871 The rank of a revision is the size of the sub-graph it defines as a
868 872 head. Equivalently, the rank of a revision `r` is the size of the set
869 873 `ancestors(r)`, `r` included.
870 874
871 875 This method returns the rank retrieved from the revlog in constant
872 876 time. It makes no attempt at computing unknown values for versions of
873 877 the revlog which do not persist the rank.
874 878 """
875 879 rank = self.index[rev][ENTRY_RANK]
876 880 if self._format_version != CHANGELOGV2 or rank == RANK_UNKNOWN:
877 881 return None
878 882 if rev == nullrev:
879 883 return 0 # convention
880 884 return rank
881 885
882 886 def chainbase(self, rev):
883 887 base = self._chainbasecache.get(rev)
884 888 if base is not None:
885 889 return base
886 890
887 891 index = self.index
888 892 iterrev = rev
889 893 base = index[iterrev][3]
890 894 while base != iterrev:
891 895 iterrev = base
892 896 base = index[iterrev][3]
893 897
894 898 self._chainbasecache[rev] = base
895 899 return base
896 900
897 901 def linkrev(self, rev):
898 902 return self.index[rev][4]
899 903
900 904 def parentrevs(self, rev):
901 905 try:
902 906 entry = self.index[rev]
903 907 except IndexError:
904 908 if rev == wdirrev:
905 909 raise error.WdirUnsupported
906 910 raise
907 911
908 912 if self.canonical_parent_order and entry[5] == nullrev:
909 913 return entry[6], entry[5]
910 914 else:
911 915 return entry[5], entry[6]
912 916
913 917 # fast parentrevs(rev) where rev isn't filtered
914 918 _uncheckedparentrevs = parentrevs
915 919
916 920 def node(self, rev):
917 921 try:
918 922 return self.index[rev][7]
919 923 except IndexError:
920 924 if rev == wdirrev:
921 925 raise error.WdirUnsupported
922 926 raise
923 927
924 928 # Derived from index values.
925 929
926 930 def end(self, rev):
927 931 return self.start(rev) + self.length(rev)
928 932
929 933 def parents(self, node):
930 934 i = self.index
931 935 d = i[self.rev(node)]
932 936 # inline node() to avoid function call overhead
933 937 if self.canonical_parent_order and d[5] == self.nullid:
934 938 return i[d[6]][7], i[d[5]][7]
935 939 else:
936 940 return i[d[5]][7], i[d[6]][7]
937 941
938 942 def chainlen(self, rev):
939 943 return self._chaininfo(rev)[0]
940 944
941 945 def _chaininfo(self, rev):
942 946 chaininfocache = self._chaininfocache
943 947 if rev in chaininfocache:
944 948 return chaininfocache[rev]
945 949 index = self.index
946 950 generaldelta = self._generaldelta
947 951 iterrev = rev
948 952 e = index[iterrev]
949 953 clen = 0
950 954 compresseddeltalen = 0
951 955 while iterrev != e[3]:
952 956 clen += 1
953 957 compresseddeltalen += e[1]
954 958 if generaldelta:
955 959 iterrev = e[3]
956 960 else:
957 961 iterrev -= 1
958 962 if iterrev in chaininfocache:
959 963 t = chaininfocache[iterrev]
960 964 clen += t[0]
961 965 compresseddeltalen += t[1]
962 966 break
963 967 e = index[iterrev]
964 968 else:
965 969 # Add text length of base since decompressing that also takes
966 970 # work. For cache hits the length is already included.
967 971 compresseddeltalen += e[1]
968 972 r = (clen, compresseddeltalen)
969 973 chaininfocache[rev] = r
970 974 return r
971 975
972 976 def _deltachain(self, rev, stoprev=None):
973 977 """Obtain the delta chain for a revision.
974 978
975 979 ``stoprev`` specifies a revision to stop at. If not specified, we
976 980 stop at the base of the chain.
977 981
978 982 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
979 983 revs in ascending order and ``stopped`` is a bool indicating whether
980 984 ``stoprev`` was hit.
981 985 """
982 986 # Try C implementation.
983 987 try:
984 988 return self.index.deltachain(rev, stoprev, self._generaldelta)
985 989 except AttributeError:
986 990 pass
987 991
988 992 chain = []
989 993
990 994 # Alias to prevent attribute lookup in tight loop.
991 995 index = self.index
992 996 generaldelta = self._generaldelta
993 997
994 998 iterrev = rev
995 999 e = index[iterrev]
996 1000 while iterrev != e[3] and iterrev != stoprev:
997 1001 chain.append(iterrev)
998 1002 if generaldelta:
999 1003 iterrev = e[3]
1000 1004 else:
1001 1005 iterrev -= 1
1002 1006 e = index[iterrev]
1003 1007
1004 1008 if iterrev == stoprev:
1005 1009 stopped = True
1006 1010 else:
1007 1011 chain.append(iterrev)
1008 1012 stopped = False
1009 1013
1010 1014 chain.reverse()
1011 1015 return chain, stopped
1012 1016
1013 1017 def ancestors(self, revs, stoprev=0, inclusive=False):
1014 1018 """Generate the ancestors of 'revs' in reverse revision order.
1015 1019 Does not generate revs lower than stoprev.
1016 1020
1017 1021 See the documentation for ancestor.lazyancestors for more details."""
1018 1022
1019 1023 # first, make sure start revisions aren't filtered
1020 1024 revs = list(revs)
1021 1025 checkrev = self.node
1022 1026 for r in revs:
1023 1027 checkrev(r)
1024 1028 # and we're sure ancestors aren't filtered as well
1025 1029
1026 1030 if rustancestor is not None and self.index.rust_ext_compat:
1027 1031 lazyancestors = rustancestor.LazyAncestors
1028 1032 arg = self.index
1029 1033 else:
1030 1034 lazyancestors = ancestor.lazyancestors
1031 1035 arg = self._uncheckedparentrevs
1032 1036 return lazyancestors(arg, revs, stoprev=stoprev, inclusive=inclusive)
1033 1037
1034 1038 def descendants(self, revs):
1035 1039 return dagop.descendantrevs(revs, self.revs, self.parentrevs)
1036 1040
1037 1041 def findcommonmissing(self, common=None, heads=None):
1038 1042 """Return a tuple of the ancestors of common and the ancestors of heads
1039 1043 that are not ancestors of common. In revset terminology, we return the
1040 1044 tuple:
1041 1045
1042 1046 ::common, (::heads) - (::common)
1043 1047
1044 1048 The list is sorted by revision number, meaning it is
1045 1049 topologically sorted.
1046 1050
1047 1051 'heads' and 'common' are both lists of node IDs. If heads is
1048 1052 not supplied, uses all of the revlog's heads. If common is not
1049 1053 supplied, uses nullid."""
1050 1054 if common is None:
1051 1055 common = [self.nullid]
1052 1056 if heads is None:
1053 1057 heads = self.heads()
1054 1058
1055 1059 common = [self.rev(n) for n in common]
1056 1060 heads = [self.rev(n) for n in heads]
1057 1061
1058 1062 # we want the ancestors, but inclusive
1059 1063 class lazyset:
1060 1064 def __init__(self, lazyvalues):
1061 1065 self.addedvalues = set()
1062 1066 self.lazyvalues = lazyvalues
1063 1067
1064 1068 def __contains__(self, value):
1065 1069 return value in self.addedvalues or value in self.lazyvalues
1066 1070
1067 1071 def __iter__(self):
1068 1072 added = self.addedvalues
1069 1073 for r in added:
1070 1074 yield r
1071 1075 for r in self.lazyvalues:
1072 1076 if not r in added:
1073 1077 yield r
1074 1078
1075 1079 def add(self, value):
1076 1080 self.addedvalues.add(value)
1077 1081
1078 1082 def update(self, values):
1079 1083 self.addedvalues.update(values)
1080 1084
1081 1085 has = lazyset(self.ancestors(common))
1082 1086 has.add(nullrev)
1083 1087 has.update(common)
1084 1088
1085 1089 # take all ancestors from heads that aren't in has
1086 1090 missing = set()
1087 1091 visit = collections.deque(r for r in heads if r not in has)
1088 1092 while visit:
1089 1093 r = visit.popleft()
1090 1094 if r in missing:
1091 1095 continue
1092 1096 else:
1093 1097 missing.add(r)
1094 1098 for p in self.parentrevs(r):
1095 1099 if p not in has:
1096 1100 visit.append(p)
1097 1101 missing = list(missing)
1098 1102 missing.sort()
1099 1103 return has, [self.node(miss) for miss in missing]
1100 1104
1101 1105 def incrementalmissingrevs(self, common=None):
1102 1106 """Return an object that can be used to incrementally compute the
1103 1107 revision numbers of the ancestors of arbitrary sets that are not
1104 1108 ancestors of common. This is an ancestor.incrementalmissingancestors
1105 1109 object.
1106 1110
1107 1111 'common' is a list of revision numbers. If common is not supplied, uses
1108 1112 nullrev.
1109 1113 """
1110 1114 if common is None:
1111 1115 common = [nullrev]
1112 1116
1113 1117 if rustancestor is not None and self.index.rust_ext_compat:
1114 1118 return rustancestor.MissingAncestors(self.index, common)
1115 1119 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1116 1120
1117 1121 def findmissingrevs(self, common=None, heads=None):
1118 1122 """Return the revision numbers of the ancestors of heads that
1119 1123 are not ancestors of common.
1120 1124
1121 1125 More specifically, return a list of revision numbers corresponding to
1122 1126 nodes N such that every N satisfies the following constraints:
1123 1127
1124 1128 1. N is an ancestor of some node in 'heads'
1125 1129 2. N is not an ancestor of any node in 'common'
1126 1130
1127 1131 The list is sorted by revision number, meaning it is
1128 1132 topologically sorted.
1129 1133
1130 1134 'heads' and 'common' are both lists of revision numbers. If heads is
1131 1135 not supplied, uses all of the revlog's heads. If common is not
1132 1136 supplied, uses nullid."""
1133 1137 if common is None:
1134 1138 common = [nullrev]
1135 1139 if heads is None:
1136 1140 heads = self.headrevs()
1137 1141
1138 1142 inc = self.incrementalmissingrevs(common=common)
1139 1143 return inc.missingancestors(heads)
1140 1144
1141 1145 def findmissing(self, common=None, heads=None):
1142 1146 """Return the ancestors of heads that are not ancestors of common.
1143 1147
1144 1148 More specifically, return a list of nodes N such that every N
1145 1149 satisfies the following constraints:
1146 1150
1147 1151 1. N is an ancestor of some node in 'heads'
1148 1152 2. N is not an ancestor of any node in 'common'
1149 1153
1150 1154 The list is sorted by revision number, meaning it is
1151 1155 topologically sorted.
1152 1156
1153 1157 'heads' and 'common' are both lists of node IDs. If heads is
1154 1158 not supplied, uses all of the revlog's heads. If common is not
1155 1159 supplied, uses nullid."""
1156 1160 if common is None:
1157 1161 common = [self.nullid]
1158 1162 if heads is None:
1159 1163 heads = self.heads()
1160 1164
1161 1165 common = [self.rev(n) for n in common]
1162 1166 heads = [self.rev(n) for n in heads]
1163 1167
1164 1168 inc = self.incrementalmissingrevs(common=common)
1165 1169 return [self.node(r) for r in inc.missingancestors(heads)]
1166 1170
1167 1171 def nodesbetween(self, roots=None, heads=None):
1168 1172 """Return a topological path from 'roots' to 'heads'.
1169 1173
1170 1174 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1171 1175 topologically sorted list of all nodes N that satisfy both of
1172 1176 these constraints:
1173 1177
1174 1178 1. N is a descendant of some node in 'roots'
1175 1179 2. N is an ancestor of some node in 'heads'
1176 1180
1177 1181 Every node is considered to be both a descendant and an ancestor
1178 1182 of itself, so every reachable node in 'roots' and 'heads' will be
1179 1183 included in 'nodes'.
1180 1184
1181 1185 'outroots' is the list of reachable nodes in 'roots', i.e., the
1182 1186 subset of 'roots' that is returned in 'nodes'. Likewise,
1183 1187 'outheads' is the subset of 'heads' that is also in 'nodes'.
1184 1188
1185 1189 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1186 1190 unspecified, uses nullid as the only root. If 'heads' is
1187 1191 unspecified, uses list of all of the revlog's heads."""
1188 1192 nonodes = ([], [], [])
1189 1193 if roots is not None:
1190 1194 roots = list(roots)
1191 1195 if not roots:
1192 1196 return nonodes
1193 1197 lowestrev = min([self.rev(n) for n in roots])
1194 1198 else:
1195 1199 roots = [self.nullid] # Everybody's a descendant of nullid
1196 1200 lowestrev = nullrev
1197 1201 if (lowestrev == nullrev) and (heads is None):
1198 1202 # We want _all_ the nodes!
1199 1203 return (
1200 1204 [self.node(r) for r in self],
1201 1205 [self.nullid],
1202 1206 list(self.heads()),
1203 1207 )
1204 1208 if heads is None:
1205 1209 # All nodes are ancestors, so the latest ancestor is the last
1206 1210 # node.
1207 1211 highestrev = len(self) - 1
1208 1212 # Set ancestors to None to signal that every node is an ancestor.
1209 1213 ancestors = None
1210 1214 # Set heads to an empty dictionary for later discovery of heads
1211 1215 heads = {}
1212 1216 else:
1213 1217 heads = list(heads)
1214 1218 if not heads:
1215 1219 return nonodes
1216 1220 ancestors = set()
1217 1221 # Turn heads into a dictionary so we can remove 'fake' heads.
1218 1222 # Also, later we will be using it to filter out the heads we can't
1219 1223 # find from roots.
1220 1224 heads = dict.fromkeys(heads, False)
1221 1225 # Start at the top and keep marking parents until we're done.
1222 1226 nodestotag = set(heads)
1223 1227 # Remember where the top was so we can use it as a limit later.
1224 1228 highestrev = max([self.rev(n) for n in nodestotag])
1225 1229 while nodestotag:
1226 1230 # grab a node to tag
1227 1231 n = nodestotag.pop()
1228 1232 # Never tag nullid
1229 1233 if n == self.nullid:
1230 1234 continue
1231 1235 # A node's revision number represents its place in a
1232 1236 # topologically sorted list of nodes.
1233 1237 r = self.rev(n)
1234 1238 if r >= lowestrev:
1235 1239 if n not in ancestors:
1236 1240 # If we are possibly a descendant of one of the roots
1237 1241 # and we haven't already been marked as an ancestor
1238 1242 ancestors.add(n) # Mark as ancestor
1239 1243 # Add non-nullid parents to list of nodes to tag.
1240 1244 nodestotag.update(
1241 1245 [p for p in self.parents(n) if p != self.nullid]
1242 1246 )
1243 1247 elif n in heads: # We've seen it before, is it a fake head?
1244 1248 # So it is, real heads should not be the ancestors of
1245 1249 # any other heads.
1246 1250 heads.pop(n)
1247 1251 if not ancestors:
1248 1252 return nonodes
1249 1253 # Now that we have our set of ancestors, we want to remove any
1250 1254 # roots that are not ancestors.
1251 1255
1252 1256 # If one of the roots was nullid, everything is included anyway.
1253 1257 if lowestrev > nullrev:
1254 1258 # But, since we weren't, let's recompute the lowest rev to not
1255 1259 # include roots that aren't ancestors.
1256 1260
1257 1261 # Filter out roots that aren't ancestors of heads
1258 1262 roots = [root for root in roots if root in ancestors]
1259 1263 # Recompute the lowest revision
1260 1264 if roots:
1261 1265 lowestrev = min([self.rev(root) for root in roots])
1262 1266 else:
1263 1267 # No more roots? Return empty list
1264 1268 return nonodes
1265 1269 else:
1266 1270 # We are descending from nullid, and don't need to care about
1267 1271 # any other roots.
1268 1272 lowestrev = nullrev
1269 1273 roots = [self.nullid]
1270 1274 # Transform our roots list into a set.
1271 1275 descendants = set(roots)
1272 1276 # Also, keep the original roots so we can filter out roots that aren't
1273 1277 # 'real' roots (i.e. are descended from other roots).
1274 1278 roots = descendants.copy()
1275 1279 # Our topologically sorted list of output nodes.
1276 1280 orderedout = []
1277 1281 # Don't start at nullid since we don't want nullid in our output list,
1278 1282 # and if nullid shows up in descendants, empty parents will look like
1279 1283 # they're descendants.
1280 1284 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1281 1285 n = self.node(r)
1282 1286 isdescendant = False
1283 1287 if lowestrev == nullrev: # Everybody is a descendant of nullid
1284 1288 isdescendant = True
1285 1289 elif n in descendants:
1286 1290 # n is already a descendant
1287 1291 isdescendant = True
1288 1292 # This check only needs to be done here because all the roots
1289 1293 # will start being marked is descendants before the loop.
1290 1294 if n in roots:
1291 1295 # If n was a root, check if it's a 'real' root.
1292 1296 p = tuple(self.parents(n))
1293 1297 # If any of its parents are descendants, it's not a root.
1294 1298 if (p[0] in descendants) or (p[1] in descendants):
1295 1299 roots.remove(n)
1296 1300 else:
1297 1301 p = tuple(self.parents(n))
1298 1302 # A node is a descendant if either of its parents are
1299 1303 # descendants. (We seeded the dependents list with the roots
1300 1304 # up there, remember?)
1301 1305 if (p[0] in descendants) or (p[1] in descendants):
1302 1306 descendants.add(n)
1303 1307 isdescendant = True
1304 1308 if isdescendant and ((ancestors is None) or (n in ancestors)):
1305 1309 # Only include nodes that are both descendants and ancestors.
1306 1310 orderedout.append(n)
1307 1311 if (ancestors is not None) and (n in heads):
1308 1312 # We're trying to figure out which heads are reachable
1309 1313 # from roots.
1310 1314 # Mark this head as having been reached
1311 1315 heads[n] = True
1312 1316 elif ancestors is None:
1313 1317 # Otherwise, we're trying to discover the heads.
1314 1318 # Assume this is a head because if it isn't, the next step
1315 1319 # will eventually remove it.
1316 1320 heads[n] = True
1317 1321 # But, obviously its parents aren't.
1318 1322 for p in self.parents(n):
1319 1323 heads.pop(p, None)
1320 1324 heads = [head for head, flag in heads.items() if flag]
1321 1325 roots = list(roots)
1322 1326 assert orderedout
1323 1327 assert roots
1324 1328 assert heads
1325 1329 return (orderedout, roots, heads)
1326 1330
1327 1331 def headrevs(self, revs=None):
1328 1332 if revs is None:
1329 1333 try:
1330 1334 return self.index.headrevs()
1331 1335 except AttributeError:
1332 1336 return self._headrevs()
1333 1337 if rustdagop is not None and self.index.rust_ext_compat:
1334 1338 return rustdagop.headrevs(self.index, revs)
1335 1339 return dagop.headrevs(revs, self._uncheckedparentrevs)
1336 1340
1337 1341 def computephases(self, roots):
1338 1342 return self.index.computephasesmapsets(roots)
1339 1343
1340 1344 def _headrevs(self):
1341 1345 count = len(self)
1342 1346 if not count:
1343 1347 return [nullrev]
1344 1348 # we won't iter over filtered rev so nobody is a head at start
1345 1349 ishead = [0] * (count + 1)
1346 1350 index = self.index
1347 1351 for r in self:
1348 1352 ishead[r] = 1 # I may be an head
1349 1353 e = index[r]
1350 1354 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1351 1355 return [r for r, val in enumerate(ishead) if val]
1352 1356
1353 1357 def heads(self, start=None, stop=None):
1354 1358 """return the list of all nodes that have no children
1355 1359
1356 1360 if start is specified, only heads that are descendants of
1357 1361 start will be returned
1358 1362 if stop is specified, it will consider all the revs from stop
1359 1363 as if they had no children
1360 1364 """
1361 1365 if start is None and stop is None:
1362 1366 if not len(self):
1363 1367 return [self.nullid]
1364 1368 return [self.node(r) for r in self.headrevs()]
1365 1369
1366 1370 if start is None:
1367 1371 start = nullrev
1368 1372 else:
1369 1373 start = self.rev(start)
1370 1374
1371 1375 stoprevs = {self.rev(n) for n in stop or []}
1372 1376
1373 1377 revs = dagop.headrevssubset(
1374 1378 self.revs, self.parentrevs, startrev=start, stoprevs=stoprevs
1375 1379 )
1376 1380
1377 1381 return [self.node(rev) for rev in revs]
1378 1382
1379 1383 def children(self, node):
1380 1384 """find the children of a given node"""
1381 1385 c = []
1382 1386 p = self.rev(node)
1383 1387 for r in self.revs(start=p + 1):
1384 1388 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1385 1389 if prevs:
1386 1390 for pr in prevs:
1387 1391 if pr == p:
1388 1392 c.append(self.node(r))
1389 1393 elif p == nullrev:
1390 1394 c.append(self.node(r))
1391 1395 return c
1392 1396
1393 1397 def commonancestorsheads(self, a, b):
1394 1398 """calculate all the heads of the common ancestors of nodes a and b"""
1395 1399 a, b = self.rev(a), self.rev(b)
1396 1400 ancs = self._commonancestorsheads(a, b)
1397 1401 return pycompat.maplist(self.node, ancs)
1398 1402
1399 1403 def _commonancestorsheads(self, *revs):
1400 1404 """calculate all the heads of the common ancestors of revs"""
1401 1405 try:
1402 1406 ancs = self.index.commonancestorsheads(*revs)
1403 1407 except (AttributeError, OverflowError): # C implementation failed
1404 1408 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1405 1409 return ancs
1406 1410
1407 1411 def isancestor(self, a, b):
1408 1412 """return True if node a is an ancestor of node b
1409 1413
1410 1414 A revision is considered an ancestor of itself."""
1411 1415 a, b = self.rev(a), self.rev(b)
1412 1416 return self.isancestorrev(a, b)
1413 1417
1414 1418 def isancestorrev(self, a, b):
1415 1419 """return True if revision a is an ancestor of revision b
1416 1420
1417 1421 A revision is considered an ancestor of itself.
1418 1422
1419 1423 The implementation of this is trivial but the use of
1420 1424 reachableroots is not."""
1421 1425 if a == nullrev:
1422 1426 return True
1423 1427 elif a == b:
1424 1428 return True
1425 1429 elif a > b:
1426 1430 return False
1427 1431 return bool(self.reachableroots(a, [b], [a], includepath=False))
1428 1432
1429 1433 def reachableroots(self, minroot, heads, roots, includepath=False):
1430 1434 """return (heads(::(<roots> and <roots>::<heads>)))
1431 1435
1432 1436 If includepath is True, return (<roots>::<heads>)."""
1433 1437 try:
1434 1438 return self.index.reachableroots2(
1435 1439 minroot, heads, roots, includepath
1436 1440 )
1437 1441 except AttributeError:
1438 1442 return dagop._reachablerootspure(
1439 1443 self.parentrevs, minroot, roots, heads, includepath
1440 1444 )
1441 1445
1442 1446 def ancestor(self, a, b):
1443 1447 """calculate the "best" common ancestor of nodes a and b"""
1444 1448
1445 1449 a, b = self.rev(a), self.rev(b)
1446 1450 try:
1447 1451 ancs = self.index.ancestors(a, b)
1448 1452 except (AttributeError, OverflowError):
1449 1453 ancs = ancestor.ancestors(self.parentrevs, a, b)
1450 1454 if ancs:
1451 1455 # choose a consistent winner when there's a tie
1452 1456 return min(map(self.node, ancs))
1453 1457 return self.nullid
1454 1458
1455 1459 def _match(self, id):
1456 1460 if isinstance(id, int):
1457 1461 # rev
1458 1462 return self.node(id)
1459 1463 if len(id) == self.nodeconstants.nodelen:
1460 1464 # possibly a binary node
1461 1465 # odds of a binary node being all hex in ASCII are 1 in 10**25
1462 1466 try:
1463 1467 node = id
1464 1468 self.rev(node) # quick search the index
1465 1469 return node
1466 1470 except error.LookupError:
1467 1471 pass # may be partial hex id
1468 1472 try:
1469 1473 # str(rev)
1470 1474 rev = int(id)
1471 1475 if b"%d" % rev != id:
1472 1476 raise ValueError
1473 1477 if rev < 0:
1474 1478 rev = len(self) + rev
1475 1479 if rev < 0 or rev >= len(self):
1476 1480 raise ValueError
1477 1481 return self.node(rev)
1478 1482 except (ValueError, OverflowError):
1479 1483 pass
1480 1484 if len(id) == 2 * self.nodeconstants.nodelen:
1481 1485 try:
1482 1486 # a full hex nodeid?
1483 1487 node = bin(id)
1484 1488 self.rev(node)
1485 1489 return node
1486 1490 except (binascii.Error, error.LookupError):
1487 1491 pass
1488 1492
1489 1493 def _partialmatch(self, id):
1490 1494 # we don't care wdirfilenodeids as they should be always full hash
1491 1495 maybewdir = self.nodeconstants.wdirhex.startswith(id)
1492 1496 ambiguous = False
1493 1497 try:
1494 1498 partial = self.index.partialmatch(id)
1495 1499 if partial and self.hasnode(partial):
1496 1500 if maybewdir:
1497 1501 # single 'ff...' match in radix tree, ambiguous with wdir
1498 1502 ambiguous = True
1499 1503 else:
1500 1504 return partial
1501 1505 elif maybewdir:
1502 1506 # no 'ff...' match in radix tree, wdir identified
1503 1507 raise error.WdirUnsupported
1504 1508 else:
1505 1509 return None
1506 1510 except error.RevlogError:
1507 1511 # parsers.c radix tree lookup gave multiple matches
1508 1512 # fast path: for unfiltered changelog, radix tree is accurate
1509 1513 if not getattr(self, 'filteredrevs', None):
1510 1514 ambiguous = True
1511 1515 # fall through to slow path that filters hidden revisions
1512 1516 except (AttributeError, ValueError):
1513 1517 # we are pure python, or key is not hex
1514 1518 pass
1515 1519 if ambiguous:
1516 1520 raise error.AmbiguousPrefixLookupError(
1517 1521 id, self.display_id, _(b'ambiguous identifier')
1518 1522 )
1519 1523
1520 1524 if id in self._pcache:
1521 1525 return self._pcache[id]
1522 1526
1523 1527 if len(id) <= 40:
1524 1528 # hex(node)[:...]
1525 1529 l = len(id) // 2 * 2 # grab an even number of digits
1526 1530 try:
1527 1531 # we're dropping the last digit, so let's check that it's hex,
1528 1532 # to avoid the expensive computation below if it's not
1529 1533 if len(id) % 2 > 0:
1530 1534 if not (id[-1] in hexdigits):
1531 1535 return None
1532 1536 prefix = bin(id[:l])
1533 1537 except binascii.Error:
1534 1538 pass
1535 1539 else:
1536 1540 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1537 1541 nl = [
1538 1542 n for n in nl if hex(n).startswith(id) and self.hasnode(n)
1539 1543 ]
1540 1544 if self.nodeconstants.nullhex.startswith(id):
1541 1545 nl.append(self.nullid)
1542 1546 if len(nl) > 0:
1543 1547 if len(nl) == 1 and not maybewdir:
1544 1548 self._pcache[id] = nl[0]
1545 1549 return nl[0]
1546 1550 raise error.AmbiguousPrefixLookupError(
1547 1551 id, self.display_id, _(b'ambiguous identifier')
1548 1552 )
1549 1553 if maybewdir:
1550 1554 raise error.WdirUnsupported
1551 1555 return None
1552 1556
1553 1557 def lookup(self, id):
1554 1558 """locate a node based on:
1555 1559 - revision number or str(revision number)
1556 1560 - nodeid or subset of hex nodeid
1557 1561 """
1558 1562 n = self._match(id)
1559 1563 if n is not None:
1560 1564 return n
1561 1565 n = self._partialmatch(id)
1562 1566 if n:
1563 1567 return n
1564 1568
1565 1569 raise error.LookupError(id, self.display_id, _(b'no match found'))
1566 1570
1567 1571 def shortest(self, node, minlength=1):
1568 1572 """Find the shortest unambiguous prefix that matches node."""
1569 1573
1570 1574 def isvalid(prefix):
1571 1575 try:
1572 1576 matchednode = self._partialmatch(prefix)
1573 1577 except error.AmbiguousPrefixLookupError:
1574 1578 return False
1575 1579 except error.WdirUnsupported:
1576 1580 # single 'ff...' match
1577 1581 return True
1578 1582 if matchednode is None:
1579 1583 raise error.LookupError(node, self.display_id, _(b'no node'))
1580 1584 return True
1581 1585
1582 1586 def maybewdir(prefix):
1583 1587 return all(c == b'f' for c in pycompat.iterbytestr(prefix))
1584 1588
1585 1589 hexnode = hex(node)
1586 1590
1587 1591 def disambiguate(hexnode, minlength):
1588 1592 """Disambiguate against wdirid."""
1589 1593 for length in range(minlength, len(hexnode) + 1):
1590 1594 prefix = hexnode[:length]
1591 1595 if not maybewdir(prefix):
1592 1596 return prefix
1593 1597
1594 1598 if not getattr(self, 'filteredrevs', None):
1595 1599 try:
1596 1600 length = max(self.index.shortest(node), minlength)
1597 1601 return disambiguate(hexnode, length)
1598 1602 except error.RevlogError:
1599 1603 if node != self.nodeconstants.wdirid:
1600 1604 raise error.LookupError(
1601 1605 node, self.display_id, _(b'no node')
1602 1606 )
1603 1607 except AttributeError:
1604 1608 # Fall through to pure code
1605 1609 pass
1606 1610
1607 1611 if node == self.nodeconstants.wdirid:
1608 1612 for length in range(minlength, len(hexnode) + 1):
1609 1613 prefix = hexnode[:length]
1610 1614 if isvalid(prefix):
1611 1615 return prefix
1612 1616
1613 1617 for length in range(minlength, len(hexnode) + 1):
1614 1618 prefix = hexnode[:length]
1615 1619 if isvalid(prefix):
1616 1620 return disambiguate(hexnode, length)
1617 1621
1618 1622 def cmp(self, node, text):
1619 1623 """compare text with a given file revision
1620 1624
1621 1625 returns True if text is different than what is stored.
1622 1626 """
1623 1627 p1, p2 = self.parents(node)
1624 1628 return storageutil.hashrevisionsha1(text, p1, p2) != node
1625 1629
1626 1630 def _getsegmentforrevs(self, startrev, endrev, df=None):
1627 1631 """Obtain a segment of raw data corresponding to a range of revisions.
1628 1632
1629 1633 Accepts the start and end revisions and an optional already-open
1630 1634 file handle to be used for reading. If the file handle is read, its
1631 1635 seek position will not be preserved.
1632 1636
1633 1637 Requests for data may be satisfied by a cache.
1634 1638
1635 1639 Returns a 2-tuple of (offset, data) for the requested range of
1636 1640 revisions. Offset is the integer offset from the beginning of the
1637 1641 revlog and data is a str or buffer of the raw byte data.
1638 1642
1639 1643 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1640 1644 to determine where each revision's data begins and ends.
1641 1645 """
1642 1646 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1643 1647 # (functions are expensive).
1644 1648 index = self.index
1645 1649 istart = index[startrev]
1646 1650 start = int(istart[0] >> 16)
1647 1651 if startrev == endrev:
1648 1652 end = start + istart[1]
1649 1653 else:
1650 1654 iend = index[endrev]
1651 1655 end = int(iend[0] >> 16) + iend[1]
1652 1656
1653 1657 if self._inline:
1654 1658 start += (startrev + 1) * self.index.entry_size
1655 1659 end += (endrev + 1) * self.index.entry_size
1656 1660 length = end - start
1657 1661
1658 1662 return start, self._segmentfile.read_chunk(start, length, df)
1659 1663
1660 1664 def _chunk(self, rev, df=None):
1661 1665 """Obtain a single decompressed chunk for a revision.
1662 1666
1663 1667 Accepts an integer revision and an optional already-open file handle
1664 1668 to be used for reading. If used, the seek position of the file will not
1665 1669 be preserved.
1666 1670
1667 1671 Returns a str holding uncompressed data for the requested revision.
1668 1672 """
1669 1673 compression_mode = self.index[rev][10]
1670 1674 data = self._getsegmentforrevs(rev, rev, df=df)[1]
1671 1675 if compression_mode == COMP_MODE_PLAIN:
1672 1676 return data
1673 1677 elif compression_mode == COMP_MODE_DEFAULT:
1674 1678 return self._decompressor(data)
1675 1679 elif compression_mode == COMP_MODE_INLINE:
1676 1680 return self.decompress(data)
1677 1681 else:
1678 1682 msg = b'unknown compression mode %d'
1679 1683 msg %= compression_mode
1680 1684 raise error.RevlogError(msg)
1681 1685
1682 1686 def _chunks(self, revs, df=None, targetsize=None):
1683 1687 """Obtain decompressed chunks for the specified revisions.
1684 1688
1685 1689 Accepts an iterable of numeric revisions that are assumed to be in
1686 1690 ascending order. Also accepts an optional already-open file handle
1687 1691 to be used for reading. If used, the seek position of the file will
1688 1692 not be preserved.
1689 1693
1690 1694 This function is similar to calling ``self._chunk()`` multiple times,
1691 1695 but is faster.
1692 1696
1693 1697 Returns a list with decompressed data for each requested revision.
1694 1698 """
1695 1699 if not revs:
1696 1700 return []
1697 1701 start = self.start
1698 1702 length = self.length
1699 1703 inline = self._inline
1700 1704 iosize = self.index.entry_size
1701 1705 buffer = util.buffer
1702 1706
1703 1707 l = []
1704 1708 ladd = l.append
1705 1709
1706 1710 if not self._withsparseread:
1707 1711 slicedchunks = (revs,)
1708 1712 else:
1709 1713 slicedchunks = deltautil.slicechunk(
1710 1714 self, revs, targetsize=targetsize
1711 1715 )
1712 1716
1713 1717 for revschunk in slicedchunks:
1714 1718 firstrev = revschunk[0]
1715 1719 # Skip trailing revisions with empty diff
1716 1720 for lastrev in revschunk[::-1]:
1717 1721 if length(lastrev) != 0:
1718 1722 break
1719 1723
1720 1724 try:
1721 1725 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
1722 1726 except OverflowError:
1723 1727 # issue4215 - we can't cache a run of chunks greater than
1724 1728 # 2G on Windows
1725 1729 return [self._chunk(rev, df=df) for rev in revschunk]
1726 1730
1727 1731 decomp = self.decompress
1728 1732 # self._decompressor might be None, but will not be used in that case
1729 1733 def_decomp = self._decompressor
1730 1734 for rev in revschunk:
1731 1735 chunkstart = start(rev)
1732 1736 if inline:
1733 1737 chunkstart += (rev + 1) * iosize
1734 1738 chunklength = length(rev)
1735 1739 comp_mode = self.index[rev][10]
1736 1740 c = buffer(data, chunkstart - offset, chunklength)
1737 1741 if comp_mode == COMP_MODE_PLAIN:
1738 1742 ladd(c)
1739 1743 elif comp_mode == COMP_MODE_INLINE:
1740 1744 ladd(decomp(c))
1741 1745 elif comp_mode == COMP_MODE_DEFAULT:
1742 1746 ladd(def_decomp(c))
1743 1747 else:
1744 1748 msg = b'unknown compression mode %d'
1745 1749 msg %= comp_mode
1746 1750 raise error.RevlogError(msg)
1747 1751
1748 1752 return l
1749 1753
1750 1754 def deltaparent(self, rev):
1751 1755 """return deltaparent of the given revision"""
1752 1756 base = self.index[rev][3]
1753 1757 if base == rev:
1754 1758 return nullrev
1755 1759 elif self._generaldelta:
1756 1760 return base
1757 1761 else:
1758 1762 return rev - 1
1759 1763
1760 1764 def issnapshot(self, rev):
1761 1765 """tells whether rev is a snapshot"""
1762 1766 if not self._sparserevlog:
1763 1767 return self.deltaparent(rev) == nullrev
1764 1768 elif util.safehasattr(self.index, b'issnapshot'):
1765 1769 # directly assign the method to cache the testing and access
1766 1770 self.issnapshot = self.index.issnapshot
1767 1771 return self.issnapshot(rev)
1768 1772 if rev == nullrev:
1769 1773 return True
1770 1774 entry = self.index[rev]
1771 1775 base = entry[3]
1772 1776 if base == rev:
1773 1777 return True
1774 1778 if base == nullrev:
1775 1779 return True
1776 1780 p1 = entry[5]
1777 1781 while self.length(p1) == 0:
1778 1782 b = self.deltaparent(p1)
1779 1783 if b == p1:
1780 1784 break
1781 1785 p1 = b
1782 1786 p2 = entry[6]
1783 1787 while self.length(p2) == 0:
1784 1788 b = self.deltaparent(p2)
1785 1789 if b == p2:
1786 1790 break
1787 1791 p2 = b
1788 1792 if base == p1 or base == p2:
1789 1793 return False
1790 1794 return self.issnapshot(base)
1791 1795
1792 1796 def snapshotdepth(self, rev):
1793 1797 """number of snapshot in the chain before this one"""
1794 1798 if not self.issnapshot(rev):
1795 1799 raise error.ProgrammingError(b'revision %d not a snapshot')
1796 1800 return len(self._deltachain(rev)[0]) - 1
1797 1801
1798 1802 def revdiff(self, rev1, rev2):
1799 1803 """return or calculate a delta between two revisions
1800 1804
1801 1805 The delta calculated is in binary form and is intended to be written to
1802 1806 revlog data directly. So this function needs raw revision data.
1803 1807 """
1804 1808 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1805 1809 return bytes(self._chunk(rev2))
1806 1810
1807 1811 return mdiff.textdiff(self.rawdata(rev1), self.rawdata(rev2))
1808 1812
1809 1813 def revision(self, nodeorrev, _df=None):
1810 1814 """return an uncompressed revision of a given node or revision
1811 1815 number.
1812 1816
1813 1817 _df - an existing file handle to read from. (internal-only)
1814 1818 """
1815 1819 return self._revisiondata(nodeorrev, _df)
1816 1820
1817 1821 def sidedata(self, nodeorrev, _df=None):
1818 1822 """a map of extra data related to the changeset but not part of the hash
1819 1823
1820 1824 This function currently return a dictionary. However, more advanced
1821 1825 mapping object will likely be used in the future for a more
1822 1826 efficient/lazy code.
1823 1827 """
1824 1828 # deal with <nodeorrev> argument type
1825 1829 if isinstance(nodeorrev, int):
1826 1830 rev = nodeorrev
1827 1831 else:
1828 1832 rev = self.rev(nodeorrev)
1829 1833 return self._sidedata(rev)
1830 1834
1831 1835 def _revisiondata(self, nodeorrev, _df=None, raw=False):
1832 1836 # deal with <nodeorrev> argument type
1833 1837 if isinstance(nodeorrev, int):
1834 1838 rev = nodeorrev
1835 1839 node = self.node(rev)
1836 1840 else:
1837 1841 node = nodeorrev
1838 1842 rev = None
1839 1843
1840 1844 # fast path the special `nullid` rev
1841 1845 if node == self.nullid:
1842 1846 return b""
1843 1847
1844 1848 # ``rawtext`` is the text as stored inside the revlog. Might be the
1845 1849 # revision or might need to be processed to retrieve the revision.
1846 1850 rev, rawtext, validated = self._rawtext(node, rev, _df=_df)
1847 1851
1848 1852 if raw and validated:
1849 1853 # if we don't want to process the raw text and that raw
1850 1854 # text is cached, we can exit early.
1851 1855 return rawtext
1852 1856 if rev is None:
1853 1857 rev = self.rev(node)
1854 1858 # the revlog's flag for this revision
1855 1859 # (usually alter its state or content)
1856 1860 flags = self.flags(rev)
1857 1861
1858 1862 if validated and flags == REVIDX_DEFAULT_FLAGS:
1859 1863 # no extra flags set, no flag processor runs, text = rawtext
1860 1864 return rawtext
1861 1865
1862 1866 if raw:
1863 1867 validatehash = flagutil.processflagsraw(self, rawtext, flags)
1864 1868 text = rawtext
1865 1869 else:
1866 1870 r = flagutil.processflagsread(self, rawtext, flags)
1867 1871 text, validatehash = r
1868 1872 if validatehash:
1869 1873 self.checkhash(text, node, rev=rev)
1870 1874 if not validated:
1871 1875 self._revisioncache = (node, rev, rawtext)
1872 1876
1873 1877 return text
1874 1878
1875 1879 def _rawtext(self, node, rev, _df=None):
1876 1880 """return the possibly unvalidated rawtext for a revision
1877 1881
1878 1882 returns (rev, rawtext, validated)
1879 1883 """
1880 1884
1881 1885 # revision in the cache (could be useful to apply delta)
1882 1886 cachedrev = None
1883 1887 # An intermediate text to apply deltas to
1884 1888 basetext = None
1885 1889
1886 1890 # Check if we have the entry in cache
1887 1891 # The cache entry looks like (node, rev, rawtext)
1888 1892 if self._revisioncache:
1889 1893 if self._revisioncache[0] == node:
1890 1894 return (rev, self._revisioncache[2], True)
1891 1895 cachedrev = self._revisioncache[1]
1892 1896
1893 1897 if rev is None:
1894 1898 rev = self.rev(node)
1895 1899
1896 1900 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1897 1901 if stopped:
1898 1902 basetext = self._revisioncache[2]
1899 1903
1900 1904 # drop cache to save memory, the caller is expected to
1901 1905 # update self._revisioncache after validating the text
1902 1906 self._revisioncache = None
1903 1907
1904 1908 targetsize = None
1905 1909 rawsize = self.index[rev][2]
1906 1910 if 0 <= rawsize:
1907 1911 targetsize = 4 * rawsize
1908 1912
1909 1913 bins = self._chunks(chain, df=_df, targetsize=targetsize)
1910 1914 if basetext is None:
1911 1915 basetext = bytes(bins[0])
1912 1916 bins = bins[1:]
1913 1917
1914 1918 rawtext = mdiff.patches(basetext, bins)
1915 1919 del basetext # let us have a chance to free memory early
1916 1920 return (rev, rawtext, False)
1917 1921
1918 1922 def _sidedata(self, rev):
1919 1923 """Return the sidedata for a given revision number."""
1920 1924 index_entry = self.index[rev]
1921 1925 sidedata_offset = index_entry[8]
1922 1926 sidedata_size = index_entry[9]
1923 1927
1924 1928 if self._inline:
1925 1929 sidedata_offset += self.index.entry_size * (1 + rev)
1926 1930 if sidedata_size == 0:
1927 1931 return {}
1928 1932
1929 1933 if self._docket.sidedata_end < sidedata_offset + sidedata_size:
1930 1934 filename = self._sidedatafile
1931 1935 end = self._docket.sidedata_end
1932 1936 offset = sidedata_offset
1933 1937 length = sidedata_size
1934 1938 m = FILE_TOO_SHORT_MSG % (filename, length, offset, end)
1935 1939 raise error.RevlogError(m)
1936 1940
1937 1941 comp_segment = self._segmentfile_sidedata.read_chunk(
1938 1942 sidedata_offset, sidedata_size
1939 1943 )
1940 1944
1941 1945 comp = self.index[rev][11]
1942 1946 if comp == COMP_MODE_PLAIN:
1943 1947 segment = comp_segment
1944 1948 elif comp == COMP_MODE_DEFAULT:
1945 1949 segment = self._decompressor(comp_segment)
1946 1950 elif comp == COMP_MODE_INLINE:
1947 1951 segment = self.decompress(comp_segment)
1948 1952 else:
1949 1953 msg = b'unknown compression mode %d'
1950 1954 msg %= comp
1951 1955 raise error.RevlogError(msg)
1952 1956
1953 1957 sidedata = sidedatautil.deserialize_sidedata(segment)
1954 1958 return sidedata
1955 1959
1956 1960 def rawdata(self, nodeorrev, _df=None):
1957 1961 """return an uncompressed raw data of a given node or revision number.
1958 1962
1959 1963 _df - an existing file handle to read from. (internal-only)
1960 1964 """
1961 1965 return self._revisiondata(nodeorrev, _df, raw=True)
1962 1966
1963 1967 def hash(self, text, p1, p2):
1964 1968 """Compute a node hash.
1965 1969
1966 1970 Available as a function so that subclasses can replace the hash
1967 1971 as needed.
1968 1972 """
1969 1973 return storageutil.hashrevisionsha1(text, p1, p2)
1970 1974
1971 1975 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1972 1976 """Check node hash integrity.
1973 1977
1974 1978 Available as a function so that subclasses can extend hash mismatch
1975 1979 behaviors as needed.
1976 1980 """
1977 1981 try:
1978 1982 if p1 is None and p2 is None:
1979 1983 p1, p2 = self.parents(node)
1980 1984 if node != self.hash(text, p1, p2):
1981 1985 # Clear the revision cache on hash failure. The revision cache
1982 1986 # only stores the raw revision and clearing the cache does have
1983 1987 # the side-effect that we won't have a cache hit when the raw
1984 1988 # revision data is accessed. But this case should be rare and
1985 1989 # it is extra work to teach the cache about the hash
1986 1990 # verification state.
1987 1991 if self._revisioncache and self._revisioncache[0] == node:
1988 1992 self._revisioncache = None
1989 1993
1990 1994 revornode = rev
1991 1995 if revornode is None:
1992 1996 revornode = templatefilters.short(hex(node))
1993 1997 raise error.RevlogError(
1994 1998 _(b"integrity check failed on %s:%s")
1995 1999 % (self.display_id, pycompat.bytestr(revornode))
1996 2000 )
1997 2001 except error.RevlogError:
1998 2002 if self._censorable and storageutil.iscensoredtext(text):
1999 2003 raise error.CensoredNodeError(self.display_id, node, text)
2000 2004 raise
2001 2005
2002 2006 def _enforceinlinesize(self, tr):
2003 2007 """Check if the revlog is too big for inline and convert if so.
2004 2008
2005 2009 This should be called after revisions are added to the revlog. If the
2006 2010 revlog has grown too large to be an inline revlog, it will convert it
2007 2011 to use multiple index and data files.
2008 2012 """
2009 2013 tiprev = len(self) - 1
2010 2014 total_size = self.start(tiprev) + self.length(tiprev)
2011 2015 if not self._inline or total_size < _maxinline:
2012 2016 return
2013 2017
2014 2018 troffset = tr.findoffset(self._indexfile)
2015 2019 if troffset is None:
2016 2020 raise error.RevlogError(
2017 2021 _(b"%s not found in the transaction") % self._indexfile
2018 2022 )
2019 2023 trindex = None
2020 2024 tr.add(self._datafile, 0)
2021 2025
2022 2026 existing_handles = False
2023 2027 if self._writinghandles is not None:
2024 2028 existing_handles = True
2025 2029 fp = self._writinghandles[0]
2026 2030 fp.flush()
2027 2031 fp.close()
2028 2032 # We can't use the cached file handle after close(). So prevent
2029 2033 # its usage.
2030 2034 self._writinghandles = None
2031 2035 self._segmentfile.writing_handle = None
2032 2036 # No need to deal with sidedata writing handle as it is only
2033 2037 # relevant with revlog-v2 which is never inline, not reaching
2034 2038 # this code
2035 2039
2036 2040 new_dfh = self._datafp(b'w+')
2037 2041 new_dfh.truncate(0) # drop any potentially existing data
2038 2042 try:
2039 2043 with self._indexfp() as read_ifh:
2040 2044 for r in self:
2041 2045 new_dfh.write(self._getsegmentforrevs(r, r, df=read_ifh)[1])
2042 2046 if (
2043 2047 trindex is None
2044 2048 and troffset
2045 2049 <= self.start(r) + r * self.index.entry_size
2046 2050 ):
2047 2051 trindex = r
2048 2052 new_dfh.flush()
2049 2053
2050 2054 if trindex is None:
2051 2055 trindex = 0
2052 2056
2053 2057 with self.__index_new_fp() as fp:
2054 2058 self._format_flags &= ~FLAG_INLINE_DATA
2055 2059 self._inline = False
2056 2060 for i in self:
2057 2061 e = self.index.entry_binary(i)
2058 2062 if i == 0 and self._docket is None:
2059 2063 header = self._format_flags | self._format_version
2060 2064 header = self.index.pack_header(header)
2061 2065 e = header + e
2062 2066 fp.write(e)
2063 2067 if self._docket is not None:
2064 2068 self._docket.index_end = fp.tell()
2065 2069
2066 2070 # There is a small transactional race here. If the rename of
2067 2071 # the index fails, we should remove the datafile. It is more
2068 2072 # important to ensure that the data file is not truncated
2069 2073 # when the index is replaced as otherwise data is lost.
2070 2074 tr.replace(self._datafile, self.start(trindex))
2071 2075
2072 2076 # the temp file replace the real index when we exit the context
2073 2077 # manager
2074 2078
2075 2079 tr.replace(self._indexfile, trindex * self.index.entry_size)
2076 2080 nodemaputil.setup_persistent_nodemap(tr, self)
2077 2081 self._segmentfile = randomaccessfile.randomaccessfile(
2078 2082 self.opener,
2079 2083 self._datafile,
2080 2084 self._chunkcachesize,
2081 2085 )
2082 2086
2083 2087 if existing_handles:
2084 2088 # switched from inline to conventional reopen the index
2085 2089 ifh = self.__index_write_fp()
2086 2090 self._writinghandles = (ifh, new_dfh, None)
2087 2091 self._segmentfile.writing_handle = new_dfh
2088 2092 new_dfh = None
2089 2093 # No need to deal with sidedata writing handle as it is only
2090 2094 # relevant with revlog-v2 which is never inline, not reaching
2091 2095 # this code
2092 2096 finally:
2093 2097 if new_dfh is not None:
2094 2098 new_dfh.close()
2095 2099
2096 2100 def _nodeduplicatecallback(self, transaction, node):
2097 2101 """called when trying to add a node already stored."""
2098 2102
2099 2103 @contextlib.contextmanager
2100 2104 def reading(self):
2101 2105 """Context manager that keeps data and sidedata files open for reading"""
2102 2106 with self._segmentfile.reading():
2103 2107 with self._segmentfile_sidedata.reading():
2104 2108 yield
2105 2109
2106 2110 @contextlib.contextmanager
2107 2111 def _writing(self, transaction):
2108 2112 if self._trypending:
2109 2113 msg = b'try to write in a `trypending` revlog: %s'
2110 2114 msg %= self.display_id
2111 2115 raise error.ProgrammingError(msg)
2112 2116 if self._writinghandles is not None:
2113 2117 yield
2114 2118 else:
2115 2119 ifh = dfh = sdfh = None
2116 2120 try:
2117 2121 r = len(self)
2118 2122 # opening the data file.
2119 2123 dsize = 0
2120 2124 if r:
2121 2125 dsize = self.end(r - 1)
2122 2126 dfh = None
2123 2127 if not self._inline:
2124 2128 try:
2125 2129 dfh = self._datafp(b"r+")
2126 2130 if self._docket is None:
2127 2131 dfh.seek(0, os.SEEK_END)
2128 2132 else:
2129 2133 dfh.seek(self._docket.data_end, os.SEEK_SET)
2130 2134 except FileNotFoundError:
2131 2135 dfh = self._datafp(b"w+")
2132 2136 transaction.add(self._datafile, dsize)
2133 2137 if self._sidedatafile is not None:
2134 2138 # revlog-v2 does not inline, help Pytype
2135 2139 assert dfh is not None
2136 2140 try:
2137 2141 sdfh = self.opener(self._sidedatafile, mode=b"r+")
2138 2142 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2139 2143 except FileNotFoundError:
2140 2144 sdfh = self.opener(self._sidedatafile, mode=b"w+")
2141 2145 transaction.add(
2142 2146 self._sidedatafile, self._docket.sidedata_end
2143 2147 )
2144 2148
2145 2149 # opening the index file.
2146 2150 isize = r * self.index.entry_size
2147 2151 ifh = self.__index_write_fp()
2148 2152 if self._inline:
2149 2153 transaction.add(self._indexfile, dsize + isize)
2150 2154 else:
2151 2155 transaction.add(self._indexfile, isize)
2152 2156 # exposing all file handle for writing.
2153 2157 self._writinghandles = (ifh, dfh, sdfh)
2154 2158 self._segmentfile.writing_handle = ifh if self._inline else dfh
2155 2159 self._segmentfile_sidedata.writing_handle = sdfh
2156 2160 yield
2157 2161 if self._docket is not None:
2158 2162 self._write_docket(transaction)
2159 2163 finally:
2160 2164 self._writinghandles = None
2161 2165 self._segmentfile.writing_handle = None
2162 2166 self._segmentfile_sidedata.writing_handle = None
2163 2167 if dfh is not None:
2164 2168 dfh.close()
2165 2169 if sdfh is not None:
2166 2170 sdfh.close()
2167 2171 # closing the index file last to avoid exposing referent to
2168 2172 # potential unflushed data content.
2169 2173 if ifh is not None:
2170 2174 ifh.close()
2171 2175
2172 2176 def _write_docket(self, transaction):
2173 2177 """write the current docket on disk
2174 2178
2175 2179 Exist as a method to help changelog to implement transaction logic
2176 2180
2177 2181 We could also imagine using the same transaction logic for all revlog
2178 2182 since docket are cheap."""
2179 2183 self._docket.write(transaction)
2180 2184
2181 2185 def addrevision(
2182 2186 self,
2183 2187 text,
2184 2188 transaction,
2185 2189 link,
2186 2190 p1,
2187 2191 p2,
2188 2192 cachedelta=None,
2189 2193 node=None,
2190 2194 flags=REVIDX_DEFAULT_FLAGS,
2191 2195 deltacomputer=None,
2192 2196 sidedata=None,
2193 2197 ):
2194 2198 """add a revision to the log
2195 2199
2196 2200 text - the revision data to add
2197 2201 transaction - the transaction object used for rollback
2198 2202 link - the linkrev data to add
2199 2203 p1, p2 - the parent nodeids of the revision
2200 2204 cachedelta - an optional precomputed delta
2201 2205 node - nodeid of revision; typically node is not specified, and it is
2202 2206 computed by default as hash(text, p1, p2), however subclasses might
2203 2207 use different hashing method (and override checkhash() in such case)
2204 2208 flags - the known flags to set on the revision
2205 2209 deltacomputer - an optional deltacomputer instance shared between
2206 2210 multiple calls
2207 2211 """
2208 2212 if link == nullrev:
2209 2213 raise error.RevlogError(
2210 2214 _(b"attempted to add linkrev -1 to %s") % self.display_id
2211 2215 )
2212 2216
2213 2217 if sidedata is None:
2214 2218 sidedata = {}
2215 2219 elif sidedata and not self.hassidedata:
2216 2220 raise error.ProgrammingError(
2217 2221 _(b"trying to add sidedata to a revlog who don't support them")
2218 2222 )
2219 2223
2220 2224 if flags:
2221 2225 node = node or self.hash(text, p1, p2)
2222 2226
2223 2227 rawtext, validatehash = flagutil.processflagswrite(self, text, flags)
2224 2228
2225 2229 # If the flag processor modifies the revision data, ignore any provided
2226 2230 # cachedelta.
2227 2231 if rawtext != text:
2228 2232 cachedelta = None
2229 2233
2230 2234 if len(rawtext) > _maxentrysize:
2231 2235 raise error.RevlogError(
2232 2236 _(
2233 2237 b"%s: size of %d bytes exceeds maximum revlog storage of 2GiB"
2234 2238 )
2235 2239 % (self.display_id, len(rawtext))
2236 2240 )
2237 2241
2238 2242 node = node or self.hash(rawtext, p1, p2)
2239 2243 rev = self.index.get_rev(node)
2240 2244 if rev is not None:
2241 2245 return rev
2242 2246
2243 2247 if validatehash:
2244 2248 self.checkhash(rawtext, node, p1=p1, p2=p2)
2245 2249
2246 2250 return self.addrawrevision(
2247 2251 rawtext,
2248 2252 transaction,
2249 2253 link,
2250 2254 p1,
2251 2255 p2,
2252 2256 node,
2253 2257 flags,
2254 2258 cachedelta=cachedelta,
2255 2259 deltacomputer=deltacomputer,
2256 2260 sidedata=sidedata,
2257 2261 )
2258 2262
2259 2263 def addrawrevision(
2260 2264 self,
2261 2265 rawtext,
2262 2266 transaction,
2263 2267 link,
2264 2268 p1,
2265 2269 p2,
2266 2270 node,
2267 2271 flags,
2268 2272 cachedelta=None,
2269 2273 deltacomputer=None,
2270 2274 sidedata=None,
2271 2275 ):
2272 2276 """add a raw revision with known flags, node and parents
2273 2277 useful when reusing a revision not stored in this revlog (ex: received
2274 2278 over wire, or read from an external bundle).
2275 2279 """
2276 2280 with self._writing(transaction):
2277 2281 return self._addrevision(
2278 2282 node,
2279 2283 rawtext,
2280 2284 transaction,
2281 2285 link,
2282 2286 p1,
2283 2287 p2,
2284 2288 flags,
2285 2289 cachedelta,
2286 2290 deltacomputer=deltacomputer,
2287 2291 sidedata=sidedata,
2288 2292 )
2289 2293
2290 2294 def compress(self, data):
2291 2295 """Generate a possibly-compressed representation of data."""
2292 2296 if not data:
2293 2297 return b'', data
2294 2298
2295 2299 compressed = self._compressor.compress(data)
2296 2300
2297 2301 if compressed:
2298 2302 # The revlog compressor added the header in the returned data.
2299 2303 return b'', compressed
2300 2304
2301 2305 if data[0:1] == b'\0':
2302 2306 return b'', data
2303 2307 return b'u', data
2304 2308
2305 2309 def decompress(self, data):
2306 2310 """Decompress a revlog chunk.
2307 2311
2308 2312 The chunk is expected to begin with a header identifying the
2309 2313 format type so it can be routed to an appropriate decompressor.
2310 2314 """
2311 2315 if not data:
2312 2316 return data
2313 2317
2314 2318 # Revlogs are read much more frequently than they are written and many
2315 2319 # chunks only take microseconds to decompress, so performance is
2316 2320 # important here.
2317 2321 #
2318 2322 # We can make a few assumptions about revlogs:
2319 2323 #
2320 2324 # 1) the majority of chunks will be compressed (as opposed to inline
2321 2325 # raw data).
2322 2326 # 2) decompressing *any* data will likely by at least 10x slower than
2323 2327 # returning raw inline data.
2324 2328 # 3) we want to prioritize common and officially supported compression
2325 2329 # engines
2326 2330 #
2327 2331 # It follows that we want to optimize for "decompress compressed data
2328 2332 # when encoded with common and officially supported compression engines"
2329 2333 # case over "raw data" and "data encoded by less common or non-official
2330 2334 # compression engines." That is why we have the inline lookup first
2331 2335 # followed by the compengines lookup.
2332 2336 #
2333 2337 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2334 2338 # compressed chunks. And this matters for changelog and manifest reads.
2335 2339 t = data[0:1]
2336 2340
2337 2341 if t == b'x':
2338 2342 try:
2339 2343 return _zlibdecompress(data)
2340 2344 except zlib.error as e:
2341 2345 raise error.RevlogError(
2342 2346 _(b'revlog decompress error: %s')
2343 2347 % stringutil.forcebytestr(e)
2344 2348 )
2345 2349 # '\0' is more common than 'u' so it goes first.
2346 2350 elif t == b'\0':
2347 2351 return data
2348 2352 elif t == b'u':
2349 2353 return util.buffer(data, 1)
2350 2354
2351 2355 compressor = self._get_decompressor(t)
2352 2356
2353 2357 return compressor.decompress(data)
2354 2358
2355 2359 def _addrevision(
2356 2360 self,
2357 2361 node,
2358 2362 rawtext,
2359 2363 transaction,
2360 2364 link,
2361 2365 p1,
2362 2366 p2,
2363 2367 flags,
2364 2368 cachedelta,
2365 2369 alwayscache=False,
2366 2370 deltacomputer=None,
2367 2371 sidedata=None,
2368 2372 ):
2369 2373 """internal function to add revisions to the log
2370 2374
2371 2375 see addrevision for argument descriptions.
2372 2376
2373 2377 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2374 2378
2375 2379 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2376 2380 be used.
2377 2381
2378 2382 invariants:
2379 2383 - rawtext is optional (can be None); if not set, cachedelta must be set.
2380 2384 if both are set, they must correspond to each other.
2381 2385 """
2382 2386 if node == self.nullid:
2383 2387 raise error.RevlogError(
2384 2388 _(b"%s: attempt to add null revision") % self.display_id
2385 2389 )
2386 2390 if (
2387 2391 node == self.nodeconstants.wdirid
2388 2392 or node in self.nodeconstants.wdirfilenodeids
2389 2393 ):
2390 2394 raise error.RevlogError(
2391 2395 _(b"%s: attempt to add wdir revision") % self.display_id
2392 2396 )
2393 2397 if self._writinghandles is None:
2394 2398 msg = b'adding revision outside `revlog._writing` context'
2395 2399 raise error.ProgrammingError(msg)
2396 2400
2397 2401 if self._inline:
2398 2402 fh = self._writinghandles[0]
2399 2403 else:
2400 2404 fh = self._writinghandles[1]
2401 2405
2402 2406 btext = [rawtext]
2403 2407
2404 2408 curr = len(self)
2405 2409 prev = curr - 1
2406 2410
2407 2411 offset = self._get_data_offset(prev)
2408 2412
2409 2413 if self._concurrencychecker:
2410 2414 ifh, dfh, sdfh = self._writinghandles
2411 2415 # XXX no checking for the sidedata file
2412 2416 if self._inline:
2413 2417 # offset is "as if" it were in the .d file, so we need to add on
2414 2418 # the size of the entry metadata.
2415 2419 self._concurrencychecker(
2416 2420 ifh, self._indexfile, offset + curr * self.index.entry_size
2417 2421 )
2418 2422 else:
2419 2423 # Entries in the .i are a consistent size.
2420 2424 self._concurrencychecker(
2421 2425 ifh, self._indexfile, curr * self.index.entry_size
2422 2426 )
2423 2427 self._concurrencychecker(dfh, self._datafile, offset)
2424 2428
2425 2429 p1r, p2r = self.rev(p1), self.rev(p2)
2426 2430
2427 2431 # full versions are inserted when the needed deltas
2428 2432 # become comparable to the uncompressed text
2429 2433 if rawtext is None:
2430 2434 # need rawtext size, before changed by flag processors, which is
2431 2435 # the non-raw size. use revlog explicitly to avoid filelog's extra
2432 2436 # logic that might remove metadata size.
2433 2437 textlen = mdiff.patchedsize(
2434 2438 revlog.size(self, cachedelta[0]), cachedelta[1]
2435 2439 )
2436 2440 else:
2437 2441 textlen = len(rawtext)
2438 2442
2439 2443 if deltacomputer is None:
2440 2444 write_debug = None
2441 2445 if self._debug_delta:
2442 2446 write_debug = transaction._report
2443 2447 deltacomputer = deltautil.deltacomputer(
2444 2448 self, write_debug=write_debug
2445 2449 )
2446 2450
2447 2451 revinfo = revlogutils.revisioninfo(
2448 2452 node,
2449 2453 p1,
2450 2454 p2,
2451 2455 btext,
2452 2456 textlen,
2453 2457 cachedelta,
2454 2458 flags,
2455 2459 )
2456 2460
2457 2461 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2458 2462
2459 2463 compression_mode = COMP_MODE_INLINE
2460 2464 if self._docket is not None:
2461 2465 default_comp = self._docket.default_compression_header
2462 2466 r = deltautil.delta_compression(default_comp, deltainfo)
2463 2467 compression_mode, deltainfo = r
2464 2468
2465 2469 sidedata_compression_mode = COMP_MODE_INLINE
2466 2470 if sidedata and self.hassidedata:
2467 2471 sidedata_compression_mode = COMP_MODE_PLAIN
2468 2472 serialized_sidedata = sidedatautil.serialize_sidedata(sidedata)
2469 2473 sidedata_offset = self._docket.sidedata_end
2470 2474 h, comp_sidedata = self.compress(serialized_sidedata)
2471 2475 if (
2472 2476 h != b'u'
2473 2477 and comp_sidedata[0:1] != b'\0'
2474 2478 and len(comp_sidedata) < len(serialized_sidedata)
2475 2479 ):
2476 2480 assert not h
2477 2481 if (
2478 2482 comp_sidedata[0:1]
2479 2483 == self._docket.default_compression_header
2480 2484 ):
2481 2485 sidedata_compression_mode = COMP_MODE_DEFAULT
2482 2486 serialized_sidedata = comp_sidedata
2483 2487 else:
2484 2488 sidedata_compression_mode = COMP_MODE_INLINE
2485 2489 serialized_sidedata = comp_sidedata
2486 2490 else:
2487 2491 serialized_sidedata = b""
2488 2492 # Don't store the offset if the sidedata is empty, that way
2489 2493 # we can easily detect empty sidedata and they will be no different
2490 2494 # than ones we manually add.
2491 2495 sidedata_offset = 0
2492 2496
2493 2497 rank = RANK_UNKNOWN
2494 2498 if self._format_version == CHANGELOGV2:
2495 2499 if (p1r, p2r) == (nullrev, nullrev):
2496 2500 rank = 1
2497 2501 elif p1r != nullrev and p2r == nullrev:
2498 2502 rank = 1 + self.fast_rank(p1r)
2499 2503 elif p1r == nullrev and p2r != nullrev:
2500 2504 rank = 1 + self.fast_rank(p2r)
2501 2505 else: # merge node
2502 2506 if rustdagop is not None and self.index.rust_ext_compat:
2503 2507 rank = rustdagop.rank(self.index, p1r, p2r)
2504 2508 else:
2505 2509 pmin, pmax = sorted((p1r, p2r))
2506 2510 rank = 1 + self.fast_rank(pmax)
2507 2511 rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin]))
2508 2512
2509 2513 e = revlogutils.entry(
2510 2514 flags=flags,
2511 2515 data_offset=offset,
2512 2516 data_compressed_length=deltainfo.deltalen,
2513 2517 data_uncompressed_length=textlen,
2514 2518 data_compression_mode=compression_mode,
2515 2519 data_delta_base=deltainfo.base,
2516 2520 link_rev=link,
2517 2521 parent_rev_1=p1r,
2518 2522 parent_rev_2=p2r,
2519 2523 node_id=node,
2520 2524 sidedata_offset=sidedata_offset,
2521 2525 sidedata_compressed_length=len(serialized_sidedata),
2522 2526 sidedata_compression_mode=sidedata_compression_mode,
2523 2527 rank=rank,
2524 2528 )
2525 2529
2526 2530 self.index.append(e)
2527 2531 entry = self.index.entry_binary(curr)
2528 2532 if curr == 0 and self._docket is None:
2529 2533 header = self._format_flags | self._format_version
2530 2534 header = self.index.pack_header(header)
2531 2535 entry = header + entry
2532 2536 self._writeentry(
2533 2537 transaction,
2534 2538 entry,
2535 2539 deltainfo.data,
2536 2540 link,
2537 2541 offset,
2538 2542 serialized_sidedata,
2539 2543 sidedata_offset,
2540 2544 )
2541 2545
2542 2546 rawtext = btext[0]
2543 2547
2544 2548 if alwayscache and rawtext is None:
2545 2549 rawtext = deltacomputer.buildtext(revinfo, fh)
2546 2550
2547 2551 if type(rawtext) == bytes: # only accept immutable objects
2548 2552 self._revisioncache = (node, curr, rawtext)
2549 2553 self._chainbasecache[curr] = deltainfo.chainbase
2550 2554 return curr
2551 2555
2552 2556 def _get_data_offset(self, prev):
2553 2557 """Returns the current offset in the (in-transaction) data file.
2554 2558 Versions < 2 of the revlog can get this 0(1), revlog v2 needs a docket
2555 2559 file to store that information: since sidedata can be rewritten to the
2556 2560 end of the data file within a transaction, you can have cases where, for
2557 2561 example, rev `n` does not have sidedata while rev `n - 1` does, leading
2558 2562 to `n - 1`'s sidedata being written after `n`'s data.
2559 2563
2560 2564 TODO cache this in a docket file before getting out of experimental."""
2561 2565 if self._docket is None:
2562 2566 return self.end(prev)
2563 2567 else:
2564 2568 return self._docket.data_end
2565 2569
2566 2570 def _writeentry(
2567 2571 self, transaction, entry, data, link, offset, sidedata, sidedata_offset
2568 2572 ):
2569 2573 # Files opened in a+ mode have inconsistent behavior on various
2570 2574 # platforms. Windows requires that a file positioning call be made
2571 2575 # when the file handle transitions between reads and writes. See
2572 2576 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2573 2577 # platforms, Python or the platform itself can be buggy. Some versions
2574 2578 # of Solaris have been observed to not append at the end of the file
2575 2579 # if the file was seeked to before the end. See issue4943 for more.
2576 2580 #
2577 2581 # We work around this issue by inserting a seek() before writing.
2578 2582 # Note: This is likely not necessary on Python 3. However, because
2579 2583 # the file handle is reused for reads and may be seeked there, we need
2580 2584 # to be careful before changing this.
2581 2585 if self._writinghandles is None:
2582 2586 msg = b'adding revision outside `revlog._writing` context'
2583 2587 raise error.ProgrammingError(msg)
2584 2588 ifh, dfh, sdfh = self._writinghandles
2585 2589 if self._docket is None:
2586 2590 ifh.seek(0, os.SEEK_END)
2587 2591 else:
2588 2592 ifh.seek(self._docket.index_end, os.SEEK_SET)
2589 2593 if dfh:
2590 2594 if self._docket is None:
2591 2595 dfh.seek(0, os.SEEK_END)
2592 2596 else:
2593 2597 dfh.seek(self._docket.data_end, os.SEEK_SET)
2594 2598 if sdfh:
2595 2599 sdfh.seek(self._docket.sidedata_end, os.SEEK_SET)
2596 2600
2597 2601 curr = len(self) - 1
2598 2602 if not self._inline:
2599 2603 transaction.add(self._datafile, offset)
2600 2604 if self._sidedatafile:
2601 2605 transaction.add(self._sidedatafile, sidedata_offset)
2602 2606 transaction.add(self._indexfile, curr * len(entry))
2603 2607 if data[0]:
2604 2608 dfh.write(data[0])
2605 2609 dfh.write(data[1])
2606 2610 if sidedata:
2607 2611 sdfh.write(sidedata)
2608 2612 ifh.write(entry)
2609 2613 else:
2610 2614 offset += curr * self.index.entry_size
2611 2615 transaction.add(self._indexfile, offset)
2612 2616 ifh.write(entry)
2613 2617 ifh.write(data[0])
2614 2618 ifh.write(data[1])
2615 2619 assert not sidedata
2616 2620 self._enforceinlinesize(transaction)
2617 2621 if self._docket is not None:
2618 2622 # revlog-v2 always has 3 writing handles, help Pytype
2619 2623 wh1 = self._writinghandles[0]
2620 2624 wh2 = self._writinghandles[1]
2621 2625 wh3 = self._writinghandles[2]
2622 2626 assert wh1 is not None
2623 2627 assert wh2 is not None
2624 2628 assert wh3 is not None
2625 2629 self._docket.index_end = wh1.tell()
2626 2630 self._docket.data_end = wh2.tell()
2627 2631 self._docket.sidedata_end = wh3.tell()
2628 2632
2629 2633 nodemaputil.setup_persistent_nodemap(transaction, self)
2630 2634
2631 2635 def addgroup(
2632 2636 self,
2633 2637 deltas,
2634 2638 linkmapper,
2635 2639 transaction,
2636 2640 alwayscache=False,
2637 2641 addrevisioncb=None,
2638 2642 duplicaterevisioncb=None,
2639 2643 ):
2640 2644 """
2641 2645 add a delta group
2642 2646
2643 2647 given a set of deltas, add them to the revision log. the
2644 2648 first delta is against its parent, which should be in our
2645 2649 log, the rest are against the previous delta.
2646 2650
2647 2651 If ``addrevisioncb`` is defined, it will be called with arguments of
2648 2652 this revlog and the node that was added.
2649 2653 """
2650 2654
2651 2655 if self._adding_group:
2652 2656 raise error.ProgrammingError(b'cannot nest addgroup() calls')
2653 2657
2654 2658 self._adding_group = True
2655 2659 empty = True
2656 2660 try:
2657 2661 with self._writing(transaction):
2658 2662 write_debug = None
2659 2663 if self._debug_delta:
2660 2664 write_debug = transaction._report
2661 2665 deltacomputer = deltautil.deltacomputer(
2662 2666 self,
2663 2667 write_debug=write_debug,
2664 2668 )
2665 2669 # loop through our set of deltas
2666 2670 for data in deltas:
2667 2671 (
2668 2672 node,
2669 2673 p1,
2670 2674 p2,
2671 2675 linknode,
2672 2676 deltabase,
2673 2677 delta,
2674 2678 flags,
2675 2679 sidedata,
2676 2680 ) = data
2677 2681 link = linkmapper(linknode)
2678 2682 flags = flags or REVIDX_DEFAULT_FLAGS
2679 2683
2680 2684 rev = self.index.get_rev(node)
2681 2685 if rev is not None:
2682 2686 # this can happen if two branches make the same change
2683 2687 self._nodeduplicatecallback(transaction, rev)
2684 2688 if duplicaterevisioncb:
2685 2689 duplicaterevisioncb(self, rev)
2686 2690 empty = False
2687 2691 continue
2688 2692
2689 2693 for p in (p1, p2):
2690 2694 if not self.index.has_node(p):
2691 2695 raise error.LookupError(
2692 2696 p, self.radix, _(b'unknown parent')
2693 2697 )
2694 2698
2695 2699 if not self.index.has_node(deltabase):
2696 2700 raise error.LookupError(
2697 2701 deltabase, self.display_id, _(b'unknown delta base')
2698 2702 )
2699 2703
2700 2704 baserev = self.rev(deltabase)
2701 2705
2702 2706 if baserev != nullrev and self.iscensored(baserev):
2703 2707 # if base is censored, delta must be full replacement in a
2704 2708 # single patch operation
2705 2709 hlen = struct.calcsize(b">lll")
2706 2710 oldlen = self.rawsize(baserev)
2707 2711 newlen = len(delta) - hlen
2708 2712 if delta[:hlen] != mdiff.replacediffheader(
2709 2713 oldlen, newlen
2710 2714 ):
2711 2715 raise error.CensoredBaseError(
2712 2716 self.display_id, self.node(baserev)
2713 2717 )
2714 2718
2715 2719 if not flags and self._peek_iscensored(baserev, delta):
2716 2720 flags |= REVIDX_ISCENSORED
2717 2721
2718 2722 # We assume consumers of addrevisioncb will want to retrieve
2719 2723 # the added revision, which will require a call to
2720 2724 # revision(). revision() will fast path if there is a cache
2721 2725 # hit. So, we tell _addrevision() to always cache in this case.
2722 2726 # We're only using addgroup() in the context of changegroup
2723 2727 # generation so the revision data can always be handled as raw
2724 2728 # by the flagprocessor.
2725 2729 rev = self._addrevision(
2726 2730 node,
2727 2731 None,
2728 2732 transaction,
2729 2733 link,
2730 2734 p1,
2731 2735 p2,
2732 2736 flags,
2733 2737 (baserev, delta),
2734 2738 alwayscache=alwayscache,
2735 2739 deltacomputer=deltacomputer,
2736 2740 sidedata=sidedata,
2737 2741 )
2738 2742
2739 2743 if addrevisioncb:
2740 2744 addrevisioncb(self, rev)
2741 2745 empty = False
2742 2746 finally:
2743 2747 self._adding_group = False
2744 2748 return not empty
2745 2749
2746 2750 def iscensored(self, rev):
2747 2751 """Check if a file revision is censored."""
2748 2752 if not self._censorable:
2749 2753 return False
2750 2754
2751 2755 return self.flags(rev) & REVIDX_ISCENSORED
2752 2756
2753 2757 def _peek_iscensored(self, baserev, delta):
2754 2758 """Quickly check if a delta produces a censored revision."""
2755 2759 if not self._censorable:
2756 2760 return False
2757 2761
2758 2762 return storageutil.deltaiscensored(delta, baserev, self.rawsize)
2759 2763
2760 2764 def getstrippoint(self, minlink):
2761 2765 """find the minimum rev that must be stripped to strip the linkrev
2762 2766
2763 2767 Returns a tuple containing the minimum rev and a set of all revs that
2764 2768 have linkrevs that will be broken by this strip.
2765 2769 """
2766 2770 return storageutil.resolvestripinfo(
2767 2771 minlink,
2768 2772 len(self) - 1,
2769 2773 self.headrevs(),
2770 2774 self.linkrev,
2771 2775 self.parentrevs,
2772 2776 )
2773 2777
2774 2778 def strip(self, minlink, transaction):
2775 2779 """truncate the revlog on the first revision with a linkrev >= minlink
2776 2780
2777 2781 This function is called when we're stripping revision minlink and
2778 2782 its descendants from the repository.
2779 2783
2780 2784 We have to remove all revisions with linkrev >= minlink, because
2781 2785 the equivalent changelog revisions will be renumbered after the
2782 2786 strip.
2783 2787
2784 2788 So we truncate the revlog on the first of these revisions, and
2785 2789 trust that the caller has saved the revisions that shouldn't be
2786 2790 removed and that it'll re-add them after this truncation.
2787 2791 """
2788 2792 if len(self) == 0:
2789 2793 return
2790 2794
2791 2795 rev, _ = self.getstrippoint(minlink)
2792 2796 if rev == len(self):
2793 2797 return
2794 2798
2795 2799 # first truncate the files on disk
2796 2800 data_end = self.start(rev)
2797 2801 if not self._inline:
2798 2802 transaction.add(self._datafile, data_end)
2799 2803 end = rev * self.index.entry_size
2800 2804 else:
2801 2805 end = data_end + (rev * self.index.entry_size)
2802 2806
2803 2807 if self._sidedatafile:
2804 2808 sidedata_end = self.sidedata_cut_off(rev)
2805 2809 transaction.add(self._sidedatafile, sidedata_end)
2806 2810
2807 2811 transaction.add(self._indexfile, end)
2808 2812 if self._docket is not None:
2809 2813 # XXX we could, leverage the docket while stripping. However it is
2810 2814 # not powerfull enough at the time of this comment
2811 2815 self._docket.index_end = end
2812 2816 self._docket.data_end = data_end
2813 2817 self._docket.sidedata_end = sidedata_end
2814 2818 self._docket.write(transaction, stripping=True)
2815 2819
2816 2820 # then reset internal state in memory to forget those revisions
2817 2821 self._revisioncache = None
2818 2822 self._chaininfocache = util.lrucachedict(500)
2819 2823 self._segmentfile.clear_cache()
2820 2824 self._segmentfile_sidedata.clear_cache()
2821 2825
2822 2826 del self.index[rev:-1]
2823 2827
2824 2828 def checksize(self):
2825 2829 """Check size of index and data files
2826 2830
2827 2831 return a (dd, di) tuple.
2828 2832 - dd: extra bytes for the "data" file
2829 2833 - di: extra bytes for the "index" file
2830 2834
2831 2835 A healthy revlog will return (0, 0).
2832 2836 """
2833 2837 expected = 0
2834 2838 if len(self):
2835 2839 expected = max(0, self.end(len(self) - 1))
2836 2840
2837 2841 try:
2838 2842 with self._datafp() as f:
2839 2843 f.seek(0, io.SEEK_END)
2840 2844 actual = f.tell()
2841 2845 dd = actual - expected
2842 2846 except FileNotFoundError:
2843 2847 dd = 0
2844 2848
2845 2849 try:
2846 2850 f = self.opener(self._indexfile)
2847 2851 f.seek(0, io.SEEK_END)
2848 2852 actual = f.tell()
2849 2853 f.close()
2850 2854 s = self.index.entry_size
2851 2855 i = max(0, actual // s)
2852 2856 di = actual - (i * s)
2853 2857 if self._inline:
2854 2858 databytes = 0
2855 2859 for r in self:
2856 2860 databytes += max(0, self.length(r))
2857 2861 dd = 0
2858 2862 di = actual - len(self) * s - databytes
2859 2863 except FileNotFoundError:
2860 2864 di = 0
2861 2865
2862 2866 return (dd, di)
2863 2867
2864 2868 def files(self):
2865 2869 res = [self._indexfile]
2866 2870 if self._docket_file is None:
2867 2871 if not self._inline:
2868 2872 res.append(self._datafile)
2869 2873 else:
2870 2874 res.append(self._docket_file)
2871 2875 res.extend(self._docket.old_index_filepaths(include_empty=False))
2872 2876 if self._docket.data_end:
2873 2877 res.append(self._datafile)
2874 2878 res.extend(self._docket.old_data_filepaths(include_empty=False))
2875 2879 if self._docket.sidedata_end:
2876 2880 res.append(self._sidedatafile)
2877 2881 res.extend(self._docket.old_sidedata_filepaths(include_empty=False))
2878 2882 return res
2879 2883
2880 2884 def emitrevisions(
2881 2885 self,
2882 2886 nodes,
2883 2887 nodesorder=None,
2884 2888 revisiondata=False,
2885 2889 assumehaveparentrevisions=False,
2886 2890 deltamode=repository.CG_DELTAMODE_STD,
2887 2891 sidedata_helpers=None,
2888 2892 ):
2889 2893 if nodesorder not in (b'nodes', b'storage', b'linear', None):
2890 2894 raise error.ProgrammingError(
2891 2895 b'unhandled value for nodesorder: %s' % nodesorder
2892 2896 )
2893 2897
2894 2898 if nodesorder is None and not self._generaldelta:
2895 2899 nodesorder = b'storage'
2896 2900
2897 2901 if (
2898 2902 not self._storedeltachains
2899 2903 and deltamode != repository.CG_DELTAMODE_PREV
2900 2904 ):
2901 2905 deltamode = repository.CG_DELTAMODE_FULL
2902 2906
2903 2907 return storageutil.emitrevisions(
2904 2908 self,
2905 2909 nodes,
2906 2910 nodesorder,
2907 2911 revlogrevisiondelta,
2908 2912 deltaparentfn=self.deltaparent,
2909 2913 candeltafn=self.candelta,
2910 2914 rawsizefn=self.rawsize,
2911 2915 revdifffn=self.revdiff,
2912 2916 flagsfn=self.flags,
2913 2917 deltamode=deltamode,
2914 2918 revisiondata=revisiondata,
2915 2919 assumehaveparentrevisions=assumehaveparentrevisions,
2916 2920 sidedata_helpers=sidedata_helpers,
2917 2921 )
2918 2922
2919 2923 DELTAREUSEALWAYS = b'always'
2920 2924 DELTAREUSESAMEREVS = b'samerevs'
2921 2925 DELTAREUSENEVER = b'never'
2922 2926
2923 2927 DELTAREUSEFULLADD = b'fulladd'
2924 2928
2925 2929 DELTAREUSEALL = {b'always', b'samerevs', b'never', b'fulladd'}
2926 2930
2927 2931 def clone(
2928 2932 self,
2929 2933 tr,
2930 2934 destrevlog,
2931 2935 addrevisioncb=None,
2932 2936 deltareuse=DELTAREUSESAMEREVS,
2933 2937 forcedeltabothparents=None,
2934 2938 sidedata_helpers=None,
2935 2939 ):
2936 2940 """Copy this revlog to another, possibly with format changes.
2937 2941
2938 2942 The destination revlog will contain the same revisions and nodes.
2939 2943 However, it may not be bit-for-bit identical due to e.g. delta encoding
2940 2944 differences.
2941 2945
2942 2946 The ``deltareuse`` argument control how deltas from the existing revlog
2943 2947 are preserved in the destination revlog. The argument can have the
2944 2948 following values:
2945 2949
2946 2950 DELTAREUSEALWAYS
2947 2951 Deltas will always be reused (if possible), even if the destination
2948 2952 revlog would not select the same revisions for the delta. This is the
2949 2953 fastest mode of operation.
2950 2954 DELTAREUSESAMEREVS
2951 2955 Deltas will be reused if the destination revlog would pick the same
2952 2956 revisions for the delta. This mode strikes a balance between speed
2953 2957 and optimization.
2954 2958 DELTAREUSENEVER
2955 2959 Deltas will never be reused. This is the slowest mode of execution.
2956 2960 This mode can be used to recompute deltas (e.g. if the diff/delta
2957 2961 algorithm changes).
2958 2962 DELTAREUSEFULLADD
2959 2963 Revision will be re-added as if their were new content. This is
2960 2964 slower than DELTAREUSEALWAYS but allow more mechanism to kicks in.
2961 2965 eg: large file detection and handling.
2962 2966
2963 2967 Delta computation can be slow, so the choice of delta reuse policy can
2964 2968 significantly affect run time.
2965 2969
2966 2970 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2967 2971 two extremes. Deltas will be reused if they are appropriate. But if the
2968 2972 delta could choose a better revision, it will do so. This means if you
2969 2973 are converting a non-generaldelta revlog to a generaldelta revlog,
2970 2974 deltas will be recomputed if the delta's parent isn't a parent of the
2971 2975 revision.
2972 2976
2973 2977 In addition to the delta policy, the ``forcedeltabothparents``
2974 2978 argument controls whether to force compute deltas against both parents
2975 2979 for merges. By default, the current default is used.
2976 2980
2977 2981 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
2978 2982 `sidedata_helpers`.
2979 2983 """
2980 2984 if deltareuse not in self.DELTAREUSEALL:
2981 2985 raise ValueError(
2982 2986 _(b'value for deltareuse invalid: %s') % deltareuse
2983 2987 )
2984 2988
2985 2989 if len(destrevlog):
2986 2990 raise ValueError(_(b'destination revlog is not empty'))
2987 2991
2988 2992 if getattr(self, 'filteredrevs', None):
2989 2993 raise ValueError(_(b'source revlog has filtered revisions'))
2990 2994 if getattr(destrevlog, 'filteredrevs', None):
2991 2995 raise ValueError(_(b'destination revlog has filtered revisions'))
2992 2996
2993 2997 # lazydelta and lazydeltabase controls whether to reuse a cached delta,
2994 2998 # if possible.
2995 2999 oldlazydelta = destrevlog._lazydelta
2996 3000 oldlazydeltabase = destrevlog._lazydeltabase
2997 3001 oldamd = destrevlog._deltabothparents
2998 3002
2999 3003 try:
3000 3004 if deltareuse == self.DELTAREUSEALWAYS:
3001 3005 destrevlog._lazydeltabase = True
3002 3006 destrevlog._lazydelta = True
3003 3007 elif deltareuse == self.DELTAREUSESAMEREVS:
3004 3008 destrevlog._lazydeltabase = False
3005 3009 destrevlog._lazydelta = True
3006 3010 elif deltareuse == self.DELTAREUSENEVER:
3007 3011 destrevlog._lazydeltabase = False
3008 3012 destrevlog._lazydelta = False
3009 3013
3010 3014 destrevlog._deltabothparents = forcedeltabothparents or oldamd
3011 3015
3012 3016 self._clone(
3013 3017 tr,
3014 3018 destrevlog,
3015 3019 addrevisioncb,
3016 3020 deltareuse,
3017 3021 forcedeltabothparents,
3018 3022 sidedata_helpers,
3019 3023 )
3020 3024
3021 3025 finally:
3022 3026 destrevlog._lazydelta = oldlazydelta
3023 3027 destrevlog._lazydeltabase = oldlazydeltabase
3024 3028 destrevlog._deltabothparents = oldamd
3025 3029
3026 3030 def _clone(
3027 3031 self,
3028 3032 tr,
3029 3033 destrevlog,
3030 3034 addrevisioncb,
3031 3035 deltareuse,
3032 3036 forcedeltabothparents,
3033 3037 sidedata_helpers,
3034 3038 ):
3035 3039 """perform the core duty of `revlog.clone` after parameter processing"""
3036 3040 write_debug = None
3037 3041 if self._debug_delta:
3038 3042 write_debug = tr._report
3039 3043 deltacomputer = deltautil.deltacomputer(
3040 3044 destrevlog,
3041 3045 write_debug=write_debug,
3042 3046 )
3043 3047 index = self.index
3044 3048 for rev in self:
3045 3049 entry = index[rev]
3046 3050
3047 3051 # Some classes override linkrev to take filtered revs into
3048 3052 # account. Use raw entry from index.
3049 3053 flags = entry[0] & 0xFFFF
3050 3054 linkrev = entry[4]
3051 3055 p1 = index[entry[5]][7]
3052 3056 p2 = index[entry[6]][7]
3053 3057 node = entry[7]
3054 3058
3055 3059 # (Possibly) reuse the delta from the revlog if allowed and
3056 3060 # the revlog chunk is a delta.
3057 3061 cachedelta = None
3058 3062 rawtext = None
3059 3063 if deltareuse == self.DELTAREUSEFULLADD:
3060 3064 text = self._revisiondata(rev)
3061 3065 sidedata = self.sidedata(rev)
3062 3066
3063 3067 if sidedata_helpers is not None:
3064 3068 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3065 3069 self, sidedata_helpers, sidedata, rev
3066 3070 )
3067 3071 flags = flags | new_flags[0] & ~new_flags[1]
3068 3072
3069 3073 destrevlog.addrevision(
3070 3074 text,
3071 3075 tr,
3072 3076 linkrev,
3073 3077 p1,
3074 3078 p2,
3075 3079 cachedelta=cachedelta,
3076 3080 node=node,
3077 3081 flags=flags,
3078 3082 deltacomputer=deltacomputer,
3079 3083 sidedata=sidedata,
3080 3084 )
3081 3085 else:
3082 3086 if destrevlog._lazydelta:
3083 3087 dp = self.deltaparent(rev)
3084 3088 if dp != nullrev:
3085 3089 cachedelta = (dp, bytes(self._chunk(rev)))
3086 3090
3087 3091 sidedata = None
3088 3092 if not cachedelta:
3089 3093 rawtext = self._revisiondata(rev)
3090 3094 sidedata = self.sidedata(rev)
3091 3095 if sidedata is None:
3092 3096 sidedata = self.sidedata(rev)
3093 3097
3094 3098 if sidedata_helpers is not None:
3095 3099 (sidedata, new_flags) = sidedatautil.run_sidedata_helpers(
3096 3100 self, sidedata_helpers, sidedata, rev
3097 3101 )
3098 3102 flags = flags | new_flags[0] & ~new_flags[1]
3099 3103
3100 3104 with destrevlog._writing(tr):
3101 3105 destrevlog._addrevision(
3102 3106 node,
3103 3107 rawtext,
3104 3108 tr,
3105 3109 linkrev,
3106 3110 p1,
3107 3111 p2,
3108 3112 flags,
3109 3113 cachedelta,
3110 3114 deltacomputer=deltacomputer,
3111 3115 sidedata=sidedata,
3112 3116 )
3113 3117
3114 3118 if addrevisioncb:
3115 3119 addrevisioncb(self, rev, node)
3116 3120
3117 3121 def censorrevision(self, tr, censornode, tombstone=b''):
3118 3122 if self._format_version == REVLOGV0:
3119 3123 raise error.RevlogError(
3120 3124 _(b'cannot censor with version %d revlogs')
3121 3125 % self._format_version
3122 3126 )
3123 3127 elif self._format_version == REVLOGV1:
3124 3128 rewrite.v1_censor(self, tr, censornode, tombstone)
3125 3129 else:
3126 3130 rewrite.v2_censor(self, tr, censornode, tombstone)
3127 3131
3128 3132 def verifyintegrity(self, state):
3129 3133 """Verifies the integrity of the revlog.
3130 3134
3131 3135 Yields ``revlogproblem`` instances describing problems that are
3132 3136 found.
3133 3137 """
3134 3138 dd, di = self.checksize()
3135 3139 if dd:
3136 3140 yield revlogproblem(error=_(b'data length off by %d bytes') % dd)
3137 3141 if di:
3138 3142 yield revlogproblem(error=_(b'index contains %d extra bytes') % di)
3139 3143
3140 3144 version = self._format_version
3141 3145
3142 3146 # The verifier tells us what version revlog we should be.
3143 3147 if version != state[b'expectedversion']:
3144 3148 yield revlogproblem(
3145 3149 warning=_(b"warning: '%s' uses revlog format %d; expected %d")
3146 3150 % (self.display_id, version, state[b'expectedversion'])
3147 3151 )
3148 3152
3149 3153 state[b'skipread'] = set()
3150 3154 state[b'safe_renamed'] = set()
3151 3155
3152 3156 for rev in self:
3153 3157 node = self.node(rev)
3154 3158
3155 3159 # Verify contents. 4 cases to care about:
3156 3160 #
3157 3161 # common: the most common case
3158 3162 # rename: with a rename
3159 3163 # meta: file content starts with b'\1\n', the metadata
3160 3164 # header defined in filelog.py, but without a rename
3161 3165 # ext: content stored externally
3162 3166 #
3163 3167 # More formally, their differences are shown below:
3164 3168 #
3165 3169 # | common | rename | meta | ext
3166 3170 # -------------------------------------------------------
3167 3171 # flags() | 0 | 0 | 0 | not 0
3168 3172 # renamed() | False | True | False | ?
3169 3173 # rawtext[0:2]=='\1\n'| False | True | True | ?
3170 3174 #
3171 3175 # "rawtext" means the raw text stored in revlog data, which
3172 3176 # could be retrieved by "rawdata(rev)". "text"
3173 3177 # mentioned below is "revision(rev)".
3174 3178 #
3175 3179 # There are 3 different lengths stored physically:
3176 3180 # 1. L1: rawsize, stored in revlog index
3177 3181 # 2. L2: len(rawtext), stored in revlog data
3178 3182 # 3. L3: len(text), stored in revlog data if flags==0, or
3179 3183 # possibly somewhere else if flags!=0
3180 3184 #
3181 3185 # L1 should be equal to L2. L3 could be different from them.
3182 3186 # "text" may or may not affect commit hash depending on flag
3183 3187 # processors (see flagutil.addflagprocessor).
3184 3188 #
3185 3189 # | common | rename | meta | ext
3186 3190 # -------------------------------------------------
3187 3191 # rawsize() | L1 | L1 | L1 | L1
3188 3192 # size() | L1 | L2-LM | L1(*) | L1 (?)
3189 3193 # len(rawtext) | L2 | L2 | L2 | L2
3190 3194 # len(text) | L2 | L2 | L2 | L3
3191 3195 # len(read()) | L2 | L2-LM | L2-LM | L3 (?)
3192 3196 #
3193 3197 # LM: length of metadata, depending on rawtext
3194 3198 # (*): not ideal, see comment in filelog.size
3195 3199 # (?): could be "- len(meta)" if the resolved content has
3196 3200 # rename metadata
3197 3201 #
3198 3202 # Checks needed to be done:
3199 3203 # 1. length check: L1 == L2, in all cases.
3200 3204 # 2. hash check: depending on flag processor, we may need to
3201 3205 # use either "text" (external), or "rawtext" (in revlog).
3202 3206
3203 3207 try:
3204 3208 skipflags = state.get(b'skipflags', 0)
3205 3209 if skipflags:
3206 3210 skipflags &= self.flags(rev)
3207 3211
3208 3212 _verify_revision(self, skipflags, state, node)
3209 3213
3210 3214 l1 = self.rawsize(rev)
3211 3215 l2 = len(self.rawdata(node))
3212 3216
3213 3217 if l1 != l2:
3214 3218 yield revlogproblem(
3215 3219 error=_(b'unpacked size is %d, %d expected') % (l2, l1),
3216 3220 node=node,
3217 3221 )
3218 3222
3219 3223 except error.CensoredNodeError:
3220 3224 if state[b'erroroncensored']:
3221 3225 yield revlogproblem(
3222 3226 error=_(b'censored file data'), node=node
3223 3227 )
3224 3228 state[b'skipread'].add(node)
3225 3229 except Exception as e:
3226 3230 yield revlogproblem(
3227 3231 error=_(b'unpacking %s: %s')
3228 3232 % (short(node), stringutil.forcebytestr(e)),
3229 3233 node=node,
3230 3234 )
3231 3235 state[b'skipread'].add(node)
3232 3236
3233 3237 def storageinfo(
3234 3238 self,
3235 3239 exclusivefiles=False,
3236 3240 sharedfiles=False,
3237 3241 revisionscount=False,
3238 3242 trackedsize=False,
3239 3243 storedsize=False,
3240 3244 ):
3241 3245 d = {}
3242 3246
3243 3247 if exclusivefiles:
3244 3248 d[b'exclusivefiles'] = [(self.opener, self._indexfile)]
3245 3249 if not self._inline:
3246 3250 d[b'exclusivefiles'].append((self.opener, self._datafile))
3247 3251
3248 3252 if sharedfiles:
3249 3253 d[b'sharedfiles'] = []
3250 3254
3251 3255 if revisionscount:
3252 3256 d[b'revisionscount'] = len(self)
3253 3257
3254 3258 if trackedsize:
3255 3259 d[b'trackedsize'] = sum(map(self.rawsize, iter(self)))
3256 3260
3257 3261 if storedsize:
3258 3262 d[b'storedsize'] = sum(
3259 3263 self.opener.stat(path).st_size for path in self.files()
3260 3264 )
3261 3265
3262 3266 return d
3263 3267
3264 3268 def rewrite_sidedata(self, transaction, helpers, startrev, endrev):
3265 3269 if not self.hassidedata:
3266 3270 return
3267 3271 # revlog formats with sidedata support does not support inline
3268 3272 assert not self._inline
3269 3273 if not helpers[1] and not helpers[2]:
3270 3274 # Nothing to generate or remove
3271 3275 return
3272 3276
3273 3277 new_entries = []
3274 3278 # append the new sidedata
3275 3279 with self._writing(transaction):
3276 3280 ifh, dfh, sdfh = self._writinghandles
3277 3281 dfh.seek(self._docket.sidedata_end, os.SEEK_SET)
3278 3282
3279 3283 current_offset = sdfh.tell()
3280 3284 for rev in range(startrev, endrev + 1):
3281 3285 entry = self.index[rev]
3282 3286 new_sidedata, flags = sidedatautil.run_sidedata_helpers(
3283 3287 store=self,
3284 3288 sidedata_helpers=helpers,
3285 3289 sidedata={},
3286 3290 rev=rev,
3287 3291 )
3288 3292
3289 3293 serialized_sidedata = sidedatautil.serialize_sidedata(
3290 3294 new_sidedata
3291 3295 )
3292 3296
3293 3297 sidedata_compression_mode = COMP_MODE_INLINE
3294 3298 if serialized_sidedata and self.hassidedata:
3295 3299 sidedata_compression_mode = COMP_MODE_PLAIN
3296 3300 h, comp_sidedata = self.compress(serialized_sidedata)
3297 3301 if (
3298 3302 h != b'u'
3299 3303 and comp_sidedata[0] != b'\0'
3300 3304 and len(comp_sidedata) < len(serialized_sidedata)
3301 3305 ):
3302 3306 assert not h
3303 3307 if (
3304 3308 comp_sidedata[0]
3305 3309 == self._docket.default_compression_header
3306 3310 ):
3307 3311 sidedata_compression_mode = COMP_MODE_DEFAULT
3308 3312 serialized_sidedata = comp_sidedata
3309 3313 else:
3310 3314 sidedata_compression_mode = COMP_MODE_INLINE
3311 3315 serialized_sidedata = comp_sidedata
3312 3316 if entry[8] != 0 or entry[9] != 0:
3313 3317 # rewriting entries that already have sidedata is not
3314 3318 # supported yet, because it introduces garbage data in the
3315 3319 # revlog.
3316 3320 msg = b"rewriting existing sidedata is not supported yet"
3317 3321 raise error.Abort(msg)
3318 3322
3319 3323 # Apply (potential) flags to add and to remove after running
3320 3324 # the sidedata helpers
3321 3325 new_offset_flags = entry[0] | flags[0] & ~flags[1]
3322 3326 entry_update = (
3323 3327 current_offset,
3324 3328 len(serialized_sidedata),
3325 3329 new_offset_flags,
3326 3330 sidedata_compression_mode,
3327 3331 )
3328 3332
3329 3333 # the sidedata computation might have move the file cursors around
3330 3334 sdfh.seek(current_offset, os.SEEK_SET)
3331 3335 sdfh.write(serialized_sidedata)
3332 3336 new_entries.append(entry_update)
3333 3337 current_offset += len(serialized_sidedata)
3334 3338 self._docket.sidedata_end = sdfh.tell()
3335 3339
3336 3340 # rewrite the new index entries
3337 3341 ifh.seek(startrev * self.index.entry_size)
3338 3342 for i, e in enumerate(new_entries):
3339 3343 rev = startrev + i
3340 3344 self.index.replace_sidedata_info(rev, *e)
3341 3345 packed = self.index.entry_binary(rev)
3342 3346 if rev == 0 and self._docket is None:
3343 3347 header = self._format_flags | self._format_version
3344 3348 header = self.index.pack_header(header)
3345 3349 packed = header + packed
3346 3350 ifh.write(packed)
@@ -1,821 +1,821 b''
1 1
2 2 $ HGMERGE=true; export HGMERGE
3 3 $ echo '[extensions]' >> $HGRCPATH
4 4 $ echo 'convert =' >> $HGRCPATH
5 5 $ glog()
6 6 > {
7 7 > hg log -G --template '{rev} "{desc}" files: {files}\n' "$@"
8 8 > }
9 9 $ hg init source
10 10 $ cd source
11 11 $ echo foo > foo
12 12 $ echo baz > baz
13 13 $ mkdir -p dir/subdir
14 14 $ echo dir/file >> dir/file
15 15 $ echo dir/file2 >> dir/file2
16 16 $ echo dir/file3 >> dir/file3 # to be corrupted in rev 0
17 17 $ echo dir/subdir/file3 >> dir/subdir/file3
18 18 $ echo dir/subdir/file4 >> dir/subdir/file4
19 19 $ hg ci -d '0 0' -qAm '0: add foo baz dir/'
20 20 $ echo bar > bar
21 21 $ echo quux > quux
22 22 $ echo dir/file4 >> dir/file4 # to be corrupted in rev 1
23 23 $ hg copy foo copied
24 24 $ hg ci -d '1 0' -qAm '1: add bar quux; copy foo to copied'
25 25 $ echo >> foo
26 26 $ hg ci -d '2 0' -m '2: change foo'
27 27 $ hg up -qC 1
28 28 $ echo >> bar
29 29 $ echo >> quux
30 30 $ hg ci -d '3 0' -m '3: change bar quux'
31 31 created new head
32 32 $ hg up -qC 2
33 33 $ hg merge -qr 3
34 34 $ echo >> bar
35 35 $ echo >> baz
36 36 $ hg ci -d '4 0' -m '4: first merge; change bar baz'
37 37 $ echo >> bar
38 38 $ echo 1 >> baz
39 39 $ echo >> quux
40 40 $ hg ci -d '5 0' -m '5: change bar baz quux'
41 41 $ hg up -qC 4
42 42 $ echo >> foo
43 43 $ echo 2 >> baz
44 44 $ hg ci -d '6 0' -m '6: change foo baz'
45 45 created new head
46 46 $ hg up -qC 5
47 47 $ hg merge -qr 6
48 48 $ echo >> bar
49 49 $ hg ci -d '7 0' -m '7: second merge; change bar'
50 50 $ echo >> foo
51 51 $ hg ci -m '8: change foo'
52 52 $ glog
53 53 @ 8 "8: change foo" files: foo
54 54 |
55 55 o 7 "7: second merge; change bar" files: bar baz
56 56 |\
57 57 | o 6 "6: change foo baz" files: baz foo
58 58 | |
59 59 o | 5 "5: change bar baz quux" files: bar baz quux
60 60 |/
61 61 o 4 "4: first merge; change bar baz" files: bar baz
62 62 |\
63 63 | o 3 "3: change bar quux" files: bar quux
64 64 | |
65 65 o | 2 "2: change foo" files: foo
66 66 |/
67 67 o 1 "1: add bar quux; copy foo to copied" files: bar copied dir/file4 quux
68 68 |
69 69 o 0 "0: add foo baz dir/" files: baz dir/file dir/file2 dir/file3 dir/subdir/file3 dir/subdir/file4 foo
70 70
71 71
72 72 final file versions in this repo:
73 73
74 74 $ hg manifest --debug
75 75 9463f52fe115e377cf2878d4fc548117211063f2 644 bar
76 76 94c1be4dfde2ee8d78db8bbfcf81210813307c3d 644 baz
77 77 7711d36246cc83e61fb29cd6d4ef394c63f1ceaf 644 copied
78 78 3e20847584beff41d7cd16136b7331ab3d754be0 644 dir/file
79 79 75e6d3f8328f5f6ace6bf10b98df793416a09dca 644 dir/file2
80 80 e96dce0bc6a217656a3a410e5e6bec2c4f42bf7c 644 dir/file3
81 81 6edd55f559cdce67132b12ca09e09cee08b60442 644 dir/file4
82 82 5fe139720576e18e34bcc9f79174db8897c8afe9 644 dir/subdir/file3
83 83 57a1c1511590f3de52874adfa04effe8a77d64af 644 dir/subdir/file4
84 84 9a7b52012991e4873687192c3e17e61ba3e837a3 644 foo
85 85 bc3eca3f47023a3e70ca0d8cc95a22a6827db19d 644 quux
86 86 $ hg debugrename copied
87 87 copied renamed from foo:2ed2a3912a0b24502043eae84ee4b279c18b90dd
88 88
89 89 $ cd ..
90 90
91 91
92 92 Test interaction with startrev and verify that changing it is handled properly:
93 93
94 94 $ > empty
95 95 $ hg convert --filemap empty source movingstart --config convert.hg.startrev=3 -r4
96 96 initializing destination movingstart repository
97 97 scanning source...
98 98 sorting...
99 99 converting...
100 100 1 3: change bar quux
101 101 0 4: first merge; change bar baz
102 102 $ hg convert --filemap empty source movingstart
103 103 scanning source...
104 104 sorting...
105 105 converting...
106 106 3 5: change bar baz quux
107 107 2 6: change foo baz
108 108 1 7: second merge; change bar
109 109 warning: af455ce4166b3c9c88e6309c2b9332171dcea595 parent 61e22ca76c3b3e93df20338c4e02ce286898e825 is missing
110 110 warning: cf908b3eeedc301c9272ebae931da966d5b326c7 parent 59e1ab45c888289513b7354484dac8a88217beab is missing
111 111 0 8: change foo
112 112
113 113
114 114 splitrepo tests
115 115
116 116 $ splitrepo()
117 117 > {
118 118 > msg="$1"
119 119 > files="$2"
120 120 > opts=$3
121 121 > echo "% $files: $msg"
122 122 > prefix=`echo "$files" | sed -e 's/ /-/g'`
123 123 > fmap="$prefix.fmap"
124 124 > repo="$prefix.repo"
125 125 > for i in $files; do
126 126 > echo "include $i" >> "$fmap"
127 127 > done
128 128 > hg -q convert $opts --filemap "$fmap" --datesort source "$repo"
129 129 > hg up -q -R "$repo"
130 130 > glog -R "$repo"
131 131 > hg -R "$repo" manifest --debug
132 132 > }
133 133 $ splitrepo 'skip unwanted merges; use 1st parent in 1st merge, 2nd in 2nd' foo
134 134 % foo: skip unwanted merges; use 1st parent in 1st merge, 2nd in 2nd
135 135 @ 3 "8: change foo" files: foo
136 136 |
137 137 o 2 "6: change foo baz" files: foo
138 138 |
139 139 o 1 "2: change foo" files: foo
140 140 |
141 141 o 0 "0: add foo baz dir/" files: foo
142 142
143 143 9a7b52012991e4873687192c3e17e61ba3e837a3 644 foo
144 144 $ splitrepo 'merges are not merges anymore' bar
145 145 % bar: merges are not merges anymore
146 146 @ 4 "7: second merge; change bar" files: bar
147 147 |
148 148 o 3 "5: change bar baz quux" files: bar
149 149 |
150 150 o 2 "4: first merge; change bar baz" files: bar
151 151 |
152 152 o 1 "3: change bar quux" files: bar
153 153 |
154 154 o 0 "1: add bar quux; copy foo to copied" files: bar
155 155
156 156 9463f52fe115e377cf2878d4fc548117211063f2 644 bar
157 157 $ splitrepo '1st merge is not a merge anymore; 2nd still is' baz
158 158 % baz: 1st merge is not a merge anymore; 2nd still is
159 159 @ 4 "7: second merge; change bar" files: baz
160 160 |\
161 161 | o 3 "6: change foo baz" files: baz
162 162 | |
163 163 o | 2 "5: change bar baz quux" files: baz
164 164 |/
165 165 o 1 "4: first merge; change bar baz" files: baz
166 166 |
167 167 o 0 "0: add foo baz dir/" files: baz
168 168
169 169 94c1be4dfde2ee8d78db8bbfcf81210813307c3d 644 baz
170 170 $ splitrepo 'we add additional merges when they are interesting' 'foo quux'
171 171 % foo quux: we add additional merges when they are interesting
172 172 @ 8 "8: change foo" files: foo
173 173 |
174 174 o 7 "7: second merge; change bar" files:
175 175 |\
176 176 | o 6 "6: change foo baz" files: foo
177 177 | |
178 178 o | 5 "5: change bar baz quux" files: quux
179 179 |/
180 180 o 4 "4: first merge; change bar baz" files:
181 181 |\
182 182 | o 3 "3: change bar quux" files: quux
183 183 | |
184 184 o | 2 "2: change foo" files: foo
185 185 |/
186 186 o 1 "1: add bar quux; copy foo to copied" files: quux
187 187 |
188 188 o 0 "0: add foo baz dir/" files: foo
189 189
190 190 9a7b52012991e4873687192c3e17e61ba3e837a3 644 foo
191 191 bc3eca3f47023a3e70ca0d8cc95a22a6827db19d 644 quux
192 192 $ splitrepo 'partial conversion' 'bar quux' '-r 3'
193 193 % bar quux: partial conversion
194 194 @ 1 "3: change bar quux" files: bar quux
195 195 |
196 196 o 0 "1: add bar quux; copy foo to copied" files: bar quux
197 197
198 198 b79105bedc55102f394e90a789c9c380117c1b4a 644 bar
199 199 db0421cc6b685a458c8d86c7d5c004f94429ea23 644 quux
200 200 $ splitrepo 'complete the partial conversion' 'bar quux'
201 201 % bar quux: complete the partial conversion
202 202 @ 4 "7: second merge; change bar" files: bar
203 203 |
204 204 o 3 "5: change bar baz quux" files: bar quux
205 205 |
206 206 o 2 "4: first merge; change bar baz" files: bar
207 207 |
208 208 o 1 "3: change bar quux" files: bar quux
209 209 |
210 210 o 0 "1: add bar quux; copy foo to copied" files: bar quux
211 211
212 212 9463f52fe115e377cf2878d4fc548117211063f2 644 bar
213 213 bc3eca3f47023a3e70ca0d8cc95a22a6827db19d 644 quux
214 214 $ rm -r foo.repo
215 215 $ splitrepo 'partial conversion' 'foo' '-r 3'
216 216 % foo: partial conversion
217 217 @ 0 "0: add foo baz dir/" files: foo
218 218
219 219 2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 foo
220 220 $ splitrepo 'complete the partial conversion' 'foo'
221 221 % foo: complete the partial conversion
222 222 @ 3 "8: change foo" files: foo
223 223 |
224 224 o 2 "6: change foo baz" files: foo
225 225 |
226 226 o 1 "2: change foo" files: foo
227 227 |
228 228 o 0 "0: add foo baz dir/" files: foo
229 229
230 230 9a7b52012991e4873687192c3e17e61ba3e837a3 644 foo
231 231 $ splitrepo 'copied file; source not included in new repo' copied
232 232 % copied: copied file; source not included in new repo
233 233 @ 0 "1: add bar quux; copy foo to copied" files: copied
234 234
235 235 2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 copied
236 236 $ hg --cwd copied.repo debugrename copied
237 237 copied not renamed
238 238 $ splitrepo 'copied file; source included in new repo' 'foo copied'
239 239 % foo copied: copied file; source included in new repo
240 240 @ 4 "8: change foo" files: foo
241 241 |
242 242 o 3 "6: change foo baz" files: foo
243 243 |
244 244 o 2 "2: change foo" files: foo
245 245 |
246 246 o 1 "1: add bar quux; copy foo to copied" files: copied
247 247 |
248 248 o 0 "0: add foo baz dir/" files: foo
249 249
250 250 7711d36246cc83e61fb29cd6d4ef394c63f1ceaf 644 copied
251 251 9a7b52012991e4873687192c3e17e61ba3e837a3 644 foo
252 252 $ hg --cwd foo-copied.repo debugrename copied
253 253 copied renamed from foo:2ed2a3912a0b24502043eae84ee4b279c18b90dd
254 254
255 255 verify the top level 'include .' if there is no other includes:
256 256
257 257 $ echo "exclude something" > default.fmap
258 258 $ hg convert -q --filemap default.fmap -r1 source dummydest2
259 259 $ hg -R dummydest2 log --template '{rev} {node|short} {desc|firstline}\n'
260 260 1 61e22ca76c3b 1: add bar quux; copy foo to copied
261 261 0 c085cf2ee7fe 0: add foo baz dir/
262 262
263 263 $ echo "include somethingelse" >> default.fmap
264 264 $ hg convert -q --filemap default.fmap -r1 source dummydest3
265 265 $ hg -R dummydest3 log --template '{rev} {node|short} {desc|firstline}\n'
266 266
267 267 $ echo "include ." >> default.fmap
268 268 $ hg convert -q --filemap default.fmap -r1 source dummydest4
269 269 $ hg -R dummydest4 log --template '{rev} {node|short} {desc|firstline}\n'
270 270 1 61e22ca76c3b 1: add bar quux; copy foo to copied
271 271 0 c085cf2ee7fe 0: add foo baz dir/
272 272
273 273 ensure that the filemap contains duplicated slashes (issue3612)
274 274
275 275 $ cat > renames.fmap <<EOF
276 276 > include dir
277 277 > exclude dir/file2
278 278 > rename dir dir2//dir3
279 279 > include foo
280 280 > include copied
281 281 > rename foo foo2/
282 282 > rename copied ./copied2
283 283 > exclude dir/subdir
284 284 > include dir/subdir/file3
285 285 > EOF
286 286 #if reporevlogstore
287 287 $ rm source/.hg/store/data/dir/file3.i
288 288 $ rm source/.hg/store/data/dir/file4.i
289 289 #endif
290 290 #if reposimplestore
291 291 $ rm -rf source/.hg/store/data/dir/file3
292 292 $ rm -rf source/.hg/store/data/dir/file4
293 293 #endif
294 294 $ hg -q convert --filemap renames.fmap --datesort source dummydest
295 abort: data/dir/file3@e96dce0bc6a217656a3a410e5e6bec2c4f42bf7c: no match found (reporevlogstore !)
295 abort: dir/file3@e96dce0bc6a217656a3a410e5e6bec2c4f42bf7c: no match found (reporevlogstore !)
296 296 abort: data/dir/file3/index@e96dce0bc6a2: no node (reposimplestore !)
297 297 [50]
298 298 $ hg -q convert --filemap renames.fmap --datesort --config convert.hg.ignoreerrors=1 source renames.repo
299 ignoring: data/dir/file3@e96dce0bc6a217656a3a410e5e6bec2c4f42bf7c: no match found (reporevlogstore !)
300 ignoring: data/dir/file4@6edd55f559cdce67132b12ca09e09cee08b60442: no match found (reporevlogstore !)
299 ignoring: dir/file3@e96dce0bc6a217656a3a410e5e6bec2c4f42bf7c: no match found (reporevlogstore !)
300 ignoring: dir/file4@6edd55f559cdce67132b12ca09e09cee08b60442: no match found (reporevlogstore !)
301 301 ignoring: data/dir/file3/index@e96dce0bc6a2: no node (reposimplestore !)
302 302 ignoring: data/dir/file4/index@6edd55f559cd: no node (reposimplestore !)
303 303 $ hg up -q -R renames.repo
304 304 $ glog -R renames.repo
305 305 @ 4 "8: change foo" files: foo2
306 306 |
307 307 o 3 "6: change foo baz" files: foo2
308 308 |
309 309 o 2 "2: change foo" files: foo2
310 310 |
311 311 o 1 "1: add bar quux; copy foo to copied" files: copied2
312 312 |
313 313 o 0 "0: add foo baz dir/" files: dir2/dir3/file dir2/dir3/subdir/file3 foo2
314 314
315 315 $ hg -R renames.repo verify
316 316 checking changesets
317 317 checking manifests
318 318 crosschecking files in changesets and manifests
319 319 checking files
320 320 checked 5 changesets with 7 changes to 4 files
321 321
322 322 $ hg -R renames.repo manifest --debug
323 323 d43feacba7a4f1f2080dde4a4b985bd8a0236d46 644 copied2
324 324 3e20847584beff41d7cd16136b7331ab3d754be0 644 dir2/dir3/file
325 325 5fe139720576e18e34bcc9f79174db8897c8afe9 644 dir2/dir3/subdir/file3
326 326 9a7b52012991e4873687192c3e17e61ba3e837a3 644 foo2
327 327 $ hg --cwd renames.repo debugrename copied2
328 328 copied2 renamed from foo2:2ed2a3912a0b24502043eae84ee4b279c18b90dd
329 329
330 330 copied:
331 331
332 332 $ hg --cwd source cat copied
333 333 foo
334 334
335 335 copied2:
336 336
337 337 $ hg --cwd renames.repo cat copied2
338 338 foo
339 339
340 340 filemap errors
341 341
342 342 $ cat > errors.fmap <<EOF
343 343 > include dir/ # beware that comments changes error line numbers!
344 344 > exclude /dir
345 345 > rename dir//dir /dir//dir/ "out of sync"
346 346 > include
347 347 > EOF
348 348 $ hg -q convert --filemap errors.fmap source errors.repo
349 349 errors.fmap:3: superfluous / in include '/dir'
350 350 errors.fmap:3: superfluous / in rename '/dir'
351 351 errors.fmap:4: unknown directive 'out of sync'
352 352 errors.fmap:5: path to exclude is missing
353 353 abort: errors in filemap
354 354 [255]
355 355
356 356 test branch closing revision pruning if branch is pruned
357 357
358 358 $ hg init branchpruning
359 359 $ cd branchpruning
360 360 $ hg branch foo
361 361 marked working directory as branch foo
362 362 (branches are permanent and global, did you want a bookmark?)
363 363 $ echo a > a
364 364 $ hg ci -Am adda
365 365 adding a
366 366 $ hg ci --close-branch -m closefoo
367 367 $ hg up 0
368 368 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
369 369 $ hg branch empty
370 370 marked working directory as branch empty
371 371 (branches are permanent and global, did you want a bookmark?)
372 372 $ hg ci -m emptybranch
373 373 $ hg ci --close-branch -m closeempty
374 374 $ hg up 0
375 375 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
376 376 $ hg branch default
377 377 marked working directory as branch default
378 378 (branches are permanent and global, did you want a bookmark?)
379 379 $ echo b > b
380 380 $ hg ci -Am addb
381 381 adding b
382 382 $ hg ci --close-branch -m closedefault
383 383 $ cat > filemap <<EOF
384 384 > include b
385 385 > EOF
386 386 $ cd ..
387 387 $ hg convert branchpruning branchpruning-hg1
388 388 initializing destination branchpruning-hg1 repository
389 389 scanning source...
390 390 sorting...
391 391 converting...
392 392 5 adda
393 393 4 closefoo
394 394 3 emptybranch
395 395 2 closeempty
396 396 1 addb
397 397 0 closedefault
398 398 $ glog -R branchpruning-hg1
399 399 _ 5 "closedefault" files:
400 400 |
401 401 o 4 "addb" files: b
402 402 |
403 403 | _ 3 "closeempty" files:
404 404 | |
405 405 | o 2 "emptybranch" files:
406 406 |/
407 407 | _ 1 "closefoo" files:
408 408 |/
409 409 o 0 "adda" files: a
410 410
411 411
412 412 exercise incremental conversion at the same time
413 413
414 414 $ hg convert -r0 --filemap branchpruning/filemap branchpruning branchpruning-hg2
415 415 initializing destination branchpruning-hg2 repository
416 416 scanning source...
417 417 sorting...
418 418 converting...
419 419 0 adda
420 420 $ hg convert -r4 --filemap branchpruning/filemap branchpruning branchpruning-hg2
421 421 scanning source...
422 422 sorting...
423 423 converting...
424 424 0 addb
425 425 $ hg convert --filemap branchpruning/filemap branchpruning branchpruning-hg2
426 426 scanning source...
427 427 sorting...
428 428 converting...
429 429 3 closefoo
430 430 2 emptybranch
431 431 1 closeempty
432 432 0 closedefault
433 433 $ glog -R branchpruning-hg2
434 434 _ 1 "closedefault" files:
435 435 |
436 436 o 0 "addb" files: b
437 437
438 438 Include directives dropped empty commits, but other directives don't
439 439
440 440 $ cat > branchpruning/exclude_filemap <<EOF
441 441 > exclude a
442 442 > EOF
443 443 $ hg convert --filemap branchpruning/exclude_filemap branchpruning branchpruning-hg-exclude
444 444 initializing destination branchpruning-hg-exclude repository
445 445 scanning source...
446 446 sorting...
447 447 converting...
448 448 5 adda
449 449 4 closefoo
450 450 3 emptybranch
451 451 2 closeempty
452 452 1 addb
453 453 0 closedefault
454 454
455 455 $ glog -R branchpruning-hg-exclude
456 456 _ 3 "closedefault" files:
457 457 |
458 458 o 2 "addb" files: b
459 459
460 460 _ 1 "closeempty" files:
461 461 |
462 462 o 0 "emptybranch" files:
463 463
464 464
465 465 Test rebuilding of map with unknown revisions in shamap - it used to crash
466 466
467 467 $ cd branchpruning
468 468 $ hg up -r 2
469 469 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
470 470 $ hg merge 4
471 471 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
472 472 (branch merge, don't forget to commit)
473 473 $ hg ci -m 'merging something'
474 474 $ cd ..
475 475 $ echo "53792d18237d2b64971fa571936869156655338d 6d955580116e82c4b029bd30f321323bae71a7f0" >> branchpruning-hg2/.hg/shamap
476 476 $ hg convert --filemap branchpruning/filemap branchpruning branchpruning-hg2 --debug --config progress.debug=true
477 477 run hg source pre-conversion action
478 478 run hg sink pre-conversion action
479 479 scanning source...
480 480 scanning: 1/7 revisions (14.29%)
481 481 sorting...
482 482 converting...
483 483 0 merging something
484 484 source: 2503605b178fe50e8fbbb0e77b97939540aa8c87
485 485 converting: 0/1 revisions (0.00%)
486 486 unknown revmap source: 53792d18237d2b64971fa571936869156655338d
487 487 run hg sink post-conversion action
488 488 run hg source post-conversion action
489 489
490 490
491 491 filemap rename undoing revision rename
492 492
493 493 $ hg init renameundo
494 494 $ cd renameundo
495 495 $ echo 1 > a
496 496 $ echo 1 > c
497 497 $ hg ci -qAm add
498 498 $ hg mv -q a b/a
499 499 $ hg mv -q c b/c
500 500 $ hg ci -qm rename
501 501 $ echo 2 > b/a
502 502 $ echo 2 > b/c
503 503 $ hg ci -qm modify
504 504 $ cd ..
505 505
506 506 $ echo "rename b ." > renameundo.fmap
507 507 $ hg convert --filemap renameundo.fmap renameundo renameundo2
508 508 initializing destination renameundo2 repository
509 509 scanning source...
510 510 sorting...
511 511 converting...
512 512 2 add
513 513 1 rename
514 514 filtering out empty revision
515 515 repository tip rolled back to revision 0 (undo convert)
516 516 0 modify
517 517 $ glog -R renameundo2
518 518 o 1 "modify" files: a c
519 519 |
520 520 o 0 "add" files: a c
521 521
522 522
523 523
524 524 test merge parents/empty merges pruning
525 525
526 526 $ glog()
527 527 > {
528 528 > hg log -G --template '{rev}:{node|short}@{branch} "{desc}" files: {files}\n' "$@"
529 529 > }
530 530
531 531 test anonymous branch pruning
532 532
533 533 $ hg init anonymousbranch
534 534 $ cd anonymousbranch
535 535 $ echo a > a
536 536 $ echo b > b
537 537 $ hg ci -Am add
538 538 adding a
539 539 adding b
540 540 $ echo a >> a
541 541 $ hg ci -m changea
542 542 $ hg up 0
543 543 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
544 544 $ echo b >> b
545 545 $ hg ci -m changeb
546 546 created new head
547 547 $ hg up 1
548 548 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
549 549 $ hg merge
550 550 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
551 551 (branch merge, don't forget to commit)
552 552 $ hg ci -m merge
553 553 $ cd ..
554 554
555 555 $ cat > filemap <<EOF
556 556 > include a
557 557 > EOF
558 558 $ hg convert --filemap filemap anonymousbranch anonymousbranch-hg
559 559 initializing destination anonymousbranch-hg repository
560 560 scanning source...
561 561 sorting...
562 562 converting...
563 563 3 add
564 564 2 changea
565 565 1 changeb
566 566 0 merge
567 567 $ glog -R anonymousbranch
568 568 @ 3:c71d5201a498@default "merge" files:
569 569 |\
570 570 | o 2:607eb44b17f9@default "changeb" files: b
571 571 | |
572 572 o | 1:1f60ea617824@default "changea" files: a
573 573 |/
574 574 o 0:0146e6129113@default "add" files: a b
575 575
576 576 $ glog -R anonymousbranch-hg
577 577 o 1:cda818e7219b@default "changea" files: a
578 578 |
579 579 o 0:c334dc3be0da@default "add" files: a
580 580
581 581 $ cat anonymousbranch-hg/.hg/shamap
582 582 0146e6129113dba9ac90207cfdf2d7ed35257ae5 c334dc3be0daa2a4e9ce4d2e2bdcba40c09d4916
583 583 1f60ea61782421edf8d051ff4fcb61b330f26a4a cda818e7219b5f7f3fb9f49780054ed6a1905ec3
584 584 607eb44b17f9348cd5cbd26e16af87ba77b0b037 c334dc3be0daa2a4e9ce4d2e2bdcba40c09d4916
585 585 c71d5201a498b2658d105a6bf69d7a0df2649aea cda818e7219b5f7f3fb9f49780054ed6a1905ec3
586 586
587 587 $ cat > filemap <<EOF
588 588 > include b
589 589 > EOF
590 590 $ hg convert --filemap filemap anonymousbranch anonymousbranch-hg2
591 591 initializing destination anonymousbranch-hg2 repository
592 592 scanning source...
593 593 sorting...
594 594 converting...
595 595 3 add
596 596 2 changea
597 597 1 changeb
598 598 0 merge
599 599 $ glog -R anonymousbranch
600 600 @ 3:c71d5201a498@default "merge" files:
601 601 |\
602 602 | o 2:607eb44b17f9@default "changeb" files: b
603 603 | |
604 604 o | 1:1f60ea617824@default "changea" files: a
605 605 |/
606 606 o 0:0146e6129113@default "add" files: a b
607 607
608 608 $ glog -R anonymousbranch-hg2
609 609 o 1:62dd350b0df6@default "changeb" files: b
610 610 |
611 611 o 0:4b9ced861657@default "add" files: b
612 612
613 613 $ cat anonymousbranch-hg2/.hg/shamap
614 614 0146e6129113dba9ac90207cfdf2d7ed35257ae5 4b9ced86165703791653059a1db6ed864630a523
615 615 1f60ea61782421edf8d051ff4fcb61b330f26a4a 4b9ced86165703791653059a1db6ed864630a523
616 616 607eb44b17f9348cd5cbd26e16af87ba77b0b037 62dd350b0df695f7d2c82a02e0499b16fd790f22
617 617 c71d5201a498b2658d105a6bf69d7a0df2649aea 62dd350b0df695f7d2c82a02e0499b16fd790f22
618 618
619 619 test named branch pruning
620 620
621 621 $ hg init namedbranch
622 622 $ cd namedbranch
623 623 $ echo a > a
624 624 $ echo b > b
625 625 $ hg ci -Am add
626 626 adding a
627 627 adding b
628 628 $ echo a >> a
629 629 $ hg ci -m changea
630 630 $ hg up 0
631 631 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
632 632 $ hg branch foo
633 633 marked working directory as branch foo
634 634 (branches are permanent and global, did you want a bookmark?)
635 635 $ echo b >> b
636 636 $ hg ci -m changeb
637 637 $ hg up default
638 638 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
639 639 $ hg merge foo
640 640 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
641 641 (branch merge, don't forget to commit)
642 642 $ hg ci -m merge
643 643 $ cd ..
644 644
645 645 $ cat > filemap <<EOF
646 646 > include a
647 647 > EOF
648 648 $ hg convert --filemap filemap namedbranch namedbranch-hg
649 649 initializing destination namedbranch-hg repository
650 650 scanning source...
651 651 sorting...
652 652 converting...
653 653 3 add
654 654 2 changea
655 655 1 changeb
656 656 0 merge
657 657 $ glog -R namedbranch
658 658 @ 3:73899bcbe45c@default "merge" files:
659 659 |\
660 660 | o 2:8097982d19fc@foo "changeb" files: b
661 661 | |
662 662 o | 1:1f60ea617824@default "changea" files: a
663 663 |/
664 664 o 0:0146e6129113@default "add" files: a b
665 665
666 666 $ glog -R namedbranch-hg
667 667 o 1:cda818e7219b@default "changea" files: a
668 668 |
669 669 o 0:c334dc3be0da@default "add" files: a
670 670
671 671
672 672 $ cd namedbranch
673 673 $ hg --config extensions.mq= strip tip
674 674 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
675 675 saved backup bundle to $TESTTMP/namedbranch/.hg/strip-backup/73899bcbe45c-92adf160-backup.hg
676 676 $ hg up foo
677 677 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
678 678 $ hg merge default
679 679 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
680 680 (branch merge, don't forget to commit)
681 681 $ hg ci -m merge
682 682 $ cd ..
683 683
684 684 $ hg convert --filemap filemap namedbranch namedbranch-hg2
685 685 initializing destination namedbranch-hg2 repository
686 686 scanning source...
687 687 sorting...
688 688 converting...
689 689 3 add
690 690 2 changea
691 691 1 changeb
692 692 0 merge
693 693 $ glog -R namedbranch
694 694 @ 3:e1959de76e1b@foo "merge" files:
695 695 |\
696 696 | o 2:8097982d19fc@foo "changeb" files: b
697 697 | |
698 698 o | 1:1f60ea617824@default "changea" files: a
699 699 |/
700 700 o 0:0146e6129113@default "add" files: a b
701 701
702 702 $ glog -R namedbranch-hg2
703 703 o 2:dcf314454667@foo "merge" files:
704 704 |\
705 705 | o 1:cda818e7219b@default "changea" files: a
706 706 |/
707 707 o 0:c334dc3be0da@default "add" files: a
708 708
709 709 $ cd ..
710 710
711 711 test converting merges into a repo that contains other files
712 712
713 713 $ hg init merge-test1
714 714 $ cd merge-test1
715 715 $ touch a && hg commit -Aqm 'add a'
716 716 $ echo a > a && hg commit -Aqm 'edit a'
717 717 $ hg up -q 0
718 718 $ touch b && hg commit -Aqm 'add b'
719 719 $ hg merge -q 1 && hg commit -qm 'merge a & b'
720 720
721 721 $ cd ..
722 722 $ hg init merge-test2
723 723 $ cd merge-test2
724 724 $ mkdir converted
725 725 $ touch converted/a toberemoved && hg commit -Aqm 'add converted/a & toberemoved'
726 726 $ touch x && rm toberemoved && hg commit -Aqm 'add x & remove tobremoved'
727 727 $ cd ..
728 728 $ hg log -G -T '{shortest(node)} {desc}' -R merge-test1
729 729 @ 1191 merge a & b
730 730 |\
731 731 | o 9077 add b
732 732 | |
733 733 o | d19f edit a
734 734 |/
735 735 o ac82 add a
736 736
737 737 $ hg log -G -T '{shortest(node)} {desc}' -R merge-test2
738 738 @ 150e add x & remove tobremoved
739 739 |
740 740 o bbac add converted/a & toberemoved
741 741
742 742 - Build a shamap where the target converted/a is in on top of an unrelated
743 743 - change to 'x'. This simulates using convert to merge several repositories
744 744 - together.
745 745 $ cat >> merge-test2/.hg/shamap <<EOF
746 746 > $(hg -R merge-test1 log -r 0 -T '{node}') $(hg -R merge-test2 log -r 0 -T '{node}')
747 747 > $(hg -R merge-test1 log -r 1 -T '{node}') $(hg -R merge-test2 log -r 1 -T '{node}')
748 748 > EOF
749 749 $ cat >> merge-test-filemap <<EOF
750 750 > rename . converted/
751 751 > EOF
752 752 $ hg convert --filemap merge-test-filemap merge-test1 merge-test2 --traceback
753 753 scanning source...
754 754 sorting...
755 755 converting...
756 756 1 add b
757 757 0 merge a & b
758 758 $ hg -R merge-test2 manifest -r tip
759 759 converted/a
760 760 converted/b
761 761 x
762 762 $ hg -R merge-test2 log -G -T '{shortest(node)} {desc}\n{files % "- {file}\n"}\n'
763 763 o e2ff merge a & b
764 764 |\ - converted/a
765 765 | |
766 766 | o 2995 add b
767 767 | | - converted/b
768 768 | |
769 769 @ | 150e add x & remove tobremoved
770 770 |/ - toberemoved
771 771 | - x
772 772 |
773 773 o bbac add converted/a & toberemoved
774 774 - converted/a
775 775 - toberemoved
776 776
777 777 $ cd ..
778 778
779 779 Test case where cleanp2 contains a file that doesn't exist in p2 - for
780 780 example because filemap changed.
781 781
782 782 $ hg init cleanp2
783 783 $ cd cleanp2
784 784 $ touch f f1 f2 && hg ci -Aqm '0'
785 785 $ echo f1 > f1 && echo >> f && hg ci -m '1'
786 786 $ hg up -qr0 && echo f2 > f2 && echo >> f && hg ci -qm '2'
787 787 $ echo "include f" > filemap
788 788 $ hg convert --filemap filemap .
789 789 assuming destination .-hg
790 790 initializing destination .-hg repository
791 791 scanning source...
792 792 sorting...
793 793 converting...
794 794 2 0
795 795 1 1
796 796 0 2
797 797 $ hg merge && hg ci -qm '3'
798 798 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
799 799 (branch merge, don't forget to commit)
800 800 $ echo "include ." > filemap
801 801 $ hg convert --filemap filemap .
802 802 assuming destination .-hg
803 803 scanning source...
804 804 sorting...
805 805 converting...
806 806 0 3
807 807 $ hg -R .-hg log -G -T '{shortest(node)} {desc}\n{files % "- {file}\n"}\n'
808 808 o bbfe 3
809 809 |\
810 810 | o 33a0 2
811 811 | | - f
812 812 | |
813 813 o | f73e 1
814 814 |/ - f
815 815 |
816 816 o d681 0
817 817 - f
818 818
819 819 $ hg -R .-hg mani -r tip
820 820 f
821 821 $ cd ..
@@ -1,227 +1,227 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > convert=
4 4 > [convert]
5 5 > hg.saverev=False
6 6 > EOF
7 7 $ hg init orig
8 8 $ cd orig
9 9 $ echo foo > foo
10 10 $ echo bar > bar
11 11 $ hg ci -qAm 'add foo bar' -d '0 0'
12 12 $ echo >> foo
13 13 $ hg ci -m 'change foo' -d '1 0'
14 14 $ hg up -qC 0
15 15 $ hg copy --after --force foo bar
16 16 $ hg copy foo baz
17 17 $ hg ci -m 'make bar and baz copies of foo' -d '2 0'
18 18 created new head
19 19
20 20 Test that template can print all file copies (issue4362)
21 21 $ hg log -r . --template "{file_copies % ' File: {file_copy}\n'}"
22 22 File: bar (foo)
23 23 File: baz (foo)
24 24
25 25 $ hg bookmark premerge1
26 26 $ hg merge -r 1
27 27 merging baz and foo to baz
28 28 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
29 29 (branch merge, don't forget to commit)
30 30 $ hg ci -m 'merge local copy' -d '3 0'
31 31 $ hg up -C 1
32 32 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
33 33 (leaving bookmark premerge1)
34 34 $ hg bookmark premerge2
35 35 $ hg merge 2
36 36 merging foo and baz to baz
37 37 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
38 38 (branch merge, don't forget to commit)
39 39 $ hg ci -m 'merge remote copy' -d '4 0'
40 40 created new head
41 41
42 42 Make and delete some tags
43 43
44 44 $ hg tag that
45 45 $ hg tag --remove that
46 46 $ hg tag this
47 47
48 48 #if execbit
49 49 $ chmod +x baz
50 50 #else
51 51 $ echo some other change to make sure we get a rev 5 > baz
52 52 #endif
53 53 $ hg ci -m 'mark baz executable' -d '5 0'
54 54 $ cd ..
55 55 $ hg convert --datesort orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
56 56 initializing destination new repository
57 57 scanning source...
58 58 sorting...
59 59 converting...
60 60 8 add foo bar
61 61 7 change foo
62 62 6 make bar and baz copies of foo
63 63 5 merge local copy
64 64 4 merge remote copy
65 65 3 Added tag that for changeset 8601262d7472
66 66 2 Removed tag that
67 67 1 Added tag this for changeset 706614b458c1
68 68 0 mark baz executable
69 69 updating bookmarks
70 70 $ cd new
71 71 $ hg out ../orig
72 72 comparing with ../orig
73 73 searching for changes
74 74 no changes found
75 75 [1]
76 76 #if execbit
77 77 $ hg bookmarks
78 78 premerge1 3:973ef48a98a4
79 79 premerge2 8:c4968fdf2e5d
80 80 #else
81 81 Different hash because no x bit
82 82 $ hg bookmarks
83 83 premerge1 3:973ef48a98a4
84 84 premerge2 8:1cc21e701444
85 85 #endif
86 86
87 87 Test that redoing a convert results in an identical graph
88 88 $ cd ../
89 89 $ rm new/.hg/shamap
90 90 $ hg convert --datesort orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
91 91 scanning source...
92 92 sorting...
93 93 converting...
94 94 8 add foo bar
95 95 7 change foo
96 96 6 make bar and baz copies of foo
97 97 5 merge local copy
98 98 4 merge remote copy
99 99 3 Added tag that for changeset 8601262d7472
100 100 2 Removed tag that
101 101 1 Added tag this for changeset 706614b458c1
102 102 0 mark baz executable
103 103 updating bookmarks
104 104 $ hg -R new log -G -T '{rev} {desc}'
105 105 o 8 mark baz executable
106 106 |
107 107 o 7 Added tag this for changeset 706614b458c1
108 108 |
109 109 o 6 Removed tag that
110 110 |
111 111 o 5 Added tag that for changeset 8601262d7472
112 112 |
113 113 o 4 merge remote copy
114 114 |\
115 115 +---o 3 merge local copy
116 116 | |/
117 117 | o 2 make bar and baz copies of foo
118 118 | |
119 119 o | 1 change foo
120 120 |/
121 121 o 0 add foo bar
122 122
123 123
124 124 check shamap LF and CRLF handling
125 125
126 126 $ cat > rewrite.py <<EOF
127 127 > import sys
128 128 > # Interlace LF and CRLF
129 129 > lines = [(l.rstrip() + ((i % 2) and b'\n' or b'\r\n'))
130 130 > for i, l in enumerate(open(sys.argv[1], 'rb'))]
131 131 > open(sys.argv[1], 'wb').write(b''.join(lines))
132 132 > EOF
133 133 $ "$PYTHON" rewrite.py new/.hg/shamap
134 134 $ cd orig
135 135 $ hg up -qC 1
136 136 $ echo foo >> foo
137 137 $ hg ci -qm 'change foo again'
138 138 $ hg up -qC 2
139 139 $ echo foo >> foo
140 140 $ hg ci -qm 'change foo again again'
141 141 $ cd ..
142 142 $ hg convert --datesort orig new 2>&1 | grep -v 'subversion python bindings could not be loaded'
143 143 scanning source...
144 144 sorting...
145 145 converting...
146 146 1 change foo again again
147 147 0 change foo again
148 148 updating bookmarks
149 149
150 150 init broken repository
151 151
152 152 $ hg init broken
153 153 $ cd broken
154 154 $ echo a >> a
155 155 $ echo b >> b
156 156 $ hg ci -qAm init
157 157 $ echo a >> a
158 158 $ echo b >> b
159 159 $ hg copy b c
160 160 $ hg ci -qAm changeall
161 161 $ hg up -qC 0
162 162 $ echo bc >> b
163 163 $ hg ci -m changebagain
164 164 created new head
165 165 $ HGMERGE=internal:local hg -q merge
166 166 $ hg ci -m merge
167 167 $ hg mv b d
168 168 $ hg ci -m moveb
169 169
170 170 break it
171 171
172 172 #if reporevlogstore
173 173 $ rm .hg/store/data/b.*
174 174 #endif
175 175 #if reposimplestore
176 176 $ rm .hg/store/data/b/*
177 177 #endif
178 178 $ cd ..
179 179 $ hg --config convert.hg.ignoreerrors=True convert broken fixed
180 180 initializing destination fixed repository
181 181 scanning source...
182 182 sorting...
183 183 converting...
184 184 4 init
185 ignoring: data/b@1e88685f5ddec574a34c70af492f95b6debc8741: no match found (reporevlogstore !)
185 ignoring: b@1e88685f5ddec574a34c70af492f95b6debc8741: no match found (reporevlogstore !)
186 186 ignoring: data/b/index@1e88685f5dde: no node (reposimplestore !)
187 187 3 changeall
188 188 2 changebagain
189 189 1 merge
190 190 0 moveb
191 191 $ hg -R fixed verify
192 192 checking changesets
193 193 checking manifests
194 194 crosschecking files in changesets and manifests
195 195 checking files
196 196 checked 5 changesets with 5 changes to 3 files
197 197
198 198 manifest -r 0
199 199
200 200 $ hg -R fixed manifest -r 0
201 201 a
202 202
203 203 manifest -r tip
204 204
205 205 $ hg -R fixed manifest -r tip
206 206 a
207 207 c
208 208 d
209 209 $ cd ..
210 210
211 211 $ hg init commit-references
212 212 $ cd commit-references
213 213 $ echo a > a
214 214 $ hg ci -Aqm initial
215 215 $ echo b > b
216 216 $ hg ci -Aqm 'the previous commit was 1451231c8757'
217 217 $ echo c > c
218 218 $ hg ci -Aqm 'the working copy is called ffffffffffff'
219 219
220 220 $ cd ..
221 221 $ hg convert commit-references new-commit-references -q \
222 222 > --config convert.hg.sourcename=yes
223 223 $ cd new-commit-references
224 224 $ hg log -T '{node|short} {desc}\n'
225 225 fe295c9e6bc6 the working copy is called ffffffffffff
226 226 642508659503 the previous commit was c2491f685436
227 227 c2491f685436 initial
@@ -1,1240 +1,1240 b''
1 1 #require no-reposimplestore no-chg
2 2
3 3 $ hg init requirements
4 4 $ cd requirements
5 5
6 6 # LFS not loaded by default.
7 7
8 8 $ hg config extensions
9 9 [1]
10 10
11 11 # Adding lfs to requires file will auto-load lfs extension.
12 12
13 13 $ echo lfs >> .hg/requires
14 14 $ hg config extensions
15 15 extensions.lfs=
16 16
17 17 # But only if there is no config entry for the extension already.
18 18
19 19 $ cat > .hg/hgrc << EOF
20 20 > [extensions]
21 21 > lfs=!
22 22 > EOF
23 23
24 24 $ hg config extensions
25 25 abort: repository requires features unknown to this Mercurial: lfs
26 26 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
27 27 [255]
28 28
29 29 $ cat > .hg/hgrc << EOF
30 30 > [extensions]
31 31 > lfs=
32 32 > EOF
33 33
34 34 $ hg config extensions
35 35 extensions.lfs=
36 36
37 37 $ cat > .hg/hgrc << EOF
38 38 > [extensions]
39 39 > lfs = missing.py
40 40 > EOF
41 41
42 42 $ hg config extensions
43 43 \*\*\* failed to import extension "lfs" from missing.py: [Errno *] $ENOENT$: 'missing.py' (glob)
44 44 abort: repository requires features unknown to this Mercurial: lfs
45 45 (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
46 46 [255]
47 47
48 48 $ cd ..
49 49
50 50 # Initial setup
51 51
52 52 $ cat >> $HGRCPATH << EOF
53 53 > [extensions]
54 54 > lfs=
55 55 > [lfs]
56 56 > # Test deprecated config
57 57 > threshold=1000B
58 58 > EOF
59 59
60 60 $ LONG=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
61 61
62 62 # Prepare server and enable extension
63 63 $ hg init server
64 64 $ hg clone -q server client
65 65 $ cd client
66 66
67 67 # Commit small file
68 68 $ echo s > smallfile
69 69 $ echo '**.py = LF' > .hgeol
70 70 $ hg --config lfs.track='"size(\">1000B\")"' commit -Aqm "add small file"
71 71 hg: parse error: unsupported file pattern: size(">1000B")
72 72 (paths must be prefixed with "path:")
73 73 [10]
74 74 $ hg --config lfs.track='size(">1000B")' commit -Aqm "add small file"
75 75
76 76 # Commit large file
77 77 $ echo $LONG > largefile
78 78 $ hg debugrequires | grep lfs
79 79 [1]
80 80 $ hg commit --traceback -Aqm "add large file"
81 81 $ hg debugrequires | grep lfs
82 82 lfs
83 83
84 84 # Ensure metadata is stored
85 85 $ hg debugdata largefile 0
86 86 version https://git-lfs.github.com/spec/v1
87 87 oid sha256:f11e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
88 88 size 1501
89 89 x-is-binary 0
90 90
91 91 # Check the blobstore is populated
92 92 $ find .hg/store/lfs/objects | sort
93 93 .hg/store/lfs/objects
94 94 .hg/store/lfs/objects/f1
95 95 .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
96 96
97 97 # Check the blob stored contains the actual contents of the file
98 98 $ cat .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
99 99 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
100 100
101 101 # Push changes to the server
102 102
103 103 $ hg push
104 104 pushing to $TESTTMP/server
105 105 searching for changes
106 106 abort: lfs.url needs to be configured
107 107 [255]
108 108
109 109 $ cat >> $HGRCPATH << EOF
110 110 > [lfs]
111 111 > url=file:$TESTTMP/dummy-remote/
112 112 > EOF
113 113
114 114 Push to a local non-lfs repo with the extension enabled will add the
115 115 lfs requirement
116 116
117 117 $ hg debugrequires -R $TESTTMP/server/ | grep lfs
118 118 [1]
119 119 $ hg push -v | egrep -v '^(uncompressed| )'
120 120 pushing to $TESTTMP/server
121 121 searching for changes
122 122 lfs: found f11e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b in the local lfs store
123 123 2 changesets found
124 124 adding changesets
125 125 adding manifests
126 126 adding file changes
127 127 calling hook pretxnchangegroup.lfs: hgext.lfs.checkrequireslfs
128 128 added 2 changesets with 3 changes to 3 files
129 129 $ hg debugrequires -R $TESTTMP/server/ | grep lfs
130 130 lfs
131 131
132 132 # Unknown URL scheme
133 133
134 134 $ hg push --config lfs.url=ftp://foobar
135 135 abort: lfs: unknown url scheme: ftp
136 136 [255]
137 137
138 138 $ cd ../
139 139
140 140 # Initialize new client (not cloning) and setup extension
141 141 $ hg init client2
142 142 $ cd client2
143 143 $ cat >> .hg/hgrc <<EOF
144 144 > [paths]
145 145 > default = $TESTTMP/server
146 146 > EOF
147 147
148 148 # Pull from server
149 149
150 150 Pulling a local lfs repo into a local non-lfs repo with the extension
151 151 enabled adds the lfs requirement
152 152
153 153 $ hg debugrequires | grep lfs || true
154 154 $ hg debugrequires -R $TESTTMP/server/ | grep lfs
155 155 lfs
156 156 $ hg pull default
157 157 pulling from $TESTTMP/server
158 158 requesting all changes
159 159 adding changesets
160 160 adding manifests
161 161 adding file changes
162 162 added 2 changesets with 3 changes to 3 files
163 163 new changesets 0ead593177f7:b88141481348
164 164 (run 'hg update' to get a working copy)
165 165 $ hg debugrequires | grep lfs
166 166 lfs
167 167 $ hg debugrequires -R $TESTTMP/server/ | grep lfs
168 168 lfs
169 169
170 170 # Check the blobstore is not yet populated
171 171 $ [ -d .hg/store/lfs/objects ]
172 172 [1]
173 173
174 174 # Update to the last revision containing the large file
175 175 $ hg update
176 176 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
177 177
178 178 # Check the blobstore has been populated on update
179 179 $ find .hg/store/lfs/objects | sort
180 180 .hg/store/lfs/objects
181 181 .hg/store/lfs/objects/f1
182 182 .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
183 183
184 184 # Check the contents of the file are fetched from blobstore when requested
185 185 $ hg cat -r . largefile
186 186 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
187 187
188 188 # Check the file has been copied in the working copy
189 189 $ cat largefile
190 190 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
191 191
192 192 $ cd ..
193 193
194 194 # Check rename, and switch between large and small files
195 195
196 196 $ hg init repo3
197 197 $ cd repo3
198 198 $ cat >> .hg/hgrc << EOF
199 199 > [lfs]
200 200 > track=size(">10B")
201 201 > EOF
202 202
203 203 $ echo LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS > large
204 204 $ echo SHORTER > small
205 205 $ hg add . -q
206 206 $ hg commit -m 'commit with lfs content'
207 207
208 208 $ hg files -r . 'set:added()'
209 209 large
210 210 small
211 211 $ hg files -r . 'set:added() & lfs()'
212 212 large
213 213
214 214 $ hg mv large l
215 215 $ hg mv small s
216 216 $ hg status 'set:removed()'
217 217 R large
218 218 R small
219 219 $ hg status 'set:removed() & lfs()'
220 220 R large
221 221 $ hg commit -m 'renames'
222 222
223 223 $ hg cat -r . l -T '{rawdata}\n'
224 224 version https://git-lfs.github.com/spec/v1
225 225 oid sha256:66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
226 226 size 39
227 227 x-hg-copy large
228 228 x-hg-copyrev 2c531e0992ff3107c511b53cb82a91b6436de8b2
229 229 x-is-binary 0
230 230
231 231
232 232 $ hg files -r . 'set:copied()'
233 233 l
234 234 s
235 235 $ hg files -r . 'set:copied() & lfs()'
236 236 l
237 237 $ hg status --change . 'set:removed()'
238 238 R large
239 239 R small
240 240 $ hg status --change . 'set:removed() & lfs()'
241 241 R large
242 242
243 243 $ echo SHORT > l
244 244 $ echo BECOME-LARGER-FROM-SHORTER > s
245 245 $ hg commit -m 'large to small, small to large'
246 246
247 247 $ echo 1 >> l
248 248 $ echo 2 >> s
249 249 $ hg commit -m 'random modifications'
250 250
251 251 $ echo RESTORE-TO-BE-LARGE > l
252 252 $ echo SHORTER > s
253 253 $ hg commit -m 'switch large and small again'
254 254
255 255 # Test lfs_files template
256 256
257 257 $ hg log -r 'all()' -T '{rev} {join(lfs_files, ", ")}\n'
258 258 0 large
259 259 1 l, large
260 260 2 s
261 261 3 s
262 262 4 l
263 263
264 264 # Push and pull the above repo
265 265
266 266 $ hg --cwd .. init repo4
267 267 $ hg push ../repo4
268 268 pushing to ../repo4
269 269 searching for changes
270 270 adding changesets
271 271 adding manifests
272 272 adding file changes
273 273 added 5 changesets with 10 changes to 4 files
274 274
275 275 $ hg --cwd .. init repo5
276 276 $ hg --cwd ../repo5 pull ../repo3
277 277 pulling from ../repo3
278 278 requesting all changes
279 279 adding changesets
280 280 adding manifests
281 281 adding file changes
282 282 added 5 changesets with 10 changes to 4 files
283 283 new changesets fd47a419c4f7:5adf850972b9
284 284 (run 'hg update' to get a working copy)
285 285
286 286 $ cd ..
287 287
288 288 # Test clone
289 289
290 290 $ hg init repo6
291 291 $ cd repo6
292 292 $ cat >> .hg/hgrc << EOF
293 293 > [lfs]
294 294 > track=size(">30B")
295 295 > EOF
296 296
297 297 $ echo LARGE-BECAUSE-IT-IS-MORE-THAN-30-BYTES > large
298 298 $ echo SMALL > small
299 299 $ hg commit -Aqm 'create a lfs file' large small
300 300 $ hg debuglfsupload -r 'all()' -v
301 301 lfs: found 8e92251415339ae9b148c8da89ed5ec665905166a1ab11b09dca8fad83344738 in the local lfs store
302 302
303 303 $ cd ..
304 304
305 305 $ hg clone repo6 repo7
306 306 updating to branch default
307 307 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
308 308 $ cd repo7
309 309 $ cat large
310 310 LARGE-BECAUSE-IT-IS-MORE-THAN-30-BYTES
311 311 $ cat small
312 312 SMALL
313 313
314 314 $ cd ..
315 315
316 316 $ hg --config extensions.share= share repo7 sharedrepo
317 317 updating working directory
318 318 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
319 319 $ hg debugrequires -R sharedrepo/ | grep lfs
320 320 lfs
321 321
322 322 # Test rename and status
323 323
324 324 $ hg init repo8
325 325 $ cd repo8
326 326 $ cat >> .hg/hgrc << EOF
327 327 > [lfs]
328 328 > track=size(">10B")
329 329 > EOF
330 330
331 331 $ echo THIS-IS-LFS-BECAUSE-10-BYTES > a1
332 332 $ echo SMALL > a2
333 333 $ hg commit -m a -A a1 a2
334 334 $ hg status
335 335 $ hg mv a1 b1
336 336 $ hg mv a2 a1
337 337 $ hg mv b1 a2
338 338 $ hg commit -m b
339 339 $ hg status
340 340 >>> with open('a2', 'wb') as f:
341 341 ... f.write(b'\1\nSTART-WITH-HG-FILELOG-METADATA') and None
342 342 >>> with open('a1', 'wb') as f:
343 343 ... f.write(b'\1\nMETA\n') and None
344 344 $ hg commit -m meta
345 345 $ hg status
346 346 $ hg log -T '{rev}: {file_copies} | {file_dels} | {file_adds}\n'
347 347 2: | |
348 348 1: a1 (a2)a2 (a1) | |
349 349 0: | | a1 a2
350 350
351 351 $ for n in a1 a2; do
352 352 > for r in 0 1 2; do
353 353 > printf '\n%s @ %s\n' $n $r
354 354 > hg debugdata $n $r
355 355 > done
356 356 > done
357 357
358 358 a1 @ 0
359 359 version https://git-lfs.github.com/spec/v1
360 360 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
361 361 size 29
362 362 x-is-binary 0
363 363
364 364 a1 @ 1
365 365 \x01 (esc)
366 366 copy: a2
367 367 copyrev: 50470ad23cf937b1f4b9f80bfe54df38e65b50d9
368 368 \x01 (esc)
369 369 SMALL
370 370
371 371 a1 @ 2
372 372 \x01 (esc)
373 373 \x01 (esc)
374 374 \x01 (esc)
375 375 META
376 376
377 377 a2 @ 0
378 378 SMALL
379 379
380 380 a2 @ 1
381 381 version https://git-lfs.github.com/spec/v1
382 382 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
383 383 size 29
384 384 x-hg-copy a1
385 385 x-hg-copyrev be23af27908a582af43e5cda209a5a9b319de8d4
386 386 x-is-binary 0
387 387
388 388 a2 @ 2
389 389 version https://git-lfs.github.com/spec/v1
390 390 oid sha256:876dadc86a8542f9798048f2c47f51dbf8e4359aed883e8ec80c5db825f0d943
391 391 size 32
392 392 x-is-binary 0
393 393
394 394 # Verify commit hashes include rename metadata
395 395
396 396 $ hg log -T '{rev}:{node|short} {desc}\n'
397 397 2:0fae949de7fa meta
398 398 1:9cd6bdffdac0 b
399 399 0:7f96794915f7 a
400 400
401 401 $ cd ..
402 402
403 403 # Test bundle
404 404
405 405 $ hg init repo9
406 406 $ cd repo9
407 407 $ cat >> .hg/hgrc << EOF
408 408 > [lfs]
409 409 > track=size(">10B")
410 410 > [diff]
411 411 > git=1
412 412 > EOF
413 413
414 414 $ for i in 0 single two three 4; do
415 415 > echo 'THIS-IS-LFS-'$i > a
416 416 > hg commit -m a-$i -A a
417 417 > done
418 418
419 419 $ hg update 2 -q
420 420 $ echo 'THIS-IS-LFS-2-CHILD' > a
421 421 $ hg commit -m branching -q
422 422
423 423 $ hg bundle --base 1 bundle.hg -v
424 424 lfs: found 5ab7a3739a5feec94a562d070a14f36dba7cad17e5484a4a89eea8e5f3166888 in the local lfs store
425 425 lfs: found a9c7d1cd6ce2b9bbdf46ed9a862845228717b921c089d0d42e3bcaed29eb612e in the local lfs store
426 426 lfs: found f693890c49c409ec33673b71e53f297681f76c1166daf33b2ad7ebf8b1d3237e in the local lfs store
427 427 lfs: found fda198fea753eb66a252e9856915e1f5cddbe41723bd4b695ece2604ad3c9f75 in the local lfs store
428 428 4 changesets found
429 429 uncompressed size of bundle content:
430 430 * (changelog) (glob)
431 431 * (manifests) (glob)
432 432 * a (glob)
433 433 $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
434 434 $ hg -R bundle.hg log -p -T '{rev} {desc}\n' a
435 435 5 branching
436 436 diff --git a/a b/a
437 437 --- a/a
438 438 +++ b/a
439 439 @@ -1,1 +1,1 @@
440 440 -THIS-IS-LFS-two
441 441 +THIS-IS-LFS-2-CHILD
442 442
443 443 4 a-4
444 444 diff --git a/a b/a
445 445 --- a/a
446 446 +++ b/a
447 447 @@ -1,1 +1,1 @@
448 448 -THIS-IS-LFS-three
449 449 +THIS-IS-LFS-4
450 450
451 451 3 a-three
452 452 diff --git a/a b/a
453 453 --- a/a
454 454 +++ b/a
455 455 @@ -1,1 +1,1 @@
456 456 -THIS-IS-LFS-two
457 457 +THIS-IS-LFS-three
458 458
459 459 2 a-two
460 460 diff --git a/a b/a
461 461 --- a/a
462 462 +++ b/a
463 463 @@ -1,1 +1,1 @@
464 464 -THIS-IS-LFS-single
465 465 +THIS-IS-LFS-two
466 466
467 467 1 a-single
468 468 diff --git a/a b/a
469 469 --- a/a
470 470 +++ b/a
471 471 @@ -1,1 +1,1 @@
472 472 -THIS-IS-LFS-0
473 473 +THIS-IS-LFS-single
474 474
475 475 0 a-0
476 476 diff --git a/a b/a
477 477 new file mode 100644
478 478 --- /dev/null
479 479 +++ b/a
480 480 @@ -0,0 +1,1 @@
481 481 +THIS-IS-LFS-0
482 482
483 483 $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
484 484 $ hg -R bundle-again.hg log -p -T '{rev} {desc}\n' a
485 485 5 branching
486 486 diff --git a/a b/a
487 487 --- a/a
488 488 +++ b/a
489 489 @@ -1,1 +1,1 @@
490 490 -THIS-IS-LFS-two
491 491 +THIS-IS-LFS-2-CHILD
492 492
493 493 4 a-4
494 494 diff --git a/a b/a
495 495 --- a/a
496 496 +++ b/a
497 497 @@ -1,1 +1,1 @@
498 498 -THIS-IS-LFS-three
499 499 +THIS-IS-LFS-4
500 500
501 501 3 a-three
502 502 diff --git a/a b/a
503 503 --- a/a
504 504 +++ b/a
505 505 @@ -1,1 +1,1 @@
506 506 -THIS-IS-LFS-two
507 507 +THIS-IS-LFS-three
508 508
509 509 2 a-two
510 510 diff --git a/a b/a
511 511 --- a/a
512 512 +++ b/a
513 513 @@ -1,1 +1,1 @@
514 514 -THIS-IS-LFS-single
515 515 +THIS-IS-LFS-two
516 516
517 517 1 a-single
518 518 diff --git a/a b/a
519 519 --- a/a
520 520 +++ b/a
521 521 @@ -1,1 +1,1 @@
522 522 -THIS-IS-LFS-0
523 523 +THIS-IS-LFS-single
524 524
525 525 0 a-0
526 526 diff --git a/a b/a
527 527 new file mode 100644
528 528 --- /dev/null
529 529 +++ b/a
530 530 @@ -0,0 +1,1 @@
531 531 +THIS-IS-LFS-0
532 532
533 533 $ cd ..
534 534
535 535 # Test isbinary
536 536
537 537 $ hg init repo10
538 538 $ cd repo10
539 539 $ cat >> .hg/hgrc << EOF
540 540 > [extensions]
541 541 > lfs=
542 542 > [lfs]
543 543 > track=all()
544 544 > EOF
545 545 $ "$PYTHON" <<'EOF'
546 546 > def write(path, content):
547 547 > with open(path, 'wb') as f:
548 548 > f.write(content)
549 549 > write('a', b'\0\0')
550 550 > write('b', b'\1\n')
551 551 > write('c', b'\1\n\0')
552 552 > write('d', b'xx')
553 553 > EOF
554 554 $ hg add a b c d
555 555 $ hg diff --stat
556 556 a | Bin
557 557 b | 1 +
558 558 c | Bin
559 559 d | 1 +
560 560 4 files changed, 2 insertions(+), 0 deletions(-)
561 561 $ hg commit -m binarytest
562 562 $ cat > $TESTTMP/dumpbinary.py << EOF
563 563 > from mercurial.utils import (
564 564 > stringutil,
565 565 > )
566 566 > def reposetup(ui, repo):
567 567 > for n in (b'a', b'b', b'c', b'd'):
568 568 > ui.write((b'%s: binary=%s\n')
569 569 > % (n, stringutil.pprint(repo[b'.'][n].isbinary())))
570 570 > EOF
571 571 $ hg --config extensions.dumpbinary=$TESTTMP/dumpbinary.py id --trace
572 572 a: binary=True
573 573 b: binary=False
574 574 c: binary=True
575 575 d: binary=False
576 576 b55353847f02 tip
577 577
578 578 Binary blobs don't need to be present to be skipped in filesets. (And their
579 579 absence doesn't cause an abort.)
580 580
581 581 $ rm .hg/store/lfs/objects/96/a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7
582 582 $ rm .hg/store/lfs/objects/92/f76135a4baf4faccb8586a60faf830c2bdfce147cefa188aaf4b790bd01b7e
583 583
584 584 $ hg files --debug -r . 'set:eol("unix")' --config 'experimental.lfs.disableusercache=True'
585 585 lfs: found c04b5bb1a5b2eb3e9cd4805420dba5a9d133da5b7adeeafb5474c4adae9faa80 in the local lfs store
586 586 2 b
587 587 lfs: found 5dde896887f6754c9b15bfe3a441ae4806df2fde94001311e08bf110622e0bbe in the local lfs store
588 588
589 589 $ hg files --debug -r . 'set:binary()' --config 'experimental.lfs.disableusercache=True'
590 590 2 a
591 591 3 c
592 592
593 593 $ cd ..
594 594
595 595 # Test fctx.cmp fastpath - diff without LFS blobs
596 596
597 597 $ hg init repo12
598 598 $ cd repo12
599 599 $ cat >> .hg/hgrc <<EOF
600 600 > [lfs]
601 601 > threshold=1
602 602 > EOF
603 603 $ cat > ../patch.diff <<EOF
604 604 > # HG changeset patch
605 605 > 2
606 606 >
607 607 > diff --git a/a b/a
608 608 > old mode 100644
609 609 > new mode 100755
610 610 > EOF
611 611
612 612 $ for i in 1 2 3; do
613 613 > cp ../repo10/a a
614 614 > if [ $i = 3 ]; then
615 615 > # make a content-only change
616 616 > hg import -q --bypass ../patch.diff
617 617 > hg update -q
618 618 > rm ../patch.diff
619 619 > else
620 620 > echo $i >> a
621 621 > hg commit -m $i -A a
622 622 > fi
623 623 > done
624 624 $ [ -d .hg/store/lfs/objects ]
625 625
626 626 $ cd ..
627 627
628 628 $ hg clone repo12 repo13 --noupdate
629 629 $ cd repo13
630 630 $ hg log --removed -p a -T '{desc}\n' --config diff.nobinary=1 --git
631 631 2
632 632 diff --git a/a b/a
633 633 old mode 100644
634 634 new mode 100755
635 635
636 636 2
637 637 diff --git a/a b/a
638 638 Binary file a has changed
639 639
640 640 1
641 641 diff --git a/a b/a
642 642 new file mode 100644
643 643 Binary file a has changed
644 644
645 645 $ [ -d .hg/store/lfs/objects ]
646 646 [1]
647 647
648 648 $ cd ..
649 649
650 650 # Test filter
651 651
652 652 $ hg init repo11
653 653 $ cd repo11
654 654 $ cat >> .hg/hgrc << EOF
655 655 > [lfs]
656 656 > track=(**.a & size(">5B")) | (**.b & !size(">5B"))
657 657 > | (**.c & "path:d" & !"path:d/c.c") | size(">10B")
658 658 > EOF
659 659
660 660 $ mkdir a
661 661 $ echo aaaaaa > a/1.a
662 662 $ echo a > a/2.a
663 663 $ echo aaaaaa > 1.b
664 664 $ echo a > 2.b
665 665 $ echo a > 1.c
666 666 $ mkdir d
667 667 $ echo a > d/c.c
668 668 $ echo a > d/d.c
669 669 $ echo aaaaaaaaaaaa > x
670 670 $ hg add . -q
671 671 $ hg commit -m files
672 672
673 673 $ for p in a/1.a a/2.a 1.b 2.b 1.c d/c.c d/d.c x; do
674 674 > if hg debugdata $p 0 2>&1 | grep git-lfs >/dev/null; then
675 675 > echo "${p}: is lfs"
676 676 > else
677 677 > echo "${p}: not lfs"
678 678 > fi
679 679 > done
680 680 a/1.a: is lfs
681 681 a/2.a: not lfs
682 682 1.b: not lfs
683 683 2.b: is lfs
684 684 1.c: not lfs
685 685 d/c.c: not lfs
686 686 d/d.c: is lfs
687 687 x: is lfs
688 688
689 689 $ cd ..
690 690
691 691 # Verify the repos
692 692
693 693 $ cat > $TESTTMP/dumpflog.py << EOF
694 694 > # print raw revision sizes, flags, and hashes for certain files
695 695 > import hashlib
696 696 > from mercurial.node import short
697 697 > from mercurial import (
698 698 > pycompat,
699 699 > revlog,
700 700 > )
701 701 > from mercurial.utils import (
702 702 > procutil,
703 703 > stringutil,
704 704 > )
705 705 > def hash(rawtext):
706 706 > h = hashlib.sha512()
707 707 > h.update(rawtext)
708 708 > return pycompat.sysbytes(h.hexdigest()[:4])
709 709 > def reposetup(ui, repo):
710 710 > # these 2 files are interesting
711 711 > for name in [b'l', b's']:
712 712 > fl = repo.file(name)
713 713 > if len(fl) == 0:
714 714 > continue
715 715 > sizes = [fl._revlog.rawsize(i) for i in fl]
716 716 > texts = [fl.rawdata(i) for i in fl]
717 717 > flags = [int(fl._revlog.flags(i)) for i in fl]
718 718 > hashes = [hash(t) for t in texts]
719 719 > procutil.stdout.write(b' %s: rawsizes=%r flags=%r hashes=%s\n'
720 720 > % (name, sizes, flags, stringutil.pprint(hashes)))
721 721 > EOF
722 722
723 723 $ for i in client client2 server repo3 repo4 repo5 repo6 repo7 repo8 repo9 \
724 724 > repo10; do
725 725 > echo 'repo:' $i
726 726 > hg --cwd $i verify --config extensions.dumpflog=$TESTTMP/dumpflog.py -q
727 727 > done
728 728 repo: client
729 729 repo: client2
730 730 repo: server
731 731 repo: repo3
732 732 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
733 733 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
734 734 repo: repo4
735 735 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
736 736 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
737 737 repo: repo5
738 738 l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
739 739 s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
740 740 repo: repo6
741 741 repo: repo7
742 742 repo: repo8
743 743 repo: repo9
744 744 repo: repo10
745 745
746 746 repo13 doesn't have any cached lfs files and its source never pushed its
747 747 files. Therefore, the files don't exist in the remote store. Use the files in
748 748 the user cache.
749 749
750 750 $ test -d $TESTTMP/repo13/.hg/store/lfs/objects
751 751 [1]
752 752
753 753 $ hg --config extensions.share= share repo13 repo14
754 754 updating working directory
755 755 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
756 756 $ hg -R repo14 -q verify
757 757
758 758 $ hg clone repo13 repo15
759 759 updating to branch default
760 760 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
761 761 $ hg -R repo15 -q verify
762 762
763 763 If the source repo doesn't have the blob (maybe it was pulled or cloned with
764 764 --noupdate), the blob is still accessible via the global cache to send to the
765 765 remote store.
766 766
767 767 $ rm -rf $TESTTMP/repo15/.hg/store/lfs
768 768 $ hg init repo16
769 769 $ hg -R repo15 push repo16
770 770 pushing to repo16
771 771 searching for changes
772 772 adding changesets
773 773 adding manifests
774 774 adding file changes
775 775 added 3 changesets with 2 changes to 1 files
776 776 $ hg -R repo15 -q verify
777 777
778 778 Test damaged file scenarios. (This also damages the usercache because of the
779 779 hardlinks.)
780 780
781 781 $ echo 'damage' >> repo5/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
782 782
783 783 Repo with damaged lfs objects in any revision will fail verification.
784 784
785 785 $ hg -R repo5 verify
786 786 checking changesets
787 787 checking manifests
788 788 crosschecking files in changesets and manifests
789 789 checking files
790 l@1: unpacking 46a2f24864bc: integrity check failed on data/l:0
791 large@0: unpacking 2c531e0992ff: integrity check failed on data/large:0
790 l@1: unpacking 46a2f24864bc: integrity check failed on l:0
791 large@0: unpacking 2c531e0992ff: integrity check failed on large:0
792 792 checked 5 changesets with 10 changes to 4 files
793 793 2 integrity errors encountered!
794 794 (first damaged changeset appears to be 0)
795 795 [1]
796 796
797 797 Updates work after cloning a damaged repo, if the damaged lfs objects aren't in
798 798 the update destination. Those objects won't be added to the new repo's store
799 799 because they aren't accessed.
800 800
801 801 $ hg clone -v repo5 fromcorrupt
802 802 updating to branch default
803 803 resolving manifests
804 804 getting l
805 805 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the usercache
806 806 getting s
807 807 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
808 808 $ test -f fromcorrupt/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
809 809 [1]
810 810
811 811 Verify will not try to download lfs blobs, if told not to process lfs content.
812 812 The extension makes sure that the filelog.renamed() path is taken on a missing
813 813 blob, and the output shows that it isn't fetched.
814 814
815 815 $ cat > $TESTTMP/lfsrename.py <<EOF
816 816 > import sys
817 817 >
818 818 > from mercurial import (
819 819 > exthelper,
820 820 > pycompat,
821 821 > )
822 822 >
823 823 > from hgext.lfs import (
824 824 > pointer,
825 825 > wrapper,
826 826 > )
827 827 >
828 828 > eh = exthelper.exthelper()
829 829 > uisetup = eh.finaluisetup
830 830 >
831 831 > @eh.wrapfunction(wrapper, b'filelogrenamed')
832 832 > def filelogrenamed(orig, orig1, self, node):
833 833 > ret = orig(orig1, self, node)
834 834 > if wrapper._islfs(self._revlog, node) and ret:
835 835 > rawtext = self._revlog.rawdata(node)
836 836 > metadata = pointer.deserialize(rawtext)
837 837 > print('lfs blob %s renamed %s -> %s'
838 838 > % (pycompat.sysstr(metadata[b'oid']),
839 839 > pycompat.sysstr(ret[0]),
840 840 > pycompat.fsdecode(self._revlog.filename)))
841 841 > sys.stdout.flush()
842 842 > return ret
843 843 > EOF
844 844
845 845 $ hg -R fromcorrupt --config lfs.usercache=emptycache verify -v --no-lfs \
846 846 > --config extensions.x=$TESTTMP/lfsrename.py
847 847 repository uses revlog format 1
848 848 checking changesets
849 849 checking manifests
850 850 crosschecking files in changesets and manifests
851 851 checking files
852 852 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
853 853 lfs blob sha256:66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e renamed large -> l
854 854 checked 5 changesets with 10 changes to 4 files
855 855
856 856 Verify will not try to download lfs blobs, if told not to by the config option
857 857
858 858 $ hg -R fromcorrupt --config lfs.usercache=emptycache verify -v \
859 859 > --config verify.skipflags=8192 \
860 860 > --config extensions.x=$TESTTMP/lfsrename.py
861 861 repository uses revlog format 1
862 862 checking changesets
863 863 checking manifests
864 864 crosschecking files in changesets and manifests
865 865 checking files
866 866 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
867 867 lfs blob sha256:66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e renamed large -> l
868 868 checked 5 changesets with 10 changes to 4 files
869 869
870 870 Verify will copy/link all lfs objects into the local store that aren't already
871 871 present. Bypass the corrupted usercache to show that verify works when fed by
872 872 the (uncorrupted) remote store.
873 873
874 874 $ hg -R fromcorrupt --config lfs.usercache=emptycache verify -v
875 875 repository uses revlog format 1
876 876 checking changesets
877 877 checking manifests
878 878 crosschecking files in changesets and manifests
879 879 checking files
880 880 lfs: adding 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e to the usercache
881 881 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
882 882 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
883 883 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
884 884 lfs: adding 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 to the usercache
885 885 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
886 886 lfs: adding b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c to the usercache
887 887 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
888 888 checked 5 changesets with 10 changes to 4 files
889 889
890 890 Verify will not copy/link a corrupted file from the usercache into the local
891 891 store, and poison it. (The verify with a good remote now works.)
892 892
893 893 $ rm -r fromcorrupt/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
894 894 $ hg -R fromcorrupt verify -v
895 895 repository uses revlog format 1
896 896 checking changesets
897 897 checking manifests
898 898 crosschecking files in changesets and manifests
899 899 checking files
900 l@1: unpacking 46a2f24864bc: integrity check failed on data/l:0
900 l@1: unpacking 46a2f24864bc: integrity check failed on l:0
901 901 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
902 large@0: unpacking 2c531e0992ff: integrity check failed on data/large:0
902 large@0: unpacking 2c531e0992ff: integrity check failed on large:0
903 903 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
904 904 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
905 905 checked 5 changesets with 10 changes to 4 files
906 906 2 integrity errors encountered!
907 907 (first damaged changeset appears to be 0)
908 908 [1]
909 909 $ hg -R fromcorrupt --config lfs.usercache=emptycache verify -v
910 910 repository uses revlog format 1
911 911 checking changesets
912 912 checking manifests
913 913 crosschecking files in changesets and manifests
914 914 checking files
915 915 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the usercache
916 916 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
917 917 lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
918 918 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
919 919 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
920 920 checked 5 changesets with 10 changes to 4 files
921 921
922 922 Damaging a file required by the update destination fails the update.
923 923
924 924 $ echo 'damage' >> $TESTTMP/dummy-remote/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
925 925 $ hg --config lfs.usercache=emptycache clone -v repo5 fromcorrupt2
926 926 updating to branch default
927 927 resolving manifests
928 928 abort: corrupt remote lfs object: 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
929 929 [255]
930 930
931 931 A corrupted lfs blob is not transferred from a file://remotestore to the
932 932 usercache or local store.
933 933
934 934 $ test -f emptycache/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
935 935 [1]
936 936 $ test -f fromcorrupt2/.hg/store/lfs/objects/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
937 937 [1]
938 938
939 939 $ hg -R fromcorrupt2 verify
940 940 checking changesets
941 941 checking manifests
942 942 crosschecking files in changesets and manifests
943 943 checking files
944 l@1: unpacking 46a2f24864bc: integrity check failed on data/l:0
945 large@0: unpacking 2c531e0992ff: integrity check failed on data/large:0
944 l@1: unpacking 46a2f24864bc: integrity check failed on l:0
945 large@0: unpacking 2c531e0992ff: integrity check failed on large:0
946 946 checked 5 changesets with 10 changes to 4 files
947 947 2 integrity errors encountered!
948 948 (first damaged changeset appears to be 0)
949 949 [1]
950 950
951 951 Corrupt local files are not sent upstream. (The alternate dummy remote
952 952 avoids the corrupt lfs object in the original remote.)
953 953
954 954 $ mkdir $TESTTMP/dummy-remote2
955 955 $ hg init dest
956 956 $ hg -R fromcorrupt2 --config lfs.url=file:///$TESTTMP/dummy-remote2 push -v dest
957 957 pushing to dest
958 958 searching for changes
959 959 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
960 960 abort: detected corrupt lfs object: 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
961 961 (run hg verify)
962 962 [255]
963 963
964 964 $ hg -R fromcorrupt2 --config lfs.url=file:///$TESTTMP/dummy-remote2 verify -v
965 965 repository uses revlog format 1
966 966 checking changesets
967 967 checking manifests
968 968 crosschecking files in changesets and manifests
969 969 checking files
970 l@1: unpacking 46a2f24864bc: integrity check failed on data/l:0
970 l@1: unpacking 46a2f24864bc: integrity check failed on l:0
971 971 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store
972 large@0: unpacking 2c531e0992ff: integrity check failed on data/large:0
972 large@0: unpacking 2c531e0992ff: integrity check failed on large:0
973 973 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
974 974 lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
975 975 checked 5 changesets with 10 changes to 4 files
976 976 2 integrity errors encountered!
977 977 (first damaged changeset appears to be 0)
978 978 [1]
979 979
980 980 $ cat $TESTTMP/dummy-remote2/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | $TESTDIR/f --sha256
981 981 sha256=22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
982 982 $ cat fromcorrupt2/.hg/store/lfs/objects/22/f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b | $TESTDIR/f --sha256
983 983 sha256=22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b
984 984 $ test -f $TESTTMP/dummy-remote2/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
985 985 [1]
986 986
987 987 Accessing a corrupt file will complain
988 988
989 989 $ hg --cwd fromcorrupt2 cat -r 0 large
990 abort: integrity check failed on data/large:0
990 abort: integrity check failed on large:0
991 991 [50]
992 992
993 993 lfs -> normal -> lfs round trip conversions are possible. The 'none()'
994 994 predicate on the command line will override whatever is configured globally and
995 995 locally, and ensures everything converts to a regular file. For lfs -> normal,
996 996 there's no 'lfs' destination repo requirement. For normal -> lfs, there is.
997 997
998 998 $ hg --config extensions.convert= --config 'lfs.track=none()' \
999 999 > convert repo8 convert_normal
1000 1000 initializing destination convert_normal repository
1001 1001 scanning source...
1002 1002 sorting...
1003 1003 converting...
1004 1004 2 a
1005 1005 1 b
1006 1006 0 meta
1007 1007 $ hg debugrequires -R convert_normal | grep 'lfs'
1008 1008 [1]
1009 1009 $ hg --cwd convert_normal cat a1 -r 0 -T '{rawdata}'
1010 1010 THIS-IS-LFS-BECAUSE-10-BYTES
1011 1011
1012 1012 $ hg --config extensions.convert= --config lfs.threshold=10B \
1013 1013 > convert convert_normal convert_lfs
1014 1014 initializing destination convert_lfs repository
1015 1015 scanning source...
1016 1016 sorting...
1017 1017 converting...
1018 1018 2 a
1019 1019 1 b
1020 1020 0 meta
1021 1021
1022 1022 $ hg --cwd convert_lfs cat -r 0 a1 -T '{rawdata}'
1023 1023 version https://git-lfs.github.com/spec/v1
1024 1024 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
1025 1025 size 29
1026 1026 x-is-binary 0
1027 1027 $ hg --cwd convert_lfs debugdata a1 0
1028 1028 version https://git-lfs.github.com/spec/v1
1029 1029 oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
1030 1030 size 29
1031 1031 x-is-binary 0
1032 1032 $ hg --cwd convert_lfs log -r 0 -T "{lfs_files % '{lfspointer % '{key}={value}\n'}'}"
1033 1033 version=https://git-lfs.github.com/spec/v1
1034 1034 oid=sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
1035 1035 size=29
1036 1036 x-is-binary=0
1037 1037 $ hg --cwd convert_lfs log -r 0 \
1038 1038 > -T '{lfs_files % "{get(lfspointer, "oid")}\n"}{lfs_files % "{lfspointer.oid}\n"}'
1039 1039 sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
1040 1040 sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
1041 1041 $ hg --cwd convert_lfs log -r 0 -T '{lfs_files % "{lfspointer}\n"}'
1042 1042 version=https://git-lfs.github.com/spec/v1 oid=sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024 size=29 x-is-binary=0
1043 1043 $ hg --cwd convert_lfs \
1044 1044 > log -r 'all()' -T '{rev}: {lfs_files % "{file}: {lfsoid}\n"}'
1045 1045 0: a1: 5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
1046 1046 1: a2: 5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
1047 1047 2: a2: 876dadc86a8542f9798048f2c47f51dbf8e4359aed883e8ec80c5db825f0d943
1048 1048
1049 1049 $ hg debugrequires -R convert_lfs | grep 'lfs'
1050 1050 lfs
1051 1051
1052 1052 The hashes in all stages of the conversion are unchanged.
1053 1053
1054 1054 $ hg -R repo8 log -T '{node|short}\n'
1055 1055 0fae949de7fa
1056 1056 9cd6bdffdac0
1057 1057 7f96794915f7
1058 1058 $ hg -R convert_normal log -T '{node|short}\n'
1059 1059 0fae949de7fa
1060 1060 9cd6bdffdac0
1061 1061 7f96794915f7
1062 1062 $ hg -R convert_lfs log -T '{node|short}\n'
1063 1063 0fae949de7fa
1064 1064 9cd6bdffdac0
1065 1065 7f96794915f7
1066 1066
1067 1067 This convert is trickier, because it contains deleted files (via `hg mv`)
1068 1068
1069 1069 $ hg --config extensions.convert= --config lfs.threshold=1000M \
1070 1070 > convert repo3 convert_normal2
1071 1071 initializing destination convert_normal2 repository
1072 1072 scanning source...
1073 1073 sorting...
1074 1074 converting...
1075 1075 4 commit with lfs content
1076 1076 3 renames
1077 1077 2 large to small, small to large
1078 1078 1 random modifications
1079 1079 0 switch large and small again
1080 1080 $ hg debugrequires -R convert_normal2 | grep 'lfs'
1081 1081 [1]
1082 1082 $ hg --cwd convert_normal2 debugdata large 0
1083 1083 LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS
1084 1084
1085 1085 $ hg --config extensions.convert= --config lfs.threshold=10B \
1086 1086 > convert convert_normal2 convert_lfs2
1087 1087 initializing destination convert_lfs2 repository
1088 1088 scanning source...
1089 1089 sorting...
1090 1090 converting...
1091 1091 4 commit with lfs content
1092 1092 3 renames
1093 1093 2 large to small, small to large
1094 1094 1 random modifications
1095 1095 0 switch large and small again
1096 1096 $ hg debugrequires -R convert_lfs2 | grep 'lfs'
1097 1097 lfs
1098 1098 $ hg --cwd convert_lfs2 debugdata large 0
1099 1099 version https://git-lfs.github.com/spec/v1
1100 1100 oid sha256:66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
1101 1101 size 39
1102 1102 x-is-binary 0
1103 1103
1104 1104 Committing deleted files works:
1105 1105
1106 1106 $ hg init $TESTTMP/repo-del
1107 1107 $ cd $TESTTMP/repo-del
1108 1108 $ echo 1 > A
1109 1109 $ hg commit -m 'add A' -A A
1110 1110 $ hg rm A
1111 1111 $ hg commit -m 'rm A'
1112 1112
1113 1113 Bad .hglfs files will block the commit with a useful message
1114 1114
1115 1115 $ cat > .hglfs << EOF
1116 1116 > [track]
1117 1117 > **.test = size(">5B")
1118 1118 > bad file ... no commit
1119 1119 > EOF
1120 1120
1121 1121 $ echo x > file.txt
1122 1122 $ hg ci -Aqm 'should fail'
1123 1123 config error at .hglfs:3: bad file ... no commit
1124 1124 [30]
1125 1125
1126 1126 $ cat > .hglfs << EOF
1127 1127 > [track]
1128 1128 > **.test = size(">5B")
1129 1129 > ** = nonexistent()
1130 1130 > EOF
1131 1131
1132 1132 $ hg ci -Aqm 'should fail'
1133 1133 abort: parse error in .hglfs: unknown identifier: nonexistent
1134 1134 [255]
1135 1135
1136 1136 '**' works out to mean all files.
1137 1137
1138 1138 $ cat > .hglfs << EOF
1139 1139 > [track]
1140 1140 > path:.hglfs = none()
1141 1141 > **.test = size(">5B")
1142 1142 > **.exclude = none()
1143 1143 > ** = size(">10B")
1144 1144 > EOF
1145 1145
1146 1146 The LFS policy takes effect without tracking the .hglfs file
1147 1147
1148 1148 $ echo 'largefile' > lfs.test
1149 1149 $ echo '012345678901234567890' > nolfs.exclude
1150 1150 $ echo '01234567890123456' > lfs.catchall
1151 1151 $ hg add *
1152 1152 $ hg ci -qm 'before add .hglfs'
1153 1153 $ hg log -r . -T '{rev}: {lfs_files % "{file}: {lfsoid}\n"}\n'
1154 1154 2: lfs.catchall: d4ec46c2869ba22eceb42a729377432052d9dd75d82fc40390ebaadecee87ee9
1155 1155 lfs.test: 5489e6ced8c36a7b267292bde9fd5242a5f80a7482e8f23fa0477393dfaa4d6c
1156 1156
1157 1157 The .hglfs file works when tracked
1158 1158
1159 1159 $ echo 'largefile2' > lfs.test
1160 1160 $ echo '012345678901234567890a' > nolfs.exclude
1161 1161 $ echo '01234567890123456a' > lfs.catchall
1162 1162 $ hg ci -Aqm 'after adding .hglfs'
1163 1163 $ hg log -r . -T '{rev}: {lfs_files % "{file}: {lfsoid}\n"}\n'
1164 1164 3: lfs.catchall: 31f43b9c62b540126b0ad5884dc013d21a61c9329b77de1fceeae2fc58511573
1165 1165 lfs.test: 8acd23467967bc7b8cc5a280056589b0ba0b17ff21dbd88a7b6474d6290378a6
1166 1166
1167 1167 The LFS policy stops when the .hglfs is gone
1168 1168
1169 1169 $ mv .hglfs .hglfs_
1170 1170 $ echo 'largefile3' > lfs.test
1171 1171 $ echo '012345678901234567890abc' > nolfs.exclude
1172 1172 $ echo '01234567890123456abc' > lfs.catchall
1173 1173 $ hg ci -qm 'file test' -X .hglfs
1174 1174 $ hg log -r . -T '{rev}: {lfs_files % "{file}: {lfsoid}\n"}\n'
1175 1175 4:
1176 1176
1177 1177 $ mv .hglfs_ .hglfs
1178 1178 $ echo '012345678901234567890abc' > lfs.test
1179 1179 $ hg ci -m 'back to lfs'
1180 1180 $ hg rm lfs.test
1181 1181 $ hg ci -qm 'remove lfs'
1182 1182
1183 1183 {lfs_files} will list deleted files too
1184 1184
1185 1185 $ hg log -T "{lfs_files % '{rev} {file}: {lfspointer.oid}\n'}"
1186 1186 6 lfs.test:
1187 1187 5 lfs.test: sha256:43f8f41171b6f62a6b61ba4ce98a8a6c1649240a47ebafd43120aa215ac9e7f6
1188 1188 3 lfs.catchall: sha256:31f43b9c62b540126b0ad5884dc013d21a61c9329b77de1fceeae2fc58511573
1189 1189 3 lfs.test: sha256:8acd23467967bc7b8cc5a280056589b0ba0b17ff21dbd88a7b6474d6290378a6
1190 1190 2 lfs.catchall: sha256:d4ec46c2869ba22eceb42a729377432052d9dd75d82fc40390ebaadecee87ee9
1191 1191 2 lfs.test: sha256:5489e6ced8c36a7b267292bde9fd5242a5f80a7482e8f23fa0477393dfaa4d6c
1192 1192
1193 1193 $ hg log -r 'file("set:lfs()")' -T '{rev} {join(lfs_files, ", ")}\n'
1194 1194 2 lfs.catchall, lfs.test
1195 1195 3 lfs.catchall, lfs.test
1196 1196 5 lfs.test
1197 1197 6 lfs.test
1198 1198
1199 1199 $ cd ..
1200 1200
1201 1201 Unbundling adds a requirement to a non-lfs repo, if necessary.
1202 1202
1203 1203 $ hg bundle -R $TESTTMP/repo-del -qr 0 --base null nolfs.hg
1204 1204 $ hg bundle -R convert_lfs2 -qr tip --base null lfs.hg
1205 1205 $ hg init unbundle
1206 1206 $ hg pull -R unbundle -q nolfs.hg
1207 1207 $ hg debugrequires -R unbundle | grep lfs
1208 1208 [1]
1209 1209 $ hg pull -R unbundle -q lfs.hg
1210 1210 $ hg debugrequires -R unbundle | grep lfs
1211 1211 lfs
1212 1212
1213 1213 $ hg init no_lfs
1214 1214 $ cat >> no_lfs/.hg/hgrc <<EOF
1215 1215 > [experimental]
1216 1216 > changegroup3 = True
1217 1217 > [extensions]
1218 1218 > lfs=!
1219 1219 > EOF
1220 1220 $ cp -R no_lfs no_lfs2
1221 1221
1222 1222 Pushing from a local lfs repo to a local repo without an lfs requirement and
1223 1223 with lfs disabled, fails.
1224 1224
1225 1225 $ hg push -R convert_lfs2 no_lfs
1226 1226 pushing to no_lfs
1227 1227 abort: required features are not supported in the destination: lfs
1228 1228 [255]
1229 1229 $ hg debugrequires -R no_lfs/ | grep lfs
1230 1230 [1]
1231 1231
1232 1232 Pulling from a local lfs repo to a local repo without an lfs requirement and
1233 1233 with lfs disabled, fails.
1234 1234
1235 1235 $ hg pull -R no_lfs2 convert_lfs2
1236 1236 pulling from convert_lfs2
1237 1237 abort: required features are not supported in the destination: lfs
1238 1238 [255]
1239 1239 $ hg debugrequires -R no_lfs2/ | grep lfs
1240 1240 [1]
@@ -1,225 +1,225 b''
1 1 #testcases lfs-on lfs-off
2 2
3 3 #if lfs-on
4 4 $ cat >> $HGRCPATH <<EOF
5 5 > [extensions]
6 6 > lfs =
7 7 > EOF
8 8 #endif
9 9
10 10 $ . "$TESTDIR/narrow-library.sh"
11 11
12 12 create full repo
13 13
14 14 $ hg init master
15 15 $ cd master
16 16 $ cat >> .hg/hgrc <<EOF
17 17 > [narrow]
18 18 > serveellipses=True
19 19 > EOF
20 20
21 21 $ mkdir inside
22 22 $ echo 1 > inside/f
23 23 $ mkdir inside2
24 24 $ echo 1 > inside2/f
25 25 $ mkdir outside
26 26 $ echo 1 > outside/f
27 27 $ hg ci -Aqm 'initial'
28 28
29 29 $ echo 2 > inside/f
30 30 $ hg ci -qm 'inside 2'
31 31
32 32 $ echo 2 > inside2/f
33 33 $ hg ci -qm 'inside2 2'
34 34
35 35 $ echo 2 > outside/f
36 36 $ hg ci -qm 'outside 2'
37 37
38 38 $ cd ..
39 39
40 40 $ hg clone --narrow ssh://user@dummy/master narrow --include inside
41 41 requesting all changes
42 42 adding changesets
43 43 adding manifests
44 44 adding file changes
45 45 added 3 changesets with 2 changes to 1 files
46 46 new changesets *:* (glob)
47 47 updating to branch default
48 48 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 49
50 50 $ hg clone --narrow ssh://user@dummy/master narrow2 --include inside --include inside2
51 51 requesting all changes
52 52 adding changesets
53 53 adding manifests
54 54 adding file changes
55 55 added 4 changesets with 4 changes to 2 files
56 56 new changesets *:* (glob)
57 57 updating to branch default
58 58 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 59
60 60 Can push to wider repo if change does not affect paths in wider repo that are
61 61 not also in narrower repo
62 62
63 63 $ cd narrow
64 64 $ echo 3 > inside/f
65 65 $ hg ci -m 'inside 3'
66 66 $ hg push ssh://user@dummy/narrow2
67 67 pushing to ssh://user@dummy/narrow2
68 68 searching for changes
69 69 remote: adding changesets
70 70 remote: adding manifests
71 71 remote: adding file changes
72 72 remote: added 1 changesets with 1 changes to 1 files
73 73
74 74 Can push to narrower repo if change affects only paths within remote's
75 75 narrow spec
76 76
77 77 $ cd ../narrow2
78 78 $ cat >> .hg/hgrc <<EOF
79 79 > [narrow]
80 80 > serveellipses=True
81 81 > EOF
82 82 $ hg co -r 'desc("inside 3")'
83 83 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 84 $ echo 4 > inside/f
85 85 $ hg ci -m 'inside 4'
86 86 $ hg push ssh://user@dummy/narrow
87 87 pushing to ssh://user@dummy/narrow
88 88 searching for changes
89 89 remote: adding changesets
90 90 remote: adding manifests
91 91 remote: adding file changes
92 92 remote: added 1 changesets with 1 changes to 1 files
93 93
94 94 Can push to narrow repo if change affects only paths outside remote's
95 95 narrow spec
96 96
97 97 $ echo 3 > inside2/f
98 98 $ hg ci -m 'inside2 3'
99 99 TODO: this should be successful
100 100 $ hg push ssh://user@dummy/narrow
101 101 pushing to ssh://user@dummy/narrow
102 102 searching for changes
103 103 remote: adding changesets
104 104 remote: adding manifests
105 105 remote: adding file changes
106 106 remote: transaction abort!
107 107 remote: rollback completed
108 108 remote: abort: data/inside2/f@4a1aa07735e673e20c00fae80f40dc301ee30616: unknown parent (reporevlogstore !)
109 109 remote: abort: data/inside2/f/index@4a1aa07735e6: no node (reposimplestore !)
110 110 abort: stream ended unexpectedly (got 0 bytes, expected 4)
111 111 [255]
112 112
113 113 Can pull from wider repo if change affects only paths outside remote's
114 114 narrow spec
115 115 $ echo 4 > inside2/f
116 116 $ hg ci -m 'inside2 4'
117 117 $ hg log -G -T '{rev} {node|short} {files}\n'
118 118 @ 7 d78a96df731d inside2/f
119 119 |
120 120 o 6 8c26f5218962 inside2/f
121 121 |
122 122 o 5 ba3480e2f9de inside/f
123 123 |
124 124 o 4 4e5edd526618 inside/f
125 125 |
126 126 o 3 81e7e07b7ab0 outside/f
127 127 |
128 128 o 2 f3993b8c0c2b inside2/f
129 129 |
130 130 o 1 8cd66ca966b4 inside/f
131 131 |
132 132 o 0 c8057d6f53ab inside/f inside2/f outside/f
133 133
134 134 $ cd ../narrow
135 135 $ hg log -G -T '{rev} {node|short} {files}\n'
136 136 o 4 ba3480e2f9de inside/f
137 137 |
138 138 @ 3 4e5edd526618 inside/f
139 139 |
140 140 o 2 81e7e07b7ab0 outside/f
141 141 |
142 142 o 1 8cd66ca966b4 inside/f
143 143 |
144 144 o 0 c8057d6f53ab inside/f inside2/f outside/f
145 145
146 146 $ hg pull ssh://user@dummy/narrow2
147 147 pulling from ssh://user@dummy/narrow2
148 148 searching for changes
149 149 adding changesets
150 150 adding manifests
151 151 adding file changes
152 152 added 1 changesets with 0 changes to 0 files
153 153 new changesets d78a96df731d
154 154 (run 'hg update' to get a working copy)
155 155
156 156 Check that the resulting history is valid in the full repo
157 157
158 158 $ cd ../narrow2
159 159 $ hg push ssh://user@dummy/master
160 160 pushing to ssh://user@dummy/master
161 161 searching for changes
162 162 remote: adding changesets
163 163 remote: adding manifests
164 164 remote: adding file changes
165 165 remote: added 4 changesets with 4 changes to 2 files
166 166 $ cd ../master
167 167 $ hg verify
168 168 checking changesets
169 169 checking manifests
170 170 crosschecking files in changesets and manifests
171 171 checking files
172 172 checked 8 changesets with 10 changes to 3 files
173 173
174 174 Can not push to wider repo if change affects paths in wider repo that are
175 175 not also in narrower repo
176 176 $ cd ../master
177 177 $ hg co -r 'desc("inside2 4")'
178 178 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
179 179 $ echo 5 > inside2/f
180 180 $ hg ci -m 'inside2 5'
181 181 $ hg log -G -T '{rev} {node|short} {files}\n'
182 182 @ 8 5970befb64ba inside2/f
183 183 |
184 184 o 7 d78a96df731d inside2/f
185 185 |
186 186 o 6 8c26f5218962 inside2/f
187 187 |
188 188 o 5 ba3480e2f9de inside/f
189 189 |
190 190 o 4 4e5edd526618 inside/f
191 191 |
192 192 o 3 81e7e07b7ab0 outside/f
193 193 |
194 194 o 2 f3993b8c0c2b inside2/f
195 195 |
196 196 o 1 8cd66ca966b4 inside/f
197 197 |
198 198 o 0 c8057d6f53ab inside/f inside2/f outside/f
199 199
200 200 $ cd ../narrow
201 201 $ hg pull
202 202 pulling from ssh://user@dummy/master
203 203 searching for changes
204 204 adding changesets
205 205 adding manifests
206 206 adding file changes
207 207 added 1 changesets with 0 changes to 0 files
208 208 new changesets * (glob)
209 209 (run 'hg update' to get a working copy)
210 210 TODO: this should tell the user that their narrow clone does not have the
211 211 necessary content to be able to push to the target
212 212
213 213 TODO: lfs shouldn't abort like this
214 214 $ hg push ssh://user@dummy/narrow2 || true
215 215 pushing to ssh://user@dummy/narrow2
216 216 searching for changes
217 217 remote: adding changesets
218 218 remote: adding manifests
219 219 remote: adding file changes
220 220 remote: added 1 changesets with 0 changes to 0 files (no-lfs-on !)
221 remote: error: pretxnchangegroup.lfs hook raised an exception: data/inside2/f@f59b4e0218355383d2789196f1092abcf2262b0c: no match found (lfs-on !)
221 remote: error: pretxnchangegroup.lfs hook raised an exception: inside2/f@f59b4e0218355383d2789196f1092abcf2262b0c: no match found (lfs-on !)
222 222 remote: transaction abort! (lfs-on !)
223 223 remote: rollback completed (lfs-on !)
224 remote: abort: data/inside2/f@f59b4e0218355383d2789196f1092abcf2262b0c: no match found (lfs-on !)
224 remote: abort: inside2/f@f59b4e0218355383d2789196f1092abcf2262b0c: no match found (lfs-on !)
225 225 abort: stream ended unexpectedly (got 0 bytes, expected 4) (lfs-on !)
@@ -1,364 +1,364 b''
1 1 #require reporevlogstore
2 2
3 3 prepare repo
4 4
5 5 $ hg init a
6 6 $ cd a
7 7 $ echo "some text" > FOO.txt
8 8 $ echo "another text" > bar.txt
9 9 $ echo "more text" > QUICK.txt
10 10 $ hg add
11 11 adding FOO.txt
12 12 adding QUICK.txt
13 13 adding bar.txt
14 14 $ hg ci -mtest1
15 15
16 16 verify
17 17
18 18 $ hg verify
19 19 checking changesets
20 20 checking manifests
21 21 crosschecking files in changesets and manifests
22 22 checking files
23 23 checked 1 changesets with 3 changes to 3 files
24 24
25 25 verify with journal
26 26
27 27 $ touch .hg/store/journal
28 28 $ hg verify
29 29 abandoned transaction found - run hg recover
30 30 checking changesets
31 31 checking manifests
32 32 crosschecking files in changesets and manifests
33 33 checking files
34 34 checked 1 changesets with 3 changes to 3 files
35 35 $ rm .hg/store/journal
36 36
37 37 introduce some bugs in repo
38 38
39 39 $ cd .hg/store/data
40 40 $ mv _f_o_o.txt.i X_f_o_o.txt.i
41 41 $ mv bar.txt.i xbar.txt.i
42 42 $ rm _q_u_i_c_k.txt.i
43 43
44 44 $ hg verify
45 45 checking changesets
46 46 checking manifests
47 47 crosschecking files in changesets and manifests
48 48 checking files
49 49 warning: revlog 'data/FOO.txt.i' not in fncache!
50 50 0: empty or missing FOO.txt
51 51 FOO.txt@0: manifest refers to unknown revision f62022d3d590
52 52 warning: revlog 'data/QUICK.txt.i' not in fncache!
53 53 0: empty or missing QUICK.txt
54 54 QUICK.txt@0: manifest refers to unknown revision 88b857db8eba
55 55 warning: revlog 'data/bar.txt.i' not in fncache!
56 56 0: empty or missing bar.txt
57 57 bar.txt@0: manifest refers to unknown revision 256559129457
58 58 checked 1 changesets with 0 changes to 3 files
59 59 3 warnings encountered!
60 60 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
61 61 6 integrity errors encountered!
62 62 (first damaged changeset appears to be 0)
63 63 [1]
64 64
65 65 $ cd ../../..
66 66 $ cd ..
67 67
68 68 Set up a repo for testing missing revlog entries
69 69
70 70 $ hg init missing-entries
71 71 $ cd missing-entries
72 72 $ echo 0 > file
73 73 $ hg ci -Aqm0
74 74 $ cp -R .hg/store .hg/store-partial
75 75 $ echo 1 > file
76 76 $ hg ci -Aqm1
77 77 $ cp -R .hg/store .hg/store-full
78 78
79 79 Entire changelog missing
80 80
81 81 $ rm .hg/store/00changelog.*
82 82 $ hg verify -q
83 83 0: empty or missing changelog
84 84 manifest@0: d0b6632564d4 not in changesets
85 85 manifest@1: 941fc4534185 not in changesets
86 86 3 integrity errors encountered!
87 87 (first damaged changeset appears to be 0)
88 88 [1]
89 89 $ cp -R .hg/store-full/. .hg/store
90 90
91 91 Entire manifest log missing
92 92
93 93 $ rm .hg/store/00manifest.*
94 94 $ hg verify -q
95 95 0: empty or missing manifest
96 96 1 integrity errors encountered!
97 97 (first damaged changeset appears to be 0)
98 98 [1]
99 99 $ cp -R .hg/store-full/. .hg/store
100 100
101 101 Entire filelog missing
102 102
103 103 $ rm .hg/store/data/file.*
104 104 $ hg verify -q
105 105 warning: revlog 'data/file.i' not in fncache!
106 106 0: empty or missing file
107 107 file@0: manifest refers to unknown revision 362fef284ce2
108 108 file@1: manifest refers to unknown revision c10f2164107d
109 109 1 warnings encountered!
110 110 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
111 111 3 integrity errors encountered!
112 112 (first damaged changeset appears to be 0)
113 113 [1]
114 114 $ cp -R .hg/store-full/. .hg/store
115 115
116 116 Entire changelog and manifest log missing
117 117
118 118 $ rm .hg/store/00changelog.*
119 119 $ rm .hg/store/00manifest.*
120 120 $ hg verify -q
121 121 warning: orphan data file 'data/file.i'
122 122 1 warnings encountered!
123 123 $ cp -R .hg/store-full/. .hg/store
124 124
125 125 Entire changelog and filelog missing
126 126
127 127 $ rm .hg/store/00changelog.*
128 128 $ rm .hg/store/data/file.*
129 129 $ hg verify -q
130 130 0: empty or missing changelog
131 131 manifest@0: d0b6632564d4 not in changesets
132 132 manifest@1: 941fc4534185 not in changesets
133 133 warning: revlog 'data/file.i' not in fncache!
134 134 ?: empty or missing file
135 135 file@0: manifest refers to unknown revision 362fef284ce2
136 136 file@1: manifest refers to unknown revision c10f2164107d
137 137 1 warnings encountered!
138 138 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
139 139 6 integrity errors encountered!
140 140 (first damaged changeset appears to be 0)
141 141 [1]
142 142 $ cp -R .hg/store-full/. .hg/store
143 143
144 144 Entire manifest log and filelog missing
145 145
146 146 $ rm .hg/store/00manifest.*
147 147 $ rm .hg/store/data/file.*
148 148 $ hg verify -q
149 149 0: empty or missing manifest
150 150 warning: revlog 'data/file.i' not in fncache!
151 151 0: empty or missing file
152 152 1 warnings encountered!
153 153 hint: run "hg debugrebuildfncache" to recover from corrupt fncache
154 154 2 integrity errors encountered!
155 155 (first damaged changeset appears to be 0)
156 156 [1]
157 157 $ cp -R .hg/store-full/. .hg/store
158 158
159 159 Changelog missing entry
160 160
161 161 $ cp -f .hg/store-partial/00changelog.* .hg/store
162 162 $ hg verify -q
163 163 manifest@?: rev 1 points to nonexistent changeset 1
164 164 manifest@?: 941fc4534185 not in changesets
165 165 file@?: rev 1 points to nonexistent changeset 1
166 166 (expected 0)
167 167 1 warnings encountered!
168 168 3 integrity errors encountered!
169 169 [1]
170 170 $ cp -R .hg/store-full/. .hg/store
171 171
172 172 Manifest log missing entry
173 173
174 174 $ cp -f .hg/store-partial/00manifest.* .hg/store
175 175 $ hg verify -q
176 176 manifest@1: changeset refers to unknown revision 941fc4534185
177 177 file@1: c10f2164107d not in manifests
178 178 2 integrity errors encountered!
179 179 (first damaged changeset appears to be 1)
180 180 [1]
181 181 $ cp -R .hg/store-full/. .hg/store
182 182
183 183 Filelog missing entry
184 184
185 185 $ cp -f .hg/store-partial/data/file.* .hg/store/data
186 186 $ hg verify -q
187 187 file@1: manifest refers to unknown revision c10f2164107d
188 188 1 integrity errors encountered!
189 189 (first damaged changeset appears to be 1)
190 190 [1]
191 191 $ cp -R .hg/store-full/. .hg/store
192 192
193 193 Changelog and manifest log missing entry
194 194
195 195 $ cp -f .hg/store-partial/00changelog.* .hg/store
196 196 $ cp -f .hg/store-partial/00manifest.* .hg/store
197 197 $ hg verify -q
198 198 file@?: rev 1 points to nonexistent changeset 1
199 199 (expected 0)
200 200 file@?: c10f2164107d not in manifests
201 201 1 warnings encountered!
202 202 2 integrity errors encountered!
203 203 [1]
204 204 $ cp -R .hg/store-full/. .hg/store
205 205
206 206 Changelog and filelog missing entry
207 207
208 208 $ cp -f .hg/store-partial/00changelog.* .hg/store
209 209 $ cp -f .hg/store-partial/data/file.* .hg/store/data
210 210 $ hg verify -q
211 211 manifest@?: rev 1 points to nonexistent changeset 1
212 212 manifest@?: 941fc4534185 not in changesets
213 213 file@?: manifest refers to unknown revision c10f2164107d
214 214 3 integrity errors encountered!
215 215 [1]
216 216 $ cp -R .hg/store-full/. .hg/store
217 217
218 218 Manifest and filelog missing entry
219 219
220 220 $ cp -f .hg/store-partial/00manifest.* .hg/store
221 221 $ cp -f .hg/store-partial/data/file.* .hg/store/data
222 222 $ hg verify -q
223 223 manifest@1: changeset refers to unknown revision 941fc4534185
224 224 1 integrity errors encountered!
225 225 (first damaged changeset appears to be 1)
226 226 [1]
227 227 $ cp -R .hg/store-full/. .hg/store
228 228
229 229 Corrupt changelog base node to cause failure to read revision
230 230
231 231 $ printf abcd | dd conv=notrunc of=.hg/store/00changelog.i bs=1 seek=16 \
232 232 > 2> /dev/null
233 233 $ hg verify -q
234 234 0: unpacking changeset 08b1860757c2: * (glob)
235 235 manifest@?: rev 0 points to unexpected changeset 0
236 236 manifest@?: d0b6632564d4 not in changesets
237 237 file@?: rev 0 points to unexpected changeset 0
238 238 (expected 1)
239 239 1 warnings encountered!
240 240 4 integrity errors encountered!
241 241 (first damaged changeset appears to be 0)
242 242 [1]
243 243 $ cp -R .hg/store-full/. .hg/store
244 244
245 245 Corrupt manifest log base node to cause failure to read revision
246 246
247 247 $ printf abcd | dd conv=notrunc of=.hg/store/00manifest.i bs=1 seek=16 \
248 248 > 2> /dev/null
249 249 $ hg verify -q
250 250 manifest@0: reading delta d0b6632564d4: * (glob)
251 251 file@0: 362fef284ce2 not in manifests
252 252 2 integrity errors encountered!
253 253 (first damaged changeset appears to be 0)
254 254 [1]
255 255 $ cp -R .hg/store-full/. .hg/store
256 256
257 257 Corrupt filelog base node to cause failure to read revision
258 258
259 259 $ printf abcd | dd conv=notrunc of=.hg/store/data/file.i bs=1 seek=16 \
260 260 > 2> /dev/null
261 261 $ hg verify -q
262 262 file@0: unpacking 362fef284ce2: * (glob)
263 263 1 integrity errors encountered!
264 264 (first damaged changeset appears to be 0)
265 265 [1]
266 266 $ cp -R .hg/store-full/. .hg/store
267 267
268 268 $ cd ..
269 269
270 270 test changelog without a manifest
271 271
272 272 $ hg init b
273 273 $ cd b
274 274 $ hg branch foo
275 275 marked working directory as branch foo
276 276 (branches are permanent and global, did you want a bookmark?)
277 277 $ hg ci -m branchfoo
278 278 $ hg verify
279 279 checking changesets
280 280 checking manifests
281 281 crosschecking files in changesets and manifests
282 282 checking files
283 283 checked 1 changesets with 0 changes to 0 files
284 284
285 285 test revlog corruption
286 286
287 287 $ touch a
288 288 $ hg add a
289 289 $ hg ci -m a
290 290
291 291 $ echo 'corrupted' > b
292 292 $ dd if=.hg/store/data/a.i of=start bs=1 count=20 2>/dev/null
293 293 $ cat start b > .hg/store/data/a.i
294 294
295 295 $ hg verify
296 296 checking changesets
297 297 checking manifests
298 298 crosschecking files in changesets and manifests
299 299 checking files
300 a@1: broken revlog! (index data/a is corrupted)
300 a@1: broken revlog! (index a is corrupted)
301 301 warning: orphan data file 'data/a.i'
302 302 checked 2 changesets with 0 changes to 1 files
303 303 1 warnings encountered!
304 304 1 integrity errors encountered!
305 305 (first damaged changeset appears to be 1)
306 306 [1]
307 307
308 308 $ cd ..
309 309
310 310 test revlog format 0
311 311
312 312 $ revlog-formatv0.py
313 313 $ cd formatv0
314 314 $ hg verify
315 315 repository uses revlog format 0
316 316 checking changesets
317 317 checking manifests
318 318 crosschecking files in changesets and manifests
319 319 checking files
320 320 checked 1 changesets with 1 changes to 1 files
321 321 $ cd ..
322 322
323 323 test flag processor and skipflags
324 324
325 325 $ hg init skipflags
326 326 $ cd skipflags
327 327 $ cat >> .hg/hgrc <<EOF
328 328 > [extensions]
329 329 > flagprocessor=$RUNTESTDIR/flagprocessorext.py
330 330 > EOF
331 331 $ echo '[BASE64]content' > base64
332 332 $ hg commit -Aqm 'flag processor content' base64
333 333 $ hg verify
334 334 checking changesets
335 335 checking manifests
336 336 crosschecking files in changesets and manifests
337 337 checking files
338 338 checked 1 changesets with 1 changes to 1 files
339 339
340 340 $ cat >> $TESTTMP/break-base64.py <<EOF
341 341 > import base64
342 342 > base64.b64decode=lambda x: x
343 343 > EOF
344 344 $ cat >> .hg/hgrc <<EOF
345 345 > breakbase64=$TESTTMP/break-base64.py
346 346 > EOF
347 347
348 348 $ hg verify
349 349 checking changesets
350 350 checking manifests
351 351 crosschecking files in changesets and manifests
352 352 checking files
353 base64@0: unpacking 794cee7777cb: integrity check failed on data/base64:0
353 base64@0: unpacking 794cee7777cb: integrity check failed on base64:0
354 354 checked 1 changesets with 1 changes to 1 files
355 355 1 integrity errors encountered!
356 356 (first damaged changeset appears to be 0)
357 357 [1]
358 358 $ hg verify --config verify.skipflags=2147483647
359 359 checking changesets
360 360 checking manifests
361 361 crosschecking files in changesets and manifests
362 362 checking files
363 363 checked 1 changesets with 1 changes to 1 files
364 364
General Comments 0
You need to be logged in to leave comments. Login now