comparison contrib/phabricator.py @ 33200:04cf9927f350

phabricator: add phabread command to read patches This patch adds a `phabread` command generating plain-text patches from Phabricator, suitable for `hg import`. It respects `hg:meta` so user and date information might be preserved. And it removes `Summary:` field name which makes the commit message a bit tidier. To support stacked diffs, a `--stack` flag was added to read dependent patches recursively.
author Jun Wu <quark@fb.com>
date Sun, 02 Jul 2017 20:08:09 -0700
parents 228ad1e58a85
children ed61189763ef
comparison
equal deleted inserted replaced
33199:228ad1e58a85 33200:04cf9927f350
5 # This software may be used and distributed according to the terms of the 5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version. 6 # GNU General Public License version 2 or any later version.
7 """simple Phabricator integration 7 """simple Phabricator integration
8 8
9 This extension provides a ``phabsend`` command which sends a stack of 9 This extension provides a ``phabsend`` command which sends a stack of
10 changesets to Phabricator without amending commit messages. 10 changesets to Phabricator without amending commit messages, and a ``phabread``
11 command which prints a stack of revisions in a format suitable
12 for :hg:`import`.
11 13
12 By default, Phabricator requires ``Test Plan`` which might prevent some 14 By default, Phabricator requires ``Test Plan`` which might prevent some
13 changeset from being sent. The requirement could be disabled by changing 15 changeset from being sent. The requirement could be disabled by changing
14 ``differential.require-test-plan-field`` config server side. 16 ``differential.require-test-plan-field`` config server side.
15 17
23 token = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx 25 token = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
24 26
25 # Repo callsign. If a repo has a URL https://$HOST/diffusion/FOO, then its 27 # Repo callsign. If a repo has a URL https://$HOST/diffusion/FOO, then its
26 # callsign is "FOO". 28 # callsign is "FOO".
27 callsign = FOO 29 callsign = FOO
30
28 """ 31 """
29 32
30 from __future__ import absolute_import 33 from __future__ import absolute_import
31 34
32 import json 35 import json
271 action = _('skipped') 274 action = _('skipped')
272 275
273 ui.write(_('D%s: %s - %s: %s\n') % (newrevid, action, ctx, 276 ui.write(_('D%s: %s - %s: %s\n') % (newrevid, action, ctx,
274 ctx.description().split('\n')[0])) 277 ctx.description().split('\n')[0]))
275 lastrevid = newrevid 278 lastrevid = newrevid
279
280 _summaryre = re.compile('^Summary:\s*', re.M)
281
282 def readpatch(repo, params, recursive=False):
283 """generate plain-text patch readable by 'hg import'
284
285 params is passed to "differential.query". If recursive is True, also return
286 dependent patches.
287 """
288 # Differential Revisions
289 drevs = callconduit(repo, 'differential.query', params)
290 if len(drevs) == 1:
291 drev = drevs[0]
292 else:
293 raise error.Abort(_('cannot get Differential Revision %r') % params)
294
295 repo.ui.note(_('reading D%s\n') % drev[r'id'])
296
297 diffid = max(int(v) for v in drev[r'diffs'])
298 body = callconduit(repo, 'differential.getrawdiff', {'diffID': diffid})
299 desc = callconduit(repo, 'differential.getcommitmessage',
300 {'revision_id': drev[r'id']})
301 header = '# HG changeset patch\n'
302
303 # Remove potential empty "Summary:"
304 desc = _summaryre.sub('', desc)
305
306 # Try to preserve metadata (user, date) from hg:meta property
307 diffs = callconduit(repo, 'differential.querydiffs', {'ids': [diffid]})
308 props = diffs[str(diffid)][r'properties'] # could be empty list or dict
309 if props and r'hg:meta' in props:
310 meta = props[r'hg:meta']
311 for k, v in meta.items():
312 header += '# %s %s\n' % (k.capitalize(), v)
313
314 patch = ('%s%s\n%s') % (header, desc, body)
315
316 # Check dependencies
317 if recursive:
318 auxiliary = drev.get(r'auxiliary', {})
319 depends = auxiliary.get(r'phabricator:depends-on', [])
320 for phid in depends:
321 patch = readpatch(repo, {'phids': [phid]}, recursive=True) + patch
322 return patch
323
324 @command('phabread',
325 [('', 'stack', False, _('read dependencies'))],
326 _('REVID [OPTIONS]'))
327 def phabread(ui, repo, revid, **opts):
328 """print patches from Phabricator suitable for importing
329
330 REVID could be a Differential Revision identity, like ``D123``, or just the
331 number ``123``, or a full URL like ``https://phab.example.com/D123``.
332
333 If --stack is given, follow dependencies information and read all patches.
334 """
335 try:
336 revid = int(revid.split('/')[-1].replace('D', ''))
337 except ValueError:
338 raise error.Abort(_('invalid Revision ID: %s') % revid)
339 patch = readpatch(repo, {'ids': [revid]}, recursive=opts.get('stack'))
340 ui.write(patch)