hgext/git: octopus merge work-in-progress code

Josef 'Jeff' Sipek jeffpc at josefsipek.net
Thu May 2 03:14:56 UTC 2024


This is a very rough work-in-progress that I've been sitting on for far too
long.  Since I don't know when I'll have time to hack more on it, I thought
I'd share it.

The idea is to convert each octopus merge into a series of commits that pull
in one branch at a time.  These intermediate commits use made up hashes that
are a simple counter printed as a 40-digit hex number.

If someone picks it up and makes it actually work - great.  If nobody does,
I'll probably end up hacking on it some more in the next few months/years.

Jeff.

diff --git a/hgext/git/gitlog.py b/hgext/git/gitlog.py
--- a/hgext/git/gitlog.py
+++ b/hgext/git/gitlog.py
@@ -61,6 +61,14 @@ class baselog:  # revlog.revlog):
             raise error.LookupError(r, b'00changelog.i', _(b'no node'))
         return bin(t[0])
 
+    def synthetic(self, n):
+        t = self._db.execute(
+            'SELECT synthetic FROM changelog WHERE node = ?', (gitutil.togitnode(n),)
+        ).fetchone()
+        if t is None or t[0] is None:
+            return n
+        return bin(t[0])
+
     def hasnode(self, n):
         t = self._db.execute(
             'SELECT node FROM changelog WHERE node = ?',
@@ -222,6 +230,7 @@ class changelog(baselog):
             return hgchangelog._changelogrevision(
                 extra=extra, manifest=sha1nodeconstants.nullid
             )
+        n = self.synthetic(n)
         hn = gitutil.togitnode(n)
         # We've got a real commit!
         files = [
@@ -367,20 +376,13 @@ class changelog(baselog):
         return bool(self.reachableroots(a, [b], [a], includepath=False))
 
     def parentrevs(self, rev):
-        n = self.node(rev)
-        hn = gitutil.togitnode(n)
-        if hn != gitutil.nullgit:
-            c = self.gitrepo[hn]
-        else:
-            return nullrev, nullrev
-        p1 = p2 = nullrev
-        if c.parents:
-            p1 = self.rev(c.parents[0].id.raw)
-            if len(c.parents) > 2:
-                raise error.Abort(b'TODO octopus merge handling')
-            if len(c.parents) == 2:
-                p2 = self.rev(c.parents[1].id.raw)
-        return p1, p2
+        assert rev >= 0, rev
+        t = self._db.execute(
+            'SELECT p1, p2 FROM changelog WHERE rev = ?', (rev,)
+        ).fetchone()
+        if t is None:
+            raise error.LookupError(rev, b'00changelog.i', _(b'no rev %d'))
+        return self.rev(bin(t[0])), self.rev(bin(t[1]))
 
     # Private method is used at least by the tags code.
     _uncheckedparentrevs = parentrevs
@@ -459,6 +461,7 @@ class manifestlog(baselog):
         if node == sha1nodeconstants.nullid:
             # TODO: this should almost certainly be a memgittreemanifestctx
             return manifest.memtreemanifestctx(self, relpath)
+        node = self.synthetic(node)
         commit = self.gitrepo[gitutil.togitnode(node)]
         t = commit.tree
         if relpath:
diff --git a/hgext/git/index.py b/hgext/git/index.py
--- a/hgext/git/index.py
+++ b/hgext/git/index.py
@@ -43,7 +43,8 @@ CREATE TABLE changelog (
   rev INTEGER NOT NULL PRIMARY KEY,
   node TEXT NOT NULL,
   p1 TEXT,
-  p2 TEXT
+  p2 TEXT,
+  synthetic TEXT
 );
 
 CREATE UNIQUE INDEX changelog_node_idx ON changelog(node);
@@ -273,26 +274,43 @@ def _index_repo(
     prog = progress_factory(b'commits')
     # This walker is sure to visit all the revisions in history, but
     # only once.
-    for pos, commit in enumerate(walker):
+    pos = -1
+    for commit in walker:
         if prog is not None:
             prog.update(pos)
         p1 = p2 = gitutil.nullgit
-        if len(commit.parents) > 2:
-            raise error.ProgrammingError(
-                (
-                    b"git support can't handle octopus merges, "
-                    b"found a commit with %d parents :("
-                )
-                % len(commit.parents)
+        if len(commit.parents) <= 2:
+            if commit.parents:
+                p1 = commit.parents[0].id.hex
+            if len(commit.parents) == 2:
+                p2 = commit.parents[1].id.hex
+            pos += 1
+            db.execute(
+                'INSERT INTO changelog (rev, node, p1, p2, synthetic) VALUES(?, ?, ?, ?, NULL)',
+                (pos, commit.id.hex, p1, p2),
             )
-        if commit.parents:
-            p1 = commit.parents[0].id.hex
-        if len(commit.parents) == 2:
-            p2 = commit.parents[1].id.hex
-        db.execute(
-            'INSERT INTO changelog (rev, node, p1, p2) VALUES(?, ?, ?, ?)',
-            (pos, commit.id.hex, p1, p2),
-        )
+        else:
+            parents = list(commit.parents)
+
+            p1 = parents.pop(0).id.hex
+            while parents:
+                pos += 1
+
+                if len(parents) == 1:
+                    this = commit.id.hex
+                    synth = None
+                else:
+                    this = "%040x" % pos
+                    synth = commit.id.hex
+
+                p2 = parents.pop(0).id.hex
+
+                db.execute(
+                    'INSERT INTO changelog (rev, node, p1, p2, synthetic) VALUES(?, ?, ?, ?, ?)',
+                    (pos, this, p1, p2, synth),
+                )
+
+                p1 = this
 
         num_changedfiles = db.execute(
             "SELECT COUNT(*) from changedfiles WHERE node = ?",



More information about the Mercurial-devel mailing list