changeset 1685:4fd0db2f6d84

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.
author Siddharth Agarwal <sid0@fb.com>
date Mon, 25 Apr 2016 16:24:42 -0700
parents 40d7b0c4abb1
children 474db2d60202
files hgext/evolve.py tests/test-evolve.t
diffstat 2 files changed, 160 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- 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