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]