[PATCH] histedit: add support for exec/x command to histedit (issue4036)

liscju piotr.listkiewicz at gmail.com
Sun Sep 13 13:24:50 CDT 2015


# HG changeset patch
# User liscju <piotr.listkiewicz at gmail.com>
# Date 1442157106 -7200
#      Sun Sep 13 17:11:46 2015 +0200
# Node ID b7f5ee27ab1710e226e53727ca13f550707b6fc4
# Parent  fd9b1262f0e4fbcec1f66f01839bf3d4ee4cff59
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.

diff -r fd9b1262f0e4 -r b7f5ee27ab17 hgext/histedit.py
--- a/hgext/histedit.py	Sat Aug 22 15:21:45 2015 -0700
+++ 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):
@@ -498,6 +507,78 @@
                          editor=editor)
     return repo.commitctx(new)
 
+def _isdirtywc(repo):
+    s = repo.status()
+    return s.modified or s.added or s.removed or s.deleted
+
+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()
+
+        release(self.state.lock, self.state.wlock)
+        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()
+            self.state.lock = repo.lock()
+
+        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]
@@ -650,6 +731,8 @@
                'drop': drop,
                'm': message,
                'mess': message,
+               'x': execute,
+               'exec': execute,
                }
 
 @command('histedit',
@@ -903,11 +986,9 @@
 
         actobj = actiontable[action].fromrule(state, currentnode)
 
-        s = repo.status()
-        if s.modified or s.added or s.removed or s.deleted:
+        if _isdirtywc(repo):
             actobj.continuedirty()
-            s = repo.status()
-            if s.modified or s.added or s.removed or s.deleted:
+            if _isdirtywc(repo):
                 raise util.Abort(_("working copy still dirty"))
 
         parentctx, replacements = actobj.continueclean()
@@ -983,21 +1064,24 @@
         if ' ' not in r:
             raise util.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 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])
+        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 util.Abort(_('missing rules for changeset %s') %
diff -r fd9b1262f0e4 -r b7f5ee27ab17 tests/test-histedit-arguments.t
--- a/tests/test-histedit-arguments.t	Sat Aug 22 15:21:45 2015 -0700
+++ 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.
@@ -294,6 +297,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 fd9b1262f0e4 -r b7f5ee27ab17 tests/test-histedit-bookmark-motion.t
--- a/tests/test-histedit-bookmark-motion.t	Sat Aug 22 15:21:45 2015 -0700
+++ 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 fd9b1262f0e4 -r b7f5ee27ab17 tests/test-histedit-commute.t
--- a/tests/test-histedit-commute.t	Sat Aug 22 15:21:45 2015 -0700
+++ 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 fd9b1262f0e4 -r b7f5ee27ab17 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,242 @@
+  $ . "$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 fd9b1262f0e4 -r b7f5ee27ab17 tests/test-histedit-obsolete.t
--- a/tests/test-histedit-obsolete.t	Sat Aug 22 15:21:45 2015 -0700
+++ 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 fd9b1262f0e4 -r b7f5ee27ab17 tests/test-histedit-outgoing.t
--- a/tests/test-histedit-outgoing.t	Sat Aug 22 15:21:45 2015 -0700
+++ 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