changeset 4723:41885988921e

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).
author Anton Shestakov <av6@dwimlabs.net>
date Thu, 11 Jul 2019 18:07:03 +0800
parents 7839720c7c75
children 77bf84025dd5
files CHANGELOG hgext3rd/evolve/cmdrewrite.py hgext3rd/evolve/rewriteutil.py tests/test-fold.t
diffstat 4 files changed, 127 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- 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 ..