D6643: convert: add a config option to help doing identity hg->hg conversion

valentin.gatienbaron (Valentin Gatien-Baron) phabricator at mercurial-scm.org
Mon Jul 15 18:05:09 EDT 2019


valentin.gatienbaron updated this revision to Diff 15916.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D6643?vs=15904&id=15916

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D6643/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D6643

AFFECTED FILES
  hgext/convert/__init__.py
  hgext/convert/common.py
  hgext/convert/hg.py
  hgext/eol.py
  hgext/keyword.py
  hgext/lfs/__init__.py
  hgext/remotefilelog/shallowrepo.py
  mercurial/configitems.py
  mercurial/localrepo.py
  mercurial/repository.py
  tests/test-commandserver.t
  tests/test-convert-identity.t
  tests/test-convert.t

CHANGE DETAILS

diff --git a/tests/test-convert.t b/tests/test-convert.t
--- a/tests/test-convert.t
+++ b/tests/test-convert.t
@@ -373,6 +373,11 @@
                     records the given string as a 'convert_source' extra value
                     on each commit made in the target repository. The default is
                     None.
+      convert.hg.preserve-hash
+                    only works with mercurial sources. Make convert prevent
+                    performance improvement to the list of modified files in
+                    commits when such an improvement would cause the hash of a
+                    commit to change. The default is False.
   
       All Destinations
       ################
diff --git a/tests/test-convert-identity.t b/tests/test-convert-identity.t
new file mode 100644
--- /dev/null
+++ b/tests/test-convert-identity.t
@@ -0,0 +1,40 @@
+Testing that convert.hg.preserve-hash=true can be used to make hg
+convert from hg repo to hg repo preserve hashes, even if the
+computation of the files list in commits change slightly between hg
+versions.
+
+  $ cat <<'EOF' >> "$HGRCPATH"
+  > [extensions]
+  > convert =
+  > EOF
+  $ cat <<'EOF' > changefileslist.py
+  > from mercurial import (changelog, extensions)
+  > def wrap(orig, clog, manifest, files, *args, **kwargs):
+  >   return orig(clog, manifest, ["a"], *args, **kwargs)
+  > def extsetup(ui):
+  >   extensions.wrapfunction(changelog.changelog, 'add', wrap)
+  > EOF
+
+  $ hg init repo
+  $ cd repo
+  $ echo a > a; hg commit -qAm a
+  $ echo b > a; hg commit -qAm b
+  $ hg up -qr 0; echo c > c; hg commit -qAm c
+  $ hg merge -qr 1
+  $ hg commit -m_ --config extensions.x=../changefileslist.py
+  $ hg log -r . -T '{node|short} {files|json}\n'
+  c085bbe93d59 ["a"]
+
+Now that we have a commit with a files list that's not what the
+current hg version would create, check that convert either fixes it or
+keeps it depending on config:
+
+  $ hg convert -q . ../convert
+  $ hg --cwd ../convert log -r tip -T '{node|short} {files|json}\n'
+  b7c4d4bbacd3 []
+  $ rm -rf ../convert
+
+  $ hg convert -q . ../convert --config convert.hg.preserve-hash=true
+  $ hg --cwd ../convert log -r tip -T '{node|short} {files|json}\n'
+  c085bbe93d59 ["a"]
+  $ rm -rf ../convert
diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
--- a/tests/test-commandserver.t
+++ b/tests/test-commandserver.t
@@ -917,13 +917,13 @@
   >     raise error.Abort(b'fail after finalization')
   > def reposetup(ui, repo):
   >     class failrepo(repo.__class__):
-  >         def commitctx(self, ctx, error=False):
+  >         def commitctx(self, ctx, error=False, origctx=None):
   >             if self.ui.configbool(b'failafterfinalize', b'fail'):
   >                 # 'sorted()' by ASCII code on category names causes
   >                 # invoking 'fail' after finalization of changelog
   >                 # using "'cl-%i' % id(self)" as category name
   >                 self.currenttransaction().addfinalize(b'zzzzzzzz', fail)
-  >             return super(failrepo, self).commitctx(ctx, error)
+  >             return super(failrepo, self).commitctx(ctx, error, origctx)
   >     repo.__class__ = failrepo
   > EOF
 
diff --git a/mercurial/repository.py b/mercurial/repository.py
--- a/mercurial/repository.py
+++ b/mercurial/repository.py
@@ -1656,7 +1656,7 @@
                editor=False, extra=None):
         """Add a new revision to the repository."""
 
-    def commitctx(ctx, error=False):
+    def commitctx(ctx, error=False, origctx=None):
         """Commit a commitctx instance to the repository."""
 
     def destroying():
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -2578,7 +2578,7 @@
         return ret
 
     @unfilteredmethod
-    def commitctx(self, ctx, error=False):
+    def commitctx(self, ctx, error=False, origctx=None):
         """Add a new revision to current repository.
         Revision information is passed via the context argument.
 
@@ -2586,6 +2586,12 @@
         modified/added/removed files. On merge, it may be wider than the
         ctx.files() to be committed, since any file nodes derived directly
         from p1 or p2 are excluded from the committed ctx.files().
+
+        origctx is for convert to work around the problem that bug
+        fixes to the files list in changesets change hashes. For
+        convert to be the identity, it can pass an origctx and this
+        function will use the same files list when it makes sense to
+        do so.
         """
 
         p1, p2 = ctx.p1(), ctx.p2()
@@ -2701,6 +2707,9 @@
                 filesadded = filesadded or None
                 filesremoved = filesremoved or None
 
+            if origctx and origctx.manifestnode() == mn:
+                files = origctx.files()
+
             # update changelog
             self.ui.note(_("committing changelog\n"))
             self.changelog.delayupdate(tr)
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -291,6 +291,9 @@
 coreconfigitem('convert', 'hg.ignoreerrors',
     default=False,
 )
+coreconfigitem('convert', 'hg.preserve-hash',
+    default=False,
+)
 coreconfigitem('convert', 'hg.revs',
     default=None,
 )
diff --git a/hgext/remotefilelog/shallowrepo.py b/hgext/remotefilelog/shallowrepo.py
--- a/hgext/remotefilelog/shallowrepo.py
+++ b/hgext/remotefilelog/shallowrepo.py
@@ -161,7 +161,7 @@
                                                               **kwargs)
 
         @localrepo.unfilteredmethod
-        def commitctx(self, ctx, error=False):
+        def commitctx(self, ctx, error=False, origctx=None):
             """Add a new revision to current repository.
             Revision information is passed via the context argument.
             """
@@ -179,7 +179,8 @@
                         files.append((f, hex(fparent1)))
                 self.fileservice.prefetch(files)
             return super(shallowrepository, self).commitctx(ctx,
-                                                            error=error)
+                                                            error=error,
+                                                            origctx=origctx)
 
         def backgroundprefetch(self, revs, base=None, repack=False, pats=None,
                                opts=None):
diff --git a/hgext/lfs/__init__.py b/hgext/lfs/__init__.py
--- a/hgext/lfs/__init__.py
+++ b/hgext/lfs/__init__.py
@@ -227,9 +227,9 @@
 
     class lfsrepo(repo.__class__):
         @localrepo.unfilteredmethod
-        def commitctx(self, ctx, error=False):
+        def commitctx(self, ctx, error=False, origctx=None):
             repo.svfs.options['lfstrack'] = _trackedmatcher(self)
-            return super(lfsrepo, self).commitctx(ctx, error)
+            return super(lfsrepo, self).commitctx(ctx, error, origctx=origctx)
 
     repo.__class__ = lfsrepo
 
diff --git a/hgext/keyword.py b/hgext/keyword.py
--- a/hgext/keyword.py
+++ b/hgext/keyword.py
@@ -785,8 +785,8 @@
             finally:
                 del self.commitctx
 
-        def kwcommitctx(self, ctx, error=False):
-            n = super(kwrepo, self).commitctx(ctx, error)
+        def kwcommitctx(self, ctx, error=False, origctx=None):
+            n = super(kwrepo, self).commitctx(ctx, error, origctx)
             # no lock needed, only called from repo.commit() which already locks
             if not kwt.postcommit:
                 restrict = kwt.restrict
diff --git a/hgext/eol.py b/hgext/eol.py
--- a/hgext/eol.py
+++ b/hgext/eol.py
@@ -400,7 +400,7 @@
                     if wlock is not None:
                         wlock.release()
 
-        def commitctx(self, ctx, error=False):
+        def commitctx(self, ctx, error=False, origctx=None):
             for f in sorted(ctx.added() + ctx.modified()):
                 if not self._eolmatch(f):
                     continue
@@ -416,6 +416,6 @@
                 if inconsistenteol(data):
                     raise errormod.Abort(_("inconsistent newline style "
                                            "in %s\n") % f)
-            return super(eolrepo, self).commitctx(ctx, error)
+            return super(eolrepo, self).commitctx(ctx, error, origctx)
     repo.__class__ = eolrepo
     repo._hgcleardirstate()
diff --git a/hgext/convert/hg.py b/hgext/convert/hg.py
--- a/hgext/convert/hg.py
+++ b/hgext/convert/hg.py
@@ -339,7 +339,11 @@
                                    phases.phasenames[commit.phase], 'convert')
 
             with self.repo.transaction("convert") as tr:
-                node = nodemod.hex(self.repo.commitctx(ctx))
+                if self.repo.ui.config('convert', 'hg.preserve-hash'):
+                    origctx = commit.ctx
+                else:
+                    origctx = None
+                node = nodemod.hex(self.repo.commitctx(ctx, origctx=origctx))
 
                 # If the node value has changed, but the phase is lower than
                 # draft, set it back to draft since it hasn't been exposed
@@ -591,7 +595,8 @@
                              extra=ctx.extra(),
                              sortkey=ctx.rev(),
                              saverev=self.saverev,
-                             phase=ctx.phase())
+                             phase=ctx.phase(),
+                             ctx=ctx)
 
     def numcommits(self):
         return len(self.repo)
diff --git a/hgext/convert/common.py b/hgext/convert/common.py
--- a/hgext/convert/common.py
+++ b/hgext/convert/common.py
@@ -114,7 +114,7 @@
 class commit(object):
     def __init__(self, author, date, desc, parents, branch=None, rev=None,
                  extra=None, sortkey=None, saverev=True, phase=phases.draft,
-                 optparents=None):
+                 optparents=None, ctx=None):
         self.author = author or 'unknown'
         self.date = date or '0 0'
         self.desc = desc
@@ -126,6 +126,7 @@
         self.sortkey = sortkey
         self.saverev = saverev
         self.phase = phase
+        self.ctx = ctx # for hg to hg conversions
 
 class converter_source(object):
     """Conversion source interface"""
diff --git a/hgext/convert/__init__.py b/hgext/convert/__init__.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -439,6 +439,11 @@
     :convert.hg.sourcename: records the given string as a 'convert_source' extra
         value on each commit made in the target repository. The default is None.
 
+    :convert.hg.preserve-hash: only works with mercurial sources. Make convert
+        prevent performance improvement to the list of modified files in commits
+        when such an improvement would cause the hash of a commit to change.
+        The default is False.
+
     All Destinations
     ################
 



To: valentin.gatienbaron, #hg-reviewers, mharbison72
Cc: mharbison72, mjpieters, mercurial-devel


More information about the Mercurial-devel mailing list