Mercurial > public > mercurial-scm > hg-stable
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: |