Patchwork D8309: phabricator: combine commit messages into the review when folding commits

mail settings
Submitter phabricator
Date March 20, 2020, 9:24 p.m.
Message ID <>
Download mbox | patch
Permalink /patch/45846/
State Superseded
Headers show


phabricator - March 20, 2020, 9:24 p.m.
mharbison72 created this revision.
Herald added subscribers: mercurial-devel, Kwan.
Herald added a reviewer: hg-reviewers.

  No visible changes here, until an option to enable it is added to `phabsend`.
  This combines the Differential fields like Arcanist does, rather than simply
  concatenating the text blocks.  Aside from populating everything properly in the
  web interface, Phabricator fails the review create/update if repeated fields are
  seen as would happen with simple concatenation.
  On the flip side, now that the Summary and Test Plan fields can contain data
  from multiple commits, we can't just join these fields together to determine if
  an amend is needed.  If that were to happen, every single commit in the folded
  range would get amended with the combined commit message, which seems clearly
  wrong.  Aside from making a minor assumption about the content of the
  Differential Revision field (it seems they allow some minor variances with
  spacing), this means that for folded reviews, you can't post it, go to the web
  page add a missing Test Plan, and then get it added to the commit message by
  re-posting it.  I don't think that's a big deal.

  rHG Mercurial





To: mharbison72, #hg-reviewers
Cc: Kwan, mercurial-devel


diff --git a/hgext/ b/hgext/
--- a/hgext/
+++ b/hgext/
@@ -1067,14 +1067,42 @@ 
     if actions:
         transactions += actions
-    # Parse commit message and update related fields.
-    desc = ctx.description()
-    info = callconduit(
-        repo.ui, b'differential.parsecommitmessage', {b'corpus': desc}
-    )
-    for k, v in info[b'fields'].items():
-        if k in [b'title', b'summary', b'testPlan']:
-            transactions.append({b'type': k, b'value': v})
+    # When folding multiple local commits into a single review, arcanist will
+    # take the summary line of the first commit as the title, and then
+    # concatenate the rest of the remaining messages (including each of their
+    # first lines) to the rest of the first commit message (each separated by
+    # an empty line), and use that as the summary field.  Do the same here.
+    # For commits with only a one line message, there is no summary field, as
+    # this gets assigned to the title.
+    fields = util.sortdict()  # sorted for stable wire protocol in tests
+    for i, _ctx in enumerate([ctx]):
+        # Parse commit message and update related fields.
+        desc = _ctx.description()
+        info = callconduit(
+            repo.ui, b'differential.parsecommitmessage', {b'corpus': desc}
+        )
+        for k in [b'title', b'summary', b'testPlan']:
+            v = info[b'fields'].get(k)
+            if not v:
+                continue
+            if i == 0:
+                # Title, summary and test plan (if present) are taken verbatim
+                # for the first commit.
+                fields[k] = v.rstrip()
+                continue
+            elif k == b'title':
+                # Add subsequent titles (i.e. the first line of the commit
+                # message) back to the summary.
+                k = b'summary'
+            # Append any current field to the existing composite field
+            fields[k] = b'\n\n'.join(filter(None, [fields.get(k), v.rstrip()]))
+    for k, v in fields.items():
+        transactions.append({b'type': k, b'value': v})
     params = {b'transactions': transactions}
     if revid is not None:
@@ -1266,7 +1294,7 @@ 
                 old = unfi[rev]
                 drevid = drevids[i]
                 drev = [d for d in drevs if int(d[b'id']) == drevid][0]
-                newdesc = getdescfromdrev(drev)
+                newdesc = get_amended_desc(drev, old, False)
                 # Make sure commit message contain "Differential Revision"
                 if old.description() != newdesc:
                     if old.phase() == phases.public:
@@ -1593,6 +1621,33 @@ 
     return b'\n\n'.join(filter(None, [title, summary, testplan, uri]))
+def get_amended_desc(drev, ctx, folded):
+    """similar to ``getdescfromdrev``, but supports a folded series of commits
+    This is used when determining if an individual commit needs to have its
+    message amended after posting it for review.  The determination is made for
+    each individual commit, even when they were folded into one review.
+    """
+    if not folded:
+        return getdescfromdrev(drev)
+    uri = b'Differential Revision: %s' % drev[b'uri']
+    # Since the commit messages were combined when posting multiple commits
+    # with --fold, the fields can't be read from Phabricator here, or *all*
+    # affected local revisions will end up with the same commit message after
+    # the URI is amended in.  Append in the DREV line, or update it if it
+    # exists.  At worst, this means commit message or test plan updates on
+    # Phabricator aren't propagated back to the repository, but that seems
+    # reasonable for the case where local commits are effectively combined
+    # in Phabricator.
+    m =
+    if not m:
+        return b'\n\n'.join([ctx.description(), uri])
+    return _differentialrevisiondescre.sub(uri, ctx.description())
 def getdiffmeta(diff):
     """get commit metadata (date, node, user, p1) from a diff object