comparison contrib/phabricator.py @ 33498:b7a75b9a3386

phabricator: allow specifying reviewers on phabsend Sometimes people want to specify reviewer explicitly for a stack. The webpage only allows changing reviewer for one revision at a time. This patch adds a `--reviewer` flag to make it easier to specify reviewers. Test Plan: On a test Phabricator instance, enable `differential.allow-self-accept`, assign myself as a reviewer and make sure it works. Also try an invalid username and make sure it raises. Differential Revision: https://phab.mercurial-scm.org/D38
author Jun Wu <quark@fb.com>
date Tue, 11 Jul 2017 08:52:55 -0700
parents e48082e0a8d5
children 91e3dcefc9b7
comparison
equal deleted inserted replaced
33497:80e1331a7fe9 33498:b7a75b9a3386
234 'parent': ctx.p1().hex(), 234 'parent': ctx.p1().hex(),
235 }), 235 }),
236 } 236 }
237 callconduit(ctx.repo(), 'differential.setdiffproperty', params) 237 callconduit(ctx.repo(), 'differential.setdiffproperty', params)
238 238
239 def createdifferentialrevision(ctx, revid=None, parentrevid=None, oldnode=None): 239 def createdifferentialrevision(ctx, revid=None, parentrevid=None, oldnode=None,
240 actions=None):
240 """create or update a Differential Revision 241 """create or update a Differential Revision
241 242
242 If revid is None, create a new Differential Revision, otherwise update 243 If revid is None, create a new Differential Revision, otherwise update
243 revid. If parentrevid is not None, set it as a dependency. 244 revid. If parentrevid is not None, set it as a dependency.
244 245
245 If oldnode is not None, check if the patch content (without commit message 246 If oldnode is not None, check if the patch content (without commit message
246 and metadata) has changed before creating another diff. 247 and metadata) has changed before creating another diff.
248
249 If actions is not None, they will be appended to the transaction.
247 """ 250 """
248 repo = ctx.repo() 251 repo = ctx.repo()
249 if oldnode: 252 if oldnode:
250 diffopts = mdiff.diffopts(git=True, context=1) 253 diffopts = mdiff.diffopts(git=True, context=1)
251 oldctx = repo.unfiltered()[oldnode] 254 oldctx = repo.unfiltered()[oldnode]
266 if parentrevid and revid is None: 269 if parentrevid and revid is None:
267 summary = 'Depends on D%s' % parentrevid 270 summary = 'Depends on D%s' % parentrevid
268 transactions += [{'type': 'summary', 'value': summary}, 271 transactions += [{'type': 'summary', 'value': summary},
269 {'type': 'summary', 'value': ' '}] 272 {'type': 'summary', 'value': ' '}]
270 273
274 if actions:
275 transactions += actions
276
271 # Parse commit message and update related fields. 277 # Parse commit message and update related fields.
272 desc = ctx.description() 278 desc = ctx.description()
273 info = callconduit(repo, 'differential.parsecommitmessage', 279 info = callconduit(repo, 'differential.parsecommitmessage',
274 {'corpus': desc}) 280 {'corpus': desc})
275 for k, v in info[r'fields'].items(): 281 for k, v in info[r'fields'].items():
285 if not revision: 291 if not revision:
286 raise error.Abort(_('cannot create revision for %s') % ctx) 292 raise error.Abort(_('cannot create revision for %s') % ctx)
287 293
288 return revision 294 return revision
289 295
296 def userphids(repo, names):
297 """convert user names to PHIDs"""
298 query = {'constraints': {'usernames': names}}
299 result = callconduit(repo, 'user.search', query)
300 # username not found is not an error of the API. So check if we have missed
301 # some names here.
302 data = result[r'data']
303 resolved = set(entry[r'fields'][r'username'] for entry in data)
304 unresolved = set(names) - resolved
305 if unresolved:
306 raise error.Abort(_('unknown username: %s')
307 % ' '.join(sorted(unresolved)))
308 return [entry[r'phid'] for entry in data]
309
290 @command('phabsend', 310 @command('phabsend',
291 [('r', 'rev', [], _('revisions to send'), _('REV'))], 311 [('r', 'rev', [], _('revisions to send'), _('REV')),
312 ('', 'reviewer', [], _('specify reviewers'))],
292 _('REV [OPTIONS]')) 313 _('REV [OPTIONS]'))
293 def phabsend(ui, repo, *revs, **opts): 314 def phabsend(ui, repo, *revs, **opts):
294 """upload changesets to Phabricator 315 """upload changesets to Phabricator
295 316
296 If there are multiple revisions specified, they will be send as a stack 317 If there are multiple revisions specified, they will be send as a stack
305 revs = list(revs) + opts.get('rev', []) 326 revs = list(revs) + opts.get('rev', [])
306 revs = scmutil.revrange(repo, revs) 327 revs = scmutil.revrange(repo, revs)
307 328
308 if not revs: 329 if not revs:
309 raise error.Abort(_('phabsend requires at least one changeset')) 330 raise error.Abort(_('phabsend requires at least one changeset'))
331
332 actions = []
333 reviewers = opts.get('reviewer', [])
334 if reviewers:
335 phids = userphids(repo, reviewers)
336 actions.append({'type': 'reviewers.add', 'value': phids})
310 337
311 oldnodedrev = getoldnodedrevmap(repo, [repo[r].node() for r in revs]) 338 oldnodedrev = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
312 339
313 # Send patches one by one so we know their Differential Revision IDs and 340 # Send patches one by one so we know their Differential Revision IDs and
314 # can provide dependency relationship 341 # can provide dependency relationship
320 # Get Differential Revision ID 347 # Get Differential Revision ID
321 oldnode, revid = oldnodedrev.get(ctx.node(), (None, None)) 348 oldnode, revid = oldnodedrev.get(ctx.node(), (None, None))
322 if oldnode != ctx.node(): 349 if oldnode != ctx.node():
323 # Create or update Differential Revision 350 # Create or update Differential Revision
324 revision = createdifferentialrevision(ctx, revid, lastrevid, 351 revision = createdifferentialrevision(ctx, revid, lastrevid,
325 oldnode) 352 oldnode, actions)
326 newrevid = int(revision[r'object'][r'id']) 353 newrevid = int(revision[r'object'][r'id'])
327 if revid: 354 if revid:
328 action = _('updated') 355 action = _('updated')
329 else: 356 else:
330 action = _('created') 357 action = _('created')