contrib/phabricator.py
changeset 33200 04cf9927f350
parent 33199 228ad1e58a85
child 33263 ed61189763ef
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)