# HG changeset patch # User Anton Shestakov # Date 1562839623 -28800 # Node ID 41885988921e03a60f22f28b2bddb30e43f43f90 # Parent 7839720c7c75376dc0f8a4ec971f75a298790b83 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). diff -r 7839720c7c75 -r 41885988921e CHANGELOG --- 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 ------------------- diff -r 7839720c7c75 -r 41885988921e hgext3rd/evolve/cmdrewrite.py --- 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 ' diff -r 7839720c7c75 -r 41885988921e hgext3rd/evolve/rewriteutil.py --- 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 diff -r 7839720c7c75 -r 41885988921e tests/test-fold.t --- 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 ..