# HG changeset patch # User Siddharth Agarwal # Date 1461626682 25200 # Node ID 474db2d602021c5f850dbee6bf557ed3ef185f3b # Parent 4fd0db2f6d849562699337e24fac6a377158cad0 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 -r 4fd0db2f6d84 -r 474db2d60202 README --- a/README Mon Apr 25 16:24:42 2016 -0700 +++ b/README Mon Apr 25 16:24:42 2016 -0700 @@ -69,6 +69,7 @@ - hg summary now suggest 'hg evolve --continue when appropriate` - compatibility with Mercurial 3.8 'hgext' namespace package. - small improvement to the `hg split` instruction +- add a 'metaedit' command to rewrite changeset meta data. 5.3.0 -- 2016-02-11 diff -r 4fd0db2f6d84 -r 474db2d60202 hgext/evolve.py --- a/hgext/evolve.py Mon Apr 25 16:24:42 2016 -0700 +++ b/hgext/evolve.py Mon Apr 25 16:24:42 2016 -0700 @@ -3160,14 +3160,18 @@ @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: @@ -3180,10 +3184,19 @@ hg metaedit --user 'New User ' + - 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 @@ -3192,7 +3205,7 @@ 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: # @@ -3201,18 +3214,21 @@ # 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() @@ -3226,7 +3242,12 @@ 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 @@ -3248,6 +3269,8 @@ 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 -r 4fd0db2f6d84 -r 474db2d60202 tests/test-evolve.t --- a/tests/test-evolve.t Mon Apr 25 16:24:42 2016 -0700 +++ b/tests/test-evolve.t Mon Apr 25 16:24:42 2016 -0700 @@ -1468,11 +1468,26 @@ $ 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 @@ TODO: support this $ hg metaedit '.^::.' - abort: editing multiple revisions is not currently supported + abort: editing multiple revisions without --fold is not currently supported [255] + $ 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: 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: foobar + 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 @@ $ 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