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

Augie Fackler raf at durin42.com
Thu Sep 10 09:39:38 CDT 2015


On Wed, Sep 09, 2015 at 06:04:58PM +0200, liscju wrote:
> # HG changeset patch
> # User liscju <piotr.listkiewicz at gmail.com>
> # Date 1441363832 -7200
> #      Fri Sep 04 12:50:32 2015 +0200
> # Node ID 4ee403abb436e0f177dc149be13a598a61db14b8
> # Parent  fd9b1262f0e4fbcec1f66f01839bf3d4ee4cff59
> histedit: add support for exec/x command to histedit (issue4036)

This is a good start on the feature, but this at a minimum will need
some tests added and a few nits corrected. I fear there may be some
tricky edge cases exposed when you start writing tests too (see below
for an issue related to 'hg commit --amend' that I suspect you'll trip
on.)

>
> 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.
>
> Behavior is undefined if it updates to some completely unrelated
> portion of history.

Can it abort instead? Undefined behavior in my history editing tools
feels...risky.

>
> Exec command functionality is implemented in exec_ 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 ammend to this changeset

typo: amend has only one m, not two.

> and correctly perform changing history containing this changeset.

Related: there are some bugs around doing 'hg amend' in the middle of
a stream. Might need to fix those or otherwise defend against that
bug: http://bz.selenic.com/show_bug.cgi?id=4800

>
> In run method locks are released right before running command to
> allow running hg commands inside it.
>
> diff -r fd9b1262f0e4 -r 4ee403abb436 hgext/histedit.py
> --- a/hgext/histedit.py	Sat Aug 22 15:21:45 2015 -0700
> +++ b/hgext/histedit.py	Fri Sep 04 12:50:32 2015 +0200
> @@ -199,6 +199,7 @@
>  #  r, roll = like fold, but discard this commit's description
>  #  d, drop = remove commit from history
>  #  m, mess = edit commit message without changing commit content

Might be worth breaking this next line into another section, eg:

#
# Between changes you can add this:
# x, exec = run command (the rest of the line) using shell


> +#  x, exec = run command (the rest of the line) using shell
>  #
>  """)
>
> @@ -498,6 +499,46 @@
>                           editor=editor)
>      return repo.commitctx(new)
>
> +class exec_(histeditaction):
> +    def __init__(self, state, node, cmd):
> +        super(exec_, self).__init__(state, node)
> +        self.repo = state.repo
> +        self.cmd = cmd
> +
> +    @classmethod
> +    def fromrule(cls, state, cmd):
> +        return cls(state, state.parentctxnode, cmd)
> +
> +    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.')

This construction *might* be bad for internationalization, I'm not
sure. Either way, you need to wrap the string here in _().

> +
> +    def run(self):
> +        hg.update(self.repo, self.repo[self.node].parents()[0])
> +        applychanges(self.repo.ui, self.repo, self.repo[self.node], {})
> +        self.continuedirty()
> +
> +        release(self.state.lock, self.state.wlock)
> +        return_code = self.repo.ui.system(self.cmd)
> +        self.state.wlock = self.repo.wlock()
> +        self.state.lock = self.repo.lock()
> +
> +        if return_code == 0:
> +            s = self.repo.status(unknown=True)
> +            if s.modified or s.added or s.removed or s.deleted or s.unknown:

Unknown files shouldn't be considered dirty? Look later in
histedit.py for the call to continuedirty().

Might be worth teasing that out into a _dirtywc(repo) method to prevent future inconsistencies.

> +                raise error.InterventionRequired(
> +                    self.getinterventionmsg(
> +                        "Working copy dirty after exec %s" % self.cmd))

missing _()

> +            else:
> +                ctx = self.repo["."]
> +                return ctx, [(self.node, (ctx.node(),))]
> +        else:
> +            raise error.InterventionRequired(
> +                self.getinterventionmsg(
> +                    'Return code of %s is nonzero.' % self.cmd))

missing _()

Also, maybe (ab)use %r here so self.cmd gets placed in quotes. Also I think
the "is" should be "was" here. Maybe show the return code in the
output too?

> +
>  class pick(histeditaction):
>      def run(self):
>          rulectx = self.repo[self.node]
> @@ -650,6 +691,8 @@
>                 'drop': drop,
>                 'm': message,
>                 'mess': message,
> +               'x': exec_,
> +               'exec': exec_,
>                 }
>
>  @command('histedit',
> @@ -983,21 +1026,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 4ee403abb436 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	Fri Sep 04 12:50:32 2015 +0200
> @@ -70,6 +70,7 @@
>    #  r, roll = like fold, but discard this commit's description
>    #  d, drop = remove commit from history
>    #  m, mess = edit commit message without changing commit content
> +  #  x, exec = run command (the rest of the line) using shell
>    #
>    0 files updated, 0 files merged, 0 files removed, 0 files unresolved
>
> @@ -293,6 +294,7 @@
>    #  r, roll = like fold, but discard this commit's description
>    #  d, drop = remove commit from history
>    #  m, mess = edit commit message without changing commit content
> +  #  x, exec = run command (the rest of the line) using shell
>    #
>    0 files updated, 0 files merged, 0 files removed, 0 files unresolved
>
> diff -r fd9b1262f0e4 -r 4ee403abb436 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	Fri Sep 04 12:50:32 2015 +0200
> @@ -76,6 +76,7 @@
>    #  r, roll = like fold, but discard this commit's description
>    #  d, drop = remove commit from history
>    #  m, mess = edit commit message without changing commit content
> +  #  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
> @@ -137,6 +138,7 @@
>    #  r, roll = like fold, but discard this commit's description
>    #  d, drop = remove commit from history
>    #  m, mess = edit commit message without changing commit content
> +  #  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
> diff -r fd9b1262f0e4 -r 4ee403abb436 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	Fri Sep 04 12:50:32 2015 +0200
> @@ -70,6 +70,7 @@
>    #  r, roll = like fold, but discard this commit's description
>    #  d, drop = remove commit from history
>    #  m, mess = edit commit message without changing commit content
> +  #  x, exec = run command (the rest of the line) using shell
>    #
>    0 files updated, 0 files merged, 0 files removed, 0 files unresolved
>
> @@ -348,6 +349,7 @@
>    #  r, roll = like fold, but discard this commit's description
>    #  d, drop = remove commit from history
>    #  m, mess = edit commit message without changing commit content
> +  #  x, exec = run command (the rest of the line) using shell
>    #
>    0 files updated, 0 files merged, 0 files removed, 0 files unresolved
>
> diff -r fd9b1262f0e4 -r 4ee403abb436 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	Fri Sep 04 12:50:32 2015 +0200
> @@ -55,6 +55,7 @@
>    #  r, roll = like fold, but discard this commit's description
>    #  d, drop = remove commit from history
>    #  m, mess = edit commit message without changing commit content
> +  #  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
> diff -r fd9b1262f0e4 -r 4ee403abb436 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	Fri Sep 04 12:50:32 2015 +0200
> @@ -52,6 +52,7 @@
>    #  r, roll = like fold, but discard this commit's description
>    #  d, drop = remove commit from history
>    #  m, mess = edit commit message without changing commit content
> +  #  x, exec = run command (the rest of the line) using shell
>    #
>    0 files updated, 0 files merged, 0 files removed, 0 files unresolved
>    $ cd ..
> @@ -84,6 +85,7 @@
>    #  r, roll = like fold, but discard this commit's description
>    #  d, drop = remove commit from history
>    #  m, mess = edit commit message without changing commit content
> +  #  x, exec = run command (the rest of the line) using shell
>    #
>    0 files updated, 0 files merged, 0 files removed, 0 files unresolved
>    $ cd ..
> @@ -108,6 +110,7 @@
>    #  r, roll = like fold, but discard this commit's description
>    #  d, drop = remove commit from history
>    #  m, mess = edit commit message without changing commit content
> +  #  x, exec = run command (the rest of the line) using shell
>    #
>    0 files updated, 0 files merged, 0 files removed, 0 files unresolved
>
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at selenic.com
> https://selenic.com/mailman/listinfo/mercurial-devel


More information about the Mercurial-devel mailing list