changeset 1478:9603aa1ecdfd

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.
author Laurent Charignon <lcharignon@fb.com>
date Thu, 18 Jun 2015 16:48:53 -0700
parents 6ab5c0f40699
children bc4c7b1bb793
files README hgext/evolve.py tests/test-split.t
diffstat 3 files changed, 267 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/README	Mon Aug 03 11:29:27 2015 -0700
+++ b/README	Thu Jun 18 16:48:53 2015 -0700
@@ -51,6 +51,10 @@
 Changelog
 =========
 
+5.2.0 --
+
+- split: add a new command to split changesets
+
 5.2.0 -- 2015-06-25
 
 - evolve: gain a --rev option to control what revisions to evolve (issue4391)
--- a/hgext/evolve.py	Mon Aug 03 11:29:27 2015 -0700
+++ b/hgext/evolve.py	Thu Jun 18 16:48:53 2015 -0700
@@ -2554,6 +2554,76 @@
     finally:
         lockmod.release(lock, wlock)
 
+@command('^split',
+    [('r', 'rev', [], _("revision to fold")),
+    ] + commitopts + commitopts2,
+    _('hg split [OPTION]... [-r] REV'))
+def cmdsplit(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
+    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()
+        cmdutil.bailifchanged(repo)
+        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)
+
+        commands.revert(ui, repo, rev=r, all=True)
+        def haschanges():
+            modified, added, removed, deleted = repo.status()[:4]
+            return modified or added or removed or deleted
+        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")),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-split.t	Thu Jun 18 16:48:53 2015 -0700
@@ -0,0 +1,193 @@
+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"
+  $ hg split << EOF
+  > y
+  > y
+  > y
+  > n
+  > N
+  > y
+  > y
+  > EOF
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  reverting _a
+  adding _d
+  diff --git a/_a b/_a
+  1 hunks, 1 lines changed
+  examine changes to '_a'? [Ynesfdaq?] y
+  
+  @@ -1,0 +2,1 @@
+  +change to a
+  record change 1/2 to '_a'? [Ynesfdaq?] y
+  
+  diff --git a/_d b/_d
+  new file mode 100644
+  examine changes to '_d'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,1 @@
+  +_d
+  record change 2/2 to '_d'? [Ynesfdaq?] n
+  
+  created new head
+  Done splitting? [yN] N
+  diff --git a/_d b/_d
+  new file mode 100644
+  examine changes to '_d'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,1 @@
+  +_d
+  record this change to '_d'? [Ynesfdaq?] y
+  
+  no more change to split
+
+  $ hg debugobsolete
+  9e84a109b8eb081ad754681ee4b1380d17a3741f aa8f656bb307022172d2648be6fb65322f801225 0 (*) {'user': 'test'} (glob)
+  f002b57772d7f09b180c407213ae16d92996a988 0 {9e84a109b8eb081ad754681ee4b1380d17a3741f} (*) {'user': 'test'} (glob)
+  aa8f656bb307022172d2648be6fb65322f801225 8a76f55839e6badd47ed8338803d8bc16f578d68 1e105584671a463974ee2122f95979ce5e507f1a 0 (*) {'user': 'test'} (glob)
+
+Cannot split a commit with uncommited changes
+  $ hg up "desc(_c)"
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo "_cd" > _c
+  $ hg split 
+  abort: uncommitted changes
+  [255]
+
+Split a revision specified with -r
+  $ hg up "desc(_c)" -C
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo "change to b" >> _b
+  $ hg amend -m "_cprim"
+  2 new unstable changesets
+  $ hg evolve --all
+  move:[6] split0
+  atop:[9] _cprim
+  move:[7] split0
+  atop:[10] split0
+  working directory is now at * (glob)
+  $ hg log -r "desc(_cprim)" -v -p
+  changeset:   9:719157b217ac
+  parent:      1:37445b16603b
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  files:       _b _c
+  description:
+  _cprim
+  
+  
+  diff --git a/_b b/_b
+  --- a/_b
+  +++ b/_b
+  @@ -1,0 +2,1 @@
+  +change to b
+  diff --git a/_c b/_c
+  new file mode 100644
+  --- /dev/null
+  +++ b/_c
+  @@ -0,0 +1,1 @@
+  +_c
+  
+  $ hg split -r "desc(_cprim)" <<EOF
+  > y
+  > y
+  > y
+  > n
+  > y
+  > EOF
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  reverting _b
+  adding _c
+  diff --git a/_b b/_b
+  1 hunks, 1 lines changed
+  examine changes to '_b'? [Ynesfdaq?] y
+  
+  @@ -1,0 +2,1 @@
+  +change to b
+  record change 1/2 to '_b'? [Ynesfdaq?] y
+  
+  diff --git a/_c b/_c
+  new file mode 100644
+  examine changes to '_c'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,1 @@
+  +_c
+  record change 2/2 to '_c'? [Ynesfdaq?] n
+  
+  created new head
+  Done splitting? [yN] y
+
+Stop before splitting the commit completely creates a commit with all the
+remaining changes
+
+  $ hg debugobsolete
+  9e84a109b8eb081ad754681ee4b1380d17a3741f aa8f656bb307022172d2648be6fb65322f801225 0 (*) {'user': 'test'} (glob)
+  f002b57772d7f09b180c407213ae16d92996a988 0 {9e84a109b8eb081ad754681ee4b1380d17a3741f} (*) {'user': 'test'} (glob)
+  aa8f656bb307022172d2648be6fb65322f801225 8a76f55839e6badd47ed8338803d8bc16f578d68 1e105584671a463974ee2122f95979ce5e507f1a 0 (*) {'user': 'test'} (glob)
+  10200229058723ce8d67f6612c1f6b4f73b1fe73 719157b217acc43d397369a448824ed4c7a302f2 0 (*) {'user': 'test'} (glob)
+  5d0c8b0f2d3e5e1ff95f93d7da2ba06650605ab5 0 {10200229058723ce8d67f6612c1f6b4f73b1fe73} (*) {'user': 'test'} (glob)
+  8a76f55839e6badd47ed8338803d8bc16f578d68 0ea1d0d23e674ea8a6affe760741c82bb8e380f7 0 (*) {'user': 'test'} (glob)
+  1e105584671a463974ee2122f95979ce5e507f1a b6099ccb49cae181af7c59ed5603a1dfca632445 0 (*) {'user': 'test'} (glob)
+  719157b217acc43d397369a448824ed4c7a302f2 8eb71353cb2c70fc1154be3af79c0ce98898ae88 ead904640c8543606f72490c6ae10955fb11fff0 0 (*) {'user': 'test'} (glob)
+
+Cannot specify multiple revisions with -r
+  $ hg split -r "desc(_a)::"
+  abort: you can only specify one revision to split
+  [255]
+
+Cannot split a commit that is not a head if instability is not allowed
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution=createmarkers
+  > evolutioncommands=split
+  > EOF
+  $ hg split -r "desc(_c)"
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  abort: cannot split commit: 719157b217ac not a head
+  [255]
+
+