D122: phabricator: add --amend option to phabsend

quark (Jun Wu) phabricator at mercurial-scm.org
Tue Jul 18 04:20:07 UTC 2017


quark created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Previously `hg phabsend` was imitating `hg email` and won't mutate
  changesets. That works fine with reviewer-push workflow, reviewers run
  `phabread`, `import`.
  
  However, it does not work well with author-push workflow. Namely, the author
  needs to run extra commands to get the right commit message, and remove the
  local tag after push.
  
  This patch solves those issues by adding the `--amend` option, so local
  changesets will have the right commit message, and tags become unnecessary.

TEST PLAN
  Given the following DAG:
  
    o  17
    o  16
    | o  15
    | @  14
    |/
    o  13
    o  12
  
  Run `hg phabsend '(13::)-17'  --amend`, check the new DAG looks like:
  
    o  21
    | o  20
    | @  19
    |/
    o  18
    | o  17
    | x  16
    | x  13
    |/
    o  12
  
  And commit messages are updated to contain the `Differential Revision` lines.
  
  Also check `phabsend .` followed by a `phabsend . --amend`, the commit
  message will be updated and the tag will be removed.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D122

AFFECTED FILES
  contrib/phabricator.py

CHANGE DETAILS

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -38,6 +38,8 @@
 from mercurial.node import bin, nullid
 from mercurial.i18n import _
 from mercurial import (
+    cmdutil,
+    context,
     encoding,
     error,
     mdiff,
@@ -325,6 +327,7 @@
 
 @command('phabsend',
          [('r', 'rev', [], _('revisions to send'), _('REV')),
+          ('', 'amend', False, _('update commit messages')),
           ('', 'reviewer', [], _('specify reviewers'))],
          _('REV [OPTIONS]'))
 def phabsend(ui, repo, *revs, **opts):
@@ -334,16 +337,21 @@
     with a linear dependencies relationship using the order specified by the
     revset.
 
-    For the first time uploading changesets, local tags will be created to
-    maintain the association. After the first time, phabsend will check
-    obsstore and tags information so it can figure out whether to update an
-    existing Differential Revision, or create a new one.
+    If --amend is set, update commit messages so they have the
+    ``Differential Revision`` URL, remove related tags. This is similar to what
+    arcanist will do, and is more desired in author-push workflows. Otherwise,
+    use local tags to record the ``Differential Revision`` association.
+
+    phabsend will check obsstore and the above association to decide whether to
+    update an existing Differential Revision, or create a new one.
     """
     revs = list(revs) + opts.get('rev', [])
     revs = scmutil.revrange(repo, revs)
 
     if not revs:
         raise error.Abort(_('phabsend requires at least one changeset'))
+    if opts.get('amend'):
+        cmdutil.checkunfinished(repo)
 
     actions = []
     reviewers = opts.get('reviewer', [])
@@ -355,6 +363,7 @@
 
     # Send patches one by one so we know their Differential Revision IDs and
     # can provide dependency relationship
+    drevids = []
     lastrevid = None
     for rev in revs:
         ui.debug('sending rev %d\n' % rev)
@@ -372,20 +381,55 @@
             else:
                 action = _('created')
 
-            # Create a local tag to note the association
-            tagname = 'D%d' % newrevid
-            tags.tag(repo, tagname, ctx.node(), message=None, user=None,
-                     date=None, local=True)
+            # Create a local tag to note the association, if commit message
+            # does not have it already
+            m = _differentialrevisiondescre.search(ctx.description())
+            if not m or int(m.group(1)) != newrevid:
+                tagname = 'D%d' % newrevid
+                tags.tag(repo, tagname, ctx.node(), message=None, user=None,
+                         date=None, local=True)
         else:
             # Nothing changed. But still set "newrevid" so the next revision
             # could depend on this one.
             newrevid = revid
             action = _('skipped')
 
         ui.write(_('D%s: %s - %s: %s\n') % (newrevid, action, ctx,
                                             ctx.description().split('\n')[0]))
+        drevids.append(newrevid)
         lastrevid = newrevid
 
+    # Update commit messages and remove tags
+    if opts.get('amend'):
+        unfi = repo.unfiltered()
+        drevs = callconduit(repo, 'differential.query', {'ids': drevids})
+        with repo.wlock(), repo.lock(), repo.transaction('phabsend'):
+            wnode = unfi['.'].node()
+            mapping = {} # {oldnode: newnode}
+            for i, rev in enumerate(revs):
+                old = unfi[rev]
+                drevid = drevids[i]
+                drev = [d for d in drevs if int(d[r'id']) == drevid][0]
+                newdesc = getdescfromdrev(drev)
+                # Make sure commit message contain "Differential Revision"
+                if old.description() != newdesc:
+                    parents = [
+                        mapping.get(old.p1().node(), (old.p1(),))[0],
+                        mapping.get(old.p2().node(), (old.p2(),))[0],
+                    ]
+                    new = context.metadataonlyctx(
+                        repo, old, parents=parents, text=newdesc,
+                        user=old.user(), date=old.date(), extra=old.extra())
+                    mapping[old.node()] = [new.commit()]
+                # Remove local tags since it's no longer necessary
+                tagname = 'D%d' % drevid
+                if tagname in repo.tags():
+                    tags.tag(repo, tagname, nullid, message=None, user=None,
+                             date=None, local=True)
+            scmutil.cleanupnodes(repo, mapping, 'phabsend')
+            if wnode in mapping:
+                unfi.setparents(mapping[wnode][0])
+
 # Map from "hg:meta" keys to header understood by "hg import". The order is
 # consistent with "hg export" output.
 _metanamemap = util.sortdict([(r'user', 'User'), (r'date', 'Date'),



EMAIL PREFERENCES
  https://phab.mercurial-scm.org/settings/panel/emailpreferences/

To: quark, #hg-reviewers
Cc: mercurial-devel


More information about the Mercurial-devel mailing list