comparison mercurial/revset.py @ 27584:fc7c8cac6a4b

revset: use decorator to register a function as revset predicate Using decorator can localize changes for adding (or removing) a revset predicate function in source code. It is also useful to pick predicates up for specific purpose. For example, subsequent patch marks predicates as "safe" by decorator. This patch defines 'parsefuncdecl()' in 'funcregistrar' class, because this implementation can be uesd by other decorator class for fileset predicate and template function.
author FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
date Tue, 29 Dec 2015 23:58:30 +0900
parents c60a9c16ae25
children 42910f9fffeb
comparison
equal deleted inserted replaced
27583:37d50250b696 27584:fc7c8cac6a4b
20 node, 20 node,
21 obsolete as obsmod, 21 obsolete as obsmod,
22 parser, 22 parser,
23 pathutil, 23 pathutil,
24 phases, 24 phases,
25 registrar,
25 repoview, 26 repoview,
26 util, 27 util,
27 ) 28 )
28 29
29 def _revancestors(repo, revs, followfirst): 30 def _revancestors(repo, revs, followfirst):
463 syms = [s for (s, fn) in symbols.items() if keep(fn)] 464 syms = [s for (s, fn) in symbols.items() if keep(fn)]
464 raise error.UnknownIdentifier(a[1], syms) 465 raise error.UnknownIdentifier(a[1], syms)
465 466
466 # functions 467 # functions
467 468
469 # symbols are callables like:
470 # fn(repo, subset, x)
471 # with:
472 # repo - current repository instance
473 # subset - of revisions to be examined
474 # x - argument in tree form
475 symbols = {}
476
477 class predicate(registrar.funcregistrar):
478 """Decorator to register revset predicate
479
480 Usage::
481
482 @predicate('mypredicate(arg1, arg2[, arg3])')
483 def mypredicatefunc(repo, subset, x):
484 '''Explanation of this revset predicate ....
485 '''
486 pass
487
488 The first string argument of the constructor is used also in
489 online help.
490 """
491 table = symbols
492 formatdoc = "``%s``\n %s"
493 getname = registrar.funcregistrar.parsefuncdecl
494
495 @predicate('_destupdate')
468 def _destupdate(repo, subset, x): 496 def _destupdate(repo, subset, x):
469 # experimental revset for update destination 497 # experimental revset for update destination
470 args = getargsdict(x, 'limit', 'clean check') 498 args = getargsdict(x, 'limit', 'clean check')
471 return subset & baseset([destutil.destupdate(repo, **args)[0]]) 499 return subset & baseset([destutil.destupdate(repo, **args)[0]])
472 500
501 @predicate('_destmerge')
473 def _destmerge(repo, subset, x): 502 def _destmerge(repo, subset, x):
474 # experimental revset for merge destination 503 # experimental revset for merge destination
475 getargs(x, 0, 0, _("_mergedefaultdest takes no arguments")) 504 getargs(x, 0, 0, _("_mergedefaultdest takes no arguments"))
476 return subset & baseset([destutil.destmerge(repo)]) 505 return subset & baseset([destutil.destmerge(repo)])
477 506
507 @predicate('adds(pattern)')
478 def adds(repo, subset, x): 508 def adds(repo, subset, x):
479 """``adds(pattern)`` 509 """Changesets that add a file matching pattern.
480 Changesets that add a file matching pattern.
481 510
482 The pattern without explicit kind like ``glob:`` is expected to be 511 The pattern without explicit kind like ``glob:`` is expected to be
483 relative to the current directory and match against a file or a 512 relative to the current directory and match against a file or a
484 directory. 513 directory.
485 """ 514 """
486 # i18n: "adds" is a keyword 515 # i18n: "adds" is a keyword
487 pat = getstring(x, _("adds requires a pattern")) 516 pat = getstring(x, _("adds requires a pattern"))
488 return checkstatus(repo, subset, pat, 1) 517 return checkstatus(repo, subset, pat, 1)
489 518
519 @predicate('ancestor(*changeset)')
490 def ancestor(repo, subset, x): 520 def ancestor(repo, subset, x):
491 """``ancestor(*changeset)`` 521 """A greatest common ancestor of the changesets.
492 A greatest common ancestor of the changesets.
493 522
494 Accepts 0 or more changesets. 523 Accepts 0 or more changesets.
495 Will return empty list when passed no args. 524 Will return empty list when passed no args.
496 Greatest common ancestor of a single changeset is that changeset. 525 Greatest common ancestor of a single changeset is that changeset.
497 """ 526 """
517 if not heads: 546 if not heads:
518 return baseset() 547 return baseset()
519 s = _revancestors(repo, heads, followfirst) 548 s = _revancestors(repo, heads, followfirst)
520 return subset & s 549 return subset & s
521 550
551 @predicate('ancestors(set)')
522 def ancestors(repo, subset, x): 552 def ancestors(repo, subset, x):
523 """``ancestors(set)`` 553 """Changesets that are ancestors of a changeset in set.
524 Changesets that are ancestors of a changeset in set.
525 """ 554 """
526 return _ancestors(repo, subset, x) 555 return _ancestors(repo, subset, x)
527 556
557 @predicate('_firstancestors')
528 def _firstancestors(repo, subset, x): 558 def _firstancestors(repo, subset, x):
529 # ``_firstancestors(set)`` 559 # ``_firstancestors(set)``
530 # Like ``ancestors(set)`` but follows only the first parents. 560 # Like ``ancestors(set)`` but follows only the first parents.
531 return _ancestors(repo, subset, x, followfirst=True) 561 return _ancestors(repo, subset, x, followfirst=True)
532 562
545 for i in range(n): 575 for i in range(n):
546 r = cl.parentrevs(r)[0] 576 r = cl.parentrevs(r)[0]
547 ps.add(r) 577 ps.add(r)
548 return subset & ps 578 return subset & ps
549 579
580 @predicate('author(string)')
550 def author(repo, subset, x): 581 def author(repo, subset, x):
551 """``author(string)`` 582 """Alias for ``user(string)``.
552 Alias for ``user(string)``.
553 """ 583 """
554 # i18n: "author" is a keyword 584 # i18n: "author" is a keyword
555 n = encoding.lower(getstring(x, _("author requires a string"))) 585 n = encoding.lower(getstring(x, _("author requires a string")))
556 kind, pattern, matcher = _substringmatcher(n) 586 kind, pattern, matcher = _substringmatcher(n)
557 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user()))) 587 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
558 588
589 @predicate('bisect(string)')
559 def bisect(repo, subset, x): 590 def bisect(repo, subset, x):
560 """``bisect(string)`` 591 """Changesets marked in the specified bisect status:
561 Changesets marked in the specified bisect status:
562 592
563 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip 593 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
564 - ``goods``, ``bads`` : csets topologically good/bad 594 - ``goods``, ``bads`` : csets topologically good/bad
565 - ``range`` : csets taking part in the bisection 595 - ``range`` : csets taking part in the bisection
566 - ``pruned`` : csets that are goods, bads or skipped 596 - ``pruned`` : csets that are goods, bads or skipped
573 state = set(hbisect.get(repo, status)) 603 state = set(hbisect.get(repo, status))
574 return subset & state 604 return subset & state
575 605
576 # Backward-compatibility 606 # Backward-compatibility
577 # - no help entry so that we do not advertise it any more 607 # - no help entry so that we do not advertise it any more
608 @predicate('bisected')
578 def bisected(repo, subset, x): 609 def bisected(repo, subset, x):
579 return bisect(repo, subset, x) 610 return bisect(repo, subset, x)
580 611
612 @predicate('bookmark([name])')
581 def bookmark(repo, subset, x): 613 def bookmark(repo, subset, x):
582 """``bookmark([name])`` 614 """The named bookmark or all bookmarks.
583 The named bookmark or all bookmarks.
584 615
585 If `name` starts with `re:`, the remainder of the name is treated as 616 If `name` starts with `re:`, the remainder of the name is treated as
586 a regular expression. To match a bookmark that actually starts with `re:`, 617 a regular expression. To match a bookmark that actually starts with `re:`,
587 use the prefix `literal:`. 618 use the prefix `literal:`.
588 """ 619 """
614 bms = set([repo[r].rev() 645 bms = set([repo[r].rev()
615 for r in repo._bookmarks.values()]) 646 for r in repo._bookmarks.values()])
616 bms -= set([node.nullrev]) 647 bms -= set([node.nullrev])
617 return subset & bms 648 return subset & bms
618 649
650 @predicate('branch(string or set)')
619 def branch(repo, subset, x): 651 def branch(repo, subset, x):
620 """``branch(string or set)`` 652 """
621 All changesets belonging to the given branch or the branches of the given 653 All changesets belonging to the given branch or the branches of the given
622 changesets. 654 changesets.
623 655
624 If `string` starts with `re:`, the remainder of the name is treated as 656 If `string` starts with `re:`, the remainder of the name is treated as
625 a regular expression. To match a branch that actually starts with `re:`, 657 a regular expression. To match a branch that actually starts with `re:`,
650 for r in s: 682 for r in s:
651 b.add(getbi(r)[0]) 683 b.add(getbi(r)[0])
652 c = s.__contains__ 684 c = s.__contains__
653 return subset.filter(lambda r: c(r) or getbi(r)[0] in b) 685 return subset.filter(lambda r: c(r) or getbi(r)[0] in b)
654 686
687 @predicate('bumped()')
655 def bumped(repo, subset, x): 688 def bumped(repo, subset, x):
656 """``bumped()`` 689 """Mutable changesets marked as successors of public changesets.
657 Mutable changesets marked as successors of public changesets.
658 690
659 Only non-public and non-obsolete changesets can be `bumped`. 691 Only non-public and non-obsolete changesets can be `bumped`.
660 """ 692 """
661 # i18n: "bumped" is a keyword 693 # i18n: "bumped" is a keyword
662 getargs(x, 0, 0, _("bumped takes no arguments")) 694 getargs(x, 0, 0, _("bumped takes no arguments"))
663 bumped = obsmod.getrevs(repo, 'bumped') 695 bumped = obsmod.getrevs(repo, 'bumped')
664 return subset & bumped 696 return subset & bumped
665 697
698 @predicate('bundle()')
666 def bundle(repo, subset, x): 699 def bundle(repo, subset, x):
667 """``bundle()`` 700 """Changesets in the bundle.
668 Changesets in the bundle.
669 701
670 Bundle must be specified by the -R option.""" 702 Bundle must be specified by the -R option."""
671 703
672 try: 704 try:
673 bundlerevs = repo.changelog.bundlerevs 705 bundlerevs = repo.changelog.bundlerevs
721 cs.add(r) 753 cs.add(r)
722 # XXX using a set to feed the baseset is wrong. Sets are not ordered. 754 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
723 # This does not break because of other fullreposet misbehavior. 755 # This does not break because of other fullreposet misbehavior.
724 return baseset(cs) 756 return baseset(cs)
725 757
758 @predicate('children(set)')
726 def children(repo, subset, x): 759 def children(repo, subset, x):
727 """``children(set)`` 760 """Child changesets of changesets in set.
728 Child changesets of changesets in set.
729 """ 761 """
730 s = getset(repo, fullreposet(repo), x) 762 s = getset(repo, fullreposet(repo), x)
731 cs = _children(repo, subset, s) 763 cs = _children(repo, subset, s)
732 return subset & cs 764 return subset & cs
733 765
766 @predicate('closed()')
734 def closed(repo, subset, x): 767 def closed(repo, subset, x):
735 """``closed()`` 768 """Changeset is closed.
736 Changeset is closed.
737 """ 769 """
738 # i18n: "closed" is a keyword 770 # i18n: "closed" is a keyword
739 getargs(x, 0, 0, _("closed takes no arguments")) 771 getargs(x, 0, 0, _("closed takes no arguments"))
740 return subset.filter(lambda r: repo[r].closesbranch()) 772 return subset.filter(lambda r: repo[r].closesbranch())
741 773
774 @predicate('contains(pattern)')
742 def contains(repo, subset, x): 775 def contains(repo, subset, x):
743 """``contains(pattern)`` 776 """The revision's manifest contains a file matching pattern (but might not
744 The revision's manifest contains a file matching pattern (but might not
745 modify it). See :hg:`help patterns` for information about file patterns. 777 modify it). See :hg:`help patterns` for information about file patterns.
746 778
747 The pattern without explicit kind like ``glob:`` is expected to be 779 The pattern without explicit kind like ``glob:`` is expected to be
748 relative to the current directory and match against a file exactly 780 relative to the current directory and match against a file exactly
749 for efficiency. 781 for efficiency.
764 return True 796 return True
765 return False 797 return False
766 798
767 return subset.filter(matches) 799 return subset.filter(matches)
768 800
801 @predicate('converted([id])')
769 def converted(repo, subset, x): 802 def converted(repo, subset, x):
770 """``converted([id])`` 803 """Changesets converted from the given identifier in the old repository if
771 Changesets converted from the given identifier in the old repository if
772 present, or all converted changesets if no identifier is specified. 804 present, or all converted changesets if no identifier is specified.
773 """ 805 """
774 806
775 # There is exactly no chance of resolving the revision, so do a simple 807 # There is exactly no chance of resolving the revision, so do a simple
776 # string compare and hope for the best 808 # string compare and hope for the best
786 source = repo[r].extra().get('convert_revision', None) 818 source = repo[r].extra().get('convert_revision', None)
787 return source is not None and (rev is None or source.startswith(rev)) 819 return source is not None and (rev is None or source.startswith(rev))
788 820
789 return subset.filter(lambda r: _matchvalue(r)) 821 return subset.filter(lambda r: _matchvalue(r))
790 822
823 @predicate('date(interval)')
791 def date(repo, subset, x): 824 def date(repo, subset, x):
792 """``date(interval)`` 825 """Changesets within the interval, see :hg:`help dates`.
793 Changesets within the interval, see :hg:`help dates`.
794 """ 826 """
795 # i18n: "date" is a keyword 827 # i18n: "date" is a keyword
796 ds = getstring(x, _("date requires a string")) 828 ds = getstring(x, _("date requires a string"))
797 dm = util.matchdate(ds) 829 dm = util.matchdate(ds)
798 return subset.filter(lambda x: dm(repo[x].date()[0])) 830 return subset.filter(lambda x: dm(repo[x].date()[0]))
799 831
832 @predicate('desc(string)')
800 def desc(repo, subset, x): 833 def desc(repo, subset, x):
801 """``desc(string)`` 834 """Search commit message for string. The match is case-insensitive.
802 Search commit message for string. The match is case-insensitive.
803 """ 835 """
804 # i18n: "desc" is a keyword 836 # i18n: "desc" is a keyword
805 ds = encoding.lower(getstring(x, _("desc requires a string"))) 837 ds = encoding.lower(getstring(x, _("desc requires a string")))
806 838
807 def matches(x): 839 def matches(x):
827 result.sort(reverse=True) 859 result.sort(reverse=True)
828 else: 860 else:
829 result = subset & result 861 result = subset & result
830 return result 862 return result
831 863
864 @predicate('descendants(set)')
832 def descendants(repo, subset, x): 865 def descendants(repo, subset, x):
833 """``descendants(set)`` 866 """Changesets which are descendants of changesets in set.
834 Changesets which are descendants of changesets in set.
835 """ 867 """
836 return _descendants(repo, subset, x) 868 return _descendants(repo, subset, x)
837 869
870 @predicate('_firstdescendants')
838 def _firstdescendants(repo, subset, x): 871 def _firstdescendants(repo, subset, x):
839 # ``_firstdescendants(set)`` 872 # ``_firstdescendants(set)``
840 # Like ``descendants(set)`` but follows only the first parents. 873 # Like ``descendants(set)`` but follows only the first parents.
841 return _descendants(repo, subset, x, followfirst=True) 874 return _descendants(repo, subset, x, followfirst=True)
842 875
876 @predicate('destination([set])')
843 def destination(repo, subset, x): 877 def destination(repo, subset, x):
844 """``destination([set])`` 878 """Changesets that were created by a graft, transplant or rebase operation,
845 Changesets that were created by a graft, transplant or rebase operation,
846 with the given revisions specified as the source. Omitting the optional set 879 with the given revisions specified as the source. Omitting the optional set
847 is the same as passing all(). 880 is the same as passing all().
848 """ 881 """
849 if x is not None: 882 if x is not None:
850 sources = getset(repo, fullreposet(repo), x) 883 sources = getset(repo, fullreposet(repo), x)
882 r = src 915 r = src
883 src = _getrevsource(repo, r) 916 src = _getrevsource(repo, r)
884 917
885 return subset.filter(dests.__contains__) 918 return subset.filter(dests.__contains__)
886 919
920 @predicate('divergent()')
887 def divergent(repo, subset, x): 921 def divergent(repo, subset, x):
888 """``divergent()`` 922 """
889 Final successors of changesets with an alternative set of final successors. 923 Final successors of changesets with an alternative set of final successors.
890 """ 924 """
891 # i18n: "divergent" is a keyword 925 # i18n: "divergent" is a keyword
892 getargs(x, 0, 0, _("divergent takes no arguments")) 926 getargs(x, 0, 0, _("divergent takes no arguments"))
893 divergent = obsmod.getrevs(repo, 'divergent') 927 divergent = obsmod.getrevs(repo, 'divergent')
894 return subset & divergent 928 return subset & divergent
895 929
930 @predicate('extinct()')
896 def extinct(repo, subset, x): 931 def extinct(repo, subset, x):
897 """``extinct()`` 932 """Obsolete changesets with obsolete descendants only.
898 Obsolete changesets with obsolete descendants only.
899 """ 933 """
900 # i18n: "extinct" is a keyword 934 # i18n: "extinct" is a keyword
901 getargs(x, 0, 0, _("extinct takes no arguments")) 935 getargs(x, 0, 0, _("extinct takes no arguments"))
902 extincts = obsmod.getrevs(repo, 'extinct') 936 extincts = obsmod.getrevs(repo, 'extinct')
903 return subset & extincts 937 return subset & extincts
904 938
939 @predicate('extra(label, [value])')
905 def extra(repo, subset, x): 940 def extra(repo, subset, x):
906 """``extra(label, [value])`` 941 """Changesets with the given label in the extra metadata, with the given
907 Changesets with the given label in the extra metadata, with the given
908 optional value. 942 optional value.
909 943
910 If `value` starts with `re:`, the remainder of the value is treated as 944 If `value` starts with `re:`, the remainder of the value is treated as
911 a regular expression. To match a value that actually starts with `re:`, 945 a regular expression. To match a value that actually starts with `re:`,
912 use the prefix `literal:`. 946 use the prefix `literal:`.
930 extra = repo[r].extra() 964 extra = repo[r].extra()
931 return label in extra and (value is None or matcher(extra[label])) 965 return label in extra and (value is None or matcher(extra[label]))
932 966
933 return subset.filter(lambda r: _matchvalue(r)) 967 return subset.filter(lambda r: _matchvalue(r))
934 968
969 @predicate('filelog(pattern)')
935 def filelog(repo, subset, x): 970 def filelog(repo, subset, x):
936 """``filelog(pattern)`` 971 """Changesets connected to the specified filelog.
937 Changesets connected to the specified filelog.
938 972
939 For performance reasons, visits only revisions mentioned in the file-level 973 For performance reasons, visits only revisions mentioned in the file-level
940 filelog, rather than filtering through all changesets (much faster, but 974 filelog, rather than filtering through all changesets (much faster, but
941 doesn't include deletes or duplicate changes). For a slower, more accurate 975 doesn't include deletes or duplicate changes). For a slower, more accurate
942 result, use ``file()``. 976 result, use ``file()``.
1045 backrevref[fr] = rev 1079 backrevref[fr] = rev
1046 s.add(rev) 1080 s.add(rev)
1047 1081
1048 return subset & s 1082 return subset & s
1049 1083
1084 @predicate('first(set, [n])')
1050 def first(repo, subset, x): 1085 def first(repo, subset, x):
1051 """``first(set, [n])`` 1086 """An alias for limit().
1052 An alias for limit().
1053 """ 1087 """
1054 return limit(repo, subset, x) 1088 return limit(repo, subset, x)
1055 1089
1056 def _follow(repo, subset, x, name, followfirst=False): 1090 def _follow(repo, subset, x, name, followfirst=False):
1057 l = getargs(x, 0, 1, _("%s takes no arguments or a pattern") % name) 1091 l = getargs(x, 0, 1, _("%s takes no arguments or a pattern") % name)
1071 else: 1105 else:
1072 s = _revancestors(repo, baseset([c.rev()]), followfirst) 1106 s = _revancestors(repo, baseset([c.rev()]), followfirst)
1073 1107
1074 return subset & s 1108 return subset & s
1075 1109
1110 @predicate('follow([pattern])')
1076 def follow(repo, subset, x): 1111 def follow(repo, subset, x):
1077 """``follow([pattern])`` 1112 """
1078 An alias for ``::.`` (ancestors of the working directory's first parent). 1113 An alias for ``::.`` (ancestors of the working directory's first parent).
1079 If pattern is specified, the histories of files matching given 1114 If pattern is specified, the histories of files matching given
1080 pattern is followed, including copies. 1115 pattern is followed, including copies.
1081 """ 1116 """
1082 return _follow(repo, subset, x, 'follow') 1117 return _follow(repo, subset, x, 'follow')
1083 1118
1119 @predicate('_followfirst')
1084 def _followfirst(repo, subset, x): 1120 def _followfirst(repo, subset, x):
1085 # ``followfirst([pattern])`` 1121 # ``followfirst([pattern])``
1086 # Like ``follow([pattern])`` but follows only the first parent of 1122 # Like ``follow([pattern])`` but follows only the first parent of
1087 # every revisions or files revisions. 1123 # every revisions or files revisions.
1088 return _follow(repo, subset, x, '_followfirst', followfirst=True) 1124 return _follow(repo, subset, x, '_followfirst', followfirst=True)
1089 1125
1126 @predicate('all()')
1090 def getall(repo, subset, x): 1127 def getall(repo, subset, x):
1091 """``all()`` 1128 """All changesets, the same as ``0:tip``.
1092 All changesets, the same as ``0:tip``.
1093 """ 1129 """
1094 # i18n: "all" is a keyword 1130 # i18n: "all" is a keyword
1095 getargs(x, 0, 0, _("all takes no arguments")) 1131 getargs(x, 0, 0, _("all takes no arguments"))
1096 return subset & spanset(repo) # drop "null" if any 1132 return subset & spanset(repo) # drop "null" if any
1097 1133
1134 @predicate('grep(regex)')
1098 def grep(repo, subset, x): 1135 def grep(repo, subset, x):
1099 """``grep(regex)`` 1136 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1100 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1101 to ensure special escape characters are handled correctly. Unlike 1137 to ensure special escape characters are handled correctly. Unlike
1102 ``keyword(string)``, the match is case-sensitive. 1138 ``keyword(string)``, the match is case-sensitive.
1103 """ 1139 """
1104 try: 1140 try:
1105 # i18n: "grep" is a keyword 1141 # i18n: "grep" is a keyword
1114 return True 1150 return True
1115 return False 1151 return False
1116 1152
1117 return subset.filter(matches) 1153 return subset.filter(matches)
1118 1154
1155 @predicate('_matchfiles')
1119 def _matchfiles(repo, subset, x): 1156 def _matchfiles(repo, subset, x):
1120 # _matchfiles takes a revset list of prefixed arguments: 1157 # _matchfiles takes a revset list of prefixed arguments:
1121 # 1158 #
1122 # [p:foo, i:bar, x:baz] 1159 # [p:foo, i:bar, x:baz]
1123 # 1160 #
1179 return True 1216 return True
1180 return False 1217 return False
1181 1218
1182 return subset.filter(matches) 1219 return subset.filter(matches)
1183 1220
1221 @predicate('file(pattern)')
1184 def hasfile(repo, subset, x): 1222 def hasfile(repo, subset, x):
1185 """``file(pattern)`` 1223 """Changesets affecting files matched by pattern.
1186 Changesets affecting files matched by pattern.
1187 1224
1188 For a faster but less accurate result, consider using ``filelog()`` 1225 For a faster but less accurate result, consider using ``filelog()``
1189 instead. 1226 instead.
1190 1227
1191 This predicate uses ``glob:`` as the default kind of pattern. 1228 This predicate uses ``glob:`` as the default kind of pattern.
1192 """ 1229 """
1193 # i18n: "file" is a keyword 1230 # i18n: "file" is a keyword
1194 pat = getstring(x, _("file requires a pattern")) 1231 pat = getstring(x, _("file requires a pattern"))
1195 return _matchfiles(repo, subset, ('string', 'p:' + pat)) 1232 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1196 1233
1234 @predicate('head()')
1197 def head(repo, subset, x): 1235 def head(repo, subset, x):
1198 """``head()`` 1236 """Changeset is a named branch head.
1199 Changeset is a named branch head.
1200 """ 1237 """
1201 # i18n: "head" is a keyword 1238 # i18n: "head" is a keyword
1202 getargs(x, 0, 0, _("head takes no arguments")) 1239 getargs(x, 0, 0, _("head takes no arguments"))
1203 hs = set() 1240 hs = set()
1204 cl = repo.changelog 1241 cl = repo.changelog
1208 # This does not break because of other fullreposet misbehavior. 1245 # This does not break because of other fullreposet misbehavior.
1209 # XXX We should combine with subset first: 'subset & baseset(...)'. This is 1246 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
1210 # necessary to ensure we preserve the order in subset. 1247 # necessary to ensure we preserve the order in subset.
1211 return baseset(hs) & subset 1248 return baseset(hs) & subset
1212 1249
1250 @predicate('heads(set)')
1213 def heads(repo, subset, x): 1251 def heads(repo, subset, x):
1214 """``heads(set)`` 1252 """Members of set with no children in set.
1215 Members of set with no children in set.
1216 """ 1253 """
1217 s = getset(repo, subset, x) 1254 s = getset(repo, subset, x)
1218 ps = parents(repo, subset, x) 1255 ps = parents(repo, subset, x)
1219 return s - ps 1256 return s - ps
1220 1257
1258 @predicate('hidden()')
1221 def hidden(repo, subset, x): 1259 def hidden(repo, subset, x):
1222 """``hidden()`` 1260 """Hidden changesets.
1223 Hidden changesets.
1224 """ 1261 """
1225 # i18n: "hidden" is a keyword 1262 # i18n: "hidden" is a keyword
1226 getargs(x, 0, 0, _("hidden takes no arguments")) 1263 getargs(x, 0, 0, _("hidden takes no arguments"))
1227 hiddenrevs = repoview.filterrevs(repo, 'visible') 1264 hiddenrevs = repoview.filterrevs(repo, 'visible')
1228 return subset & hiddenrevs 1265 return subset & hiddenrevs
1229 1266
1267 @predicate('keyword(string)')
1230 def keyword(repo, subset, x): 1268 def keyword(repo, subset, x):
1231 """``keyword(string)`` 1269 """Search commit message, user name, and names of changed files for
1232 Search commit message, user name, and names of changed files for
1233 string. The match is case-insensitive. 1270 string. The match is case-insensitive.
1234 """ 1271 """
1235 # i18n: "keyword" is a keyword 1272 # i18n: "keyword" is a keyword
1236 kw = encoding.lower(getstring(x, _("keyword requires a string"))) 1273 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1237 1274
1240 return any(kw in encoding.lower(t) 1277 return any(kw in encoding.lower(t)
1241 for t in c.files() + [c.user(), c.description()]) 1278 for t in c.files() + [c.user(), c.description()])
1242 1279
1243 return subset.filter(matches) 1280 return subset.filter(matches)
1244 1281
1282 @predicate('limit(set[, n[, offset]])')
1245 def limit(repo, subset, x): 1283 def limit(repo, subset, x):
1246 """``limit(set[, n[, offset]])`` 1284 """First n members of set, defaulting to 1, starting from offset.
1247 First n members of set, defaulting to 1, starting from offset.
1248 """ 1285 """
1249 args = getargsdict(x, 'limit', 'set n offset') 1286 args = getargsdict(x, 'limit', 'set n offset')
1250 if 'set' not in args: 1287 if 'set' not in args:
1251 # i18n: "limit" is a keyword 1288 # i18n: "limit" is a keyword
1252 raise error.ParseError(_("limit requires one to three arguments")) 1289 raise error.ParseError(_("limit requires one to three arguments"))
1276 break 1313 break
1277 elif y in subset: 1314 elif y in subset:
1278 result.append(y) 1315 result.append(y)
1279 return baseset(result) 1316 return baseset(result)
1280 1317
1318 @predicate('last(set, [n])')
1281 def last(repo, subset, x): 1319 def last(repo, subset, x):
1282 """``last(set, [n])`` 1320 """Last n members of set, defaulting to 1.
1283 Last n members of set, defaulting to 1.
1284 """ 1321 """
1285 # i18n: "last" is a keyword 1322 # i18n: "last" is a keyword
1286 l = getargs(x, 1, 2, _("last requires one or two arguments")) 1323 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1287 try: 1324 try:
1288 lim = 1 1325 lim = 1
1302 break 1339 break
1303 elif y in subset: 1340 elif y in subset:
1304 result.append(y) 1341 result.append(y)
1305 return baseset(result) 1342 return baseset(result)
1306 1343
1344 @predicate('max(set)')
1307 def maxrev(repo, subset, x): 1345 def maxrev(repo, subset, x):
1308 """``max(set)`` 1346 """Changeset with highest revision number in set.
1309 Changeset with highest revision number in set.
1310 """ 1347 """
1311 os = getset(repo, fullreposet(repo), x) 1348 os = getset(repo, fullreposet(repo), x)
1312 try: 1349 try:
1313 m = os.max() 1350 m = os.max()
1314 if m in subset: 1351 if m in subset:
1317 # os.max() throws a ValueError when the collection is empty. 1354 # os.max() throws a ValueError when the collection is empty.
1318 # Same as python's max(). 1355 # Same as python's max().
1319 pass 1356 pass
1320 return baseset() 1357 return baseset()
1321 1358
1359 @predicate('merge()')
1322 def merge(repo, subset, x): 1360 def merge(repo, subset, x):
1323 """``merge()`` 1361 """Changeset is a merge changeset.
1324 Changeset is a merge changeset.
1325 """ 1362 """
1326 # i18n: "merge" is a keyword 1363 # i18n: "merge" is a keyword
1327 getargs(x, 0, 0, _("merge takes no arguments")) 1364 getargs(x, 0, 0, _("merge takes no arguments"))
1328 cl = repo.changelog 1365 cl = repo.changelog
1329 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1) 1366 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1330 1367
1368 @predicate('branchpoint()')
1331 def branchpoint(repo, subset, x): 1369 def branchpoint(repo, subset, x):
1332 """``branchpoint()`` 1370 """Changesets with more than one child.
1333 Changesets with more than one child.
1334 """ 1371 """
1335 # i18n: "branchpoint" is a keyword 1372 # i18n: "branchpoint" is a keyword
1336 getargs(x, 0, 0, _("branchpoint takes no arguments")) 1373 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1337 cl = repo.changelog 1374 cl = repo.changelog
1338 if not subset: 1375 if not subset:
1345 for p in cl.parentrevs(r): 1382 for p in cl.parentrevs(r):
1346 if p >= baserev: 1383 if p >= baserev:
1347 parentscount[p - baserev] += 1 1384 parentscount[p - baserev] += 1
1348 return subset.filter(lambda r: parentscount[r - baserev] > 1) 1385 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1349 1386
1387 @predicate('min(set)')
1350 def minrev(repo, subset, x): 1388 def minrev(repo, subset, x):
1351 """``min(set)`` 1389 """Changeset with lowest revision number in set.
1352 Changeset with lowest revision number in set.
1353 """ 1390 """
1354 os = getset(repo, fullreposet(repo), x) 1391 os = getset(repo, fullreposet(repo), x)
1355 try: 1392 try:
1356 m = os.min() 1393 m = os.min()
1357 if m in subset: 1394 if m in subset:
1360 # os.min() throws a ValueError when the collection is empty. 1397 # os.min() throws a ValueError when the collection is empty.
1361 # Same as python's min(). 1398 # Same as python's min().
1362 pass 1399 pass
1363 return baseset() 1400 return baseset()
1364 1401
1402 @predicate('modifies(pattern)')
1365 def modifies(repo, subset, x): 1403 def modifies(repo, subset, x):
1366 """``modifies(pattern)`` 1404 """Changesets modifying files matched by pattern.
1367 Changesets modifying files matched by pattern.
1368 1405
1369 The pattern without explicit kind like ``glob:`` is expected to be 1406 The pattern without explicit kind like ``glob:`` is expected to be
1370 relative to the current directory and match against a file or a 1407 relative to the current directory and match against a file or a
1371 directory. 1408 directory.
1372 """ 1409 """
1373 # i18n: "modifies" is a keyword 1410 # i18n: "modifies" is a keyword
1374 pat = getstring(x, _("modifies requires a pattern")) 1411 pat = getstring(x, _("modifies requires a pattern"))
1375 return checkstatus(repo, subset, pat, 0) 1412 return checkstatus(repo, subset, pat, 0)
1376 1413
1414 @predicate('named(namespace)')
1377 def named(repo, subset, x): 1415 def named(repo, subset, x):
1378 """``named(namespace)`` 1416 """The changesets in a given namespace.
1379 The changesets in a given namespace.
1380 1417
1381 If `namespace` starts with `re:`, the remainder of the string is treated as 1418 If `namespace` starts with `re:`, the remainder of the string is treated as
1382 a regular expression. To match a namespace that actually starts with `re:`, 1419 a regular expression. To match a namespace that actually starts with `re:`,
1383 use the prefix `literal:`. 1420 use the prefix `literal:`.
1384 """ 1421 """
1410 names.update(repo[n].rev() for n in ns.nodes(repo, name)) 1447 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1411 1448
1412 names -= set([node.nullrev]) 1449 names -= set([node.nullrev])
1413 return subset & names 1450 return subset & names
1414 1451
1452 @predicate('id(string)')
1415 def node_(repo, subset, x): 1453 def node_(repo, subset, x):
1416 """``id(string)`` 1454 """Revision non-ambiguously specified by the given hex string prefix.
1417 Revision non-ambiguously specified by the given hex string prefix.
1418 """ 1455 """
1419 # i18n: "id" is a keyword 1456 # i18n: "id" is a keyword
1420 l = getargs(x, 1, 1, _("id requires one argument")) 1457 l = getargs(x, 1, 1, _("id requires one argument"))
1421 # i18n: "id" is a keyword 1458 # i18n: "id" is a keyword
1422 n = getstring(l[0], _("id requires a string")) 1459 n = getstring(l[0], _("id requires a string"))
1434 if rn is None: 1471 if rn is None:
1435 return baseset() 1472 return baseset()
1436 result = baseset([rn]) 1473 result = baseset([rn])
1437 return result & subset 1474 return result & subset
1438 1475
1476 @predicate('obsolete()')
1439 def obsolete(repo, subset, x): 1477 def obsolete(repo, subset, x):
1440 """``obsolete()`` 1478 """Mutable changeset with a newer version."""
1441 Mutable changeset with a newer version."""
1442 # i18n: "obsolete" is a keyword 1479 # i18n: "obsolete" is a keyword
1443 getargs(x, 0, 0, _("obsolete takes no arguments")) 1480 getargs(x, 0, 0, _("obsolete takes no arguments"))
1444 obsoletes = obsmod.getrevs(repo, 'obsolete') 1481 obsoletes = obsmod.getrevs(repo, 'obsolete')
1445 return subset & obsoletes 1482 return subset & obsoletes
1446 1483
1484 @predicate('only(set, [set])')
1447 def only(repo, subset, x): 1485 def only(repo, subset, x):
1448 """``only(set, [set])`` 1486 """Changesets that are ancestors of the first set that are not ancestors
1449 Changesets that are ancestors of the first set that are not ancestors
1450 of any other head in the repo. If a second set is specified, the result 1487 of any other head in the repo. If a second set is specified, the result
1451 is ancestors of the first set that are not ancestors of the second set 1488 is ancestors of the first set that are not ancestors of the second set
1452 (i.e. ::<set1> - ::<set2>). 1489 (i.e. ::<set1> - ::<set2>).
1453 """ 1490 """
1454 cl = repo.changelog 1491 cl = repo.changelog
1468 results = set(cl.findmissingrevs(common=exclude, heads=include)) 1505 results = set(cl.findmissingrevs(common=exclude, heads=include))
1469 # XXX we should turn this into a baseset instead of a set, smartset may do 1506 # XXX we should turn this into a baseset instead of a set, smartset may do
1470 # some optimisations from the fact this is a baseset. 1507 # some optimisations from the fact this is a baseset.
1471 return subset & results 1508 return subset & results
1472 1509
1510 @predicate('origin([set])')
1473 def origin(repo, subset, x): 1511 def origin(repo, subset, x):
1474 """``origin([set])`` 1512 """
1475 Changesets that were specified as a source for the grafts, transplants or 1513 Changesets that were specified as a source for the grafts, transplants or
1476 rebases that created the given revisions. Omitting the optional set is the 1514 rebases that created the given revisions. Omitting the optional set is the
1477 same as passing all(). If a changeset created by these operations is itself 1515 same as passing all(). If a changeset created by these operations is itself
1478 specified as a source for one of these operations, only the source changeset 1516 specified as a source for one of these operations, only the source changeset
1479 for the first operation is selected. 1517 for the first operation is selected.
1499 o -= set([None]) 1537 o -= set([None])
1500 # XXX we should turn this into a baseset instead of a set, smartset may do 1538 # XXX we should turn this into a baseset instead of a set, smartset may do
1501 # some optimisations from the fact this is a baseset. 1539 # some optimisations from the fact this is a baseset.
1502 return subset & o 1540 return subset & o
1503 1541
1542 @predicate('outgoing([path])')
1504 def outgoing(repo, subset, x): 1543 def outgoing(repo, subset, x):
1505 """``outgoing([path])`` 1544 """Changesets not found in the specified destination repository, or the
1506 Changesets not found in the specified destination repository, or the
1507 default push location. 1545 default push location.
1508 """ 1546 """
1509 # Avoid cycles. 1547 # Avoid cycles.
1510 from . import ( 1548 from . import (
1511 discovery, 1549 discovery,
1526 repo.ui.popbuffer() 1564 repo.ui.popbuffer()
1527 cl = repo.changelog 1565 cl = repo.changelog
1528 o = set([cl.rev(r) for r in outgoing.missing]) 1566 o = set([cl.rev(r) for r in outgoing.missing])
1529 return subset & o 1567 return subset & o
1530 1568
1569 @predicate('p1([set])')
1531 def p1(repo, subset, x): 1570 def p1(repo, subset, x):
1532 """``p1([set])`` 1571 """First parent of changesets in set, or the working directory.
1533 First parent of changesets in set, or the working directory.
1534 """ 1572 """
1535 if x is None: 1573 if x is None:
1536 p = repo[x].p1().rev() 1574 p = repo[x].p1().rev()
1537 if p >= 0: 1575 if p >= 0:
1538 return subset & baseset([p]) 1576 return subset & baseset([p])
1545 ps -= set([node.nullrev]) 1583 ps -= set([node.nullrev])
1546 # XXX we should turn this into a baseset instead of a set, smartset may do 1584 # XXX we should turn this into a baseset instead of a set, smartset may do
1547 # some optimisations from the fact this is a baseset. 1585 # some optimisations from the fact this is a baseset.
1548 return subset & ps 1586 return subset & ps
1549 1587
1588 @predicate('p2([set])')
1550 def p2(repo, subset, x): 1589 def p2(repo, subset, x):
1551 """``p2([set])`` 1590 """Second parent of changesets in set, or the working directory.
1552 Second parent of changesets in set, or the working directory.
1553 """ 1591 """
1554 if x is None: 1592 if x is None:
1555 ps = repo[x].parents() 1593 ps = repo[x].parents()
1556 try: 1594 try:
1557 p = ps[1].rev() 1595 p = ps[1].rev()
1568 ps -= set([node.nullrev]) 1606 ps -= set([node.nullrev])
1569 # XXX we should turn this into a baseset instead of a set, smartset may do 1607 # XXX we should turn this into a baseset instead of a set, smartset may do
1570 # some optimisations from the fact this is a baseset. 1608 # some optimisations from the fact this is a baseset.
1571 return subset & ps 1609 return subset & ps
1572 1610
1611 @predicate('parents([set])')
1573 def parents(repo, subset, x): 1612 def parents(repo, subset, x):
1574 """``parents([set])`` 1613 """
1575 The set of all parents for all changesets in set, or the working directory. 1614 The set of all parents for all changesets in set, or the working directory.
1576 """ 1615 """
1577 if x is None: 1616 if x is None:
1578 ps = set(p.rev() for p in repo[x].parents()) 1617 ps = set(p.rev() for p in repo[x].parents())
1579 else: 1618 else:
1600 else: 1639 else:
1601 phase = repo._phasecache.phase 1640 phase = repo._phasecache.phase
1602 condition = lambda r: phase(repo, r) == target 1641 condition = lambda r: phase(repo, r) == target
1603 return subset.filter(condition, cache=False) 1642 return subset.filter(condition, cache=False)
1604 1643
1644 @predicate('draft()')
1605 def draft(repo, subset, x): 1645 def draft(repo, subset, x):
1606 """``draft()`` 1646 """Changeset in draft phase."""
1607 Changeset in draft phase."""
1608 # i18n: "draft" is a keyword 1647 # i18n: "draft" is a keyword
1609 getargs(x, 0, 0, _("draft takes no arguments")) 1648 getargs(x, 0, 0, _("draft takes no arguments"))
1610 target = phases.draft 1649 target = phases.draft
1611 return _phase(repo, subset, target) 1650 return _phase(repo, subset, target)
1612 1651
1652 @predicate('secret()')
1613 def secret(repo, subset, x): 1653 def secret(repo, subset, x):
1614 """``secret()`` 1654 """Changeset in secret phase."""
1615 Changeset in secret phase."""
1616 # i18n: "secret" is a keyword 1655 # i18n: "secret" is a keyword
1617 getargs(x, 0, 0, _("secret takes no arguments")) 1656 getargs(x, 0, 0, _("secret takes no arguments"))
1618 target = phases.secret 1657 target = phases.secret
1619 return _phase(repo, subset, target) 1658 return _phase(repo, subset, target)
1620 1659
1641 parents = cl.parentrevs(r) 1680 parents = cl.parentrevs(r)
1642 if len(parents) > 1: 1681 if len(parents) > 1:
1643 ps.add(parents[1]) 1682 ps.add(parents[1])
1644 return subset & ps 1683 return subset & ps
1645 1684
1685 @predicate('present(set)')
1646 def present(repo, subset, x): 1686 def present(repo, subset, x):
1647 """``present(set)`` 1687 """An empty set, if any revision in set isn't found; otherwise,
1648 An empty set, if any revision in set isn't found; otherwise,
1649 all revisions in set. 1688 all revisions in set.
1650 1689
1651 If any of specified revisions is not present in the local repository, 1690 If any of specified revisions is not present in the local repository,
1652 the query is normally aborted. But this predicate allows the query 1691 the query is normally aborted. But this predicate allows the query
1653 to continue even in such cases. 1692 to continue even in such cases.
1656 return getset(repo, subset, x) 1695 return getset(repo, subset, x)
1657 except error.RepoLookupError: 1696 except error.RepoLookupError:
1658 return baseset() 1697 return baseset()
1659 1698
1660 # for internal use 1699 # for internal use
1700 @predicate('_notpublic')
1661 def _notpublic(repo, subset, x): 1701 def _notpublic(repo, subset, x):
1662 getargs(x, 0, 0, "_notpublic takes no arguments") 1702 getargs(x, 0, 0, "_notpublic takes no arguments")
1663 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded 1703 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1664 if repo._phasecache._phasesets: 1704 if repo._phasecache._phasesets:
1665 s = set() 1705 s = set()
1672 phase = repo._phasecache.phase 1712 phase = repo._phasecache.phase
1673 target = phases.public 1713 target = phases.public
1674 condition = lambda r: phase(repo, r) != target 1714 condition = lambda r: phase(repo, r) != target
1675 return subset.filter(condition, cache=False) 1715 return subset.filter(condition, cache=False)
1676 1716
1717 @predicate('public()')
1677 def public(repo, subset, x): 1718 def public(repo, subset, x):
1678 """``public()`` 1719 """Changeset in public phase."""
1679 Changeset in public phase."""
1680 # i18n: "public" is a keyword 1720 # i18n: "public" is a keyword
1681 getargs(x, 0, 0, _("public takes no arguments")) 1721 getargs(x, 0, 0, _("public takes no arguments"))
1682 phase = repo._phasecache.phase 1722 phase = repo._phasecache.phase
1683 target = phases.public 1723 target = phases.public
1684 condition = lambda r: phase(repo, r) == target 1724 condition = lambda r: phase(repo, r) == target
1685 return subset.filter(condition, cache=False) 1725 return subset.filter(condition, cache=False)
1686 1726
1727 @predicate('remote([id [,path]])')
1687 def remote(repo, subset, x): 1728 def remote(repo, subset, x):
1688 """``remote([id [,path]])`` 1729 """Local revision that corresponds to the given identifier in a
1689 Local revision that corresponds to the given identifier in a
1690 remote repository, if present. Here, the '.' identifier is a 1730 remote repository, if present. Here, the '.' identifier is a
1691 synonym for the current local branch. 1731 synonym for the current local branch.
1692 """ 1732 """
1693 1733
1694 from . import hg # avoid start-up nasties 1734 from . import hg # avoid start-up nasties
1717 r = repo[n].rev() 1757 r = repo[n].rev()
1718 if r in subset: 1758 if r in subset:
1719 return baseset([r]) 1759 return baseset([r])
1720 return baseset() 1760 return baseset()
1721 1761
1762 @predicate('removes(pattern)')
1722 def removes(repo, subset, x): 1763 def removes(repo, subset, x):
1723 """``removes(pattern)`` 1764 """Changesets which remove files matching pattern.
1724 Changesets which remove files matching pattern.
1725 1765
1726 The pattern without explicit kind like ``glob:`` is expected to be 1766 The pattern without explicit kind like ``glob:`` is expected to be
1727 relative to the current directory and match against a file or a 1767 relative to the current directory and match against a file or a
1728 directory. 1768 directory.
1729 """ 1769 """
1730 # i18n: "removes" is a keyword 1770 # i18n: "removes" is a keyword
1731 pat = getstring(x, _("removes requires a pattern")) 1771 pat = getstring(x, _("removes requires a pattern"))
1732 return checkstatus(repo, subset, pat, 2) 1772 return checkstatus(repo, subset, pat, 2)
1733 1773
1774 @predicate('rev(number)')
1734 def rev(repo, subset, x): 1775 def rev(repo, subset, x):
1735 """``rev(number)`` 1776 """Revision with the given numeric identifier.
1736 Revision with the given numeric identifier.
1737 """ 1777 """
1738 # i18n: "rev" is a keyword 1778 # i18n: "rev" is a keyword
1739 l = getargs(x, 1, 1, _("rev requires one argument")) 1779 l = getargs(x, 1, 1, _("rev requires one argument"))
1740 try: 1780 try:
1741 # i18n: "rev" is a keyword 1781 # i18n: "rev" is a keyword
1745 raise error.ParseError(_("rev expects a number")) 1785 raise error.ParseError(_("rev expects a number"))
1746 if l not in repo.changelog and l != node.nullrev: 1786 if l not in repo.changelog and l != node.nullrev:
1747 return baseset() 1787 return baseset()
1748 return subset & baseset([l]) 1788 return subset & baseset([l])
1749 1789
1790 @predicate('matching(revision [, field])')
1750 def matching(repo, subset, x): 1791 def matching(repo, subset, x):
1751 """``matching(revision [, field])`` 1792 """Changesets in which a given set of fields match the set of fields in the
1752 Changesets in which a given set of fields match the set of fields in the
1753 selected revision or set. 1793 selected revision or set.
1754 1794
1755 To match more than one field pass the list of fields to match separated 1795 To match more than one field pass the list of fields to match separated
1756 by spaces (e.g. ``author description``). 1796 by spaces (e.g. ``author description``).
1757 1797
1857 return True 1897 return True
1858 return False 1898 return False
1859 1899
1860 return subset.filter(matches) 1900 return subset.filter(matches)
1861 1901
1902 @predicate('reverse(set)')
1862 def reverse(repo, subset, x): 1903 def reverse(repo, subset, x):
1863 """``reverse(set)`` 1904 """Reverse order of set.
1864 Reverse order of set.
1865 """ 1905 """
1866 l = getset(repo, subset, x) 1906 l = getset(repo, subset, x)
1867 l.reverse() 1907 l.reverse()
1868 return l 1908 return l
1869 1909
1910 @predicate('roots(set)')
1870 def roots(repo, subset, x): 1911 def roots(repo, subset, x):
1871 """``roots(set)`` 1912 """Changesets in set with no parent changeset in set.
1872 Changesets in set with no parent changeset in set.
1873 """ 1913 """
1874 s = getset(repo, fullreposet(repo), x) 1914 s = getset(repo, fullreposet(repo), x)
1875 parents = repo.changelog.parentrevs 1915 parents = repo.changelog.parentrevs
1876 def filter(r): 1916 def filter(r):
1877 for p in parents(r): 1917 for p in parents(r):
1878 if 0 <= p and p in s: 1918 if 0 <= p and p in s:
1879 return False 1919 return False
1880 return True 1920 return True
1881 return subset & s.filter(filter) 1921 return subset & s.filter(filter)
1882 1922
1923 @predicate('sort(set[, [-]key...])')
1883 def sort(repo, subset, x): 1924 def sort(repo, subset, x):
1884 """``sort(set[, [-]key...])`` 1925 """Sort set by keys. The default sort order is ascending, specify a key
1885 Sort set by keys. The default sort order is ascending, specify a key
1886 as ``-key`` to sort in descending order. 1926 as ``-key`` to sort in descending order.
1887 1927
1888 The keys can be: 1928 The keys can be:
1889 1929
1890 - ``rev`` for the revision number, 1930 - ``rev`` for the revision number,
1941 e.append(r) 1981 e.append(r)
1942 l.append(e) 1982 l.append(e)
1943 l.sort() 1983 l.sort()
1944 return baseset([e[-1] for e in l]) 1984 return baseset([e[-1] for e in l])
1945 1985
1986 @predicate('subrepo([pattern])')
1946 def subrepo(repo, subset, x): 1987 def subrepo(repo, subset, x):
1947 """``subrepo([pattern])`` 1988 """Changesets that add, modify or remove the given subrepo. If no subrepo
1948 Changesets that add, modify or remove the given subrepo. If no subrepo
1949 pattern is named, any subrepo changes are returned. 1989 pattern is named, any subrepo changes are returned.
1950 """ 1990 """
1951 # i18n: "subrepo" is a keyword 1991 # i18n: "subrepo" is a keyword
1952 args = getargs(x, 0, 1, _('subrepo takes at most one argument')) 1992 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
1953 if len(args) != 0: 1993 if len(args) != 0:
1990 kind, pattern, matcher = util.stringmatcher(pattern) 2030 kind, pattern, matcher = util.stringmatcher(pattern)
1991 if kind == 'literal': 2031 if kind == 'literal':
1992 matcher = lambda s: pattern in s 2032 matcher = lambda s: pattern in s
1993 return kind, pattern, matcher 2033 return kind, pattern, matcher
1994 2034
2035 @predicate('tag([name])')
1995 def tag(repo, subset, x): 2036 def tag(repo, subset, x):
1996 """``tag([name])`` 2037 """The specified tag by name, or all tagged revisions if no name is given.
1997 The specified tag by name, or all tagged revisions if no name is given.
1998 2038
1999 If `name` starts with `re:`, the remainder of the name is treated as 2039 If `name` starts with `re:`, the remainder of the name is treated as
2000 a regular expression. To match a tag that actually starts with `re:`, 2040 a regular expression. To match a tag that actually starts with `re:`,
2001 use the prefix `literal:`. 2041 use the prefix `literal:`.
2002 """ 2042 """
2019 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)]) 2059 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
2020 else: 2060 else:
2021 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip']) 2061 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
2022 return subset & s 2062 return subset & s
2023 2063
2064 @predicate('tagged')
2024 def tagged(repo, subset, x): 2065 def tagged(repo, subset, x):
2025 return tag(repo, subset, x) 2066 return tag(repo, subset, x)
2026 2067
2068 @predicate('unstable()')
2027 def unstable(repo, subset, x): 2069 def unstable(repo, subset, x):
2028 """``unstable()`` 2070 """Non-obsolete changesets with obsolete ancestors.
2029 Non-obsolete changesets with obsolete ancestors.
2030 """ 2071 """
2031 # i18n: "unstable" is a keyword 2072 # i18n: "unstable" is a keyword
2032 getargs(x, 0, 0, _("unstable takes no arguments")) 2073 getargs(x, 0, 0, _("unstable takes no arguments"))
2033 unstables = obsmod.getrevs(repo, 'unstable') 2074 unstables = obsmod.getrevs(repo, 'unstable')
2034 return subset & unstables 2075 return subset & unstables
2035 2076
2036 2077
2078 @predicate('user(string)')
2037 def user(repo, subset, x): 2079 def user(repo, subset, x):
2038 """``user(string)`` 2080 """User name contains string. The match is case-insensitive.
2039 User name contains string. The match is case-insensitive.
2040 2081
2041 If `string` starts with `re:`, the remainder of the string is treated as 2082 If `string` starts with `re:`, the remainder of the string is treated as
2042 a regular expression. To match a user that actually contains `re:`, use 2083 a regular expression. To match a user that actually contains `re:`, use
2043 the prefix `literal:`. 2084 the prefix `literal:`.
2044 """ 2085 """
2045 return author(repo, subset, x) 2086 return author(repo, subset, x)
2046 2087
2047 # experimental 2088 # experimental
2089 @predicate('wdir')
2048 def wdir(repo, subset, x): 2090 def wdir(repo, subset, x):
2049 # i18n: "wdir" is a keyword 2091 # i18n: "wdir" is a keyword
2050 getargs(x, 0, 0, _("wdir takes no arguments")) 2092 getargs(x, 0, 0, _("wdir takes no arguments"))
2051 if node.wdirrev in subset or isinstance(subset, fullreposet): 2093 if node.wdirrev in subset or isinstance(subset, fullreposet):
2052 return baseset([node.wdirrev]) 2094 return baseset([node.wdirrev])
2053 return baseset() 2095 return baseset()
2054 2096
2055 # for internal use 2097 # for internal use
2098 @predicate('_list')
2056 def _list(repo, subset, x): 2099 def _list(repo, subset, x):
2057 s = getstring(x, "internal error") 2100 s = getstring(x, "internal error")
2058 if not s: 2101 if not s:
2059 return baseset() 2102 return baseset()
2060 # remove duplicates here. it's difficult for caller to deduplicate sets 2103 # remove duplicates here. it's difficult for caller to deduplicate sets
2080 ls.append(r) 2123 ls.append(r)
2081 seen.add(r) 2124 seen.add(r)
2082 return baseset(ls) 2125 return baseset(ls)
2083 2126
2084 # for internal use 2127 # for internal use
2128 @predicate('_intlist')
2085 def _intlist(repo, subset, x): 2129 def _intlist(repo, subset, x):
2086 s = getstring(x, "internal error") 2130 s = getstring(x, "internal error")
2087 if not s: 2131 if not s:
2088 return baseset() 2132 return baseset()
2089 ls = [int(r) for r in s.split('\0')] 2133 ls = [int(r) for r in s.split('\0')]
2090 s = subset 2134 s = subset
2091 return baseset([r for r in ls if r in s]) 2135 return baseset([r for r in ls if r in s])
2092 2136
2093 # for internal use 2137 # for internal use
2138 @predicate('_hexlist')
2094 def _hexlist(repo, subset, x): 2139 def _hexlist(repo, subset, x):
2095 s = getstring(x, "internal error") 2140 s = getstring(x, "internal error")
2096 if not s: 2141 if not s:
2097 return baseset() 2142 return baseset()
2098 cl = repo.changelog 2143 cl = repo.changelog
2099 ls = [cl.rev(node.bin(r)) for r in s.split('\0')] 2144 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2100 s = subset 2145 s = subset
2101 return baseset([r for r in ls if r in s]) 2146 return baseset([r for r in ls if r in s])
2102
2103 symbols = {
2104 "_destupdate": _destupdate,
2105 "_destmerge": _destmerge,
2106 "adds": adds,
2107 "all": getall,
2108 "ancestor": ancestor,
2109 "ancestors": ancestors,
2110 "_firstancestors": _firstancestors,
2111 "author": author,
2112 "bisect": bisect,
2113 "bisected": bisected,
2114 "bookmark": bookmark,
2115 "branch": branch,
2116 "branchpoint": branchpoint,
2117 "bumped": bumped,
2118 "bundle": bundle,
2119 "children": children,
2120 "closed": closed,
2121 "contains": contains,
2122 "converted": converted,
2123 "date": date,
2124 "desc": desc,
2125 "descendants": descendants,
2126 "_firstdescendants": _firstdescendants,
2127 "destination": destination,
2128 "divergent": divergent,
2129 "draft": draft,
2130 "extinct": extinct,
2131 "extra": extra,
2132 "file": hasfile,
2133 "filelog": filelog,
2134 "first": first,
2135 "follow": follow,
2136 "_followfirst": _followfirst,
2137 "grep": grep,
2138 "head": head,
2139 "heads": heads,
2140 "hidden": hidden,
2141 "id": node_,
2142 "keyword": keyword,
2143 "last": last,
2144 "limit": limit,
2145 "_matchfiles": _matchfiles,
2146 "max": maxrev,
2147 "merge": merge,
2148 "min": minrev,
2149 "modifies": modifies,
2150 "named": named,
2151 "obsolete": obsolete,
2152 "only": only,
2153 "origin": origin,
2154 "outgoing": outgoing,
2155 "p1": p1,
2156 "p2": p2,
2157 "parents": parents,
2158 "present": present,
2159 "public": public,
2160 "_notpublic": _notpublic,
2161 "remote": remote,
2162 "removes": removes,
2163 "rev": rev,
2164 "reverse": reverse,
2165 "roots": roots,
2166 "sort": sort,
2167 "secret": secret,
2168 "subrepo": subrepo,
2169 "matching": matching,
2170 "tag": tag,
2171 "tagged": tagged,
2172 "user": user,
2173 "unstable": unstable,
2174 "wdir": wdir,
2175 "_list": _list,
2176 "_intlist": _intlist,
2177 "_hexlist": _hexlist,
2178 }
2179 2147
2180 # symbols which can't be used for a DoS attack for any given input 2148 # symbols which can't be used for a DoS attack for any given input
2181 # (e.g. those which accept regexes as plain strings shouldn't be included) 2149 # (e.g. those which accept regexes as plain strings shouldn't be included)
2182 # functions that just return a lot of changesets (like all) don't count here 2150 # functions that just return a lot of changesets (like all) don't count here
2183 safesymbols = set([ 2151 safesymbols = set([