559 rl._loadindex() |
560 rl._loadindex() |
560 finally: |
561 finally: |
561 util.tryunlink(new_file_path) |
562 util.tryunlink(new_file_path) |
562 |
563 |
563 |
564 |
564 def _is_revision_affected(ui, fl, filerev, path): |
565 def _is_revision_affected(fl, filerev, metadata_cache=None): |
565 """Mercurial currently (5.9rc0) uses `p1 == nullrev and p2 != nullrev` as a |
566 """Mercurial currently (5.9rc0) uses `p1 == nullrev and p2 != nullrev` as a |
566 special meaning compared to the reverse in the context of filelog-based |
567 special meaning compared to the reverse in the context of filelog-based |
567 copytracing. issue6528 exists because new code assumed that parent ordering |
568 copytracing. issue6528 exists because new code assumed that parent ordering |
568 didn't matter, so this detects if the revision contains metadata (since |
569 didn't matter, so this detects if the revision contains metadata (since |
569 it's only used for filelog-based copytracing) and its parents are in the |
570 it's only used for filelog-based copytracing) and its parents are in the |
572 raw_text = fl.rawdata(filerev) |
573 raw_text = fl.rawdata(filerev) |
573 except error.CensoredNodeError: |
574 except error.CensoredNodeError: |
574 # We don't care about censored nodes as they never carry metadata |
575 # We don't care about censored nodes as they never carry metadata |
575 return False |
576 return False |
576 has_meta = raw_text.startswith(b'\x01\n') |
577 has_meta = raw_text.startswith(b'\x01\n') |
|
578 if metadata_cache is not None: |
|
579 metadata_cache[filerev] = has_meta |
577 if has_meta: |
580 if has_meta: |
578 (p1, p2) = fl.parentrevs(filerev) |
581 (p1, p2) = fl.parentrevs(filerev) |
579 if p1 != nullrev and p2 == nullrev: |
582 if p1 != nullrev and p2 == nullrev: |
580 return True |
583 return True |
581 return False |
584 return False |
|
585 |
|
586 |
|
587 def _is_revision_affected_fast(repo, fl, filerev, metadata_cache): |
|
588 """Optimization fast-path for `_is_revision_affected`. |
|
589 |
|
590 `metadata_cache` is a dict of `{rev: has_metadata}` which allows any |
|
591 revision to check if its base has metadata, saving computation of the full |
|
592 text, instead looking at the current delta. |
|
593 |
|
594 This optimization only works if the revisions are looked at in order.""" |
|
595 rl = fl._revlog |
|
596 |
|
597 if rl.iscensored(filerev): |
|
598 # Censored revisions don't contain metadata, so they cannot be affected |
|
599 metadata_cache[filerev] = False |
|
600 return False |
|
601 |
|
602 p1, p2 = rl.parentrevs(filerev) |
|
603 if p1 == nullrev or p2 != nullrev: |
|
604 return False |
|
605 |
|
606 delta_parent = rl.deltaparent(filerev) |
|
607 parent_has_metadata = metadata_cache.get(delta_parent) |
|
608 if parent_has_metadata is None: |
|
609 is_affected = _is_revision_affected(fl, filerev, metadata_cache) |
|
610 return is_affected |
|
611 |
|
612 chunk = rl._chunk(filerev) |
|
613 if not len(chunk): |
|
614 # No diff for this revision |
|
615 return parent_has_metadata |
|
616 |
|
617 header_length = 12 |
|
618 if len(chunk) < header_length: |
|
619 raise error.Abort(_(b"patch cannot be decoded")) |
|
620 |
|
621 start, _end, _length = struct.unpack(b">lll", chunk[:header_length]) |
|
622 |
|
623 if start < 2: # len(b'\x01\n') == 2 |
|
624 # This delta does *something* to the metadata marker (if any). |
|
625 # Check it the slow way |
|
626 is_affected = _is_revision_affected(fl, filerev, metadata_cache) |
|
627 return is_affected |
|
628 |
|
629 # The diff did not remove or add the metadata header, it's then in the same |
|
630 # situation as its parent |
|
631 metadata_cache[filerev] = parent_has_metadata |
|
632 return parent_has_metadata |
582 |
633 |
583 |
634 |
584 def _from_report(ui, repo, context, from_report, dry_run): |
635 def _from_report(ui, repo, context, from_report, dry_run): |
585 """ |
636 """ |
586 Fix the revisions given in the `from_report` file, but still checks if the |
637 Fix the revisions given in the `from_report` file, but still checks if the |
601 fl.rev(binascii.unhexlify(n)) for n in filenodes.split(b',') |
652 fl.rev(binascii.unhexlify(n)) for n in filenodes.split(b',') |
602 ) |
653 ) |
603 excluded = set() |
654 excluded = set() |
604 |
655 |
605 for filerev in to_fix: |
656 for filerev in to_fix: |
606 if _is_revision_affected(ui, fl, filerev, filename): |
657 if _is_revision_affected(fl, filerev): |
607 msg = b"found affected revision %d for filelog '%s'\n" |
658 msg = b"found affected revision %d for filelog '%s'\n" |
608 ui.warn(msg % (filerev, filename)) |
659 ui.warn(msg % (filerev, filename)) |
609 else: |
660 else: |
610 msg = _(b"revision %s of file '%s' is not affected\n") |
661 msg = _(b"revision %s of file '%s' is not affected\n") |
611 msg %= (binascii.hexlify(fl.node(filerev)), filename) |
662 msg %= (binascii.hexlify(fl.node(filerev)), filename) |
661 filename = _get_filename_from_filelog_index(path) |
712 filename = _get_filename_from_filelog_index(path) |
662 fl = _filelog_from_filename(repo, filename) |
713 fl = _filelog_from_filename(repo, filename) |
663 |
714 |
664 # Set of filerevs (or hex filenodes if `to_report`) that need fixing |
715 # Set of filerevs (or hex filenodes if `to_report`) that need fixing |
665 to_fix = set() |
716 to_fix = set() |
|
717 metadata_cache = {} |
666 for filerev in fl.revs(): |
718 for filerev in fl.revs(): |
667 # TODO speed up by looking at the start of the delta |
719 affected = _is_revision_affected_fast( |
668 # If it hasn't changed, it's not worth looking at the other revs |
720 repo, fl, filerev, metadata_cache |
669 # in the same chain |
721 ) |
670 affected = _is_revision_affected(ui, fl, filerev, path) |
|
671 if affected: |
722 if affected: |
672 msg = b"found affected revision %d for filelog '%s'\n" |
723 msg = b"found affected revision %d for filelog '%s'\n" |
673 ui.warn(msg % (filerev, path)) |
724 ui.warn(msg % (filerev, path)) |
674 found_nothing = False |
725 found_nothing = False |
675 if not dry_run: |
726 if not dry_run: |