Show More
@@ -32,7 +32,9 b' Config::' | |||||
32 |
|
32 | |||
33 | from __future__ import absolute_import |
|
33 | from __future__ import absolute_import | |
34 |
|
34 | |||
|
35 | import itertools | |||
35 | import json |
|
36 | import json | |
|
37 | import operator | |||
36 | import re |
|
38 | import re | |
37 |
|
39 | |||
38 | from mercurial.node import bin, nullid |
|
40 | from mercurial.node import bin, nullid | |
@@ -44,9 +46,11 b' from mercurial import (' | |||||
44 | error, |
|
46 | error, | |
45 | mdiff, |
|
47 | mdiff, | |
46 | obsutil, |
|
48 | obsutil, | |
|
49 | parser, | |||
47 | patch, |
|
50 | patch, | |
48 | registrar, |
|
51 | registrar, | |
49 | scmutil, |
|
52 | scmutil, | |
|
53 | smartset, | |||
50 | tags, |
|
54 | tags, | |
51 | url as urlmod, |
|
55 | url as urlmod, | |
52 | util, |
|
56 | util, | |
@@ -484,11 +488,77 b' def _confirmbeforesend(repo, revs):' | |||||
484 |
|
488 | |||
485 | return True |
|
489 | return True | |
486 |
|
490 | |||
487 | def querydrev(repo, params, stack=False): |
|
491 | # Small language to specify differential revisions. Support symbols: (), :X, | |
|
492 | # +, and -. | |||
|
493 | ||||
|
494 | _elements = { | |||
|
495 | # token-type: binding-strength, primary, prefix, infix, suffix | |||
|
496 | '(': (12, None, ('group', 1, ')'), None, None), | |||
|
497 | ':': (8, None, ('ancestors', 8), None, None), | |||
|
498 | '&': (5, None, None, ('and_', 5), None), | |||
|
499 | '+': (4, None, None, ('add', 4), None), | |||
|
500 | '-': (4, None, None, ('sub', 4), None), | |||
|
501 | ')': (0, None, None, None, None), | |||
|
502 | 'symbol': (0, 'symbol', None, None, None), | |||
|
503 | 'end': (0, None, None, None, None), | |||
|
504 | } | |||
|
505 | ||||
|
506 | def _tokenize(text): | |||
|
507 | text = text.replace(' ', '') # remove space | |||
|
508 | view = memoryview(text) # zero-copy slice | |||
|
509 | special = '():+-&' | |||
|
510 | pos = 0 | |||
|
511 | length = len(text) | |||
|
512 | while pos < length: | |||
|
513 | symbol = ''.join(itertools.takewhile(lambda ch: ch not in special, | |||
|
514 | view[pos:])) | |||
|
515 | if symbol: | |||
|
516 | yield ('symbol', symbol, pos) | |||
|
517 | pos += len(symbol) | |||
|
518 | else: # special char | |||
|
519 | yield (text[pos], None, pos) | |||
|
520 | pos += 1 | |||
|
521 | yield ('end', None, pos) | |||
|
522 | ||||
|
523 | def _parse(text): | |||
|
524 | tree, pos = parser.parser(_elements).parse(_tokenize(text)) | |||
|
525 | if pos != len(text): | |||
|
526 | raise error.ParseError('invalid token', pos) | |||
|
527 | return tree | |||
|
528 | ||||
|
529 | def _parsedrev(symbol): | |||
|
530 | """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None""" | |||
|
531 | if symbol.startswith('D') and symbol[1:].isdigit(): | |||
|
532 | return int(symbol[1:]) | |||
|
533 | if symbol.isdigit(): | |||
|
534 | return int(symbol) | |||
|
535 | ||||
|
536 | def _prefetchdrevs(tree): | |||
|
537 | """return ({single-drev-id}, {ancestor-drev-id}) to prefetch""" | |||
|
538 | drevs = set() | |||
|
539 | ancestordrevs = set() | |||
|
540 | op = tree[0] | |||
|
541 | if op == 'symbol': | |||
|
542 | r = _parsedrev(tree[1]) | |||
|
543 | if r: | |||
|
544 | drevs.add(r) | |||
|
545 | elif op == 'ancestors': | |||
|
546 | r, a = _prefetchdrevs(tree[1]) | |||
|
547 | drevs.update(r) | |||
|
548 | ancestordrevs.update(r) | |||
|
549 | ancestordrevs.update(a) | |||
|
550 | else: | |||
|
551 | for t in tree[1:]: | |||
|
552 | r, a = _prefetchdrevs(t) | |||
|
553 | drevs.update(r) | |||
|
554 | ancestordrevs.update(a) | |||
|
555 | return drevs, ancestordrevs | |||
|
556 | ||||
|
557 | def querydrev(repo, spec): | |||
488 | """return a list of "Differential Revision" dicts |
|
558 | """return a list of "Differential Revision" dicts | |
489 |
|
559 | |||
490 | params is the input of "differential.query" API, and is expected to match |
|
560 | spec is a string using a simple query language, see docstring in phabread | |
491 | just a single Differential Revision. |
|
561 | for details. | |
492 |
|
562 | |||
493 | A "Differential Revision dict" looks like: |
|
563 | A "Differential Revision dict" looks like: | |
494 |
|
564 | |||
@@ -525,26 +595,13 b' def querydrev(repo, params, stack=False)' | |||||
525 | "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv", |
|
595 | "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv", | |
526 | "sourcePath": null |
|
596 | "sourcePath": null | |
527 | } |
|
597 | } | |
528 |
|
||||
529 | If stack is True, return a list of "Differential Revision dict"s in an |
|
|||
530 | order that the latter ones depend on the former ones. Otherwise, return a |
|
|||
531 | list of a unique "Differential Revision dict". |
|
|||
532 | """ |
|
598 | """ | |
533 | prefetched = {} # {id or phid: drev} |
|
|||
534 | def fetch(params): |
|
599 | def fetch(params): | |
535 | """params -> single drev or None""" |
|
600 | """params -> single drev or None""" | |
536 | key = (params.get(r'ids') or params.get(r'phids') or [None])[0] |
|
601 | key = (params.get(r'ids') or params.get(r'phids') or [None])[0] | |
537 | if key in prefetched: |
|
602 | if key in prefetched: | |
538 | return prefetched[key] |
|
603 | return prefetched[key] | |
539 | # Otherwise, send the request. If we're fetching a stack, be smarter |
|
604 | drevs = callconduit(repo, 'differential.query', params) | |
540 | # and fetch more ids in one batch, even if it could be unnecessary. |
|
|||
541 | batchparams = params |
|
|||
542 | if stack and len(params.get(r'ids', [])) == 1: |
|
|||
543 | i = int(params[r'ids'][0]) |
|
|||
544 | # developer config: phabricator.batchsize |
|
|||
545 | batchsize = repo.ui.configint('phabricator', 'batchsize', 12) |
|
|||
546 | batchparams = {'ids': range(max(1, i - batchsize), i + 1)} |
|
|||
547 | drevs = callconduit(repo, 'differential.query', batchparams) |
|
|||
548 | # Fill prefetched with the result |
|
605 | # Fill prefetched with the result | |
549 | for drev in drevs: |
|
606 | for drev in drevs: | |
550 | prefetched[drev[r'phid']] = drev |
|
607 | prefetched[drev[r'phid']] = drev | |
@@ -553,23 +610,62 b' def querydrev(repo, params, stack=False)' | |||||
553 | raise error.Abort(_('cannot get Differential Revision %r') % params) |
|
610 | raise error.Abort(_('cannot get Differential Revision %r') % params) | |
554 | return prefetched[key] |
|
611 | return prefetched[key] | |
555 |
|
612 | |||
556 | visited = set() |
|
613 | def getstack(topdrevids): | |
557 | result = [] |
|
614 | """given a top, get a stack from the bottom, [id] -> [id]""" | |
558 | queue = [params] |
|
615 | visited = set() | |
559 | while queue: |
|
616 | result = [] | |
560 | params = queue.pop() |
|
617 | queue = [{r'ids': [i]} for i in topdrevids] | |
561 | drev = fetch(params) |
|
618 | while queue: | |
562 | if drev[r'id'] in visited: |
|
619 | params = queue.pop() | |
563 | continue |
|
620 | drev = fetch(params) | |
564 | visited.add(drev[r'id']) |
|
621 | if drev[r'id'] in visited: | |
565 | result.append(drev) |
|
622 | continue | |
566 | if stack: |
|
623 | visited.add(drev[r'id']) | |
|
624 | result.append(int(drev[r'id'])) | |||
567 | auxiliary = drev.get(r'auxiliary', {}) |
|
625 | auxiliary = drev.get(r'auxiliary', {}) | |
568 | depends = auxiliary.get(r'phabricator:depends-on', []) |
|
626 | depends = auxiliary.get(r'phabricator:depends-on', []) | |
569 | for phid in depends: |
|
627 | for phid in depends: | |
570 | queue.append({'phids': [phid]}) |
|
628 | queue.append({'phids': [phid]}) | |
571 | result.reverse() |
|
629 | result.reverse() | |
572 | return result |
|
630 | return smartset.baseset(result) | |
|
631 | ||||
|
632 | # Initialize prefetch cache | |||
|
633 | prefetched = {} # {id or phid: drev} | |||
|
634 | ||||
|
635 | tree = _parse(spec) | |||
|
636 | drevs, ancestordrevs = _prefetchdrevs(tree) | |||
|
637 | ||||
|
638 | # developer config: phabricator.batchsize | |||
|
639 | batchsize = repo.ui.configint('phabricator', 'batchsize', 12) | |||
|
640 | ||||
|
641 | # Prefetch Differential Revisions in batch | |||
|
642 | tofetch = set(drevs) | |||
|
643 | for r in ancestordrevs: | |||
|
644 | tofetch.update(range(max(1, r - batchsize), r + 1)) | |||
|
645 | if drevs: | |||
|
646 | fetch({r'ids': list(tofetch)}) | |||
|
647 | getstack(list(ancestordrevs)) | |||
|
648 | ||||
|
649 | # Walk through the tree, return smartsets | |||
|
650 | def walk(tree): | |||
|
651 | op = tree[0] | |||
|
652 | if op == 'symbol': | |||
|
653 | drev = _parsedrev(tree[1]) | |||
|
654 | if drev: | |||
|
655 | return smartset.baseset([drev]) | |||
|
656 | else: | |||
|
657 | raise error.Abort(_('unknown symbol: %s') % tree[1]) | |||
|
658 | elif op in {'and_', 'add', 'sub'}: | |||
|
659 | assert len(tree) == 3 | |||
|
660 | return getattr(operator, op)(walk(tree[1]), walk(tree[2])) | |||
|
661 | elif op == 'group': | |||
|
662 | return walk(tree[1]) | |||
|
663 | elif op == 'ancestors': | |||
|
664 | return getstack(walk(tree[1])) | |||
|
665 | else: | |||
|
666 | raise error.ProgrammingError('illegal tree: %r' % tree) | |||
|
667 | ||||
|
668 | return [prefetched[r] for r in walk(tree)] | |||
573 |
|
669 | |||
574 | def getdescfromdrev(drev): |
|
670 | def getdescfromdrev(drev): | |
575 | """get description (commit message) from "Differential Revision" |
|
671 | """get description (commit message) from "Differential Revision" | |
@@ -667,18 +763,22 b' def readpatch(repo, drevs, write):' | |||||
667 |
|
763 | |||
668 | @command('phabread', |
|
764 | @command('phabread', | |
669 | [('', 'stack', False, _('read dependencies'))], |
|
765 | [('', 'stack', False, _('read dependencies'))], | |
670 |
_(' |
|
766 | _('DREVSPEC [OPTIONS]')) | |
671 |
def phabread(ui, repo, |
|
767 | def phabread(ui, repo, spec, **opts): | |
672 | """print patches from Phabricator suitable for importing |
|
768 | """print patches from Phabricator suitable for importing | |
673 |
|
769 | |||
674 |
|
|
770 | DREVSPEC could be a Differential Revision identity, like ``D123``, or just | |
675 | number ``123``, or a full URL like ``https://phab.example.com/D123``. |
|
771 | the number ``123``. It could also have common operators like ``+``, ``-``, | |
|
772 | ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to | |||
|
773 | select a stack. | |||
|
774 | ||||
|
775 | For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude | |||
|
776 | D2 and D4. | |||
676 |
|
777 | |||
677 | If --stack is given, follow dependencies information and read all patches. |
|
778 | If --stack is given, follow dependencies information and read all patches. | |
|
779 | It is equivalent to the ``:`` operator. | |||
678 | """ |
|
780 | """ | |
679 | try: |
|
781 | if opts.get('stack'): | |
680 | revid = int(revid.split('/')[-1].replace('D', '')) |
|
782 | spec = ':(%s)' % spec | |
681 | except ValueError: |
|
783 | drevs = querydrev(repo, spec) | |
682 | raise error.Abort(_('invalid Revision ID: %s') % revid) |
|
|||
683 | drevs = querydrev(repo, {'ids': [revid]}, opts.get('stack')) |
|
|||
684 | readpatch(repo, drevs, ui.write) |
|
784 | readpatch(repo, drevs, ui.write) |
General Comments 0
You need to be logged in to leave comments.
Login now