[PATCH evolve-ext-V2] uncommit: add support for interactive selection
Laurent Charignon
lcharignon at fb.com
Wed May 27 23:39:32 UTC 2015
# HG changeset patch
# User Laurent Charignon <lcharignon at fb.com>
# Date 1432747476 25200
# Wed May 27 10:24:36 2015 -0700
# Node ID 2ba8f8322588f8940ab93e3801fd23c0e1fa8456
# Parent 69e5de3e6129185469c2cbf98383ac6d58260d0c
uncommit: add support for interactive selection
This patch adds a --interactive flag to the uncommit command. This allows
the user to interactively (record and crecord) select changes to be uncommited.
diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -28,8 +28,10 @@
from StringIO import StringIO
import struct
import re
+import inspect
import socket
import errno
+import cStringIO
sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
import mercurial
@@ -1985,6 +1987,69 @@
touched.update(files)
return touched
+def _commitfilteredinteractive(repo, ctx, match, target=None):
+ """Recommit ctx with changed files not in match. Return the new
+ node identifier, or None if nothing changed.
+ """
+
+ # In older versions of mercurial context.makememctx does not accept
+ # the extra argument. If that is not the case we fail early
+ if not 'extra' in inspect.getargspec(context.makememctx).args:
+ raise util.Abort('Your version of mercurial does not support this'
+ 'feature, please update it and try again or do not'
+ 'use the interactive mode')
+ base = ctx.p1()
+ ui = repo.ui
+ branch = base.branch()
+ store = patch.filestore()
+ files = set()
+
+ if target is None:
+ target = base
+
+ try:
+ # Filter the hunks
+ diffopts = patch.difffeatureopts(ui, whitespace=True)
+ diffopts.nodates = True
+ diffopts.git = True
+
+ originaldiff = patch.diff(repo, target.node(), ctx.node(), match,
+ opts=diffopts)
+ originaldiff = list(originaldiff)
+ originalchunks = patch.parsepatch(originaldiff)
+ filterfn = cmdutil.recordfilter
+ chunks = filterfn(ui, originalchunks)
+ if not chunks:
+ raise util.Abort('no change selected')
+
+ # Write the patch
+ fp = cStringIO.StringIO()
+ for c in chunks:
+ c.write(fp)
+ fp.seek(0)
+
+ # Apply the patch
+ tmpname, message, _, _, _, _, _, _ = patch.extract(ui, fp)
+ patch.patchrepo(ui, repo, target, store, tmpname,
+ strip=1,
+ prefix='',
+ files=files,
+ eolmode=None)
+ new = context.makememctx(repo, (target, node.nullid),
+ ctx.description(),
+ ctx.user(),
+ ctx.date(),
+ branch, files, store,
+ editor=None,
+ extra=ctx.extra())
+ return repo.commitctx(new)
+
+ except patch.PatchError, err:
+ raise util.Abort('error parsing patch: %s' % err)
+
+ finally:
+ store.close()
+
def _commitfiltered(repo, ctx, match, target=None):
"""Recommit ctx with changed files not in match. Return the new
node identifier, or None if nothing changed.
@@ -2082,6 +2147,7 @@
@command('^uncommit',
[('a', 'all', None, _('uncommit all changes when no arguments given')),
+ ('i', 'interactive', None, _('interactive mode')),
('r', 'rev', '', _('revert commit content to REV instead')),
] + commands.walkopts,
_('[OPTION]... [NAME]'))
@@ -2131,10 +2197,21 @@
# Recommit the filtered changeset
tr = repo.transaction('uncommit')
newid = None
+ interactive = opts.get('interactive')
includeorexclude = opts.get('include') or opts.get('exclude')
- if (pats or includeorexclude or opts.get('all')):
+ if (pats or includeorexclude or opts.get('all') or interactive):
+
+ if interactive and not includeorexclude:
+ # Interactive with no include or exclude implies looking at
+ # all the changes
+ opts['all'] = True
+
match = scmutil.match(old, pats, opts)
- newid = _commitfiltered(repo, old, match, target=rev)
+ if interactive:
+ newid = _commitfilteredinteractive(repo, old, match,
+ target=rev)
+ else:
+ newid = _commitfiltered(repo, old, match, target=rev)
if newid is None:
raise util.Abort(_('nothing to uncommit'),
hint=_("use --all to uncommit all files"))
diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t
--- a/tests/test-uncommit.t
+++ b/tests/test-uncommit.t
@@ -7,6 +7,13 @@
$ glog() {
> hg glog --template '{rev}:{node|short}@{branch}({obsolete}/{phase}) {desc|firstline}\n' "$@"
> }
+ $ mkcommit() {
+ > echo "$1" > "$1"
+ > hg add "$1"
+ > hg ci -m "add $1"
+ > }
+
+
$ hg init repo
$ cd repo
@@ -360,3 +367,205 @@
$ hg cat b --rev .
b
b
+
+Setup for test of uncommit interactive
+
+ $ cd ..
+ $ hg init interactiverepo
+ $ cd interactiverepo
+ $ cat >> $HGRCPATH <<EOF
+ > [ui]
+ > interactive = true
+ > [diff]
+ > git = 1
+ > unified = 0
+ > EOF
+ $ mkcommit aa
+ $ mkcommit bb
+ $ mkcommit cc
+ $ echo "1" >> bb
+ $ echo "2" >> aa
+ $ hg commit --amend
+
+Uncommit interactive and select nothing: abort or it would create an empty commit
+
+ $ hg uncommit -i << EOF
+ > n
+ > n
+ > n
+ > n
+ > EOF
+ diff --git a/aa b/aa
+ 1 hunks, 1 lines changed
+ examine changes to 'aa'? [Ynesfdaq?] n
+
+ diff --git a/bb b/bb
+ 1 hunks, 1 lines changed
+ examine changes to 'bb'? [Ynesfdaq?] n
+
+ diff --git a/cc b/cc
+ new file mode 100644
+ examine changes to 'cc'? [Ynesfdaq?] n
+
+ abort: no change selected
+ [255]
+
+
+Uncommit interactive and keep only one change
+
+ $ hg uncommit -i << EOF
+ > y
+ > y
+ > n
+ > n
+ > EOF
+ diff --git a/aa b/aa
+ 1 hunks, 1 lines changed
+ examine changes to 'aa'? [Ynesfdaq?] y
+
+ @@ -1,0 +2,1 @@
+ +2
+ record change 1/3 to 'aa'? [Ynesfdaq?] y
+
+ diff --git a/bb b/bb
+ 1 hunks, 1 lines changed
+ examine changes to 'bb'? [Ynesfdaq?] n
+
+ diff --git a/cc b/cc
+ new file mode 100644
+ examine changes to 'cc'? [Ynesfdaq?] n
+
+ $ glog
+ @ 5:ce99b0615028 at default(stable/draft) add cc
+ |
+ o 1:c5e444d44616 at default(stable/draft) add bb
+ |
+ o 0:58663bb03074 at default(stable/draft) add aa
+
+
+ $ hg diff -c .
+ diff --git a/aa b/aa
+ --- a/aa
+ +++ b/aa
+ @@ -1,0 +2,1 @@
+ +2
+
+ $ hg diff
+ diff --git a/bb b/bb
+ --- a/bb
+ +++ b/bb
+ @@ -1,0 +2,1 @@
+ +1
+ diff --git a/cc b/cc
+ new file mode 100644
+ --- /dev/null
+ +++ b/cc
+ @@ -0,0 +1,1 @@
+ +cc
+
+ $ hg commit --amend
+
+Uncommit interactive and keep all of the changes
+ $ hg uncommit -i << EOF
+ > y
+ > y
+ > y
+ > y
+ > y
+ > y
+ > EOF
+ diff --git a/aa b/aa
+ 1 hunks, 1 lines changed
+ examine changes to 'aa'? [Ynesfdaq?] y
+
+ @@ -1,0 +2,1 @@
+ +2
+ record change 1/3 to 'aa'? [Ynesfdaq?] y
+
+ diff --git a/bb b/bb
+ 1 hunks, 1 lines changed
+ examine changes to 'bb'? [Ynesfdaq?] y
+
+ @@ -1,0 +2,1 @@
+ +1
+ record change 2/3 to 'bb'? [Ynesfdaq?] y
+
+ diff --git a/cc b/cc
+ new file mode 100644
+ examine changes to 'cc'? [Ynesfdaq?] y
+
+ @@ -0,0 +1,1 @@
+ +cc
+ record change 3/3 to 'cc'? [Ynesfdaq?] y
+
+ abort: changeset d8903e0fc592 cannot obsolete itself
+ [255]
+
+Uncommit interactive: editing a hunk in the commit and keep all the rest
+
+1) Create a dummy editor changing cc to 42
+ $ cat > $TESTTMP/editor.sh << '__EOF__'
+ > cat "$1" | sed "s/+cc/+42/g" > tt
+ > mv tt "$1"
+ > __EOF__
+
+2) Run the uncommit command
+ $ HGEDITOR="\"sh\" \"\${TESTTMP}/editor.sh\"" hg uncommit -i << EOF
+ > y
+ > y
+ > y
+ > y
+ > y
+ > e
+ > EOF
+ diff --git a/aa b/aa
+ 1 hunks, 1 lines changed
+ examine changes to 'aa'? [Ynesfdaq?] y
+
+ @@ -1,0 +2,1 @@
+ +2
+ record change 1/3 to 'aa'? [Ynesfdaq?] y
+
+ diff --git a/bb b/bb
+ 1 hunks, 1 lines changed
+ examine changes to 'bb'? [Ynesfdaq?] y
+
+ @@ -1,0 +2,1 @@
+ +1
+ record change 2/3 to 'bb'? [Ynesfdaq?] y
+
+ diff --git a/cc b/cc
+ new file mode 100644
+ examine changes to 'cc'? [Ynesfdaq?] y
+
+ @@ -0,0 +1,1 @@
+ +cc
+ record change 3/3 to 'cc'? [Ynesfdaq?] e
+
+
+ $ hg diff -c .
+ diff --git a/aa b/aa
+ --- a/aa
+ +++ b/aa
+ @@ -1,0 +2,1 @@
+ +2
+ diff --git a/bb b/bb
+ --- a/bb
+ +++ b/bb
+ @@ -1,0 +2,1 @@
+ +1
+ diff --git a/cc b/cc
+ new file mode 100644
+ --- /dev/null
+ +++ b/cc
+ @@ -0,0 +1,1 @@
+ +42
+
+3) As expected the workdir still contains the change of the old commit
+ $ hg diff
+ diff --git a/cc b/cc
+ new file mode 100644
+ --- /dev/null
+ +++ b/cc
+ @@ -0,0 +1,1 @@
+ +cc
More information about the Mercurial-devel
mailing list