[PATCH 4 of 4 V5] histedit: add support for exec/x command to histedit (issue4036)
liscju
piotr.listkiewicz at gmail.com
Thu Nov 12 13:17:47 UTC 2015
# HG changeset patch
# User liscju <piotr.listkiewicz at gmail.com>
# Date 1442157106 -7200
# Sun Sep 13 17:11:46 2015 +0200
# Node ID 6148ae88d2ece6e66f8d4af10ffa1564461c71ea
# Parent 9815a15a8058b59eb86d9797d84857196b04c8e9
histedit: add support for exec/x command to histedit (issue4036)
Point of this command is to be able to do some automatic munging
on one or several commits in a series.
The basic contract is that it receives a clean working copy and is
expected to leave a clean working copy if it exits 0. If either
the command leaves the working copy dirty, or it exits non-zero,
the histedit stops to give opportunity for user intervention.
Exec command functionality is implemented in execute class. It is
overriding fromrule class method to deal with initializing
object in different way than other histeditactions. It takes
state and cmd instead of state and node id of next changeset
action to perform on.
Run method of exec_ class at first updates to parent of current
changeset,apply it and commit to obtain copy of current changeset
in current directory. It is done to let cmd amend to this changeset
and correctly perform changing history containing this changeset.
In run method locks are released right before running command to
allow running hg commands inside it. Acquiring proper number of
locks is done in finally block in histedit.py:544-545 line, by
default repo.lock and repo.wlock wait forever for acquiring
them.
Caches are totally cleared while acquiring the lock again
in following instructions(histedit.py:544-546):
finally:
self.state.wlock = repo.wlock(times=wlockheld)
self.state.lock = repo.lock(times=lockheld)
repo.invalidateall()
repo.wlock invokes lock constructor with acquirefn callback set to
localrepo.invalidatedirstate.
repo.lock invokes lock constructor with acquirefn callback set to
localrepo.invalidate.
invalidateall function in localrepository has invocation only to
invalidate and invalidatedirstate, so acquiring the locks should
clear caches. Additional invocation to repo.invalidateall()
doesnt introduce additional complexity, but protect from situation
where extension overrides invalidateall() to clear additional caches
used by it.
diff -r 9815a15a8058 -r 6148ae88d2ec hgext/histedit.py
--- a/hgext/histedit.py Thu Nov 12 12:44:15 2015 +0100
+++ b/hgext/histedit.py Sun Sep 13 17:11:46 2015 +0200
@@ -40,6 +40,9 @@
# d, drop = remove commit from history
# m, mess = edit commit message without changing commit content
#
+ # Between changes you can add this:
+ # x, exec = run command (the rest of the line) using shell
+ #
In this file, lines beginning with ``#`` are ignored. You must specify a rule
for each revision in your history. For example, if you had meant to add gamma
@@ -62,6 +65,9 @@
# d, drop = remove commit from history
# m, mess = edit commit message without changing commit content
#
+ # Between changes you can add this:
+ # x, exec = run command (the rest of the line) using shell
+ #
At which point you close the editor and ``histedit`` starts working. When you
specify a ``fold`` operation, ``histedit`` will open an editor when it folds
@@ -200,6 +206,9 @@
# d, drop = remove commit from history
# m, mess = edit commit message without changing commit content
#
+# Between changes you can add this:
+# x, exec = run command (the rest of the line) using shell
+#
""")
class histeditstate(object):
@@ -505,6 +514,76 @@
def _isdirtywc(repo):
return repo[None].dirty(missing=True)
+class execute(histeditaction):
+ def __init__(self, state, cmd):
+ self.state = state
+ self.cmd = cmd
+
+ @classmethod
+ def fromrule(cls, state, cmd):
+ return cls(state, cmd)
+
+ def run(self):
+ repo = self.state.repo
+ node = self.state.parentctxnode
+
+ hg.update(repo, repo[node].parents()[0])
+ applychanges(repo.ui, repo, repo[node], {})
+ self._commit()
+
+ lockheld = self.state.lock.releaseall()
+ wlockheld = self.state.wlock.releaseall()
+ try:
+ return_code = repo.ui.system(self.cmd, cwd=repo.root)
+ except OSError:
+ raise error.InterventionRequired(
+ self._getinterventionmsg(
+ _('Execution of "%s" failed.') % self.cmd))
+ finally:
+ self.state.wlock = repo.wlock(times=wlockheld)
+ self.state.lock = repo.lock(times=lockheld)
+ repo.invalidateall()
+
+ if return_code == 0:
+ if _isdirtywc(repo):
+ raise error.InterventionRequired(
+ self._getinterventionmsg(
+ _('Working copy dirty after exec "%s".') % self.cmd))
+ else:
+ return self.continueclean()
+ else:
+ raise error.InterventionRequired(
+ self._getinterventionmsg(
+ _('Return code of "%s" is %d (expected zero).')
+ % (self.cmd, return_code)))
+
+ def continuedirty(self):
+ raise error.InterventionRequired(
+ self._getinterventionmsg(
+ _('Working copy dirty after exec "%s".') % self.cmd))
+
+ def continueclean(self):
+ ctx = self.state.repo['.']
+ node = self.state.parentctxnode
+ return ctx, [(node, (ctx.node(),))]
+
+ def _commit(self):
+ repo = self.state.repo
+ node = self.state.parentctxnode
+ rulectx = repo[node]
+
+ editor = self.commiteditor()
+ commit = commitfuncfor(repo, rulectx)
+
+ commit(text=rulectx.description(), user=rulectx.user(),
+ date=rulectx.date(), extra=rulectx.extra(), editor=editor)
+
+ def _getinterventionmsg(self, errormsg):
+ return errormsg + "\n" + _(
+ 'Make changes as needed, you may commit or record as needed '
+ 'now.\nWhen you are finished, run hg histedit --continue to '
+ 'resume.')
+
class pick(histeditaction):
def run(self):
rulectx = self.repo[self.node]
@@ -688,6 +767,8 @@
'drop': drop,
'm': message,
'mess': message,
+ 'x': execute,
+ 'exec': execute,
}
@command('histedit',
@@ -1052,21 +1133,26 @@
if ' ' not in r:
raise error.Abort(_('malformed line "%s"') % r)
action, rest = r.split(' ', 1)
- ha = rest.strip().split(' ', 1)[0]
- try:
- ha = repo[ha].hex()
- except error.RepoError:
- raise error.Abort(_('unknown changeset %s listed') % ha[:12])
- if ha not in expected:
- raise error.Abort(
- _('may not use changesets other than the ones listed'))
- if ha in seen:
- raise error.Abort(_('duplicated command for changeset %s') %
- ha[:12])
- seen.add(ha)
- if action not in actiontable or action.startswith('_'):
- raise error.Abort(_('unknown action "%s"') % action)
- parsed.append([action, ha])
+
+ if action == "x" or action == "exec":
+ parsed.append([action, rest])
+ else:
+ ha = rest.strip().split(' ', 1)[0]
+ try:
+ ha = repo[ha].hex()
+ except error.RepoError:
+ raise util.Abort(_('unknown changeset %s listed') % ha[:12])
+ if ha not in expected:
+ raise util.Abort(
+ _('may not use changesets other than the ones listed'))
+ if ha in seen:
+ raise util.Abort(_('duplicated command for changeset %s') %
+ ha[:12])
+ seen.add(ha)
+ if action not in actiontable:
+ raise util.Abort(_('unknown action "%s"') % action)
+ parsed.append([action, ha])
+
missing = sorted(expected - seen) # sort to stabilize output
if missing:
raise error.Abort(_('missing rules for changeset %s') %
diff -r 9815a15a8058 -r 6148ae88d2ec tests/test-histedit-arguments.t
--- a/tests/test-histedit-arguments.t Thu Nov 12 12:44:15 2015 +0100
+++ b/tests/test-histedit-arguments.t Sun Sep 13 17:11:46 2015 +0200
@@ -71,6 +71,9 @@
# d, drop = remove commit from history
# m, mess = edit commit message without changing commit content
#
+ # Between changes you can add this:
+ # x, exec = run command (the rest of the line) using shell
+ #
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
Run on a revision not ancestors of the current working directory.
@@ -295,6 +298,9 @@
# d, drop = remove commit from history
# m, mess = edit commit message without changing commit content
#
+ # Between changes you can add this:
+ # x, exec = run command (the rest of the line) using shell
+ #
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
Test --continue with --keep
diff -r 9815a15a8058 -r 6148ae88d2ec tests/test-histedit-bookmark-motion.t
--- a/tests/test-histedit-bookmark-motion.t Thu Nov 12 12:44:15 2015 +0100
+++ b/tests/test-histedit-bookmark-motion.t Sun Sep 13 17:11:46 2015 +0200
@@ -77,6 +77,9 @@
# d, drop = remove commit from history
# m, mess = edit commit message without changing commit content
#
+ # Between changes you can add this:
+ # x, exec = run command (the rest of the line) using shell
+ #
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg histedit 1 --commands - --verbose << EOF | grep histedit
> pick 177f92b77385 2 c
@@ -138,6 +141,9 @@
# d, drop = remove commit from history
# m, mess = edit commit message without changing commit content
#
+ # Between changes you can add this:
+ # x, exec = run command (the rest of the line) using shell
+ #
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg histedit 1 --commands - --verbose << EOF | grep histedit
> pick b346ab9a313d 1 c
diff -r 9815a15a8058 -r 6148ae88d2ec tests/test-histedit-commute.t
--- a/tests/test-histedit-commute.t Thu Nov 12 12:44:15 2015 +0100
+++ b/tests/test-histedit-commute.t Sun Sep 13 17:11:46 2015 +0200
@@ -71,6 +71,9 @@
# d, drop = remove commit from history
# m, mess = edit commit message without changing commit content
#
+ # Between changes you can add this:
+ # x, exec = run command (the rest of the line) using shell
+ #
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
edit the history
@@ -349,6 +352,9 @@
# d, drop = remove commit from history
# m, mess = edit commit message without changing commit content
#
+ # Between changes you can add this:
+ # x, exec = run command (the rest of the line) using shell
+ #
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
should also work if a commit message is missing
diff -r 9815a15a8058 -r 6148ae88d2ec tests/test-histedit-exec.t
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-exec.t Sun Sep 13 17:11:46 2015 +0200
@@ -0,0 +1,246 @@
+ $ . "$TESTDIR/histedit-helpers.sh"
+
+ $ cat >> $HGRCPATH <<EOF
+ > [extensions]
+ > histedit=
+ > EOF
+
+ $ initrepo ()
+ > {
+ > hg init r
+ > cd r
+ > for x in a b c d e f g; do
+ > echo $x > $x
+ > hg add $x
+ > hg ci -m $x
+ > done
+ > }
+
+ $ initrepo
+
+log before exec
+ $ hg log --graph
+ @ changeset: 6:3c6a8ed2ebe8
+ | tag: tip
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: g
+ |
+ o changeset: 5:652413bf663e
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: f
+ |
+ o changeset: 4:e860deea161a
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: e
+ |
+ o changeset: 3:055a42cdd887
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: d
+ |
+ o changeset: 2:177f92b77385
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: c
+ |
+ o changeset: 1:d2ae7f538514
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: b
+ |
+ o changeset: 0:cb9a9f314b8b
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: a
+
+
+exec echo "Hello World" on directory after picking last changeset
+ $ hg histedit 6 --commands - 2>&1 << EOF
+ > pick 6
+ > exec echo "Hello World"
+ > EOF
+ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ adding g
+ Hello World
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/r/.hg/strip-backup/3c6a8ed2ebe8-f7abffd4-backup.hg (glob)
+
+exec ls after revision 1
+ $ hg histedit 1 --commands - 2>&1 << EOF
+ > pick 1
+ > exec echo "ls output start:" && ls && echo "ls output ends"
+ > pick 2
+ > pick 3
+ > pick 4
+ > pick 5
+ > pick 6
+ > EOF
+ 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
+ adding b
+ ls output start:
+ a
+ b
+ ls output ends
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/r/.hg/strip-backup/d2ae7f538514-1c1cf14d-backup.hg (glob)
+
+
+exec adding b1 file in revision 1
+ $ hg histedit 1 --commands - 2>&1 << EOF
+ > pick 1
+ > exec echo b1 >> b1 && hg add b1 && hg commit --amend -m "b1"
+ > pick 2
+ > pick 3
+ > pick 4
+ > pick 5
+ > pick 6
+ > EOF
+ 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
+ adding b
+ saved backup bundle to $TESTTMP/r/.hg/strip-backup/18a73c9c72cf-c8c1f3b6-amend-backup.hg (glob)
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/r/.hg/strip-backup/0808241031aa-aa7f6930-backup.hg (glob)
+
+ $ hg log --graph
+ @ changeset: 6:4696a3362537
+ | tag: tip
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: g
+ |
+ o changeset: 5:401045adec09
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: f
+ |
+ o changeset: 4:e450e0e843f7
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: e
+ |
+ o changeset: 3:dcf604978dad
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: d
+ |
+ o changeset: 2:e6d5272deb83
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: c
+ |
+ o changeset: 1:887fd832d217
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: b1
+ |
+ o changeset: 0:cb9a9f314b8b
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: a
+
+
+ $ hg log -f b -f b1
+ changeset: 1:887fd832d217
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: b1
+
+
+exec creating new file f1 after changeset 5 leaving working dir dirty
+ $ hg histedit 5 --commands - 2>&1 << EOF
+ > pick 5
+ > exec echo "f1" >> f
+ > pick 6
+ > EOF
+ 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+ adding f
+ Working copy dirty after exec "echo "f1" >> f".
+ Make changes as needed, you may commit or record as needed now.
+ When you are finished, run hg histedit --continue to resume.
+ [1]
+
+try to continue while working directory is dirty
+ $ hg histedit --continue
+ Working copy dirty after exec "echo "f1" >> f".
+ Make changes as needed, you may commit or record as needed now.
+ When you are finished, run hg histedit --continue to resume.
+ [1]
+
+ $ hg commit -m "f1"
+ $ hg histedit --continue
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/r/.hg/strip-backup/401045adec09-e5efc200-backup.hg (glob)
+
+ $ hg log --graph -r 5:
+ @ changeset: 7:037517e744e6
+ | tag: tip
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: g
+ |
+ o changeset: 6:bd6d5e3cbcf3
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: f1
+ |
+ o changeset: 5:06ff77bf2665
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: f
+ |
+
+ $ hg log -f f
+ changeset: 6:bd6d5e3cbcf3
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: f1
+
+ changeset: 5:06ff77bf2665
+ user: test
+ date: Thu Jan 01 00:00:00 1970 +0000
+ summary: f
+
+
+exec when command returns non zero value
+ $ hg histedit 7 --commands - 2>&1 << EOF
+ > exec exit 1
+ > pick 7
+ > EOF
+ 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ reverting f
+ Return code of "exit 1" is 1 (expected zero).
+ Make changes as needed, you may commit or record as needed now.
+ When you are finished, run hg histedit --continue to resume.
+ [1]
+
+ $ hg histedit --continue
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ saved backup bundle to $TESTTMP/r/.hg/strip-backup/bd6d5e3cbcf3-8113adfc-backup.hg (glob)
+
+ $ hg log --graph -r 6:
+ @ changeset: 7:d994db4c657b
+ | tag: tip
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: g
+ |
+ o changeset: 6:41b4840c21f7
+ | user: test
+ | date: Thu Jan 01 00:00:00 1970 +0000
+ | summary: f1
+ |
diff -r 9815a15a8058 -r 6148ae88d2ec tests/test-histedit-obsolete.t
--- a/tests/test-histedit-obsolete.t Thu Nov 12 12:44:15 2015 +0100
+++ b/tests/test-histedit-obsolete.t Sun Sep 13 17:11:46 2015 +0200
@@ -56,6 +56,9 @@
# d, drop = remove commit from history
# m, mess = edit commit message without changing commit content
#
+ # Between changes you can add this:
+ # x, exec = run command (the rest of the line) using shell
+ #
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg histedit 1 --commands - --verbose <<EOF | grep histedit
> pick 177f92b77385 2 c
diff -r 9815a15a8058 -r 6148ae88d2ec tests/test-histedit-outgoing.t
--- a/tests/test-histedit-outgoing.t Thu Nov 12 12:44:15 2015 +0100
+++ b/tests/test-histedit-outgoing.t Sun Sep 13 17:11:46 2015 +0200
@@ -53,6 +53,9 @@
# d, drop = remove commit from history
# m, mess = edit commit message without changing commit content
#
+ # Between changes you can add this:
+ # x, exec = run command (the rest of the line) using shell
+ #
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ cd ..
@@ -85,6 +88,9 @@
# d, drop = remove commit from history
# m, mess = edit commit message without changing commit content
#
+ # Between changes you can add this:
+ # x, exec = run command (the rest of the line) using shell
+ #
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ cd ..
@@ -109,6 +115,9 @@
# d, drop = remove commit from history
# m, mess = edit commit message without changing commit content
#
+ # Between changes you can add this:
+ # x, exec = run command (the rest of the line) using shell
+ #
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
test to check number of roots in outgoing revisions
More information about the Mercurial-devel
mailing list