##// END OF EJS Templates
status: add support for log-like template keywords and functions...
status: add support for log-like template keywords and functions It's bound to ctx2 since "hg status" can be considered to show the status of the files at ctx2 given ctx1 as the base.

File last commit:

r38442:32fba6fe default
r38563:85e3aa21 @67 default
Show More
phabricator.py
980 lines | 35.0 KiB | text/x-python | PythonLexer
Jun Wu
phabricator: add a contrib script...
r33195 # phabricator.py - simple Phabricator integration
#
# Copyright 2017 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""simple Phabricator integration
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 This extension provides a ``phabsend`` command which sends a stack of
Jun Wu
phabsend: make --amend the default...
r33977 changesets to Phabricator, and a ``phabread`` command which prints a stack of
revisions in a format suitable for :hg:`import`, and a ``phabupdate`` command
to update statuses in batch.
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
By default, Phabricator requires ``Test Plan`` which might prevent some
changeset from being sent. The requirement could be disabled by changing
``differential.require-test-plan-field`` config server side.
Jun Wu
phabricator: add a contrib script...
r33195 Config::
[phabricator]
# Phabricator URL
url = https://phab.example.com/
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 # Repo callsign. If a repo has a URL https://$HOST/diffusion/FOO, then its
# callsign is "FOO".
callsign = FOO
Jun Wu
phabricator: add phabread command to read patches...
r33197
Jun Wu
phabricator: add a config to use curl for communication...
r34066 # curl command to use. If not set (default), use builtin HTTP library to
# communicate. If set, use the specified curl command. This could be useful
# if you need to specify advanced options that is not easily supported by
# the internal library.
curlcmd = curl --connect-timeout 2 --retry 3 --silent
Tom Prince
phabricator: specify API tokens per host, rather than per repo...
r36805
Matt Harbison
phabricator: migrate [phabricator.auth] to [auth]...
r38017 [auth]
Matt Harbison
phabricator: split auth.url into the standard auth.schemes and auth.prefix...
r38018 example.schemes = https
example.prefix = phab.example.com
Tom Prince
phabricator: specify API tokens per host, rather than per repo...
r36805 # API token. Get it from https://$HOST/conduit/login/
Matt Harbison
phabricator: migrate [phabricator.auth] to [auth]...
r38017 example.phabtoken = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
Jun Wu
phabricator: add a contrib script...
r33195 """
from __future__ import absolute_import
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 import itertools
Jun Wu
phabricator: add a contrib script...
r33195 import json
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 import operator
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 import re
Jun Wu
phabricator: add a contrib script...
r33195
Jun Wu
phabricator: verify local tags before trusting them...
r33443 from mercurial.node import bin, nullid
Jun Wu
phabricator: add a contrib script...
r33195 from mercurial.i18n import _
from mercurial import (
Jun Wu
phabricator: add --amend option to phabsend...
r33784 cmdutil,
context,
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 encoding,
Jun Wu
phabricator: add a contrib script...
r33195 error,
Matt Harbison
phabricator: split auth.url into the standard auth.schemes and auth.prefix...
r38018 httpconnection as httpconnectionmod,
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 mdiff,
Boris Feld
obsutil: rename allprecursors into allpredecessors...
r33701 obsutil,
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 parser,
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 patch,
Jun Wu
phabricator: add a contrib script...
r33195 registrar,
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 scmutil,
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 smartset,
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 tags,
Jun Wu
phabricator: add a contrib script...
r33195 url as urlmod,
util,
)
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 from mercurial.utils import (
procutil,
Tom Prince
phabricator: specify some metadata compatibly with arc...
r37818 stringutil,
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 )
Jun Wu
phabricator: add a contrib script...
r33195
cmdtable = {}
command = registrar.command(cmdtable)
Matt Harbison
phabricator: register config settings...
r38053 configtable = {}
configitem = registrar.configitem(configtable)
# developer config: phabricator.batchsize
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 configitem(b'phabricator', b'batchsize',
Matt Harbison
phabricator: register config settings...
r38053 default=12,
)
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 configitem(b'phabricator', b'callsign',
Matt Harbison
phabricator: register config settings...
r38053 default=None,
)
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 configitem(b'phabricator', b'curlcmd',
Matt Harbison
phabricator: register config settings...
r38053 default=None,
)
# developer config: phabricator.repophid
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 configitem(b'phabricator', b'repophid',
Matt Harbison
phabricator: register config settings...
r38053 default=None,
)
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 configitem(b'phabricator', b'url',
Matt Harbison
phabricator: register config settings...
r38053 default=None,
)
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 configitem(b'phabsend', b'confirm',
Matt Harbison
phabricator: register config settings...
r38053 default=False,
)
Jun Wu
phabricator: standardize colors...
r34065 colortable = {
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 b'phabricator.action.created': b'green',
b'phabricator.action.skipped': b'magenta',
b'phabricator.action.updated': b'magenta',
b'phabricator.desc': b'',
b'phabricator.drev': b'bold',
b'phabricator.node': b'',
Jun Wu
phabricator: standardize colors...
r34065 }
Jun Wu
phabricator: add a contrib script...
r33195 def urlencodenested(params):
"""like urlencode, but works with nested parameters.
For example, if params is {'a': ['b', 'c'], 'd': {'e': 'f'}}, it will be
flattened to {'a[0]': 'b', 'a[1]': 'c', 'd[e]': 'f'} and then passed to
urlencode. Note: the encoding is consistent with PHP's http_build_query.
"""
flatparams = util.sortdict()
def process(prefix, obj):
items = {list: enumerate, dict: lambda x: x.items()}.get(type(obj))
if items is None:
flatparams[prefix] = obj
else:
for k, v in items(obj):
if prefix:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 process(b'%s[%s]' % (prefix, k), v)
Jun Wu
phabricator: add a contrib script...
r33195 else:
process(k, v)
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 process(b'', params)
Jun Wu
phabricator: add a contrib script...
r33195 return util.urlreq.urlencode(flatparams)
Matt Harbison
phabricator: migrate [phabricator.auth] to [auth]...
r38017 printed_token_warning = False
Jun Wu
phabricator: add a contrib script...
r33195
Matt Harbison
phabricator: migrate [phabricator.auth] to [auth]...
r38017 def readlegacytoken(repo, url):
"""Transitional support for old phabricator tokens.
Remove before the 4.7 release.
Jun Wu
phabricator: add a contrib script...
r33195 """
Tom Prince
phabricator: specify API tokens per host, rather than per repo...
r36805 groups = {}
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 for key, val in repo.ui.configitems(b'phabricator.auth'):
if b'.' not in key:
repo.ui.warn(_(b"ignoring invalid [phabricator.auth] key '%s'\n")
Tom Prince
phabricator: specify API tokens per host, rather than per repo...
r36805 % key)
continue
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 group, setting = key.rsplit(b'.', 1)
Tom Prince
phabricator: specify API tokens per host, rather than per repo...
r36805 groups.setdefault(group, {})[setting] = val
token = None
for group, auth in groups.iteritems():
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 if url != auth.get(b'url'):
Tom Prince
phabricator: specify API tokens per host, rather than per repo...
r36805 continue
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 token = auth.get(b'token')
Tom Prince
phabricator: specify API tokens per host, rather than per repo...
r36805 if token:
break
Matt Harbison
phabricator: migrate [phabricator.auth] to [auth]...
r38017 global printed_token_warning
if token and not printed_token_warning:
printed_token_warning = True
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 repo.ui.warn(_(b'phabricator.auth.token is deprecated - please '
b'migrate to auth.phabtoken.\n'))
Matt Harbison
phabricator: migrate [phabricator.auth] to [auth]...
r38017 return token
def readurltoken(repo):
"""return conduit url, token and make sure they exist
Matt Harbison
phabricator: split auth.url into the standard auth.schemes and auth.prefix...
r38018 Currently read from [auth] config section. In the future, it might
Matt Harbison
phabricator: migrate [phabricator.auth] to [auth]...
r38017 make sense to read from .arcconfig and .arcrc as well.
"""
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 url = repo.ui.config(b'phabricator', b'url')
Matt Harbison
phabricator: migrate [phabricator.auth] to [auth]...
r38017 if not url:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 raise error.Abort(_(b'config %s.%s is required')
% (b'phabricator', b'url'))
Matt Harbison
phabricator: migrate [phabricator.auth] to [auth]...
r38017
Matt Harbison
phabricator: split auth.url into the standard auth.schemes and auth.prefix...
r38018 res = httpconnectionmod.readauthforuri(repo.ui, url, util.url(url).user)
token = None
Matt Harbison
phabricator: migrate [phabricator.auth] to [auth]...
r38017
Matt Harbison
phabricator: split auth.url into the standard auth.schemes and auth.prefix...
r38018 if res:
group, auth = res
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 repo.ui.debug(b"using auth.%s.* for authentication\n" % group)
Matt Harbison
phabricator: split auth.url into the standard auth.schemes and auth.prefix...
r38018
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 token = auth.get(b'phabtoken')
Matt Harbison
phabricator: migrate [phabricator.auth] to [auth]...
r38017
Tom Prince
phabricator: specify API tokens per host, rather than per repo...
r36805 if not token:
Matt Harbison
phabricator: migrate [phabricator.auth] to [auth]...
r38017 token = readlegacytoken(repo, url)
if not token:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 raise error.Abort(_(b'Can\'t find conduit token associated to %s')
Matt Harbison
phabricator: migrate [phabricator.auth] to [auth]...
r38017 % (url,))
Tom Prince
phabricator: specify API tokens per host, rather than per repo...
r36805
return url, token
Jun Wu
phabricator: add a contrib script...
r33195
def callconduit(repo, name, params):
"""call Conduit API, params is a dict. return json.loads result, or None"""
host, token = readurltoken(repo)
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 url, authinfo = util.url(b'/'.join([host, b'api', name])).authinfo()
repo.ui.debug(b'Conduit Call: %s %s\n' % (url, params))
Jun Wu
phabricator: add a contrib script...
r33195 params = params.copy()
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 params[b'api.token'] = token
Jun Wu
phabricator: add a config to use curl for communication...
r34066 data = urlencodenested(params)
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 curlcmd = repo.ui.config(b'phabricator', b'curlcmd')
Jun Wu
phabricator: add a config to use curl for communication...
r34066 if curlcmd:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 sin, sout = procutil.popen2(b'%s -d @- %s'
Yuya Nishihara
procutil: bulk-replace function calls to point to new module
r37138 % (curlcmd, procutil.shellquote(url)))
Jun Wu
phabricator: add a config to use curl for communication...
r34066 sin.write(data)
sin.close()
body = sout.read()
else:
urlopener = urlmod.opener(repo.ui, authinfo)
request = util.urlreq.request(url, data=data)
body = urlopener.open(request).read()
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 repo.ui.debug(b'Conduit Response: %s\n' % body)
Jun Wu
phabricator: add a contrib script...
r33195 parsed = json.loads(body)
if parsed.get(r'error_code'):
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 msg = (_(b'Conduit Error (%s): %s')
Jun Wu
phabricator: add a contrib script...
r33195 % (parsed[r'error_code'], parsed[r'error_info']))
raise error.Abort(msg)
return parsed[r'result']
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 @command(b'debugcallconduit', [], _(b'METHOD'))
Jun Wu
phabricator: add a contrib script...
r33195 def debugcallconduit(ui, repo, name):
"""call Conduit API
Call parameters are read from stdin as a JSON blob. Result will be written
to stdout as a JSON blob.
"""
params = json.loads(ui.fin.read())
result = callconduit(repo, name, params)
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 s = json.dumps(result, sort_keys=True, indent=2, separators=(b',', b': '))
ui.write(b'%s\n' % s)
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
def getrepophid(repo):
"""given callsign, return repository PHID or None"""
# developer config: phabricator.repophid
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 repophid = repo.ui.config(b'phabricator', b'repophid')
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 if repophid:
return repophid
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 callsign = repo.ui.config(b'phabricator', b'callsign')
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 if not callsign:
return None
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 query = callconduit(repo, b'diffusion.repository.search',
{b'constraints': {b'callsigns': [callsign]}})
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 if len(query[r'data']) == 0:
return None
repophid = encoding.strtolocal(query[r'data'][0][r'phid'])
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 repo.ui.setconfig(b'phabricator', b'repophid', repophid)
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 return repophid
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 _differentialrevisiontagre = re.compile(b'\AD([1-9][0-9]*)\Z')
Jun Wu
phabricator: check associated Differential Revision from commit message...
r33263 _differentialrevisiondescre = re.compile(
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 b'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M)
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
Jun Wu
phabricator: finding old nodes in batch...
r33442 def getoldnodedrevmap(repo, nodelist):
"""find previous nodes that has been sent to Phabricator
Jun Wu
phabricator: use Phabricator's last node information...
r33654 return {node: (oldnode, Differential diff, Differential Revision ID)}
Jun Wu
phabricator: finding old nodes in batch...
r33442 for node in nodelist with known previous sent versions, or associated
Jun Wu
phabricator: use Phabricator's last node information...
r33654 Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
be ``None``.
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
Jun Wu
phabricator: use Phabricator's last node information...
r33654 Examines commit messages like "Differential Revision:" to get the
association information.
Jun Wu
phabricator: check associated Differential Revision from commit message...
r33263
Jun Wu
phabricator: use Phabricator's last node information...
r33654 If such commit message line is not found, examines all precursors and their
tags. Tags with format like "D1234" are considered a match and the node
with that tag, and the number after "D" (ex. 1234) will be returned.
The ``old node``, if not None, is guaranteed to be the last diff of
corresponding Differential Revision, and exist in the repo.
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 """
Jun Wu
phabricator: finding old nodes in batch...
r33442 url, token = readurltoken(repo)
unfi = repo.unfiltered()
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 nodemap = unfi.changelog.nodemap
Jun Wu
phabricator: check associated Differential Revision from commit message...
r33263
Jun Wu
phabricator: use Phabricator's last node information...
r33654 result = {} # {node: (oldnode?, lastdiff?, drev)}
toconfirm = {} # {node: (force, {precnode}, drev)}
Jun Wu
phabricator: finding old nodes in batch...
r33442 for node in nodelist:
ctx = unfi[node]
Jun Wu
phabricator: verify local tags before trusting them...
r33443 # For tags like "D123", put them into "toconfirm" to verify later
Boris Feld
obsutil: rename allprecursors into allpredecessors...
r33701 precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
Jun Wu
phabricator: verify local tags before trusting them...
r33443 for n in precnodes:
Jun Wu
phabricator: finding old nodes in batch...
r33442 if n in nodemap:
for tag in unfi.nodetags(n):
m = _differentialrevisiontagre.match(tag)
if m:
Jun Wu
phabricator: use Phabricator's last node information...
r33654 toconfirm[node] = (0, set(precnodes), int(m.group(1)))
Jun Wu
phabricator: finding old nodes in batch...
r33442 continue
Jun Wu
phabricator: check associated Differential Revision from commit message...
r33263
Jun Wu
phabricator: use Phabricator's last node information...
r33654 # Check commit message
Jun Wu
phabricator: finding old nodes in batch...
r33442 m = _differentialrevisiondescre.search(ctx.description())
if m:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 toconfirm[node] = (1, set(precnodes), int(m.group(b'id')))
Jun Wu
phabricator: check associated Differential Revision from commit message...
r33263
Jun Wu
phabricator: verify local tags before trusting them...
r33443 # Double check if tags are genuine by collecting all old nodes from
# Phabricator, and expect precursors overlap with it.
if toconfirm:
Jun Wu
phabricator: use Phabricator's last node information...
r33654 drevs = [drev for force, precs, drev in toconfirm.values()]
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 alldiffs = callconduit(unfi, b'differential.querydiffs',
{b'revisionIDs': drevs})
Jun Wu
phabricator: use Phabricator's last node information...
r33654 getnode = lambda d: bin(encoding.unitolocal(
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 getdiffmeta(d).get(r'node', b''))) or None
Jun Wu
phabricator: use Phabricator's last node information...
r33654 for newnode, (force, precset, drev) in toconfirm.items():
diffs = [d for d in alldiffs.values()
if int(d[r'revisionID']) == drev]
# "precursors" as known by Phabricator
phprecset = set(getnode(d) for d in diffs)
# Ignore if precursors (Phabricator and local repo) do not overlap,
# and force is not set (when commit message says nothing)
if not force and not bool(phprecset & precset):
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 tagname = b'D%d' % drev
Jun Wu
phabricator: verify local tags before trusting them...
r33443 tags.tag(repo, tagname, nullid, message=None, user=None,
date=None, local=True)
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 unfi.ui.warn(_(b'D%s: local tag removed - does not match '
b'Differential history\n') % drev)
Jun Wu
phabricator: use Phabricator's last node information...
r33654 continue
# Find the last node using Phabricator metadata, and make sure it
# exists in the repo
oldnode = lastdiff = None
if diffs:
lastdiff = max(diffs, key=lambda d: int(d[r'id']))
oldnode = getnode(lastdiff)
if oldnode and oldnode not in nodemap:
oldnode = None
result[newnode] = (oldnode, lastdiff, drev)
Jun Wu
phabricator: verify local tags before trusting them...
r33443
Jun Wu
phabricator: finding old nodes in batch...
r33442 return result
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
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):
output.write(chunk)
return output.getvalue()
def creatediff(ctx):
"""create a Differential Diff"""
repo = ctx.repo()
repophid = getrepophid(repo)
# Create a "Differential Diff" via "differential.createrawdiff" API
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 params = {b'diff': getdiff(ctx, mdiff.diffopts(git=True, context=32767))}
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 if repophid:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 params[b'repositoryPHID'] = repophid
diff = callconduit(repo, b'differential.createrawdiff', params)
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 if not diff:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 raise error.Abort(_(b'cannot create diff for %s') % ctx)
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 return diff
def writediffproperties(ctx, diff):
"""write metadata to diff so patches could be applied losslessly"""
params = {
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 b'diff_id': diff[r'id'],
b'name': b'hg:meta',
b'data': json.dumps({
b'user': ctx.user(),
b'date': b'%d %d' % ctx.date(),
b'node': ctx.hex(),
b'parent': ctx.p1().hex(),
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 }),
}
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 callconduit(ctx.repo(), b'differential.setdiffproperty', params)
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
Tom Prince
phabricator: specify some metadata compatibly with arc...
r37818 params = {
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 b'diff_id': diff[r'id'],
b'name': b'local:commits',
b'data': json.dumps({
Tom Prince
phabricator: specify some metadata compatibly with arc...
r37818 ctx.hex(): {
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 b'author': stringutil.person(ctx.user()),
b'authorEmail': stringutil.email(ctx.user()),
b'time': ctx.date()[0],
Tom Prince
phabricator: specify some metadata compatibly with arc...
r37818 },
}),
}
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 callconduit(ctx.repo(), b'differential.setdiffproperty', params)
Tom Prince
phabricator: specify some metadata compatibly with arc...
r37818
Jun Wu
phabricator: allow specifying reviewers on phabsend...
r33498 def createdifferentialrevision(ctx, revid=None, parentrevid=None, oldnode=None,
Jun Wu
phabricator: update diff property even if we choose not to create a new diff...
r33655 olddiff=None, actions=None):
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 """create or update a Differential Revision
If revid is None, create a new Differential Revision, otherwise update
revid. If parentrevid is not None, set it as a dependency.
Jun Wu
phabricator: do not upload new diff if nothing changes...
r33265
If oldnode is not None, check if the patch content (without commit message
and metadata) has changed before creating another diff.
Jun Wu
phabricator: allow specifying reviewers on phabsend...
r33498
If actions is not None, they will be appended to the transaction.
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 """
repo = ctx.repo()
Jun Wu
phabricator: do not upload new diff if nothing changes...
r33265 if oldnode:
Jun Wu
phabsend: detect patch change with larger context...
r33978 diffopts = mdiff.diffopts(git=True, context=32767)
Jun Wu
phabricator: do not upload new diff if nothing changes...
r33265 oldctx = repo.unfiltered()[oldnode]
neednewdiff = (getdiff(ctx, diffopts) != getdiff(oldctx, diffopts))
else:
neednewdiff = True
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
Jun Wu
phabricator: do not upload new diff if nothing changes...
r33265 transactions = []
if neednewdiff:
diff = creatediff(ctx)
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 transactions.append({b'type': b'update', b'value': diff[r'phid']})
Jun Wu
phabricator: update diff property even if we choose not to create a new diff...
r33655 else:
# Even if we don't need to upload a new diff because the patch content
# does not change. We might still need to update its metadata so
# pushers could know the correct node metadata.
assert olddiff
diff = olddiff
writediffproperties(ctx, diff)
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
# Use a temporary summary to set dependency. There might be better ways but
# I cannot find them for now. But do not do that if we are updating an
# existing revision (revid is not None) since that introduces visible
# churns (someone edited "Summary" twice) on the web page.
if parentrevid and revid is None:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 summary = b'Depends on D%s' % parentrevid
transactions += [{b'type': b'summary', b'value': summary},
{b'type': b'summary', b'value': b' '}]
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
Jun Wu
phabricator: allow specifying reviewers on phabsend...
r33498 if actions:
transactions += actions
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 # Parse commit message and update related fields.
desc = ctx.description()
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 info = callconduit(repo, b'differential.parsecommitmessage',
{b'corpus': desc})
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 for k, v in info[r'fields'].items():
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 if k in [b'title', b'summary', b'testPlan']:
transactions.append({b'type': k, b'value': v})
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 params = {b'transactions': transactions}
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 if revid is not None:
# Update an existing Differential Revision
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 params[b'objectIdentifier'] = revid
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 revision = callconduit(repo, b'differential.revision.edit', params)
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 if not revision:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 raise error.Abort(_(b'cannot create revision for %s') % ctx)
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
Jun Wu
phabricator: add --amend option to phabsend...
r33784 return revision, diff
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
Jun Wu
phabricator: allow specifying reviewers on phabsend...
r33498 def userphids(repo, names):
"""convert user names to PHIDs"""
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 query = {b'constraints': {b'usernames': names}}
result = callconduit(repo, b'user.search', query)
Jun Wu
phabricator: allow specifying reviewers on phabsend...
r33498 # username not found is not an error of the API. So check if we have missed
# some names here.
data = result[r'data']
resolved = set(entry[r'fields'][r'username'] for entry in data)
unresolved = set(names) - resolved
if unresolved:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 raise error.Abort(_(b'unknown username: %s')
% b' '.join(sorted(unresolved)))
Jun Wu
phabricator: allow specifying reviewers on phabsend...
r33498 return [entry[r'phid'] for entry in data]
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 @command(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'confirm', None, _(b'ask for confirmation before sending'))],
_(b'REV [OPTIONS]'))
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 def phabsend(ui, repo, *revs, **opts):
"""upload changesets to Phabricator
If there are multiple revisions specified, they will be send as a stack
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.
Pulkit Goyal
phabricator: add --confirm option to phabsend command...
r33653
Jun Wu
phabricator: add --amend option to phabsend...
r33784 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.
Pulkit Goyal
phabricator: add --confirm option to phabsend command...
r33653 The --confirm option lets you confirm changesets before sending them. You
can also add following to your configuration file to make it default
Jun Wu
phabsend: polish the docstring a bit...
r33976 behaviour::
Pulkit Goyal
phabricator: add --confirm option to phabsend command...
r33653
Jun Wu
phabsend: polish the docstring a bit...
r33976 [phabsend]
confirm = true
Jun Wu
phabricator: add --amend option to phabsend...
r33784
phabsend will check obsstore and the above association to decide whether to
update an existing Differential Revision, or create a new one.
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 """
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 revs = list(revs) + opts.get(b'rev', [])
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 revs = scmutil.revrange(repo, revs)
Jun Wu
phabricator: abort if phabsend gets empty revs...
r33266 if not revs:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 raise error.Abort(_(b'phabsend requires at least one changeset'))
if opts.get(b'amend'):
Jun Wu
phabricator: add --amend option to phabsend...
r33784 cmdutil.checkunfinished(repo)
Jun Wu
phabricator: abort if phabsend gets empty revs...
r33266
Jun Wu
phabsend: show associated Differential Revisions with --confirm...
r33980 # {newnode: (oldnode, olddiff, olddrev}
oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 confirm = ui.configbool(b'phabsend', b'confirm')
confirm |= bool(opts.get(b'confirm'))
Pulkit Goyal
phabricator: add --confirm option to phabsend command...
r33653 if confirm:
Jun Wu
phabsend: show associated Differential Revisions with --confirm...
r33980 confirmed = _confirmbeforesend(repo, revs, oldmap)
Pulkit Goyal
phabricator: add --confirm option to phabsend command...
r33653 if not confirmed:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 raise error.Abort(_(b'phabsend cancelled'))
Pulkit Goyal
phabricator: add --confirm option to phabsend command...
r33653
Jun Wu
phabricator: allow specifying reviewers on phabsend...
r33498 actions = []
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 reviewers = opts.get(b'reviewer', [])
Jun Wu
phabricator: allow specifying reviewers on phabsend...
r33498 if reviewers:
phids = userphids(repo, reviewers)
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 actions.append({b'type': b'reviewers.add', b'value': phids})
Jun Wu
phabricator: allow specifying reviewers on phabsend...
r33498
Jun Wu
phabricator: add --amend option to phabsend...
r33784 drevids = [] # [int]
diffmap = {} # {newnode: diff}
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 # Send patches one by one so we know their Differential Revision IDs and
# can provide dependency relationship
lastrevid = None
for rev in revs:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 ui.debug(b'sending rev %d\n' % rev)
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 ctx = repo[rev]
# Get Differential Revision ID
Jun Wu
phabricator: use Phabricator's last node information...
r33654 oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 if oldnode != ctx.node() or opts.get(b'amend'):
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 # Create or update Differential Revision
Jun Wu
phabricator: add --amend option to phabsend...
r33784 revision, diff = createdifferentialrevision(
ctx, revid, lastrevid, oldnode, olddiff, actions)
diffmap[ctx.node()] = diff
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 newrevid = int(revision[r'object'][r'id'])
if revid:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 action = b'updated'
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 else:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 action = b'created'
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
Jun Wu
phabricator: add --amend option to phabsend...
r33784 # Create a local tag to note the association, if commit message
# does not have it already
m = _differentialrevisiondescre.search(ctx.description())
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 if not m or int(m.group(b'id')) != newrevid:
tagname = b'D%d' % newrevid
Jun Wu
phabricator: add --amend option to phabsend...
r33784 tags.tag(repo, tagname, ctx.node(), message=None, user=None,
date=None, local=True)
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 else:
# Nothing changed. But still set "newrevid" so the next revision
# could depend on this one.
newrevid = revid
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 action = b'skipped'
Jun Wu
phabricator: add phabsend command to send a stack...
r33196
Jun Wu
phabricator: standardize colors...
r34065 actiondesc = ui.label(
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 {b'created': _(b'created'),
b'skipped': _(b'skipped'),
b'updated': _(b'updated')}[action],
b'phabricator.action.%s' % action)
drevdesc = ui.label(b'D%s' % 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))
Jun Wu
phabricator: add --amend option to phabsend...
r33784 drevids.append(newrevid)
Jun Wu
phabricator: add phabsend command to send a stack...
r33196 lastrevid = newrevid
Jun Wu
phabricator: add phabread command to read patches...
r33197
Jun Wu
phabricator: add --amend option to phabsend...
r33784 # Update commit messages and remove tags
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 if opts.get(b'amend'):
Jun Wu
phabricator: add --amend option to phabsend...
r33784 unfi = repo.unfiltered()
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 drevs = callconduit(repo, b'differential.query', {b'ids': drevids})
with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'):
wnode = unfi[b'.'].node()
Jun Wu
phabricator: add --amend option to phabsend...
r33784 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())
Matt Harbison
phabricator: preserve the phase when amending in the Differential fields...
r38355
Martin von Zweigbergk
scmutil: make cleanupnodes optionally also fix the phase...
r38442 newnode = new.commit()
Matt Harbison
phabricator: preserve the phase when amending in the Differential fields...
r38355
Jun Wu
phabricator: add --amend option to phabsend...
r33784 mapping[old.node()] = [newnode]
# Update diff property
writediffproperties(unfi[newnode], diffmap[old.node()])
# Remove local tags since it's no longer necessary
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 tagname = b'D%d' % drevid
Jun Wu
phabricator: add --amend option to phabsend...
r33784 if tagname in repo.tags():
tags.tag(repo, tagname, nullid, message=None, user=None,
date=None, local=True)
Martin von Zweigbergk
scmutil: make cleanupnodes optionally also fix the phase...
r38442 scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True)
Jun Wu
phabricator: add --amend option to phabsend...
r33784 if wnode in mapping:
unfi.setparents(mapping[wnode][0])
Jun Wu
phabricator: add node and p1 to hg:meta property...
r33264 # Map from "hg:meta" keys to header understood by "hg import". The order is
# consistent with "hg export" output.
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 _metanamemap = util.sortdict([(r'user', b'User'), (r'date', b'Date'),
(r'node', b'Node ID'), (r'parent', b'Parent ')])
Jun Wu
phabricator: add node and p1 to hg:meta property...
r33264
Jun Wu
phabsend: show associated Differential Revisions with --confirm...
r33980 def _confirmbeforesend(repo, revs, oldmap):
Jun Wu
phabsend: print the actual URL with --confirm...
r33979 url, token = readurltoken(repo)
Pulkit Goyal
phabricator: add --confirm option to phabsend command...
r33653 ui = repo.ui
for rev in revs:
ctx = repo[rev]
desc = ctx.description().splitlines()[0]
Jun Wu
phabsend: show associated Differential Revisions with --confirm...
r33980 oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None))
if drevid:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 drevdesc = ui.label(b'D%s' % drevid, b'phabricator.drev')
Jun Wu
phabsend: show associated Differential Revisions with --confirm...
r33980 else:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 drevdesc = ui.label(_(b'NEW'), b'phabricator.drev')
Jun Wu
phabsend: show associated Differential Revisions with --confirm...
r33980
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 ui.write(_(b'%s - %s: %s\n')
% (drevdesc,
ui.label(bytes(ctx), b'phabricator.node'),
ui.label(desc, b'phabricator.desc')))
Pulkit Goyal
phabricator: add --confirm option to phabsend command...
r33653
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 if ui.promptchoice(_(b'Send the above changes to %s (yn)?'
b'$$ &Yes $$ &No') % url):
Pulkit Goyal
phabricator: add --confirm option to phabsend command...
r33653 return False
return True
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 _knownstatusnames = {b'accepted', b'needsreview', b'needsrevision', b'closed',
b'abandoned'}
Jun Wu
phabricator: add status to revision query language...
r33832
def _getstatusname(drev):
"""get normalized status name from a Differential Revision"""
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 return drev[r'statusName'].replace(b' ', b'').lower()
Jun Wu
phabricator: add status to revision query language...
r33832
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 # Small language to specify differential revisions. Support symbols: (), :X,
# +, and -.
_elements = {
# token-type: binding-strength, primary, prefix, infix, suffix
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 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),
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 }
def _tokenize(text):
view = memoryview(text) # zero-copy slice
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 special = b'():+-& '
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 pos = 0
length = len(text)
while pos < length:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 symbol = b''.join(itertools.takewhile(lambda ch: ch not in special,
view[pos:]))
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 if symbol:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 yield (b'symbol', symbol, pos)
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 pos += len(symbol)
Jun Wu
phabricator: add status to revision query language...
r33832 else: # special char, ignore space
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 if text[pos] != b' ':
Jun Wu
phabricator: add status to revision query language...
r33832 yield (text[pos], None, pos)
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 pos += 1
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 yield (b'end', None, pos)
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831
def _parse(text):
tree, pos = parser.parser(_elements).parse(_tokenize(text))
if pos != len(text):
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 raise error.ParseError(b'invalid token', pos)
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 return tree
def _parsedrev(symbol):
"""str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None"""
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 if symbol.startswith(b'D') and symbol[1:].isdigit():
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 return int(symbol[1:])
if symbol.isdigit():
return int(symbol)
def _prefetchdrevs(tree):
"""return ({single-drev-id}, {ancestor-drev-id}) to prefetch"""
drevs = set()
ancestordrevs = set()
op = tree[0]
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 if op == b'symbol':
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 r = _parsedrev(tree[1])
if r:
drevs.add(r)
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 elif op == b'ancestors':
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 r, a = _prefetchdrevs(tree[1])
drevs.update(r)
ancestordrevs.update(r)
ancestordrevs.update(a)
else:
for t in tree[1:]:
r, a = _prefetchdrevs(t)
drevs.update(r)
ancestordrevs.update(a)
return drevs, ancestordrevs
def querydrev(repo, spec):
Jun Wu
phabricator: rework phabread to reduce memory usage and round-trips...
r33267 """return a list of "Differential Revision" dicts
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 spec is a string using a simple query language, see docstring in phabread
for details.
Jun Wu
phabricator: rework phabread to reduce memory usage and round-trips...
r33267
A "Differential Revision dict" looks like:
{
"id": "2",
"phid": "PHID-DREV-672qvysjcczopag46qty",
"title": "example",
"uri": "https://phab.example.com/D2",
"dateCreated": "1499181406",
"dateModified": "1499182103",
"authorPHID": "PHID-USER-tv3ohwc4v4jeu34otlye",
"status": "0",
"statusName": "Needs Review",
"properties": [],
"branch": null,
"summary": "",
"testPlan": "",
"lineCount": "2",
"activeDiffPHID": "PHID-DIFF-xoqnjkobbm6k4dk6hi72",
"diffs": [
"3",
"4",
],
"commits": [],
"reviewers": [],
"ccs": [],
"hashes": [],
"auxiliary": {
"phabricator:projects": [],
"phabricator:depends-on": [
"PHID-DREV-gbapp366kutjebt7agcd"
]
},
"repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv",
"sourcePath": null
}
"""
Jun Wu
phabricator: try to fetch differential revisions in batch...
r33269 def fetch(params):
"""params -> single drev or None"""
key = (params.get(r'ids') or params.get(r'phids') or [None])[0]
if key in prefetched:
return prefetched[key]
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 drevs = callconduit(repo, b'differential.query', params)
Jun Wu
phabricator: try to fetch differential revisions in batch...
r33269 # Fill prefetched with the result
for drev in drevs:
prefetched[drev[r'phid']] = drev
prefetched[int(drev[r'id'])] = drev
if key not in prefetched:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 raise error.Abort(_(b'cannot get Differential Revision %r')
% params)
Jun Wu
phabricator: try to fetch differential revisions in batch...
r33269 return prefetched[key]
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 def getstack(topdrevids):
"""given a top, get a stack from the bottom, [id] -> [id]"""
visited = set()
result = []
queue = [{r'ids': [i]} for i in topdrevids]
while queue:
params = queue.pop()
drev = fetch(params)
if drev[r'id'] in visited:
continue
visited.add(drev[r'id'])
result.append(int(drev[r'id']))
Jun Wu
phabricator: rework phabread to reduce memory usage and round-trips...
r33267 auxiliary = drev.get(r'auxiliary', {})
depends = auxiliary.get(r'phabricator:depends-on', [])
for phid in depends:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 queue.append({b'phids': [phid]})
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 result.reverse()
return smartset.baseset(result)
# Initialize prefetch cache
prefetched = {} # {id or phid: drev}
tree = _parse(spec)
drevs, ancestordrevs = _prefetchdrevs(tree)
# developer config: phabricator.batchsize
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 batchsize = repo.ui.configint(b'phabricator', b'batchsize')
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831
# Prefetch Differential Revisions in batch
tofetch = set(drevs)
for r in ancestordrevs:
tofetch.update(range(max(1, r - batchsize), r + 1))
if drevs:
fetch({r'ids': list(tofetch)})
Jun Wu
phabricator: add status to revision query language...
r33832 validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs))
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831
# Walk through the tree, return smartsets
def walk(tree):
op = tree[0]
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 if op == b'symbol':
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 drev = _parsedrev(tree[1])
if drev:
return smartset.baseset([drev])
Jun Wu
phabricator: add status to revision query language...
r33832 elif tree[1] in _knownstatusnames:
drevs = [r for r in validids
if _getstatusname(prefetched[r]) == tree[1]]
return smartset.baseset(drevs)
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 else:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 raise error.Abort(_(b'unknown symbol: %s') % tree[1])
elif op in {b'and_', b'add', b'sub'}:
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 assert len(tree) == 3
return getattr(operator, op)(walk(tree[1]), walk(tree[2]))
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 elif op == b'group':
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 return walk(tree[1])
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 elif op == b'ancestors':
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 return getstack(walk(tree[1]))
else:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 raise error.ProgrammingError(b'illegal tree: %r' % tree)
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831
return [prefetched[r] for r in walk(tree)]
Jun Wu
phabricator: rework phabread to reduce memory usage and round-trips...
r33267
Jun Wu
phabricator: avoid calling differential.getcommitmessage...
r33268 def getdescfromdrev(drev):
"""get description (commit message) from "Differential Revision"
This is similar to differential.getcommitmessage API. But we only care
about limited fields: title, summary, test plan, and URL.
"""
title = drev[r'title']
summary = drev[r'summary'].rstrip()
testplan = drev[r'testPlan'].rstrip()
if testplan:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 testplan = b'Test Plan:\n%s' % testplan
uri = b'Differential Revision: %s' % drev[r'uri']
return b'\n\n'.join(filter(None, [title, summary, testplan, uri]))
Jun Wu
phabricator: avoid calling differential.getcommitmessage...
r33268
Jun Wu
phabricator: respect metadata sent by arc...
r33441 def getdiffmeta(diff):
"""get commit metadata (date, node, user, p1) from a diff object
The metadata could be "hg:meta", sent by phabsend, like:
"properties": {
"hg:meta": {
"date": "1499571514 25200",
"node": "98c08acae292b2faf60a279b4189beb6cff1414d",
"user": "Foo Bar <foo@example.com>",
"parent": "6d0abad76b30e4724a37ab8721d630394070fe16"
}
}
Or converted from "local:commits", sent by "arc", like:
"properties": {
"local:commits": {
"98c08acae292b2faf60a279b4189beb6cff1414d": {
"author": "Foo Bar",
"time": 1499546314,
"branch": "default",
"tag": "",
"commit": "98c08acae292b2faf60a279b4189beb6cff1414d",
"rev": "98c08acae292b2faf60a279b4189beb6cff1414d",
"local": "1000",
"parents": ["6d0abad76b30e4724a37ab8721d630394070fe16"],
"summary": "...",
"message": "...",
"authorEmail": "foo@example.com"
}
}
}
Note: metadata extracted from "local:commits" will lose time zone
information.
"""
props = diff.get(r'properties') or {}
meta = props.get(r'hg:meta')
if not meta and props.get(r'local:commits'):
commit = sorted(props[r'local:commits'].values())[0]
meta = {
r'date': r'%d 0' % commit[r'time'],
r'node': commit[r'rev'],
r'user': r'%s <%s>' % (commit[r'author'], commit[r'authorEmail']),
}
if len(commit.get(r'parents', ())) >= 1:
meta[r'parent'] = commit[r'parents'][0]
return meta or {}
Jun Wu
phabricator: change "readpatch" to be more flexible...
r33830 def readpatch(repo, drevs, write):
Jun Wu
phabricator: add phabread command to read patches...
r33197 """generate plain-text patch readable by 'hg import'
Jun Wu
phabricator: change "readpatch" to be more flexible...
r33830 write is usually ui.write. drevs is what "querydrev" returns, results of
"differential.query".
Jun Wu
phabricator: add phabread command to read patches...
r33197 """
Jun Wu
phabricator: rework phabread to reduce memory usage and round-trips...
r33267 # Prefetch hg:meta property for all diffs
diffids = sorted(set(max(int(v) for v in drev[r'diffs']) for drev in drevs))
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 diffs = callconduit(repo, b'differential.querydiffs', {b'ids': diffids})
Jun Wu
phabricator: add phabread command to read patches...
r33197
Jun Wu
phabricator: rework phabread to reduce memory usage and round-trips...
r33267 # Generate patch for each drev
for drev in drevs:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 repo.ui.note(_(b'reading D%s\n') % drev[r'id'])
Jun Wu
phabricator: add phabread command to read patches...
r33197
Jun Wu
phabricator: rework phabread to reduce memory usage and round-trips...
r33267 diffid = max(int(v) for v in drev[r'diffs'])
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 body = callconduit(repo, b'differential.getrawdiff',
{b'diffID': diffid})
Jun Wu
phabricator: avoid calling differential.getcommitmessage...
r33268 desc = getdescfromdrev(drev)
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 header = b'# HG changeset patch\n'
Jun Wu
phabricator: rework phabread to reduce memory usage and round-trips...
r33267
# Try to preserve metadata from hg:meta property. Write hg patch
# headers that can be read by the "import" command. See patchheadermap
# and extract in mercurial/patch.py for supported headers.
Jun Wu
phabricator: respect metadata sent by arc...
r33441 meta = getdiffmeta(diffs[str(diffid)])
for k in _metanamemap.keys():
if k in meta:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 header += b'# %s %s\n' % (_metanamemap[k], meta[k])
Jun Wu
phabricator: add phabread command to read patches...
r33197
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 content = b'%s%s\n%s' % (header, desc, body)
Jun Wu
phabricator: convert unicode to binary when writing patches...
r33602 write(encoding.unitolocal(content))
Jun Wu
phabricator: add phabread command to read patches...
r33197
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 @command(b'phabread',
[(b'', b'stack', False, _(b'read dependencies'))],
_(b'DREVSPEC [OPTIONS]'))
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 def phabread(ui, repo, spec, **opts):
Jun Wu
phabricator: add phabread command to read patches...
r33197 """print patches from Phabricator suitable for importing
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 DREVSPEC could be a Differential Revision identity, like ``D123``, or just
the number ``123``. It could also have common operators like ``+``, ``-``,
``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to
select a stack.
Jun Wu
phabricator: add status to revision query language...
r33832 ``abandoned``, ``accepted``, ``closed``, ``needsreview``, ``needsrevision``
could be used to filter patches by status. For performance reason, they
only represent a subset of non-status selections and cannot be used alone.
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude
Jun Wu
phabricator: add status to revision query language...
r33832 D2 and D4. ``:D9 & needsreview`` selects "Needs Review" revisions in a
stack up to D9.
Jun Wu
phabricator: add phabread command to read patches...
r33197
If --stack is given, follow dependencies information and read all patches.
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 It is equivalent to the ``:`` operator.
Jun Wu
phabricator: add phabread command to read patches...
r33197 """
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 if opts.get(b'stack'):
spec = b':(%s)' % spec
Jun Wu
phabricator: add a small language to query Differential Revisions...
r33831 drevs = querydrev(repo, spec)
Jun Wu
phabricator: change "readpatch" to be more flexible...
r33830 readpatch(repo, drevs, ui.write)
Jun Wu
phabricator: add phabupdate command to update status in batch...
r33833
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 @command(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]'))
Jun Wu
phabricator: add phabupdate command to update status in batch...
r33833 def phabupdate(ui, repo, spec, **opts):
"""update Differential Revision in batch
DREVSPEC selects revisions. See :hg:`help phabread` for its usage.
"""
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 flags = [n for n in b'accept reject abandon reclaim'.split() if opts.get(n)]
Jun Wu
phabricator: add phabupdate command to update status in batch...
r33833 if len(flags) > 1:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 raise error.Abort(_(b'%s cannot be used together') % b', '.join(flags))
Jun Wu
phabricator: add phabupdate command to update status in batch...
r33833
actions = []
for f in flags:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 actions.append({b'type': f, b'value': b'true'})
Jun Wu
phabricator: add phabupdate command to update status in batch...
r33833
drevs = querydrev(repo, spec)
for i, drev in enumerate(drevs):
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 if i + 1 == len(drevs) and opts.get(b'comment'):
actions.append({b'type': b'comment', b'value': opts[b'comment']})
Jun Wu
phabricator: add phabupdate command to update status in batch...
r33833 if actions:
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 params = {b'objectIdentifier': drev[r'phid'],
b'transactions': actions}
callconduit(repo, b'differential.revision.edit', params)
Tom Prince
phabricator: add a template item for linking to a differential review...
r35740
templatekeyword = registrar.templatekeyword()
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 @templatekeyword(b'phabreview', requires={b'ctx'})
Yuya Nishihara
templatekw: switch non-showlist template keywords to new API
r36531 def template_review(context, mapping):
Tom Prince
phabricator: add a template item for linking to a differential review...
r35740 """:phabreview: Object describing the review for this changeset.
Has attributes `url` and `id`.
"""
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 ctx = context.resource(mapping, b'ctx')
Tom Prince
phabricator: add a template item for linking to a differential review...
r35740 m = _differentialrevisiondescre.search(ctx.description())
if m:
return {
Yuya Nishihara
py3: byte-stringify literals in contrib/phabricator.py as example...
r38411 b'url': m.group(b'url'),
b'id': b"D{}".format(m.group(b'id')),
Tom Prince
phabricator: add a template item for linking to a differential review...
r35740 }