comparison hgext/git/gitlog.py @ 52649:cdbfe5e7592e

git: handle octopus merges Octopus merges in git are merge commits with more than 2 parents. To make them fit into mercurial core's assumption about commits having 0-2 parents, the git indexing code creates "sythetic" commits to represent the octopus commit as a sequence of regular 2-parent commits. The synthetic commit hashes are just an incrementing commit number (which is the same as the generated rev number). The last commit in the sequence of commits uses the actual git commit hash. As a result, `hg checkout -r <commit>` produces the same working directory as `git checkout <commit>` for all git commit hashes. The synthetic commit hashes are stored in the changelog table as any other commit - with the two parents - but they also contain the commit hash of the octopus merge commit. For example, given the git DAG (manually pruned `git log --graph`): *-. commit 23480d86e2689703b33f693907c40fbe6e1620e4 Merge branches... |\ \ | | | | | * commit 2eda9984b06c75448598ec6c0a9028e49dacf616 C | | | | * | commit 5e634a12f12fedaf7b8ef0f0fcdbb07222871953 B | |/ | | * | commit 8883a1296c5ae323a1b18d1f6410398ce43ebd3a D |/ | * commit 95f241588fded9554cae91be0fefd576f61ebfc6 A Where M is the octopus merge commit with 3 parents, the corresponding mercurial DAG is: $ hg log -G -T '{node} {desc}' @ 23480d86e2689703b33f693907c40fbe6e1620e4 Merge branches 'abc' and 'def' |\ | o 0000000000000000000000000000000000000004 Merge branches 'abc' and 'def' | |\ | | o 8883a1296c5ae323a1b18d1f6410398ce43ebd3a D | | | o---+ 2eda9984b06c75448598ec6c0a9028e49dacf616 C / / o / 5e634a12f12fedaf7b8ef0f0fcdbb07222871953 B |/ o 95f241588fded9554cae91be0fefd576f61ebfc6 A
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Sun, 10 Mar 2024 14:30:32 -0400
parents 143063a94085
children 4dadaf300fe0
comparison
equal deleted inserted replaced
52648:4e2ea270ba6a 52649:cdbfe5e7592e
152 ).fetchone() 152 ).fetchone()
153 if t is None: 153 if t is None:
154 raise error.LookupError(r, b'00changelog.i', _(b'no node')) 154 raise error.LookupError(r, b'00changelog.i', _(b'no node'))
155 return bin(t[0]) 155 return bin(t[0])
156 156
157 def synthetic(self, n):
158 """Map any node to a non-synthetic node.
159
160 Indexing may have created synthetic nodes to handle octopus merges.
161 Certain operations on these made up nodes need to actually happen on
162 the real octopus merge commit. Given any node, this function
163 returns the real commit hash. One can think of this as hg-to-git
164 commit hash translation that always works."""
165 t = self._db.execute(
166 'SELECT synthetic FROM changelog WHERE node = ?',
167 (gitutil.togitnode(n),),
168 ).fetchone()
169 if t is None or t[0] is None:
170 return n
171 return bin(t[0])
172
157 def hasnode(self, n): 173 def hasnode(self, n):
158 t = self._db.execute( 174 t = self._db.execute(
159 'SELECT node FROM changelog WHERE node = ?', 175 'SELECT node FROM changelog WHERE node = ?',
160 (pycompat.sysstr(n),), 176 (pycompat.sysstr(n),),
161 ).fetchone() 177 ).fetchone()
319 # handle looking up nullid 335 # handle looking up nullid
320 if n == sha1nodeconstants.nullid: 336 if n == sha1nodeconstants.nullid:
321 return hgchangelog._changelogrevision( 337 return hgchangelog._changelogrevision(
322 extra=extra, manifest=sha1nodeconstants.nullid 338 extra=extra, manifest=sha1nodeconstants.nullid
323 ) 339 )
340 n = self.synthetic(n)
324 hn = gitutil.togitnode(n) 341 hn = gitutil.togitnode(n)
325 # We've got a real commit! 342 # We've got a real commit!
326 files = [ 343 files = [
327 r[0] 344 r[0]
328 for r in self._db.execute( 345 for r in self._db.execute(
464 elif a > b: 481 elif a > b:
465 return False 482 return False
466 return bool(self.reachableroots(a, [b], [a], includepath=False)) 483 return bool(self.reachableroots(a, [b], [a], includepath=False))
467 484
468 def parentrevs(self, rev): 485 def parentrevs(self, rev):
469 n = self.node(rev) 486 assert rev >= 0, rev
470 hn = gitutil.togitnode(n) 487 t = self._db.execute(
471 if hn != gitutil.nullgit: 488 'SELECT p1, p2 FROM changelog WHERE rev = ?', (rev,)
472 c = self.gitrepo[hn] 489 ).fetchone()
473 else: 490 if t is None:
474 return nullrev, nullrev 491 raise error.LookupError(rev, b'00changelog.i', _(b'no rev'))
475 p1 = p2 = nullrev 492 return self.rev(bin(t[0])), self.rev(bin(t[1]))
476 if c.parents:
477 p1 = self.rev(c.parents[0].id.raw)
478 if len(c.parents) > 2:
479 raise error.Abort(b'TODO octopus merge handling')
480 if len(c.parents) == 2:
481 p2 = self.rev(c.parents[1].id.raw)
482 return p1, p2
483 493
484 # Private method is used at least by the tags code. 494 # Private method is used at least by the tags code.
485 _uncheckedparentrevs = parentrevs 495 _uncheckedparentrevs = parentrevs
486 496
487 def commonancestorsheads(self, a, b): 497 def commonancestorsheads(self, a, b):
556 566
557 def get(self, relpath, node): 567 def get(self, relpath, node):
558 if node == sha1nodeconstants.nullid: 568 if node == sha1nodeconstants.nullid:
559 # TODO: this should almost certainly be a memgittreemanifestctx 569 # TODO: this should almost certainly be a memgittreemanifestctx
560 return manifest.memtreemanifestctx(self, relpath) 570 return manifest.memtreemanifestctx(self, relpath)
571 node = self.synthetic(node)
561 commit = self.gitrepo[gitutil.togitnode(node)] 572 commit = self.gitrepo[gitutil.togitnode(node)]
562 t = commit.tree 573 t = commit.tree
563 if relpath: 574 if relpath:
564 parts = relpath.split(b'/') 575 parts = relpath.split(b'/')
565 for p in parts: 576 for p in parts: