fold: allow operations on merge commits with some conditions
It's possible to fold revision chains that include a single merge commit: just
fold everything into the merge commit while saving its other parent (so it
continues being a merge commit). It's also possible to fold revisions that
include multiple merge commits, on the condition that they merge with not more
than 2 external changesets (i.e. a changesets that aren't going to be folded).
--- a/CHANGELOG Thu Jul 11 17:04:08 2019 +0800
+++ b/CHANGELOG Thu Jul 11 18:07:03 2019 +0800
@@ -10,6 +10,7 @@
* topic: fix confusion in branch heads checking logic
* touch: now works on merge commit too
* rewind: fix behavior for merge commit
+ * fold: allow fold with merge commit
9.0.1 - in progress
-------------------
--- a/hgext3rd/evolve/cmdrewrite.py Thu Jul 11 17:04:08 2019 +0800
+++ b/hgext3rd/evolve/cmdrewrite.py Thu Jul 11 18:07:03 2019 +0800
@@ -773,7 +773,7 @@
wlock = repo.wlock()
lock = repo.lock()
- root, head = rewriteutil.foldcheck(repo, revs)
+ root, head, p2 = rewriteutil.foldcheck(repo, revs)
tr = repo.transaction('fold')
try:
@@ -794,10 +794,13 @@
if opts.get('note'):
metadata['note'] = opts['note']
- newid, unusedvariable = rewriteutil.rewrite(repo, root, allctx,
+ updates = allctx[:]
+ if p2 is not None and root.p2() != p2:
+ updates.append(p2)
+ newid, unusedvariable = rewriteutil.rewrite(repo, root, updates,
head,
[root.p1().node(),
- root.p2().node()],
+ p2.node()],
commitopts=commitopts)
phases.retractboundary(repo, tr, targetphase, [newid])
replacements = {ctx.node(): [newid] for ctx in allctx}
@@ -872,7 +875,7 @@
'not currently supported'))
if opts['fold']:
- root, head = rewriteutil.foldcheck(repo, revs)
+ root, head, p2 = rewriteutil.foldcheck(repo, revs)
else:
if repo.revs("%ld and public()", revs):
raise error.Abort(_('cannot edit commit information for public '
--- a/hgext3rd/evolve/rewriteutil.py Thu Jul 11 17:04:08 2019 +0800
+++ b/hgext3rd/evolve/rewriteutil.py Thu Jul 11 18:07:03 2019 +0800
@@ -113,7 +113,14 @@
raise error.Abort(_("cannot fold non-linear revisions "
"(multiple heads given)"))
head = repo[heads.first()]
- return root, head
+ baseparents = repo.revs('parents(%ld) - %ld', revs, revs)
+ if len(baseparents) > 2:
+ raise error.Abort(_("cannot fold revisions that merge with more than "
+ "one external changeset (not in revisions)"))
+ # root's p1 is already used as the target ctx p1
+ baseparents -= {root.p1().rev()}
+ p2 = repo[baseparents.first()]
+ return root, head, p2
def deletebookmark(repo, repomarks, bookmarks):
wlock = lock = tr = None
--- a/tests/test-fold.t Thu Jul 11 17:04:08 2019 +0800
+++ b/tests/test-fold.t Thu Jul 11 18:07:03 2019 +0800
@@ -7,9 +7,17 @@
> fold=-d "0 0"
> [extensions]
> evolve=
+ > [alias]
+ > glog = log -GT "{rev}: {desc}"
+ > glf = log -GT "{rev}: {desc} ({files})"
> [ui]
> logtemplate = '{rev} - {node|short} {desc|firstline} [{author}] ({phase}) {bookmarks}\n'
> EOF
+ $ mkcommit() {
+ > echo "$1" > "$1"
+ > hg add "$1"
+ > hg ci -qm "$1"
+ > }
$ hg init fold-tests
$ cd fold-tests/
@@ -270,3 +278,106 @@
$ cd ..
+One merge commit
+
+ $ hg init fold-a-merge
+ $ cd fold-a-merge
+
+ $ mkcommit zebra
+
+ $ hg up null
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ mkcommit apple
+ $ mkcommit banana
+
+ $ hg merge
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ (branch merge, don't forget to commit)
+ $ hg ci -m merge
+
+ $ mkcommit coconut
+
+ $ hg glf
+ @ 4: coconut (coconut)
+ |
+ o 3: merge ()
+ |\
+ | o 2: banana (banana)
+ | |
+ | o 1: apple (apple)
+ |
+ o 0: zebra (zebra)
+
+
+now we merge some of the fruits
+
+ $ hg fold --exact -r 'desc("banana")::desc("coconut")' -m 'banana+coconut in a merge with zebra'
+ 3 changesets folded
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg glf
+ @ 5: banana+coconut in a merge with zebra (banana coconut)
+ |\
+ | o 1: apple (apple)
+ |
+ o 0: zebra (zebra)
+
+
+let's go even further: zebra becomes a parent of the squashed fruit commit
+
+ $ hg fold --from -r 'desc("apple")' -m 'apple+banana+coconut is a child of zebra'
+ 2 changesets folded
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg glf
+ @ 6: apple+banana+coconut is a child of zebra (apple banana coconut)
+ |
+ o 0: zebra (zebra)
+
+
+make sure zebra exists at tip and has expected contents
+
+ $ hg cat -r tip zebra
+ zebra
+
+ $ cd ..
+
+Multiple merge commits
+
+ $ hg init fold-many-merges
+ $ cd fold-many-merges
+
+ $ hg debugbuilddag '+3 *3 /3 /4 /4'
+ $ hg glog
+ o 6: r6
+ |\
+ | o 5: r5
+ | |\
+ | | o 4: r4
+ | |/|
+ | | o 3: r3
+ | | |
+ o | | 2: r2
+ |/ /
+ o / 1: r1
+ |/
+ o 0: r0
+
+
+cannot fold 5 and 6 because they have 3 external parents in total: 1, 2, 4
+
+ $ hg fold --exact -r 5:6 -m r5+r6
+ abort: cannot fold revisions that merge with more than one external changeset (not in revisions)
+ [255]
+
+now many of the parents are included in the revisions to fold, only 0 and 3 are external
+
+ $ hg fold --exact -r 1+2+4+5+6 -m r1+r2+r4+r5+r6
+ 5 changesets folded
+
+ $ hg glog
+ o 7: r1+r2+r4+r5+r6
+ |\
+ | o 3: r3
+ |/
+ o 0: r0
+
+ $ cd ..