hgext/phabricator.py
changeset 43076 2372284d9457
parent 43047 0f90c2d2d7e8
child 43077 687b865b95ad
--- a/hgext/phabricator.py	Sat Oct 05 10:29:34 2019 -0400
+++ b/hgext/phabricator.py	Sun Oct 06 09:45:02 2019 -0400
@@ -89,24 +89,24 @@
 templatekeyword = eh.templatekeyword
 
 # developer config: phabricator.batchsize
-eh.configitem(b'phabricator', b'batchsize',
-    default=12,
+eh.configitem(
+    b'phabricator', b'batchsize', default=12,
 )
-eh.configitem(b'phabricator', b'callsign',
-    default=None,
+eh.configitem(
+    b'phabricator', b'callsign', default=None,
 )
-eh.configitem(b'phabricator', b'curlcmd',
-    default=None,
+eh.configitem(
+    b'phabricator', b'curlcmd', default=None,
 )
 # developer config: phabricator.repophid
-eh.configitem(b'phabricator', b'repophid',
-    default=None,
+eh.configitem(
+    b'phabricator', b'repophid', default=None,
 )
-eh.configitem(b'phabricator', b'url',
-    default=None,
+eh.configitem(
+    b'phabricator', b'url', default=None,
 )
-eh.configitem(b'phabsend', b'confirm',
-    default=False,
+eh.configitem(
+    b'phabsend', b'confirm', default=False,
 )
 
 colortable = {
@@ -119,15 +119,22 @@
 }
 
 _VCR_FLAGS = [
-    (b'', b'test-vcr', b'',
-     _(b'Path to a vcr file. If nonexistent, will record a new vcr transcript'
-       b', otherwise will mock all http requests using the specified vcr file.'
-       b' (ADVANCED)'
-     )),
+    (
+        b'',
+        b'test-vcr',
+        b'',
+        _(
+            b'Path to a vcr file. If nonexistent, will record a new vcr transcript'
+            b', otherwise will mock all http requests using the specified vcr file.'
+            b' (ADVANCED)'
+        ),
+    ),
 ]
 
+
 def vcrcommand(name, flags, spec, helpcategory=None, optionalrepo=False):
     fullflags = flags + _VCR_FLAGS
+
     def hgmatcher(r1, r2):
         if r1.uri != r2.uri or r1.method != r2.method:
             return False
@@ -137,9 +144,7 @@
 
     def sanitiserequest(request):
         request.body = re.sub(
-            br'cli-[a-z0-9]+',
-            br'cli-hahayouwish',
-            request.body
+            br'cli-[a-z0-9]+', br'cli-hahayouwish', request.body
         )
         return request
 
@@ -153,29 +158,46 @@
             cassette = pycompat.fsdecode(kwargs.pop(r'test_vcr', None))
             if cassette:
                 import hgdemandimport
+
                 with hgdemandimport.deactivated():
                     import vcr as vcrmod
                     import vcr.stubs as stubs
+
                     vcr = vcrmod.VCR(
                         serializer=r'json',
                         before_record_request=sanitiserequest,
                         before_record_response=sanitiseresponse,
                         custom_patches=[
-                            (urlmod, r'httpconnection',
-                             stubs.VCRHTTPConnection),
-                            (urlmod, r'httpsconnection',
-                             stubs.VCRHTTPSConnection),
-                        ])
+                            (
+                                urlmod,
+                                r'httpconnection',
+                                stubs.VCRHTTPConnection,
+                            ),
+                            (
+                                urlmod,
+                                r'httpsconnection',
+                                stubs.VCRHTTPSConnection,
+                            ),
+                        ],
+                    )
                     vcr.register_matcher(r'hgmatcher', hgmatcher)
                     with vcr.use_cassette(cassette, match_on=[r'hgmatcher']):
                         return fn(*args, **kwargs)
             return fn(*args, **kwargs)
+
         inner.__name__ = fn.__name__
         inner.__doc__ = fn.__doc__
-        return command(name, fullflags, spec, helpcategory=helpcategory,
-                       optionalrepo=optionalrepo)(inner)
+        return command(
+            name,
+            fullflags,
+            spec,
+            helpcategory=helpcategory,
+            optionalrepo=optionalrepo,
+        )(inner)
+
     return decorate
 
+
 def urlencodenested(params):
     """like urlencode, but works with nested parameters.
 
@@ -184,6 +206,7 @@
     urlencode. Note: the encoding is consistent with PHP's http_build_query.
     """
     flatparams = util.sortdict()
+
     def process(prefix, obj):
         if isinstance(obj, bool):
             obj = {True: b'true', False: b'false'}[obj]  # Python -> PHP form
@@ -197,9 +220,11 @@
                     process(b'%s[%s]' % (prefix, k), v)
                 else:
                     process(k, v)
+
     process(b'', params)
     return util.urlreq.urlencode(flatparams)
 
+
 def readurltoken(ui):
     """return conduit url, token and make sure they exist
 
@@ -208,8 +233,9 @@
     """
     url = ui.config(b'phabricator', b'url')
     if not url:
-        raise error.Abort(_(b'config %s.%s is required')
-                          % (b'phabricator', b'url'))
+        raise error.Abort(
+            _(b'config %s.%s is required') % (b'phabricator', b'url')
+        )
 
     res = httpconnectionmod.readauthforuri(ui, url, util.url(url).user)
     token = None
@@ -222,11 +248,13 @@
         token = auth.get(b'phabtoken')
 
     if not token:
-        raise error.Abort(_(b'Can\'t find conduit token associated to %s')
-                            % (url,))
+        raise error.Abort(
+            _(b'Can\'t find conduit token associated to %s') % (url,)
+        )
 
     return url, token
 
+
 def callconduit(ui, name, params):
     """call Conduit API, params is a dict. return json.loads result, or None"""
     host, token = readurltoken(ui)
@@ -237,8 +265,9 @@
     data = urlencodenested(params)
     curlcmd = ui.config(b'phabricator', b'curlcmd')
     if curlcmd:
-        sin, sout = procutil.popen2(b'%s -d @- %s'
-                                    % (curlcmd, procutil.shellquote(url)))
+        sin, sout = procutil.popen2(
+            b'%s -d @- %s' % (curlcmd, procutil.shellquote(url))
+        )
         sin.write(data)
         sin.close()
         body = sout.read()
@@ -249,17 +278,21 @@
             body = rsp.read()
     ui.debug(b'Conduit Response: %s\n' % body)
     parsed = pycompat.rapply(
-        lambda x: encoding.unitolocal(x) if isinstance(x, pycompat.unicode)
+        lambda x: encoding.unitolocal(x)
+        if isinstance(x, pycompat.unicode)
         else x,
         # json.loads only accepts bytes from py3.6+
-        json.loads(encoding.unifromlocal(body))
+        json.loads(encoding.unifromlocal(body)),
     )
     if parsed.get(b'error_code'):
-        msg = (_(b'Conduit Error (%s): %s')
-               % (parsed[b'error_code'], parsed[b'error_info']))
+        msg = _(b'Conduit Error (%s): %s') % (
+            parsed[b'error_code'],
+            parsed[b'error_info'],
+        )
         raise error.Abort(msg)
     return parsed[b'result']
 
+
 @vcrcommand(b'debugcallconduit', [], _(b'METHOD'), optionalrepo=True)
 def debugcallconduit(ui, repo, name):
     """call Conduit API
@@ -270,18 +303,21 @@
     # json.loads only accepts bytes from 3.6+
     rawparams = encoding.unifromlocal(ui.fin.read())
     # json.loads only returns unicode strings
-    params = pycompat.rapply(lambda x:
-        encoding.unitolocal(x) if isinstance(x, pycompat.unicode) else x,
-        json.loads(rawparams)
+    params = pycompat.rapply(
+        lambda x: encoding.unitolocal(x)
+        if isinstance(x, pycompat.unicode)
+        else x,
+        json.loads(rawparams),
     )
     # json.dumps only accepts unicode strings
-    result = pycompat.rapply(lambda x:
-        encoding.unifromlocal(x) if isinstance(x, bytes) else x,
-        callconduit(ui, name, params)
+    result = pycompat.rapply(
+        lambda x: encoding.unifromlocal(x) if isinstance(x, bytes) else x,
+        callconduit(ui, name, params),
     )
     s = json.dumps(result, sort_keys=True, indent=2, separators=(u',', u': '))
     ui.write(b'%s\n' % encoding.unitolocal(s))
 
+
 def getrepophid(repo):
     """given callsign, return repository PHID or None"""
     # developer config: phabricator.repophid
@@ -291,17 +327,23 @@
     callsign = repo.ui.config(b'phabricator', b'callsign')
     if not callsign:
         return None
-    query = callconduit(repo.ui, b'diffusion.repository.search',
-                        {b'constraints': {b'callsigns': [callsign]}})
+    query = callconduit(
+        repo.ui,
+        b'diffusion.repository.search',
+        {b'constraints': {b'callsigns': [callsign]}},
+    )
     if len(query[b'data']) == 0:
         return None
     repophid = query[b'data'][0][b'phid']
     repo.ui.setconfig(b'phabricator', b'repophid', repophid)
     return repophid
 
+
 _differentialrevisiontagre = re.compile(br'\AD([1-9][0-9]*)\Z')
 _differentialrevisiondescre = re.compile(
-    br'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M)
+    br'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M
+)
+
 
 def getoldnodedrevmap(repo, nodelist):
     """find previous nodes that has been sent to Phabricator
@@ -324,8 +366,8 @@
     unfi = repo.unfiltered()
     nodemap = unfi.changelog.nodemap
 
-    result = {} # {node: (oldnode?, lastdiff?, drev)}
-    toconfirm = {} # {node: (force, {precnode}, drev)}
+    result = {}  # {node: (oldnode?, lastdiff?, drev)}
+    toconfirm = {}  # {node: (force, {precnode}, drev)}
     for node in nodelist:
         ctx = unfi[node]
         # For tags like "D123", put them into "toconfirm" to verify later
@@ -347,13 +389,14 @@
     # Phabricator, and expect precursors overlap with it.
     if toconfirm:
         drevs = [drev for force, precs, drev in toconfirm.values()]
-        alldiffs = callconduit(unfi.ui, b'differential.querydiffs',
-                               {b'revisionIDs': drevs})
-        getnode = lambda d: bin(
-            getdiffmeta(d).get(b'node', b'')) or None
+        alldiffs = callconduit(
+            unfi.ui, b'differential.querydiffs', {b'revisionIDs': drevs}
+        )
+        getnode = lambda d: bin(getdiffmeta(d).get(b'node', b'')) or None
         for newnode, (force, precset, drev) in toconfirm.items():
-            diffs = [d for d in alldiffs.values()
-                     if int(d[b'revisionID']) == drev]
+            diffs = [
+                d for d in alldiffs.values() if int(d[b'revisionID']) == drev
+            ]
 
             # "precursors" as known by Phabricator
             phprecset = set(getnode(d) for d in diffs)
@@ -362,10 +405,22 @@
             # and force is not set (when commit message says nothing)
             if not force and not bool(phprecset & precset):
                 tagname = b'D%d' % drev
-                tags.tag(repo, tagname, nullid, message=None, user=None,
-                         date=None, local=True)
-                unfi.ui.warn(_(b'D%s: local tag removed - does not match '
-                               b'Differential history\n') % drev)
+                tags.tag(
+                    repo,
+                    tagname,
+                    nullid,
+                    message=None,
+                    user=None,
+                    date=None,
+                    local=True,
+                )
+                unfi.ui.warn(
+                    _(
+                        b'D%s: local tag removed - does not match '
+                        b'Differential history\n'
+                    )
+                    % drev
+                )
                 continue
 
             # Find the last node using Phabricator metadata, and make sure it
@@ -381,14 +436,17 @@
 
     return result
 
+
 def getdiff(ctx, diffopts):
     """plain-text diff without header (user, commit message, etc)"""
     output = util.stringio()
-    for chunk, _label in patch.diffui(ctx.repo(), ctx.p1().node(), ctx.node(),
-                                      None, opts=diffopts):
+    for chunk, _label in patch.diffui(
+        ctx.repo(), ctx.p1().node(), ctx.node(), None, opts=diffopts
+    ):
         output.write(chunk)
     return output.getvalue()
 
+
 def creatediff(ctx):
     """create a Differential Diff"""
     repo = ctx.repo()
@@ -402,40 +460,52 @@
         raise error.Abort(_(b'cannot create diff for %s') % ctx)
     return diff
 
+
 def writediffproperties(ctx, diff):
     """write metadata to diff so patches could be applied losslessly"""
     params = {
         b'diff_id': diff[b'id'],
         b'name': b'hg:meta',
-        b'data': templatefilters.json({
-            b'user': ctx.user(),
-            b'date': b'%d %d' % ctx.date(),
-            b'branch': ctx.branch(),
-            b'node': ctx.hex(),
-            b'parent': ctx.p1().hex(),
-        }),
+        b'data': templatefilters.json(
+            {
+                b'user': ctx.user(),
+                b'date': b'%d %d' % ctx.date(),
+                b'branch': ctx.branch(),
+                b'node': ctx.hex(),
+                b'parent': ctx.p1().hex(),
+            }
+        ),
     }
     callconduit(ctx.repo().ui, b'differential.setdiffproperty', params)
 
     params = {
         b'diff_id': diff[b'id'],
         b'name': b'local:commits',
-        b'data': templatefilters.json({
-            ctx.hex(): {
-                b'author': stringutil.person(ctx.user()),
-                b'authorEmail': stringutil.email(ctx.user()),
-                b'time': int(ctx.date()[0]),
-                b'commit': ctx.hex(),
-                b'parents': [ctx.p1().hex()],
-                b'branch': ctx.branch(),
-            },
-        }),
+        b'data': templatefilters.json(
+            {
+                ctx.hex(): {
+                    b'author': stringutil.person(ctx.user()),
+                    b'authorEmail': stringutil.email(ctx.user()),
+                    b'time': int(ctx.date()[0]),
+                    b'commit': ctx.hex(),
+                    b'parents': [ctx.p1().hex()],
+                    b'branch': ctx.branch(),
+                },
+            }
+        ),
     }
     callconduit(ctx.repo().ui, b'differential.setdiffproperty', params)
 
-def createdifferentialrevision(ctx, revid=None, parentrevphid=None,
-                               oldnode=None, olddiff=None, actions=None,
-                               comment=None):
+
+def createdifferentialrevision(
+    ctx,
+    revid=None,
+    parentrevphid=None,
+    oldnode=None,
+    olddiff=None,
+    actions=None,
+    comment=None,
+):
     """create or update a Differential Revision
 
     If revid is None, create a new Differential Revision, otherwise update
@@ -450,7 +520,7 @@
     if oldnode:
         diffopts = mdiff.diffopts(git=True, context=32767)
         oldctx = repo.unfiltered()[oldnode]
-        neednewdiff = (getdiff(ctx, diffopts) != getdiff(oldctx, diffopts))
+        neednewdiff = getdiff(ctx, diffopts) != getdiff(oldctx, diffopts)
     else:
         neednewdiff = True
 
@@ -470,16 +540,18 @@
 
     # Set the parent Revision every time, so commit re-ordering is picked-up
     if parentrevphid:
-        transactions.append({b'type': b'parents.set',
-                             b'value': [parentrevphid]})
+        transactions.append(
+            {b'type': b'parents.set', b'value': [parentrevphid]}
+        )
 
     if actions:
         transactions += actions
 
     # Parse commit message and update related fields.
     desc = ctx.description()
-    info = callconduit(repo.ui, b'differential.parsecommitmessage',
-                       {b'corpus': desc})
+    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})
@@ -495,6 +567,7 @@
 
     return revision, diff
 
+
 def userphids(repo, names):
     """convert user names to PHIDs"""
     names = [name.lower() for name in names]
@@ -506,20 +579,30 @@
     resolved = set(entry[b'fields'][b'username'].lower() for entry in data)
     unresolved = set(names) - resolved
     if unresolved:
-        raise error.Abort(_(b'unknown username: %s')
-                          % b' '.join(sorted(unresolved)))
+        raise error.Abort(
+            _(b'unknown username: %s') % b' '.join(sorted(unresolved))
+        )
     return [entry[b'phid'] for entry in data]
 
-@vcrcommand(b'phabsend',
-         [(b'r', b'rev', [], _(b'revisions to send'), _(b'REV')),
-          (b'', b'amend', True, _(b'update commit messages')),
-          (b'', b'reviewer', [], _(b'specify reviewers')),
-          (b'', b'blocker', [], _(b'specify blocking reviewers')),
-          (b'm', b'comment', b'',
-           _(b'add a comment to Revisions with new/updated Diffs')),
-          (b'', b'confirm', None, _(b'ask for confirmation before sending'))],
-         _(b'REV [OPTIONS]'),
-         helpcategory=command.CATEGORY_IMPORT_EXPORT)
+
+@vcrcommand(
+    b'phabsend',
+    [
+        (b'r', b'rev', [], _(b'revisions to send'), _(b'REV')),
+        (b'', b'amend', True, _(b'update commit messages')),
+        (b'', b'reviewer', [], _(b'specify reviewers')),
+        (b'', b'blocker', [], _(b'specify blocking reviewers')),
+        (
+            b'm',
+            b'comment',
+            b'',
+            _(b'add a comment to Revisions with new/updated Diffs'),
+        ),
+        (b'', b'confirm', None, _(b'ask for confirmation before sending')),
+    ],
+    _(b'REV [OPTIONS]'),
+    helpcategory=command.CATEGORY_IMPORT_EXPORT,
+)
 def phabsend(ui, repo, *revs, **opts):
     """upload changesets to Phabricator
 
@@ -573,14 +656,14 @@
     if reviewers:
         phids.extend(userphids(repo, reviewers))
     if blockers:
-        phids.extend(map(
-            lambda phid: b'blocking(%s)' % phid, userphids(repo, blockers)
-        ))
+        phids.extend(
+            map(lambda phid: b'blocking(%s)' % phid, userphids(repo, blockers))
+        )
     if phids:
         actions.append({b'type': b'reviewers.add', b'value': phids})
 
-    drevids = [] # [int]
-    diffmap = {} # {newnode: diff}
+    drevids = []  # [int]
+    diffmap = {}  # {newnode: diff}
 
     # Send patches one by one so we know their Differential Revision PHIDs and
     # can provide dependency relationship
@@ -594,8 +677,14 @@
         if oldnode != ctx.node() or opts.get(b'amend'):
             # Create or update Differential Revision
             revision, diff = createdifferentialrevision(
-                ctx, revid, lastrevphid, oldnode, olddiff, actions,
-                opts.get(b'comment'))
+                ctx,
+                revid,
+                lastrevphid,
+                oldnode,
+                olddiff,
+                actions,
+                opts.get(b'comment'),
+            )
             diffmap[ctx.node()] = diff
             newrevid = int(revision[b'object'][b'id'])
             newrevphid = revision[b'object'][b'phid']
@@ -609,8 +698,15 @@
             m = _differentialrevisiondescre.search(ctx.description())
             if not m or int(m.group(r'id')) != newrevid:
                 tagname = b'D%d' % newrevid
-                tags.tag(repo, tagname, ctx.node(), message=None, user=None,
-                         date=None, local=True)
+                tags.tag(
+                    repo,
+                    tagname,
+                    ctx.node(),
+                    message=None,
+                    user=None,
+                    date=None,
+                    local=True,
+                )
         else:
             # Nothing changed. But still set "newrevphid" so the next revision
             # could depend on this one and "newrevid" for the summary line.
@@ -619,15 +715,19 @@
             action = b'skipped'
 
         actiondesc = ui.label(
-            {b'created': _(b'created'),
-             b'skipped': _(b'skipped'),
-             b'updated': _(b'updated')}[action],
-            b'phabricator.action.%s' % action)
+            {
+                b'created': _(b'created'),
+                b'skipped': _(b'skipped'),
+                b'updated': _(b'updated'),
+            }[action],
+            b'phabricator.action.%s' % action,
+        )
         drevdesc = ui.label(b'D%d' % newrevid, b'phabricator.drev')
         nodedesc = ui.label(bytes(ctx), b'phabricator.node')
         desc = ui.label(ctx.description().split(b'\n')[0], b'phabricator.desc')
-        ui.write(_(b'%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc,
-                                             desc))
+        ui.write(
+            _(b'%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc, desc)
+        )
         drevids.append(newrevid)
         lastrevphid = newrevphid
 
@@ -637,7 +737,7 @@
         drevs = callconduit(ui, b'differential.query', {b'ids': drevids})
         with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'):
             wnode = unfi[b'.'].node()
-            mapping = {} # {oldnode: [newnode]}
+            mapping = {}  # {oldnode: [newnode]}
             for i, rev in enumerate(revs):
                 old = unfi[rev]
                 drevid = drevids[i]
@@ -646,16 +746,24 @@
                 # Make sure commit message contain "Differential Revision"
                 if old.description() != newdesc:
                     if old.phase() == phases.public:
-                        ui.warn(_("warning: not updating public commit %s\n")
-                                % scmutil.formatchangeid(old))
+                        ui.warn(
+                            _("warning: not updating public commit %s\n")
+                            % scmutil.formatchangeid(old)
+                        )
                         continue
                     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())
+                        repo,
+                        old,
+                        parents=parents,
+                        text=newdesc,
+                        user=old.user(),
+                        date=old.date(),
+                        extra=old.extra(),
+                    )
 
                     newnode = new.commit()
 
@@ -670,17 +778,32 @@
                 # Remove local tags since it's no longer necessary
                 tagname = b'D%d' % drevid
                 if tagname in repo.tags():
-                    tags.tag(repo, tagname, nullid, message=None, user=None,
-                             date=None, local=True)
+                    tags.tag(
+                        repo,
+                        tagname,
+                        nullid,
+                        message=None,
+                        user=None,
+                        date=None,
+                        local=True,
+                    )
             scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True)
             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([(b'user', b'User'), (b'date', b'Date'),
-                              (b'branch', b'Branch'), (b'node', b'Node ID'),
-                              (b'parent', b'Parent ')])
+_metanamemap = util.sortdict(
+    [
+        (b'user', b'User'),
+        (b'date', b'Date'),
+        (b'branch', b'Branch'),
+        (b'node', b'Node ID'),
+        (b'parent', b'Parent '),
+    ]
+)
+
 
 def _confirmbeforesend(repo, revs, oldmap):
     url, token = readurltoken(repo.ui)
@@ -694,62 +817,81 @@
         else:
             drevdesc = ui.label(_(b'NEW'), b'phabricator.drev')
 
-        ui.write(_(b'%s - %s: %s\n')
-                 % (drevdesc,
-                    ui.label(bytes(ctx), b'phabricator.node'),
-                    ui.label(desc, b'phabricator.desc')))
+        ui.write(
+            _(b'%s - %s: %s\n')
+            % (
+                drevdesc,
+                ui.label(bytes(ctx), b'phabricator.node'),
+                ui.label(desc, b'phabricator.desc'),
+            )
+        )
 
-    if ui.promptchoice(_(b'Send the above changes to %s (yn)?'
-                         b'$$ &Yes $$ &No') % url):
+    if ui.promptchoice(
+        _(b'Send the above changes to %s (yn)?' b'$$ &Yes $$ &No') % url
+    ):
         return False
 
     return True
 
-_knownstatusnames = {b'accepted', b'needsreview', b'needsrevision', b'closed',
-                     b'abandoned'}
+
+_knownstatusnames = {
+    b'accepted',
+    b'needsreview',
+    b'needsrevision',
+    b'closed',
+    b'abandoned',
+}
+
 
 def _getstatusname(drev):
     """get normalized status name from a Differential Revision"""
     return drev[b'statusName'].replace(b' ', b'').lower()
 
+
 # Small language to specify differential revisions. Support symbols: (), :X,
 # +, and -.
 
 _elements = {
     # token-type: binding-strength, primary, prefix, infix, suffix
-    b'(':      (12, None, (b'group', 1, b')'), None, None),
-    b':':      (8, None, (b'ancestors', 8), None, None),
-    b'&':      (5,  None, None, (b'and_', 5), None),
-    b'+':      (4,  None, None, (b'add', 4), None),
-    b'-':      (4,  None, None, (b'sub', 4), None),
-    b')':      (0,  None, None, None, None),
+    b'(': (12, None, (b'group', 1, b')'), None, None),
+    b':': (8, None, (b'ancestors', 8), None, None),
+    b'&': (5, None, None, (b'and_', 5), None),
+    b'+': (4, None, None, (b'add', 4), None),
+    b'-': (4, None, None, (b'sub', 4), None),
+    b')': (0, None, None, None, None),
     b'symbol': (0, b'symbol', None, None, None),
-    b'end':    (0, None, None, None, None),
+    b'end': (0, None, None, None, None),
 }
 
+
 def _tokenize(text):
-    view = memoryview(text) # zero-copy slice
+    view = memoryview(text)  # zero-copy slice
     special = b'():+-& '
     pos = 0
     length = len(text)
     while pos < length:
-        symbol = b''.join(itertools.takewhile(lambda ch: ch not in special,
-                                              pycompat.iterbytestr(view[pos:])))
+        symbol = b''.join(
+            itertools.takewhile(
+                lambda ch: ch not in special, pycompat.iterbytestr(view[pos:])
+            )
+        )
         if symbol:
             yield (b'symbol', symbol, pos)
             pos += len(symbol)
-        else: # special char, ignore space
+        else:  # special char, ignore space
             if text[pos] != b' ':
                 yield (text[pos], None, pos)
             pos += 1
     yield (b'end', None, pos)
 
+
 def _parse(text):
     tree, pos = parser.parser(_elements).parse(_tokenize(text))
     if pos != len(text):
         raise error.ParseError(b'invalid token', pos)
     return tree
 
+
 def _parsedrev(symbol):
     """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None"""
     if symbol.startswith(b'D') and symbol[1:].isdigit():
@@ -757,6 +899,7 @@
     if symbol.isdigit():
         return int(symbol)
 
+
 def _prefetchdrevs(tree):
     """return ({single-drev-id}, {ancestor-drev-id}) to prefetch"""
     drevs = set()
@@ -778,6 +921,7 @@
             ancestordrevs.update(a)
     return drevs, ancestordrevs
 
+
 def querydrev(repo, spec):
     """return a list of "Differential Revision" dicts
 
@@ -820,6 +964,7 @@
             "sourcePath": null
         }
     """
+
     def fetch(params):
         """params -> single drev or None"""
         key = (params.get(b'ids') or params.get(b'phids') or [None])[0]
@@ -831,8 +976,9 @@
             prefetched[drev[b'phid']] = drev
             prefetched[int(drev[b'id'])] = drev
         if key not in prefetched:
-            raise error.Abort(_(b'cannot get Differential Revision %r')
-                              % params)
+            raise error.Abort(
+                _(b'cannot get Differential Revision %r') % params
+            )
         return prefetched[key]
 
     def getstack(topdrevids):
@@ -855,7 +1001,7 @@
         return smartset.baseset(result)
 
     # Initialize prefetch cache
-    prefetched = {} # {id or phid: drev}
+    prefetched = {}  # {id or phid: drev}
 
     tree = _parse(spec)
     drevs, ancestordrevs = _prefetchdrevs(tree)
@@ -879,8 +1025,11 @@
             if drev:
                 return smartset.baseset([drev])
             elif tree[1] in _knownstatusnames:
-                drevs = [r for r in validids
-                         if _getstatusname(prefetched[r]) == tree[1]]
+                drevs = [
+                    r
+                    for r in validids
+                    if _getstatusname(prefetched[r]) == tree[1]
+                ]
                 return smartset.baseset(drevs)
             else:
                 raise error.Abort(_(b'unknown symbol: %s') % tree[1])
@@ -896,6 +1045,7 @@
 
     return [prefetched[r] for r in walk(tree)]
 
+
 def getdescfromdrev(drev):
     """get description (commit message) from "Differential Revision"
 
@@ -910,6 +1060,7 @@
     uri = b'Differential Revision: %s' % drev[b'uri']
     return b'\n\n'.join(filter(None, [title, summary, testplan, uri]))
 
+
 def getdiffmeta(diff):
     """get commit metadata (date, node, user, p1) from a diff object
 
@@ -954,8 +1105,10 @@
             commit = sorted(props[b'local:commits'].values())[0]
             meta = {}
             if b'author' in commit and b'authorEmail' in commit:
-                meta[b'user'] = b'%s <%s>' % (commit[b'author'],
-                                              commit[b'authorEmail'])
+                meta[b'user'] = b'%s <%s>' % (
+                    commit[b'author'],
+                    commit[b'authorEmail'],
+                )
             if b'time' in commit:
                 meta[b'date'] = b'%d 0' % int(commit[b'time'])
             if b'branch' in commit:
@@ -975,6 +1128,7 @@
         meta[b'parent'] = diff[b'sourceControlBaseRevision']
     return meta
 
+
 def readpatch(repo, drevs, write):
     """generate plain-text patch readable by 'hg import'
 
@@ -990,8 +1144,9 @@
         repo.ui.note(_(b'reading D%s\n') % drev[b'id'])
 
         diffid = max(int(v) for v in drev[b'diffs'])
-        body = callconduit(repo.ui, b'differential.getrawdiff',
-                           {b'diffID': diffid})
+        body = callconduit(
+            repo.ui, b'differential.getrawdiff', {b'diffID': diffid}
+        )
         desc = getdescfromdrev(drev)
         header = b'# HG changeset patch\n'
 
@@ -1006,10 +1161,13 @@
         content = b'%s%s\n%s' % (header, desc, body)
         write(content)
 
-@vcrcommand(b'phabread',
-         [(b'', b'stack', False, _(b'read dependencies'))],
-         _(b'DREVSPEC [OPTIONS]'),
-         helpcategory=command.CATEGORY_IMPORT_EXPORT)
+
+@vcrcommand(
+    b'phabread',
+    [(b'', b'stack', False, _(b'read dependencies'))],
+    _(b'DREVSPEC [OPTIONS]'),
+    helpcategory=command.CATEGORY_IMPORT_EXPORT,
+)
 def phabread(ui, repo, spec, **opts):
     """print patches from Phabricator suitable for importing
 
@@ -1035,14 +1193,19 @@
     drevs = querydrev(repo, spec)
     readpatch(repo, drevs, ui.write)
 
-@vcrcommand(b'phabupdate',
-         [(b'', b'accept', False, _(b'accept revisions')),
-          (b'', b'reject', False, _(b'reject revisions')),
-          (b'', b'abandon', False, _(b'abandon revisions')),
-          (b'', b'reclaim', False, _(b'reclaim revisions')),
-          (b'm', b'comment', b'', _(b'comment on the last revision')),
-          ], _(b'DREVSPEC [OPTIONS]'),
-          helpcategory=command.CATEGORY_IMPORT_EXPORT)
+
+@vcrcommand(
+    b'phabupdate',
+    [
+        (b'', b'accept', False, _(b'accept revisions')),
+        (b'', b'reject', False, _(b'reject revisions')),
+        (b'', b'abandon', False, _(b'abandon revisions')),
+        (b'', b'reclaim', False, _(b'reclaim revisions')),
+        (b'm', b'comment', b'', _(b'comment on the last revision')),
+    ],
+    _(b'DREVSPEC [OPTIONS]'),
+    helpcategory=command.CATEGORY_IMPORT_EXPORT,
+)
 def phabupdate(ui, repo, spec, **opts):
     """update Differential Revision in batch
 
@@ -1062,10 +1225,13 @@
         if i + 1 == len(drevs) and opts.get(b'comment'):
             actions.append({b'type': b'comment', b'value': opts[b'comment']})
         if actions:
-            params = {b'objectIdentifier': drev[b'phid'],
-                      b'transactions': actions}
+            params = {
+                b'objectIdentifier': drev[b'phid'],
+                b'transactions': actions,
+            }
             callconduit(ui, b'differential.revision.edit', params)
 
+
 @eh.templatekeyword(b'phabreview', requires={b'ctx'})
 def template_review(context, mapping):
     """:phabreview: Object describing the review for this changeset.
@@ -1074,10 +1240,9 @@
     ctx = context.resource(mapping, b'ctx')
     m = _differentialrevisiondescre.search(ctx.description())
     if m:
-        return templateutil.hybriddict({
-            b'url': m.group(r'url'),
-            b'id': b"D%s" % m.group(r'id'),
-        })
+        return templateutil.hybriddict(
+            {b'url': m.group(r'url'), b'id': b"D%s" % m.group(r'id'),}
+        )
     else:
         tags = ctx.repo().nodetags(ctx.node())
         for t in tags:
@@ -1087,8 +1252,5 @@
                     url += b'/'
                 url += t
 
-                return templateutil.hybriddict({
-                    b'url': url,
-                    b'id': t,
-                })
+                return templateutil.hybriddict({b'url': url, b'id': t,})
     return None