Patchwork [4,of,4,V4,evolve-ext] metaedit: add support for folding commits while editing their metadata

login
register
mail settings
Submitter Siddharth Agarwal
Date April 25, 2016, 11:34 p.m.
Message ID <71e061aa0be879ad6477.1461627283@dev666.prn1.facebook.com>
Download mbox | patch
Permalink /patch/14791/
State Accepted
Delegated to: Pierre-Yves David
Headers show

Comments

Siddharth Agarwal - April 25, 2016, 11:34 p.m.
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1461626682 25200
#      Mon Apr 25 16:24:42 2016 -0700
# Node ID 71e061aa0be879ad6477fbdb724a3a2e69e5af5b
# Parent  ea19e5ccc442ac286d4244695365034475b4fa5c
metaedit: add support for folding commits while editing their metadata

This also allows us to accept multiple commits without dealing with the thorny
algorithmic and UI issues of editing multiple commits at once.

Crucially, it is different from 'hg fold --exact' in that it also allows
'folding' a single commit and rewriting its metadata. This is really useful to
have as a single logical operation, for example while preparing a series of
multiple local changesets that will need to be pushed as a single changeset.
Simon Farnsworth - April 26, 2016, 4:14 p.m.
I like this - it's a safe subset of histedit for operations (like code 
review tools) that want to change metadata but don't want bugs to damage 
the diff.

I can't see anything wrong with this version.

Simon

On 26/04/2016 00:34, Siddharth Agarwal wrote:
> # HG changeset patch
> # User Siddharth Agarwal <sid0@fb.com>
> # Date 1461626682 25200
> #      Mon Apr 25 16:24:42 2016 -0700
> # Node ID 71e061aa0be879ad6477fbdb724a3a2e69e5af5b
> # Parent  ea19e5ccc442ac286d4244695365034475b4fa5c
> metaedit: add support for folding commits while editing their metadata
>
> This also allows us to accept multiple commits without dealing with the thorny
> algorithmic and UI issues of editing multiple commits at once.
>
> Crucially, it is different from 'hg fold --exact' in that it also allows
> 'folding' a single commit and rewriting its metadata. This is really useful to
> have as a single logical operation, for example while preparing a series of
> multiple local changesets that will need to be pushed as a single changeset.
>
> diff --git a/hgext/evolve.py b/hgext/evolve.py
> --- a/hgext/evolve.py
> +++ b/hgext/evolve.py
> @@ -3151,14 +3151,18 @@ def fold(ui, repo, *revs, **opts):
>
>   @command('^metaedit',
>            [('r', 'rev', [], _("revision to edit")),
> +         ('', 'fold', None, _("also fold specified revisions into one")),
>            ] + commitopts + commitopts2,
>            _('hg metaedit [OPTION]... [-r] [REV]'))
>   def metaedit(ui, repo, *revs, **opts):
>       """edit commit information
>
> -    Edits the commit information for the specified revision. By default, edits
> +    Edits the commit information for the specified revisions. By default, edits
>       commit information for the working directory parent.
>
> +    With --fold, also folds multiple revisions into one if necessary. In this
> +    case, the given revisions must form a linear unbroken chain.
> +
>       .. container:: verbose
>
>        Some examples:
> @@ -3171,10 +3175,19 @@ def metaedit(ui, repo, *revs, **opts):
>
>            hg metaedit --user 'New User <new-email@example.com>'
>
> +     - Combine all draft revisions that are ancestors of foo but not of @ into
> +       one::
> +
> +         hg metaedit --fold 'draft() and only(foo,@)'
> +
> +       See :hg:`help phases` for more about draft revisions, and
> +       :hg:`help revsets` for more about the `draft()` and `only()` keywords.
>       """
>       revs = list(revs)
>       revs.extend(opts['rev'])
>       if not revs:
> +        if opts['fold']:
> +            raise error.Abort(_('revisions must be specified with --fold'))
>           revs = ['.']
>
>       wlock = lock = None
> @@ -3183,7 +3196,7 @@ def metaedit(ui, repo, *revs, **opts):
>           lock = repo.lock()
>
>           revs = scmutil.revrange(repo, revs)
> -        if len(revs) > 1:
> +        if not opts['fold'] and len(revs) > 1:
>               # TODO: handle multiple revisions. This is somewhat tricky because
>               # if we want to edit a series of commits:
>               #
> @@ -3192,18 +3205,21 @@ def metaedit(ui, repo, *revs, **opts):
>               # we need to rewrite a first, then directly rewrite b on top of the
>               # new a, then rewrite c on top of the new b. So we need to handle
>               # revisions in topological order.
> -            raise error.Abort(_('editing multiple revisions is not '
> -                                'currently supported'))
> -
> -        newunstable = _disallowednewunstable(repo, revs)
> -        if newunstable:
> -            raise error.Abort(
> -                _('cannot edit commit information in the middle of a stack'),
> -                hint=_('%s will be affected') % repo[newunstable.first()])
> -        if repo.revs("%ld and public()", revs):
> -            raise error.Abort(_('cannot edit commit information for public '
> -                                'revisions'))
> -        root = head = repo[revs.first()]
> +            raise error.Abort(_('editing multiple revisions without --fold is '
> +                                'not currently supported'))
> +
> +        if opts['fold']:
> +            root, head = _foldcheck(repo, revs)
> +        else:
> +            newunstable = _disallowednewunstable(repo, revs)
> +            if newunstable:
> +                raise error.Abort(
> +                    _('cannot edit commit information in the middle of a stack'),
> +                    hint=_('%s will be affected') % repo[newunstable.first()])
> +            if repo.revs("%ld and public()", revs):
> +                raise error.Abort(_('cannot edit commit information for public '
> +                                    'revisions'))
> +            root = head = repo[revs.first()]
>
>           wctx = repo[None]
>           p1 = wctx.p1()
> @@ -3217,7 +3233,12 @@ def metaedit(ui, repo, *revs, **opts):
>               if commitopts.get('message') or commitopts.get('logfile'):
>                   commitopts['edit'] = False
>               else:
> -                msgs = [head.description()]
> +                if opts['fold']:
> +                    msgs = ["HG: This is a fold of %d changesets." % len(allctx)]
> +                    msgs += ["HG: Commit message of changeset %s.\n\n%s\n" %
> +                             (c.rev(), c.description()) for c in allctx]
> +                else:
> +                    msgs = [head.description()]
>                   commitopts['message'] =  "\n".join(msgs)
>                   commitopts['edit'] = True
>
> @@ -3239,6 +3260,8 @@ def metaedit(ui, repo, *revs, **opts):
>           finally:
>               tr.release()
>
> +        if opts['fold']:
> +            ui.status('%i changesets folded\n' % len(revs))
>           if newp1 is not None:
>               hg.update(repo, newp1)
>       finally:
> diff --git a/tests/test-evolve.t b/tests/test-evolve.t
> --- a/tests/test-evolve.t
> +++ b/tests/test-evolve.t
> @@ -1468,11 +1468,26 @@ hg metaedit
>     $ hg metaedit -r 0
>     abort: cannot edit commit information for public revisions
>     [255]
> +  $ hg metaedit --fold
> +  abort: revisions must be specified with --fold
> +  [255]
> +  $ hg metaedit -r 0 --fold
> +  abort: cannot fold public revisions
> +  [255]
> +  $ hg metaedit '36 + 42' --fold
> +  abort: cannot fold non-linear revisions (multiple roots given)
> +  [255]
> +  $ hg metaedit '36::39 + 41' --fold
> +  abort: cannot fold non-linear revisions (multiple heads given)
> +  [255]
>   check that metaedit respects allowunstable
>     $ hg metaedit '.^' --config 'experimental.evolution=createmarkers, allnewcommands'
>     abort: cannot edit commit information in the middle of a stack
>     (c904da5245b0 will be affected)
>     [255]
> +  $ hg metaedit '18::20' --fold --config 'experimental.evolution=createmarkers, allnewcommands'
> +  abort: cannot fold chain not ending with a head or with branching
> +  [255]
>     $ hg metaedit --user foobar
>     0 files updated, 0 files merged, 0 files removed, 0 files unresolved
>     $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
> @@ -1483,26 +1498,57 @@ check that metaedit respects allowunstab
>
>   TODO: support this
>     $ hg metaedit '.^::.'
> -  abort: editing multiple revisions is not currently supported
> +  abort: editing multiple revisions without --fold is not currently supported
>     [255]
>
> -no new commit is created here because the date is the same
> -  $ HGEDITOR=cat hg metaedit
> +  $ HGEDITOR=cat hg metaedit '.^::.' --fold
> +  HG: This is a fold of 2 changesets.
> +  HG: Commit message of changeset 41.
> +
> +  amended
> +
> +  HG: Commit message of changeset 43.
> +
>     will be evolved safely
>
>
> +
>     HG: Enter commit message.  Lines beginning with 'HG:' are removed.
>     HG: Leave message empty to abort commit.
>     HG: --
> -  HG: user: foobar
> +  HG: user: test
>     HG: branch 'default'
>     HG: changed a
> +  HG: changed newfile
> +  2 changesets folded
> +  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
> +
> +  $ glog -r .
> +  @  44:41bf1183869c@default(draft) amended
> +  |
> +  ~
> +
> +no new commit is created here because the date is the same
> +  $ HGEDITOR=cat hg metaedit
> +  amended
> +
> +
> +  will be evolved safely
> +
> +
> +  HG: Enter commit message.  Lines beginning with 'HG:' are removed.
> +  HG: Leave message empty to abort commit.
> +  HG: --
> +  HG: user: test
> +  HG: branch 'default'
> +  HG: changed a
> +  HG: changed newfile
>     nothing changed
>
>     $ glog -r '.^::.'
> -  @  43:62353add3dfb@default(draft) will be evolved safely
> +  @  44:41bf1183869c@default(draft) amended
>     |
> -  o  41:34ae045ec400@default(draft) amended
> +  o  36:43c3f5ef149f@default(draft) add uu
>     |
>     ~
>
> @@ -1510,15 +1556,22 @@ TODO: don't create a new commit in this
>     $ hg metaedit --config defaults.metaedit=
>     0 files updated, 0 files merged, 0 files removed, 0 files unresolved
>     $ hg log -r '.^::.' --template '{rev}: {desc|firstline}\n'
> -  41: amended
> -  44: will be evolved safely
> +  36: add uu
> +  45: amended
>
>     $ hg up .^
> -  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
> -  $ hg metaedit --user foobar2 44
> +  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
> +  $ hg metaedit --user foobar2 45
>     $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
>     42: test
>     43: foobar
> -  44: foobar
> -  45: foobar2
> -  $ hg diff -r 44 -r 45 --hidden
> +  44: test
> +  45: test
> +  46: foobar2
> +  $ hg diff -r 45 -r 46 --hidden
> +
> +'fold' one commit
> +  $ hg metaedit 39 --fold --user foobar3
> +  1 changesets folded
> +  $ hg log -r 47 --template '{rev}: {author}\n'
> +  47: foobar3
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://urldefense.proofpoint.com/v2/url?u=https-3A__www.mercurial-2Dscm.org_mailman_listinfo_mercurial-2Ddevel&d=CwIGaQ&c=5VD0RTtNlTh3ycd41b3MUw&r=mEgSWILcY4c4W3zjApBQLA&m=UHn3lAnPn2BRfDMVSc9_wLI4N4sjJYZax4FtWUqUWJY&s=nM0aEVfbyAV1jJibZ_K_QhF2VL1XOSRF1FdcDsmXi_k&e=
>
Pierre-Yves David - May 2, 2016, 4:27 p.m.
I've pushed this series.

I made two minor followup changeset to adjust details I noted while 
reading them.

On 04/26/2016 06:14 PM, Simon Farnsworth wrote:
> I like this - it's a safe subset of histedit for operations (like code 
> review tools) that want to change metadata but don't want bugs to 
> damage the diff.
>
> I can't see anything wrong with this version.
>
> Simon
>
> On 26/04/2016 00:34, Siddharth Agarwal wrote:
>> # HG changeset patch
>> # User Siddharth Agarwal <sid0@fb.com>
>> # Date 1461626682 25200
>> #      Mon Apr 25 16:24:42 2016 -0700
>> # Node ID 71e061aa0be879ad6477fbdb724a3a2e69e5af5b
>> # Parent  ea19e5ccc442ac286d4244695365034475b4fa5c
>> metaedit: add support for folding commits while editing their metadata
>>
>> This also allows us to accept multiple commits without dealing with 
>> the thorny
>> algorithmic and UI issues of editing multiple commits at once.
>>
>> Crucially, it is different from 'hg fold --exact' in that it also allows
>> 'folding' a single commit and rewriting its metadata. This is really 
>> useful to
>> have as a single logical operation, for example while preparing a 
>> series of
>> multiple local changesets that will need to be pushed as a single 
>> changeset.
>>
>> diff --git a/hgext/evolve.py b/hgext/evolve.py
>> --- a/hgext/evolve.py
>> +++ b/hgext/evolve.py
>> @@ -3151,14 +3151,18 @@ def fold(ui, repo, *revs, **opts):
>>
>>   @command('^metaedit',
>>            [('r', 'rev', [], _("revision to edit")),
>> +         ('', 'fold', None, _("also fold specified revisions into 
>> one")),
>>            ] + commitopts + commitopts2,
>>            _('hg metaedit [OPTION]... [-r] [REV]'))
>>   def metaedit(ui, repo, *revs, **opts):
>>       """edit commit information
>>
>> -    Edits the commit information for the specified revision. By 
>> default, edits
>> +    Edits the commit information for the specified revisions. By 
>> default, edits
>>       commit information for the working directory parent.
>>
>> +    With --fold, also folds multiple revisions into one if 
>> necessary. In this
>> +    case, the given revisions must form a linear unbroken chain.
>> +
>>       .. container:: verbose
>>
>>        Some examples:
>> @@ -3171,10 +3175,19 @@ def metaedit(ui, repo, *revs, **opts):
>>
>>            hg metaedit --user 'New User <new-email@example.com>'
>>
>> +     - Combine all draft revisions that are ancestors of foo but not 
>> of @ into
>> +       one::
>> +
>> +         hg metaedit --fold 'draft() and only(foo,@)'
>> +
>> +       See :hg:`help phases` for more about draft revisions, and
>> +       :hg:`help revsets` for more about the `draft()` and `only()` 
>> keywords.
>>       """
>>       revs = list(revs)
>>       revs.extend(opts['rev'])
>>       if not revs:
>> +        if opts['fold']:
>> +            raise error.Abort(_('revisions must be specified with 
>> --fold'))
>>           revs = ['.']
>>
>>       wlock = lock = None
>> @@ -3183,7 +3196,7 @@ def metaedit(ui, repo, *revs, **opts):
>>           lock = repo.lock()
>>
>>           revs = scmutil.revrange(repo, revs)
>> -        if len(revs) > 1:
>> +        if not opts['fold'] and len(revs) > 1:
>>               # TODO: handle multiple revisions. This is somewhat 
>> tricky because
>>               # if we want to edit a series of commits:
>>               #
>> @@ -3192,18 +3205,21 @@ def metaedit(ui, repo, *revs, **opts):
>>               # we need to rewrite a first, then directly rewrite b 
>> on top of the
>>               # new a, then rewrite c on top of the new b. So we need 
>> to handle
>>               # revisions in topological order.
>> -            raise error.Abort(_('editing multiple revisions is not '
>> -                                'currently supported'))
>> -
>> -        newunstable = _disallowednewunstable(repo, revs)
>> -        if newunstable:
>> -            raise error.Abort(
>> -                _('cannot edit commit information in the middle of a 
>> stack'),
>> -                hint=_('%s will be affected') % 
>> repo[newunstable.first()])
>> -        if repo.revs("%ld and public()", revs):
>> -            raise error.Abort(_('cannot edit commit information for 
>> public '
>> -                                'revisions'))
>> -        root = head = repo[revs.first()]
>> +            raise error.Abort(_('editing multiple revisions without 
>> --fold is '
>> +                                'not currently supported'))
>> +
>> +        if opts['fold']:
>> +            root, head = _foldcheck(repo, revs)
>> +        else:
>> +            newunstable = _disallowednewunstable(repo, revs)
>> +            if newunstable:
>> +                raise error.Abort(
>> +                    _('cannot edit commit information in the middle 
>> of a stack'),
>> +                    hint=_('%s will be affected') % 
>> repo[newunstable.first()])
>> +            if repo.revs("%ld and public()", revs):
>> +                raise error.Abort(_('cannot edit commit information 
>> for public '
>> +                                    'revisions'))
>> +            root = head = repo[revs.first()]
>>
>>           wctx = repo[None]
>>           p1 = wctx.p1()
>> @@ -3217,7 +3233,12 @@ def metaedit(ui, repo, *revs, **opts):
>>               if commitopts.get('message') or commitopts.get('logfile'):
>>                   commitopts['edit'] = False
>>               else:
>> -                msgs = [head.description()]
>> +                if opts['fold']:
>> +                    msgs = ["HG: This is a fold of %d changesets." % 
>> len(allctx)]
>> +                    msgs += ["HG: Commit message of changeset 
>> %s.\n\n%s\n" %
>> +                             (c.rev(), c.description()) for c in 
>> allctx]
>> +                else:
>> +                    msgs = [head.description()]
>>                   commitopts['message'] =  "\n".join(msgs)
>>                   commitopts['edit'] = True
>>
>> @@ -3239,6 +3260,8 @@ def metaedit(ui, repo, *revs, **opts):
>>           finally:
>>               tr.release()
>>
>> +        if opts['fold']:
>> +            ui.status('%i changesets folded\n' % len(revs))
>>           if newp1 is not None:
>>               hg.update(repo, newp1)
>>       finally:
>> diff --git a/tests/test-evolve.t b/tests/test-evolve.t
>> --- a/tests/test-evolve.t
>> +++ b/tests/test-evolve.t
>> @@ -1468,11 +1468,26 @@ hg metaedit
>>     $ hg metaedit -r 0
>>     abort: cannot edit commit information for public revisions
>>     [255]
>> +  $ hg metaedit --fold
>> +  abort: revisions must be specified with --fold
>> +  [255]
>> +  $ hg metaedit -r 0 --fold
>> +  abort: cannot fold public revisions
>> +  [255]
>> +  $ hg metaedit '36 + 42' --fold
>> +  abort: cannot fold non-linear revisions (multiple roots given)
>> +  [255]
>> +  $ hg metaedit '36::39 + 41' --fold
>> +  abort: cannot fold non-linear revisions (multiple heads given)
>> +  [255]
>>   check that metaedit respects allowunstable
>>     $ hg metaedit '.^' --config 
>> 'experimental.evolution=createmarkers, allnewcommands'
>>     abort: cannot edit commit information in the middle of a stack
>>     (c904da5245b0 will be affected)
>>     [255]
>> +  $ hg metaedit '18::20' --fold --config 
>> 'experimental.evolution=createmarkers, allnewcommands'
>> +  abort: cannot fold chain not ending with a head or with branching
>> +  [255]
>>     $ hg metaedit --user foobar
>>     0 files updated, 0 files merged, 0 files removed, 0 files unresolved
>>     $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
>> @@ -1483,26 +1498,57 @@ check that metaedit respects allowunstab
>>
>>   TODO: support this
>>     $ hg metaedit '.^::.'
>> -  abort: editing multiple revisions is not currently supported
>> +  abort: editing multiple revisions without --fold is not currently 
>> supported
>>     [255]
>>
>> -no new commit is created here because the date is the same
>> -  $ HGEDITOR=cat hg metaedit
>> +  $ HGEDITOR=cat hg metaedit '.^::.' --fold
>> +  HG: This is a fold of 2 changesets.
>> +  HG: Commit message of changeset 41.
>> +
>> +  amended
>> +
>> +  HG: Commit message of changeset 43.
>> +
>>     will be evolved safely
>>
>>
>> +
>>     HG: Enter commit message.  Lines beginning with 'HG:' are removed.
>>     HG: Leave message empty to abort commit.
>>     HG: --
>> -  HG: user: foobar
>> +  HG: user: test
>>     HG: branch 'default'
>>     HG: changed a
>> +  HG: changed newfile
>> +  2 changesets folded
>> +  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
>> +
>> +  $ glog -r .
>> +  @  44:41bf1183869c@default(draft) amended
>> +  |
>> +  ~
>> +
>> +no new commit is created here because the date is the same
>> +  $ HGEDITOR=cat hg metaedit
>> +  amended
>> +
>> +
>> +  will be evolved safely
>> +
>> +
>> +  HG: Enter commit message.  Lines beginning with 'HG:' are removed.
>> +  HG: Leave message empty to abort commit.
>> +  HG: --
>> +  HG: user: test
>> +  HG: branch 'default'
>> +  HG: changed a
>> +  HG: changed newfile
>>     nothing changed
>>
>>     $ glog -r '.^::.'
>> -  @  43:62353add3dfb@default(draft) will be evolved safely
>> +  @  44:41bf1183869c@default(draft) amended
>>     |
>> -  o  41:34ae045ec400@default(draft) amended
>> +  o  36:43c3f5ef149f@default(draft) add uu
>>     |
>>     ~
>>
>> @@ -1510,15 +1556,22 @@ TODO: don't create a new commit in this
>>     $ hg metaedit --config defaults.metaedit=
>>     0 files updated, 0 files merged, 0 files removed, 0 files unresolved
>>     $ hg log -r '.^::.' --template '{rev}: {desc|firstline}\n'
>> -  41: amended
>> -  44: will be evolved safely
>> +  36: add uu
>> +  45: amended
>>
>>     $ hg up .^
>> -  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
>> -  $ hg metaedit --user foobar2 44
>> +  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
>> +  $ hg metaedit --user foobar2 45
>>     $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
>>     42: test
>>     43: foobar
>> -  44: foobar
>> -  45: foobar2
>> -  $ hg diff -r 44 -r 45 --hidden
>> +  44: test
>> +  45: test
>> +  46: foobar2
>> +  $ hg diff -r 45 -r 46 --hidden
>> +
>> +'fold' one commit
>> +  $ hg metaedit 39 --fold --user foobar3
>> +  1 changesets folded
>> +  $ hg log -r 47 --template '{rev}: {author}\n'
>> +  47: foobar3
>> _______________________________________________
>> Mercurial-devel mailing list
>> Mercurial-devel@mercurial-scm.org
>> https://urldefense.proofpoint.com/v2/url?u=https-3A__www.mercurial-2Dscm.org_mailman_listinfo_mercurial-2Ddevel&d=CwIGaQ&c=5VD0RTtNlTh3ycd41b3MUw&r=mEgSWILcY4c4W3zjApBQLA&m=UHn3lAnPn2BRfDMVSc9_wLI4N4sjJYZax4FtWUqUWJY&s=nM0aEVfbyAV1jJibZ_K_QhF2VL1XOSRF1FdcDsmXi_k&e= 
>>
>>
>
Siddharth Agarwal - May 2, 2016, 5:21 p.m.
On 5/2/16 09:27, Pierre-Yves David wrote:
> I've pushed this series.

Thanks :)

>
> I made two minor followup changeset to adjust details I noted while 
> reading them.
>
> On 04/26/2016 06:14 PM, Simon Farnsworth wrote:
>> I like this - it's a safe subset of histedit for operations (like 
>> code review tools) that want to change metadata but don't want bugs 
>> to damage the diff.
>>
>> I can't see anything wrong with this version.
>>
>> Simon
>>
>> On 26/04/2016 00:34, Siddharth Agarwal wrote:
>>> # HG changeset patch
>>> # User Siddharth Agarwal <sid0@fb.com>
>>> # Date 1461626682 25200
>>> #      Mon Apr 25 16:24:42 2016 -0700
>>> # Node ID 71e061aa0be879ad6477fbdb724a3a2e69e5af5b
>>> # Parent  ea19e5ccc442ac286d4244695365034475b4fa5c
>>> metaedit: add support for folding commits while editing their metadata
>>>
>>> This also allows us to accept multiple commits without dealing with 
>>> the thorny
>>> algorithmic and UI issues of editing multiple commits at once.
>>>
>>> Crucially, it is different from 'hg fold --exact' in that it also 
>>> allows
>>> 'folding' a single commit and rewriting its metadata. This is really 
>>> useful to
>>> have as a single logical operation, for example while preparing a 
>>> series of
>>> multiple local changesets that will need to be pushed as a single 
>>> changeset.
>>>
>>> diff --git a/hgext/evolve.py b/hgext/evolve.py
>>> --- a/hgext/evolve.py
>>> +++ b/hgext/evolve.py
>>> @@ -3151,14 +3151,18 @@ def fold(ui, repo, *revs, **opts):
>>>
>>>   @command('^metaedit',
>>>            [('r', 'rev', [], _("revision to edit")),
>>> +         ('', 'fold', None, _("also fold specified revisions into 
>>> one")),
>>>            ] + commitopts + commitopts2,
>>>            _('hg metaedit [OPTION]... [-r] [REV]'))
>>>   def metaedit(ui, repo, *revs, **opts):
>>>       """edit commit information
>>>
>>> -    Edits the commit information for the specified revision. By 
>>> default, edits
>>> +    Edits the commit information for the specified revisions. By 
>>> default, edits
>>>       commit information for the working directory parent.
>>>
>>> +    With --fold, also folds multiple revisions into one if 
>>> necessary. In this
>>> +    case, the given revisions must form a linear unbroken chain.
>>> +
>>>       .. container:: verbose
>>>
>>>        Some examples:
>>> @@ -3171,10 +3175,19 @@ def metaedit(ui, repo, *revs, **opts):
>>>
>>>            hg metaedit --user 'New User <new-email@example.com>'
>>>
>>> +     - Combine all draft revisions that are ancestors of foo but 
>>> not of @ into
>>> +       one::
>>> +
>>> +         hg metaedit --fold 'draft() and only(foo,@)'
>>> +
>>> +       See :hg:`help phases` for more about draft revisions, and
>>> +       :hg:`help revsets` for more about the `draft()` and `only()` 
>>> keywords.
>>>       """
>>>       revs = list(revs)
>>>       revs.extend(opts['rev'])
>>>       if not revs:
>>> +        if opts['fold']:
>>> +            raise error.Abort(_('revisions must be specified with 
>>> --fold'))
>>>           revs = ['.']
>>>
>>>       wlock = lock = None
>>> @@ -3183,7 +3196,7 @@ def metaedit(ui, repo, *revs, **opts):
>>>           lock = repo.lock()
>>>
>>>           revs = scmutil.revrange(repo, revs)
>>> -        if len(revs) > 1:
>>> +        if not opts['fold'] and len(revs) > 1:
>>>               # TODO: handle multiple revisions. This is somewhat 
>>> tricky because
>>>               # if we want to edit a series of commits:
>>>               #
>>> @@ -3192,18 +3205,21 @@ def metaedit(ui, repo, *revs, **opts):
>>>               # we need to rewrite a first, then directly rewrite b 
>>> on top of the
>>>               # new a, then rewrite c on top of the new b. So we 
>>> need to handle
>>>               # revisions in topological order.
>>> -            raise error.Abort(_('editing multiple revisions is not '
>>> -                                'currently supported'))
>>> -
>>> -        newunstable = _disallowednewunstable(repo, revs)
>>> -        if newunstable:
>>> -            raise error.Abort(
>>> -                _('cannot edit commit information in the middle of 
>>> a stack'),
>>> -                hint=_('%s will be affected') % 
>>> repo[newunstable.first()])
>>> -        if repo.revs("%ld and public()", revs):
>>> -            raise error.Abort(_('cannot edit commit information for 
>>> public '
>>> -                                'revisions'))
>>> -        root = head = repo[revs.first()]
>>> +            raise error.Abort(_('editing multiple revisions without 
>>> --fold is '
>>> +                                'not currently supported'))
>>> +
>>> +        if opts['fold']:
>>> +            root, head = _foldcheck(repo, revs)
>>> +        else:
>>> +            newunstable = _disallowednewunstable(repo, revs)
>>> +            if newunstable:
>>> +                raise error.Abort(
>>> +                    _('cannot edit commit information in the middle 
>>> of a stack'),
>>> +                    hint=_('%s will be affected') % 
>>> repo[newunstable.first()])
>>> +            if repo.revs("%ld and public()", revs):
>>> +                raise error.Abort(_('cannot edit commit information 
>>> for public '
>>> +                                    'revisions'))
>>> +            root = head = repo[revs.first()]
>>>
>>>           wctx = repo[None]
>>>           p1 = wctx.p1()
>>> @@ -3217,7 +3233,12 @@ def metaedit(ui, repo, *revs, **opts):
>>>               if commitopts.get('message') or 
>>> commitopts.get('logfile'):
>>>                   commitopts['edit'] = False
>>>               else:
>>> -                msgs = [head.description()]
>>> +                if opts['fold']:
>>> +                    msgs = ["HG: This is a fold of %d changesets." 
>>> % len(allctx)]
>>> +                    msgs += ["HG: Commit message of changeset 
>>> %s.\n\n%s\n" %
>>> +                             (c.rev(), c.description()) for c in 
>>> allctx]
>>> +                else:
>>> +                    msgs = [head.description()]
>>>                   commitopts['message'] =  "\n".join(msgs)
>>>                   commitopts['edit'] = True
>>>
>>> @@ -3239,6 +3260,8 @@ def metaedit(ui, repo, *revs, **opts):
>>>           finally:
>>>               tr.release()
>>>
>>> +        if opts['fold']:
>>> +            ui.status('%i changesets folded\n' % len(revs))
>>>           if newp1 is not None:
>>>               hg.update(repo, newp1)
>>>       finally:
>>> diff --git a/tests/test-evolve.t b/tests/test-evolve.t
>>> --- a/tests/test-evolve.t
>>> +++ b/tests/test-evolve.t
>>> @@ -1468,11 +1468,26 @@ hg metaedit
>>>     $ hg metaedit -r 0
>>>     abort: cannot edit commit information for public revisions
>>>     [255]
>>> +  $ hg metaedit --fold
>>> +  abort: revisions must be specified with --fold
>>> +  [255]
>>> +  $ hg metaedit -r 0 --fold
>>> +  abort: cannot fold public revisions
>>> +  [255]
>>> +  $ hg metaedit '36 + 42' --fold
>>> +  abort: cannot fold non-linear revisions (multiple roots given)
>>> +  [255]
>>> +  $ hg metaedit '36::39 + 41' --fold
>>> +  abort: cannot fold non-linear revisions (multiple heads given)
>>> +  [255]
>>>   check that metaedit respects allowunstable
>>>     $ hg metaedit '.^' --config 
>>> 'experimental.evolution=createmarkers, allnewcommands'
>>>     abort: cannot edit commit information in the middle of a stack
>>>     (c904da5245b0 will be affected)
>>>     [255]
>>> +  $ hg metaedit '18::20' --fold --config 
>>> 'experimental.evolution=createmarkers, allnewcommands'
>>> +  abort: cannot fold chain not ending with a head or with branching
>>> +  [255]
>>>     $ hg metaedit --user foobar
>>>     0 files updated, 0 files merged, 0 files removed, 0 files 
>>> unresolved
>>>     $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
>>> @@ -1483,26 +1498,57 @@ check that metaedit respects allowunstab
>>>
>>>   TODO: support this
>>>     $ hg metaedit '.^::.'
>>> -  abort: editing multiple revisions is not currently supported
>>> +  abort: editing multiple revisions without --fold is not currently 
>>> supported
>>>     [255]
>>>
>>> -no new commit is created here because the date is the same
>>> -  $ HGEDITOR=cat hg metaedit
>>> +  $ HGEDITOR=cat hg metaedit '.^::.' --fold
>>> +  HG: This is a fold of 2 changesets.
>>> +  HG: Commit message of changeset 41.
>>> +
>>> +  amended
>>> +
>>> +  HG: Commit message of changeset 43.
>>> +
>>>     will be evolved safely
>>>
>>>
>>> +
>>>     HG: Enter commit message.  Lines beginning with 'HG:' are removed.
>>>     HG: Leave message empty to abort commit.
>>>     HG: --
>>> -  HG: user: foobar
>>> +  HG: user: test
>>>     HG: branch 'default'
>>>     HG: changed a
>>> +  HG: changed newfile
>>> +  2 changesets folded
>>> +  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
>>> +
>>> +  $ glog -r .
>>> +  @  44:41bf1183869c@default(draft) amended
>>> +  |
>>> +  ~
>>> +
>>> +no new commit is created here because the date is the same
>>> +  $ HGEDITOR=cat hg metaedit
>>> +  amended
>>> +
>>> +
>>> +  will be evolved safely
>>> +
>>> +
>>> +  HG: Enter commit message.  Lines beginning with 'HG:' are removed.
>>> +  HG: Leave message empty to abort commit.
>>> +  HG: --
>>> +  HG: user: test
>>> +  HG: branch 'default'
>>> +  HG: changed a
>>> +  HG: changed newfile
>>>     nothing changed
>>>
>>>     $ glog -r '.^::.'
>>> -  @  43:62353add3dfb@default(draft) will be evolved safely
>>> +  @  44:41bf1183869c@default(draft) amended
>>>     |
>>> -  o  41:34ae045ec400@default(draft) amended
>>> +  o  36:43c3f5ef149f@default(draft) add uu
>>>     |
>>>     ~
>>>
>>> @@ -1510,15 +1556,22 @@ TODO: don't create a new commit in this
>>>     $ hg metaedit --config defaults.metaedit=
>>>     0 files updated, 0 files merged, 0 files removed, 0 files 
>>> unresolved
>>>     $ hg log -r '.^::.' --template '{rev}: {desc|firstline}\n'
>>> -  41: amended
>>> -  44: will be evolved safely
>>> +  36: add uu
>>> +  45: amended
>>>
>>>     $ hg up .^
>>> -  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
>>> -  $ hg metaedit --user foobar2 44
>>> +  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
>>> +  $ hg metaedit --user foobar2 45
>>>     $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
>>>     42: test
>>>     43: foobar
>>> -  44: foobar
>>> -  45: foobar2
>>> -  $ hg diff -r 44 -r 45 --hidden
>>> +  44: test
>>> +  45: test
>>> +  46: foobar2
>>> +  $ hg diff -r 45 -r 46 --hidden
>>> +
>>> +'fold' one commit
>>> +  $ hg metaedit 39 --fold --user foobar3
>>> +  1 changesets folded
>>> +  $ hg log -r 47 --template '{rev}: {author}\n'
>>> +  47: foobar3
>>> _______________________________________________
>>> Mercurial-devel mailing list
>>> Mercurial-devel@mercurial-scm.org
>>> https://urldefense.proofpoint.com/v2/url?u=https-3A__www.mercurial-2Dscm.org_mailman_listinfo_mercurial-2Ddevel&d=CwIGaQ&c=5VD0RTtNlTh3ycd41b3MUw&r=mEgSWILcY4c4W3zjApBQLA&m=UHn3lAnPn2BRfDMVSc9_wLI4N4sjJYZax4FtWUqUWJY&s=nM0aEVfbyAV1jJibZ_K_QhF2VL1XOSRF1FdcDsmXi_k&e= 
>>>
>>>
>>
>
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

Patch

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -3151,14 +3151,18 @@  def fold(ui, repo, *revs, **opts):
 
 @command('^metaedit',
          [('r', 'rev', [], _("revision to edit")),
+         ('', 'fold', None, _("also fold specified revisions into one")),
          ] + commitopts + commitopts2,
          _('hg metaedit [OPTION]... [-r] [REV]'))
 def metaedit(ui, repo, *revs, **opts):
     """edit commit information
 
-    Edits the commit information for the specified revision. By default, edits
+    Edits the commit information for the specified revisions. By default, edits
     commit information for the working directory parent.
 
+    With --fold, also folds multiple revisions into one if necessary. In this
+    case, the given revisions must form a linear unbroken chain.
+
     .. container:: verbose
 
      Some examples:
@@ -3171,10 +3175,19 @@  def metaedit(ui, repo, *revs, **opts):
 
          hg metaedit --user 'New User <new-email@example.com>'
 
+     - Combine all draft revisions that are ancestors of foo but not of @ into
+       one::
+
+         hg metaedit --fold 'draft() and only(foo,@)'
+
+       See :hg:`help phases` for more about draft revisions, and
+       :hg:`help revsets` for more about the `draft()` and `only()` keywords.
     """
     revs = list(revs)
     revs.extend(opts['rev'])
     if not revs:
+        if opts['fold']:
+            raise error.Abort(_('revisions must be specified with --fold'))
         revs = ['.']
 
     wlock = lock = None
@@ -3183,7 +3196,7 @@  def metaedit(ui, repo, *revs, **opts):
         lock = repo.lock()
 
         revs = scmutil.revrange(repo, revs)
-        if len(revs) > 1:
+        if not opts['fold'] and len(revs) > 1:
             # TODO: handle multiple revisions. This is somewhat tricky because
             # if we want to edit a series of commits:
             #
@@ -3192,18 +3205,21 @@  def metaedit(ui, repo, *revs, **opts):
             # we need to rewrite a first, then directly rewrite b on top of the
             # new a, then rewrite c on top of the new b. So we need to handle
             # revisions in topological order.
-            raise error.Abort(_('editing multiple revisions is not '
-                                'currently supported'))
-
-        newunstable = _disallowednewunstable(repo, revs)
-        if newunstable:
-            raise error.Abort(
-                _('cannot edit commit information in the middle of a stack'),
-                hint=_('%s will be affected') % repo[newunstable.first()])
-        if repo.revs("%ld and public()", revs):
-            raise error.Abort(_('cannot edit commit information for public '
-                                'revisions'))
-        root = head = repo[revs.first()]
+            raise error.Abort(_('editing multiple revisions without --fold is '
+                                'not currently supported'))
+
+        if opts['fold']:
+            root, head = _foldcheck(repo, revs)
+        else:
+            newunstable = _disallowednewunstable(repo, revs)
+            if newunstable:
+                raise error.Abort(
+                    _('cannot edit commit information in the middle of a stack'),
+                    hint=_('%s will be affected') % repo[newunstable.first()])
+            if repo.revs("%ld and public()", revs):
+                raise error.Abort(_('cannot edit commit information for public '
+                                    'revisions'))
+            root = head = repo[revs.first()]
 
         wctx = repo[None]
         p1 = wctx.p1()
@@ -3217,7 +3233,12 @@  def metaedit(ui, repo, *revs, **opts):
             if commitopts.get('message') or commitopts.get('logfile'):
                 commitopts['edit'] = False
             else:
-                msgs = [head.description()]
+                if opts['fold']:
+                    msgs = ["HG: This is a fold of %d changesets." % len(allctx)]
+                    msgs += ["HG: Commit message of changeset %s.\n\n%s\n" %
+                             (c.rev(), c.description()) for c in allctx]
+                else:
+                    msgs = [head.description()]
                 commitopts['message'] =  "\n".join(msgs)
                 commitopts['edit'] = True
 
@@ -3239,6 +3260,8 @@  def metaedit(ui, repo, *revs, **opts):
         finally:
             tr.release()
 
+        if opts['fold']:
+            ui.status('%i changesets folded\n' % len(revs))
         if newp1 is not None:
             hg.update(repo, newp1)
     finally:
diff --git a/tests/test-evolve.t b/tests/test-evolve.t
--- a/tests/test-evolve.t
+++ b/tests/test-evolve.t
@@ -1468,11 +1468,26 @@  hg metaedit
   $ hg metaedit -r 0
   abort: cannot edit commit information for public revisions
   [255]
+  $ hg metaedit --fold
+  abort: revisions must be specified with --fold
+  [255]
+  $ hg metaedit -r 0 --fold
+  abort: cannot fold public revisions
+  [255]
+  $ hg metaedit '36 + 42' --fold
+  abort: cannot fold non-linear revisions (multiple roots given)
+  [255]
+  $ hg metaedit '36::39 + 41' --fold
+  abort: cannot fold non-linear revisions (multiple heads given)
+  [255]
 check that metaedit respects allowunstable
   $ hg metaedit '.^' --config 'experimental.evolution=createmarkers, allnewcommands'
   abort: cannot edit commit information in the middle of a stack
   (c904da5245b0 will be affected)
   [255]
+  $ hg metaedit '18::20' --fold --config 'experimental.evolution=createmarkers, allnewcommands'
+  abort: cannot fold chain not ending with a head or with branching
+  [255]
   $ hg metaedit --user foobar
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
@@ -1483,26 +1498,57 @@  check that metaedit respects allowunstab
 
 TODO: support this
   $ hg metaedit '.^::.'
-  abort: editing multiple revisions is not currently supported
+  abort: editing multiple revisions without --fold is not currently supported
   [255]
 
-no new commit is created here because the date is the same
-  $ HGEDITOR=cat hg metaedit
+  $ HGEDITOR=cat hg metaedit '.^::.' --fold
+  HG: This is a fold of 2 changesets.
+  HG: Commit message of changeset 41.
+  
+  amended
+  
+  HG: Commit message of changeset 43.
+  
   will be evolved safely
   
   
+  
   HG: Enter commit message.  Lines beginning with 'HG:' are removed.
   HG: Leave message empty to abort commit.
   HG: --
-  HG: user: foobar
+  HG: user: test
   HG: branch 'default'
   HG: changed a
+  HG: changed newfile
+  2 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ glog -r .
+  @  44:41bf1183869c@default(draft) amended
+  |
+  ~
+
+no new commit is created here because the date is the same
+  $ HGEDITOR=cat hg metaedit
+  amended
+  
+  
+  will be evolved safely
+  
+  
+  HG: Enter commit message.  Lines beginning with 'HG:' are removed.
+  HG: Leave message empty to abort commit.
+  HG: --
+  HG: user: test
+  HG: branch 'default'
+  HG: changed a
+  HG: changed newfile
   nothing changed
 
   $ glog -r '.^::.'
-  @  43:62353add3dfb@default(draft) will be evolved safely
+  @  44:41bf1183869c@default(draft) amended
   |
-  o  41:34ae045ec400@default(draft) amended
+  o  36:43c3f5ef149f@default(draft) add uu
   |
   ~
 
@@ -1510,15 +1556,22 @@  TODO: don't create a new commit in this 
   $ hg metaedit --config defaults.metaedit=
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -r '.^::.' --template '{rev}: {desc|firstline}\n'
-  41: amended
-  44: will be evolved safely
+  36: add uu
+  45: amended
 
   $ hg up .^
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg metaedit --user foobar2 44
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg metaedit --user foobar2 45
   $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
   42: test
   43: foobar
-  44: foobar
-  45: foobar2
-  $ hg diff -r 44 -r 45 --hidden
+  44: test
+  45: test
+  46: foobar2
+  $ hg diff -r 45 -r 46 --hidden
+
+'fold' one commit
+  $ hg metaedit 39 --fold --user foobar3
+  1 changesets folded
+  $ hg log -r 47 --template '{rev}: {author}\n'
+  47: foobar3