mercurial/revlogutils/rewrite.py
branchstable
changeset 47816 32e21ac3adb1
parent 47815 b30a53ffbf9b
child 47817 855463b5fe49
equal deleted inserted replaced
47815:b30a53ffbf9b 47816:32e21ac3adb1
     8 # GNU General Public License version 2 or any later version.
     8 # GNU General Public License version 2 or any later version.
     9 
     9 
    10 import binascii
    10 import binascii
    11 import contextlib
    11 import contextlib
    12 import os
    12 import os
       
    13 import struct
    13 
    14 
    14 from ..node import (
    15 from ..node import (
    15     nullrev,
    16     nullrev,
    16 )
    17 )
    17 from .constants import (
    18 from .constants import (
   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: