Mercurial > public > mercurial-scm > hg-stable
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) |