Mercurial > public > mercurial-scm > hg-stable
comparison contrib/phabricator.py @ 33202: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
33201:228ad1e58a85 | 33202: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) |