Mercurial > public > mercurial-scm > hg
comparison hgext/git/index.py @ 52624: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 | 4e2ea270ba6a |
children | 27a0bfe770eb |
comparison
equal
deleted
inserted
replaced
52623:4e2ea270ba6a | 52624:cdbfe5e7592e |
---|---|
16 from . import gitutil | 16 from . import gitutil |
17 | 17 |
18 | 18 |
19 pygit2 = gitutil.get_pygit2() | 19 pygit2 = gitutil.get_pygit2() |
20 | 20 |
21 _CURRENT_SCHEMA_VERSION = 2 | 21 _CURRENT_SCHEMA_VERSION = 3 |
22 _SCHEMA = ( | 22 _SCHEMA = ( |
23 """ | 23 """ |
24 CREATE TABLE refs ( | 24 CREATE TABLE refs ( |
25 -- node and name are unique together. There may be more than one name for | 25 -- node and name are unique together. There may be more than one name for |
26 -- a given node, and there may be no name at all for a given node (in the | 26 -- a given node, and there may be no name at all for a given node (in the |
45 -- A total ordering of the changelog | 45 -- A total ordering of the changelog |
46 CREATE TABLE changelog ( | 46 CREATE TABLE changelog ( |
47 rev INTEGER NOT NULL PRIMARY KEY, | 47 rev INTEGER NOT NULL PRIMARY KEY, |
48 node TEXT NOT NULL, | 48 node TEXT NOT NULL, |
49 p1 TEXT, | 49 p1 TEXT, |
50 p2 TEXT | 50 p2 TEXT, |
51 synthetic TEXT | |
51 ); | 52 ); |
52 | 53 |
53 CREATE UNIQUE INDEX changelog_node_idx ON changelog(node); | 54 CREATE UNIQUE INDEX changelog_node_idx ON changelog(node); |
54 CREATE UNIQUE INDEX changelog_node_rev_idx ON changelog(rev, node); | 55 CREATE UNIQUE INDEX changelog_node_rev_idx ON changelog(rev, node); |
55 | 56 |
304 if prog is not None: | 305 if prog is not None: |
305 prog.complete() | 306 prog.complete() |
306 prog = progress_factory(b'commits') | 307 prog = progress_factory(b'commits') |
307 # This walker is sure to visit all the revisions in history, but | 308 # This walker is sure to visit all the revisions in history, but |
308 # only once. | 309 # only once. |
309 for pos, commit in enumerate(walker): | 310 pos = -1 |
311 for commit in walker: | |
310 if prog is not None: | 312 if prog is not None: |
311 prog.update(pos) | 313 prog.update(pos) |
312 p1 = p2 = gitutil.nullgit | 314 p1 = p2 = gitutil.nullgit |
313 if len(commit.parents) > 2: | 315 if len(commit.parents) <= 2: |
314 raise error.ProgrammingError( | 316 if commit.parents: |
315 ( | 317 p1 = commit.parents[0].id.hex |
316 b"git support can't handle octopus merges, " | 318 if len(commit.parents) == 2: |
317 b"found a commit with %d parents :(" | 319 p2 = commit.parents[1].id.hex |
320 pos += 1 | |
321 db.execute( | |
322 'INSERT INTO changelog (rev, node, p1, p2, synthetic) VALUES(?, ?, ?, ?, NULL)', | |
323 (pos, commit.id.hex, p1, p2), | |
324 ) | |
325 else: | |
326 parents = list(commit.parents) | |
327 | |
328 p1 = parents.pop(0).id.hex | |
329 while parents: | |
330 pos += 1 | |
331 | |
332 if len(parents) == 1: | |
333 this = commit.id.hex | |
334 synth = None | |
335 else: | |
336 this = "%040x" % pos | |
337 synth = commit.id.hex | |
338 | |
339 p2 = parents.pop(0).id.hex | |
340 | |
341 db.execute( | |
342 'INSERT INTO changelog (rev, node, p1, p2, synthetic) VALUES(?, ?, ?, ?, ?)', | |
343 (pos, this, p1, p2, synth), | |
318 ) | 344 ) |
319 % len(commit.parents) | 345 |
320 ) | 346 p1 = this |
321 if commit.parents: | |
322 p1 = commit.parents[0].id.hex | |
323 if len(commit.parents) == 2: | |
324 p2 = commit.parents[1].id.hex | |
325 db.execute( | |
326 'INSERT INTO changelog (rev, node, p1, p2) VALUES(?, ?, ?, ?)', | |
327 (pos, commit.id.hex, p1, p2), | |
328 ) | |
329 | 347 |
330 num_changedfiles = db.execute( | 348 num_changedfiles = db.execute( |
331 "SELECT COUNT(*) from changedfiles WHERE node = ?", | 349 "SELECT COUNT(*) from changedfiles WHERE node = ?", |
332 (commit.id.hex,), | 350 (commit.id.hex,), |
333 ).fetchone()[0] | 351 ).fetchone()[0] |