Patchwork [evolve-ext] evolve: add a command to split commits

login
register
mail settings
Submitter Laurent Charignon
Date Aug. 7, 2015, 6:17 p.m.
Message ID <71776915-E719-41F6-A579-99885AC177E4@fb.com>
Download mbox | patch
Permalink /patch/10141/
State Changes Requested
Headers show

Comments

Laurent Charignon - Aug. 7, 2015, 6:17 p.m.
On Aug 7, 2015, at 11:09 AM, Pierre-Yves David <pierre-yves.david@ens-lyon.org<mailto:pierre-yves.david@ens-lyon.org>> wrote:



On 08/07/2015 10:53 AM, Laurent Charignon wrote:
# HG changeset patch
# User Laurent Charignon <lcharignon@fb.com<mailto:lcharignon@fb.com>>
# Date 1434671333 25200
#      Thu Jun 18 16:48:53 2015 -0700
# Node ID ec33aec0415556a456660e09ad61094c34aacb04
# Parent  8e6de39b724d854cb92d2de3a0472ffb03627034
evolve: add a command to split commits

Before this patch, to split commit one had to use prune. This patch adds a
new command called split that prompts the user interactively to split a given
changeset with record/crecord.

Patch

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -2554,6 +2554,75 @@  def commitwrapper(orig, ui, repo, *arg,
     finally:
         lockmod.release(lock, wlock)

+@command('^split',
+    [('r', 'rev', [], _("revision to fold")),
+    ] + commitopts + commitopts2,
+    _('hg split [OPTION]... [-r] REV'))
+def split(ui, repo, *revs, **opts):
+    """Split the current commit using interactive selection
+
+    By default, split the current revision by prompting for all its hunk to be
+    redistributed into new changesets.
+
+    Use --rev for splitting a given changeset instead.
+    """
+    tr = wlock = lock = None
+    cmdutil.bailifchanged(repo)
+    newcommits = []
+
+    revopt = opts.get('rev')
+    if revopt:
+        revs = scmutil.revrange(repo, revopt)
+        if len(revs) != 1:
+            raise util.Abort(_("you can only specify one revision to split"))
+        else:
+            rev = list(revs)[0]
+            commands.update(ui, repo, rev)
+    else:
+        rev = '.'
+
+    try:
+        wlock = repo.wlock()
+        lock = repo.lock()
+        tr = repo.transaction('split')
+        ctx = repo[rev]
+        r = ctx.rev()
+        disallowunstable = not obsolete.isenabled(repo,
+                                                  obsolete.allowunstableopt)
+        if disallowunstable:
+            # XXX We should check head revs
+            if repo.revs("(%d::) - %d", rev, rev):
+                raise util.Abort(_("cannot split commit: %s not a head" % ctx))
+
+        if len(ctx.parents()) > 1:
+            raise util.Abort(_("cannot split merge commits"))
+        prev = ctx.p1()
+        hg.update(repo, prev)

I would be happier if we could do all the diffing, recording and patching directly in memory. But I'm fine with this as a first step. However, if you do an update here, you have to check if the working copy has any change or this can result in some butchery.

cf mercurial.cmdutil.bailifchanged

It is done above, you can check. We can add a test though!



+
+        commands.revert(ui, repo, rev=r, all=True)
+        def haschanges():
+            return len(list(patch.diff(repo))) != 0

We usually use status for such check.

cf mercurial.cmdutil.bailifchanged

+        while haschanges():
+            pats = ()
+            cmdutil.dorecord(ui, repo, commands.commit, 'commit', False,
+                             cmdutil.recordfilter, *pats, **opts)
+            # TODO: Does no seem like the best way to do this
+            # We should make dorecord return the newly created commit
+            newcommits.append(repo['.'])
+            if haschanges():
+                if ui.prompt('Done splitting? [yN]', default='n') == 'y':
+                    commands.commit(ui, repo, **opts)
+                    newcommits.append(repo['.'])
+                    break
+            else:
+                ui.status("no more change to split\n")
+
+        obsolete.createmarkers(repo, [(repo[r], newcommits)])
+        tr.close()
+    finally:
+        lockmod.release(tr, lock, wlock)
+
+
 @eh.wrapcommand('strip', extension='strip', opts=[
     ('', 'bundle', None, _("delete the commit entirely and move it to a "
         "backup bundle")),
diff --git a/tests/test-split.t b/tests/test-split.t
new file mode 100644
--- /dev/null
+++ b/tests/test-split.t
@@ -0,0 +1,184 @@ 
+test of the split command
+-----------------------
+
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > fold=-d "0 0"
+  > split=-d "0 0"
+  > amend=-d "0 0"
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [phases]
+  > publish = False
+  > [diff]
+  > git = 1
+  > unified = 0
+  > [ui]
+  > interactive = true
+  > [extensions]
+  > hgext.graphlog=
+  > EOF
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+  $ mkcommit() {
+  >    echo "$1" > "$1"
+  >    hg add "$1"
+  >    hg ci -m "add $1"
+  > }
+
+
+Basic case, split a head
+  $ hg init testsplit
+  $ cd testsplit
+  $ mkcommit _a
+  $ mkcommit _b
+  $ mkcommit _c
+  $ mkcommit _d
+  $ echo "change to a" >> _a
+  $ hg amend
+  $ hg debugobsolete
+  9e84a109b8eb081ad754681ee4b1380d17a3741f aa8f656bb307022172d2648be6fb65322f801225 0 (*) {'user': 'test'} (glob)
+  f002b57772d7f09b180c407213ae16d92996a988 0 {9e84a109b8eb081ad754681ee4b1380d17a3741f} (*) {'user': 'test'} (glob)
+
+To create commits with the number of split
+  $ export NUM=0
+  $ export HGEDITOR="NUM=$((NUM+1)); echo split$NUM > $1"

You probably want to document (or some pointeur) to the NUM logic.

I will send a V2 with more details



--
Pierre-Yves David


Laurent