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') |