commands: introduce a new command to edit commit metadata
This patch introduces metaedit, a command to metadata of a set of revisions
without updating the working copy. This is particularly relevant for
repositories where changing the parent of the working copy is time-consuming.
We could add more stack manipulation operations to metaedit in the future.
--- a/hgext/evolve.py Mon Apr 25 16:24:42 2016 -0700
+++ b/hgext/evolve.py Mon Apr 25 16:24:42 2016 -0700
@@ -3158,6 +3158,101 @@
finally:
lockmod.release(lock, wlock)
+@command('^metaedit',
+ [('r', 'rev', [], _("revision to edit")),
+ ] + 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
+ commit information for the working directory parent.
+
+ .. container:: verbose
+
+ Some examples:
+
+ - Edit the commit message for the working directory parent::
+
+ hg metaedit
+
+ - Change the username for the working directory parent::
+
+ hg metaedit --user 'New User <new-email@example.com>'
+
+ """
+ revs = list(revs)
+ revs.extend(opts['rev'])
+ if not revs:
+ revs = ['.']
+
+ wlock = lock = None
+ try:
+ wlock = repo.wlock()
+ lock = repo.lock()
+
+ revs = scmutil.revrange(repo, revs)
+ if len(revs) > 1:
+ # TODO: handle multiple revisions. This is somewhat tricky because
+ # if we want to edit a series of commits:
+ #
+ # a ---- b ---- c
+ #
+ # 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()]
+
+ wctx = repo[None]
+ p1 = wctx.p1()
+ tr = repo.transaction('metaedit')
+ newp1 = None
+ try:
+ commitopts = opts.copy()
+ allctx = [repo[r] for r in revs]
+ targetphase = max(c.phase() for c in allctx)
+
+ if commitopts.get('message') or commitopts.get('logfile'):
+ commitopts['edit'] = False
+ else:
+ msgs = [head.description()]
+ commitopts['message'] = "\n".join(msgs)
+ commitopts['edit'] = True
+
+ # TODO: if the author and message are the same, don't create a new
+ # hash. Right now we create a new hash because the date can be
+ # different.
+ newid, created = rewrite(repo, root, allctx, head,
+ [root.p1().node(), root.p2().node()],
+ commitopts=commitopts)
+ if created:
+ if p1.rev() in revs:
+ newp1 = newid
+ phases.retractboundary(repo, tr, targetphase, [newid])
+ obsolete.createmarkers(repo, [(ctx, (repo[newid],))
+ for ctx in allctx])
+ else:
+ ui.status(_("nothing changed\n"))
+ tr.close()
+ finally:
+ tr.release()
+
+ if newp1 is not None:
+ hg.update(repo, newp1)
+ finally:
+ lockmod.release(lock, wlock)
+
def _foldcheck(repo, revs):
roots = repo.revs('roots(%ld)', revs)
if len(roots) > 1:
--- 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
@@ -2,6 +2,7 @@
> [defaults]
> amend=-d "0 0"
> fold=-d "0 0"
+ > metaedit=-d "0 0"
> [web]
> push_ssl = false
> allow_push = *
@@ -1457,3 +1458,67 @@
$ hg status newlyadded
A newlyadded
+
+hg metaedit
+-----------
+
+ $ hg update --clean .
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ rm newlyadded
+ $ hg metaedit -r 0
+ abort: cannot edit commit information for public revisions
+ [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 --user foobar
+ 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
+ 42: test
+ 43: foobar
+ $ hg log --template '{rev}: {author}\n' -r .
+ 43: foobar
+
+TODO: support this
+ $ hg metaedit '.^::.'
+ abort: editing multiple revisions is not currently supported
+ [255]
+
+no new commit is created here because the date is the same
+ $ HGEDITOR=cat hg metaedit
+ 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: branch 'default'
+ HG: changed a
+ nothing changed
+
+ $ glog -r '.^::.'
+ @ 43:62353add3dfb@default(draft) will be evolved safely
+ |
+ o 41:34ae045ec400@default(draft) amended
+ |
+ ~
+
+TODO: don't create a new commit in this case
+ $ 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
+
+ $ hg up .^
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+ $ hg metaedit --user foobar2 44
+ $ hg log --template '{rev}: {author}\n' -r '42:' --hidden
+ 42: test
+ 43: foobar
+ 44: foobar
+ 45: foobar2
+ $ hg diff -r 44 -r 45 --hidden