comparison contrib/phabricator.py @ 33267:dba9f88659a3

phabricator: rework phabread to reduce memory usage and round-trips This patch reworked phabread a bit so it fetches the lightweight "Differential Revision" metadata for a stack first. Then read other data. This allows the code to: a) send 1 request to get all `hg:meta` data instead of N requests b) patches are read in desired order, no need to buffer the output "b)" reduces the memory usage from O(N^2) to O(N) since we no longer keep old patch contents in memory. The above `N` means the number of patches in the stack.
author Jun Wu <quark@fb.com>
date Tue, 04 Jul 2017 16:36:48 -0700
parents 5b2391b46906
children 85391b95961d
comparison
equal deleted inserted replaced
33266:5b2391b46906 33267:dba9f88659a3
312 # Map from "hg:meta" keys to header understood by "hg import". The order is 312 # Map from "hg:meta" keys to header understood by "hg import". The order is
313 # consistent with "hg export" output. 313 # consistent with "hg export" output.
314 _metanamemap = util.sortdict([(r'user', 'User'), (r'date', 'Date'), 314 _metanamemap = util.sortdict([(r'user', 'User'), (r'date', 'Date'),
315 (r'node', 'Node ID'), (r'parent', 'Parent ')]) 315 (r'node', 'Node ID'), (r'parent', 'Parent ')])
316 316
317 def readpatch(repo, params, recursive=False): 317 def querydrev(repo, params, stack=False):
318 """return a list of "Differential Revision" dicts
319
320 params is the input of "differential.query" API, and is expected to match
321 just a single Differential Revision.
322
323 A "Differential Revision dict" looks like:
324
325 {
326 "id": "2",
327 "phid": "PHID-DREV-672qvysjcczopag46qty",
328 "title": "example",
329 "uri": "https://phab.example.com/D2",
330 "dateCreated": "1499181406",
331 "dateModified": "1499182103",
332 "authorPHID": "PHID-USER-tv3ohwc4v4jeu34otlye",
333 "status": "0",
334 "statusName": "Needs Review",
335 "properties": [],
336 "branch": null,
337 "summary": "",
338 "testPlan": "",
339 "lineCount": "2",
340 "activeDiffPHID": "PHID-DIFF-xoqnjkobbm6k4dk6hi72",
341 "diffs": [
342 "3",
343 "4",
344 ],
345 "commits": [],
346 "reviewers": [],
347 "ccs": [],
348 "hashes": [],
349 "auxiliary": {
350 "phabricator:projects": [],
351 "phabricator:depends-on": [
352 "PHID-DREV-gbapp366kutjebt7agcd"
353 ]
354 },
355 "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv",
356 "sourcePath": null
357 }
358
359 If stack is True, return a list of "Differential Revision dict"s in an
360 order that the latter ones depend on the former ones. Otherwise, return a
361 list of a unique "Differential Revision dict".
362 """
363 result = []
364 queue = [params]
365 while queue:
366 params = queue.pop()
367 drevs = callconduit(repo, 'differential.query', params)
368 if len(drevs) != 1:
369 raise error.Abort(_('cannot get Differential Revision %r') % params)
370 drev = drevs[0]
371 result.append(drev)
372 if stack:
373 auxiliary = drev.get(r'auxiliary', {})
374 depends = auxiliary.get(r'phabricator:depends-on', [])
375 for phid in depends:
376 queue.append({'phids': [phid]})
377 result.reverse()
378 return result
379
380 def readpatch(repo, params, write, stack=False):
318 """generate plain-text patch readable by 'hg import' 381 """generate plain-text patch readable by 'hg import'
319 382
320 params is passed to "differential.query". If recursive is True, also return 383 write is usually ui.write. params is passed to "differential.query". If
321 dependent patches. 384 stack is True, also write dependent patches.
322 """ 385 """
323 # Differential Revisions 386 # Differential Revisions
324 drevs = callconduit(repo, 'differential.query', params) 387 drevs = querydrev(repo, params, stack)
325 if len(drevs) == 1: 388
326 drev = drevs[0] 389 # Prefetch hg:meta property for all diffs
327 else: 390 diffids = sorted(set(max(int(v) for v in drev[r'diffs']) for drev in drevs))
328 raise error.Abort(_('cannot get Differential Revision %r') % params) 391 diffs = callconduit(repo, 'differential.querydiffs', {'ids': diffids})
329 392
330 repo.ui.note(_('reading D%s\n') % drev[r'id']) 393 # Generate patch for each drev
331 394 for drev in drevs:
332 diffid = max(int(v) for v in drev[r'diffs']) 395 repo.ui.note(_('reading D%s\n') % drev[r'id'])
333 body = callconduit(repo, 'differential.getrawdiff', {'diffID': diffid}) 396
334 desc = callconduit(repo, 'differential.getcommitmessage', 397 diffid = max(int(v) for v in drev[r'diffs'])
335 {'revision_id': drev[r'id']}) 398 body = callconduit(repo, 'differential.getrawdiff', {'diffID': diffid})
336 header = '# HG changeset patch\n' 399 desc = callconduit(repo, 'differential.getcommitmessage',
337 400 {'revision_id': drev[r'id']})
338 # Remove potential empty "Summary:" 401 header = '# HG changeset patch\n'
339 desc = _summaryre.sub('', desc) 402
340 403 # Remove potential empty "Summary:"
341 # Try to preserve metadata from hg:meta property. Write hg patch headers 404 desc = _summaryre.sub('', desc)
342 # that can be read by the "import" command. See patchheadermap and extract 405
343 # in mercurial/patch.py for supported headers. 406 # Try to preserve metadata from hg:meta property. Write hg patch
344 diffs = callconduit(repo, 'differential.querydiffs', {'ids': [diffid]}) 407 # headers that can be read by the "import" command. See patchheadermap
345 props = diffs[str(diffid)][r'properties'] # could be empty list or dict 408 # and extract in mercurial/patch.py for supported headers.
346 if props and r'hg:meta' in props: 409 props = diffs[str(diffid)][r'properties'] # could be empty list or dict
347 meta = props[r'hg:meta'] 410 if props and r'hg:meta' in props:
348 for k in _metanamemap.keys(): 411 meta = props[r'hg:meta']
349 if k in meta: 412 for k in _metanamemap.keys():
350 header += '# %s %s\n' % (_metanamemap[k], meta[k]) 413 if k in meta:
351 414 header += '# %s %s\n' % (_metanamemap[k], meta[k])
352 patch = ('%s%s\n%s') % (header, desc, body) 415
353 416 write(('%s%s\n%s') % (header, desc, body))
354 # Check dependencies
355 if recursive:
356 auxiliary = drev.get(r'auxiliary', {})
357 depends = auxiliary.get(r'phabricator:depends-on', [])
358 for phid in depends:
359 patch = readpatch(repo, {'phids': [phid]}, recursive=True) + patch
360 return patch
361 417
362 @command('phabread', 418 @command('phabread',
363 [('', 'stack', False, _('read dependencies'))], 419 [('', 'stack', False, _('read dependencies'))],
364 _('REVID [OPTIONS]')) 420 _('REVID [OPTIONS]'))
365 def phabread(ui, repo, revid, **opts): 421 def phabread(ui, repo, revid, **opts):
372 """ 428 """
373 try: 429 try:
374 revid = int(revid.split('/')[-1].replace('D', '')) 430 revid = int(revid.split('/')[-1].replace('D', ''))
375 except ValueError: 431 except ValueError:
376 raise error.Abort(_('invalid Revision ID: %s') % revid) 432 raise error.Abort(_('invalid Revision ID: %s') % revid)
377 patch = readpatch(repo, {'ids': [revid]}, recursive=opts.get('stack')) 433 readpatch(repo, {'ids': [revid]}, ui.write, opts.get('stack'))
378 ui.write(patch)