comparison mercurial/revset.py @ 27587:c8dc480142a8

revset: use decorator to mark a predicate as safe Using decorator can localize changes for adding (or removing) a "safe" revset predicate function in source code. To avoid accidentaly treating unsuitable predicates as safe, this patch uses False as default value of "safe" argument. This forces safe predicates to be decorated with explicit 'safe=True'.
author FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
date Tue, 29 Dec 2015 23:58:30 +0900
parents 42910f9fffeb
children b502138f5faa
comparison
equal deleted inserted replaced
27586:42910f9fffeb 27587:c8dc480142a8
472 # repo - current repository instance 472 # repo - current repository instance
473 # subset - of revisions to be examined 473 # subset - of revisions to be examined
474 # x - argument in tree form 474 # x - argument in tree form
475 symbols = {} 475 symbols = {}
476 476
477 # symbols which can't be used for a DoS attack for any given input
478 # (e.g. those which accept regexes as plain strings shouldn't be included)
479 # functions that just return a lot of changesets (like all) don't count here
480 safesymbols = set()
481
477 class predicate(registrar.funcregistrar): 482 class predicate(registrar.funcregistrar):
478 """Decorator to register revset predicate 483 """Decorator to register revset predicate
479 484
480 Usage:: 485 Usage::
481 486
493 """ 498 """
494 table = symbols 499 table = symbols
495 formatdoc = "``%s``\n %s" 500 formatdoc = "``%s``\n %s"
496 getname = registrar.funcregistrar.parsefuncdecl 501 getname = registrar.funcregistrar.parsefuncdecl
497 502
503 def __init__(self, decl, safe=False):
504 """'safe' indicates whether a predicate is safe for DoS attack
505 """
506 super(predicate, self).__init__(decl)
507 self.safe = safe
508
509 def extraaction(self, name, func):
510 if self.safe:
511 safesymbols.add(name)
512
498 class extpredicate(registrar.delayregistrar): 513 class extpredicate(registrar.delayregistrar):
499 """Decorator to register revset predicate in extensions 514 """Decorator to register revset predicate in extensions
500 515
501 Usage:: 516 Usage::
502 517
527 def _destmerge(repo, subset, x): 542 def _destmerge(repo, subset, x):
528 # experimental revset for merge destination 543 # experimental revset for merge destination
529 getargs(x, 0, 0, _("_mergedefaultdest takes no arguments")) 544 getargs(x, 0, 0, _("_mergedefaultdest takes no arguments"))
530 return subset & baseset([destutil.destmerge(repo)]) 545 return subset & baseset([destutil.destmerge(repo)])
531 546
532 @predicate('adds(pattern)') 547 @predicate('adds(pattern)', safe=True)
533 def adds(repo, subset, x): 548 def adds(repo, subset, x):
534 """Changesets that add a file matching pattern. 549 """Changesets that add a file matching pattern.
535 550
536 The pattern without explicit kind like ``glob:`` is expected to be 551 The pattern without explicit kind like ``glob:`` is expected to be
537 relative to the current directory and match against a file or a 552 relative to the current directory and match against a file or a
539 """ 554 """
540 # i18n: "adds" is a keyword 555 # i18n: "adds" is a keyword
541 pat = getstring(x, _("adds requires a pattern")) 556 pat = getstring(x, _("adds requires a pattern"))
542 return checkstatus(repo, subset, pat, 1) 557 return checkstatus(repo, subset, pat, 1)
543 558
544 @predicate('ancestor(*changeset)') 559 @predicate('ancestor(*changeset)', safe=True)
545 def ancestor(repo, subset, x): 560 def ancestor(repo, subset, x):
546 """A greatest common ancestor of the changesets. 561 """A greatest common ancestor of the changesets.
547 562
548 Accepts 0 or more changesets. 563 Accepts 0 or more changesets.
549 Will return empty list when passed no args. 564 Will return empty list when passed no args.
571 if not heads: 586 if not heads:
572 return baseset() 587 return baseset()
573 s = _revancestors(repo, heads, followfirst) 588 s = _revancestors(repo, heads, followfirst)
574 return subset & s 589 return subset & s
575 590
576 @predicate('ancestors(set)') 591 @predicate('ancestors(set)', safe=True)
577 def ancestors(repo, subset, x): 592 def ancestors(repo, subset, x):
578 """Changesets that are ancestors of a changeset in set. 593 """Changesets that are ancestors of a changeset in set.
579 """ 594 """
580 return _ancestors(repo, subset, x) 595 return _ancestors(repo, subset, x)
581 596
582 @predicate('_firstancestors') 597 @predicate('_firstancestors', safe=True)
583 def _firstancestors(repo, subset, x): 598 def _firstancestors(repo, subset, x):
584 # ``_firstancestors(set)`` 599 # ``_firstancestors(set)``
585 # Like ``ancestors(set)`` but follows only the first parents. 600 # Like ``ancestors(set)`` but follows only the first parents.
586 return _ancestors(repo, subset, x, followfirst=True) 601 return _ancestors(repo, subset, x, followfirst=True)
587 602
600 for i in range(n): 615 for i in range(n):
601 r = cl.parentrevs(r)[0] 616 r = cl.parentrevs(r)[0]
602 ps.add(r) 617 ps.add(r)
603 return subset & ps 618 return subset & ps
604 619
605 @predicate('author(string)') 620 @predicate('author(string)', safe=True)
606 def author(repo, subset, x): 621 def author(repo, subset, x):
607 """Alias for ``user(string)``. 622 """Alias for ``user(string)``.
608 """ 623 """
609 # i18n: "author" is a keyword 624 # i18n: "author" is a keyword
610 n = encoding.lower(getstring(x, _("author requires a string"))) 625 n = encoding.lower(getstring(x, _("author requires a string")))
611 kind, pattern, matcher = _substringmatcher(n) 626 kind, pattern, matcher = _substringmatcher(n)
612 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user()))) 627 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
613 628
614 @predicate('bisect(string)') 629 @predicate('bisect(string)', safe=True)
615 def bisect(repo, subset, x): 630 def bisect(repo, subset, x):
616 """Changesets marked in the specified bisect status: 631 """Changesets marked in the specified bisect status:
617 632
618 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip 633 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
619 - ``goods``, ``bads`` : csets topologically good/bad 634 - ``goods``, ``bads`` : csets topologically good/bad
628 state = set(hbisect.get(repo, status)) 643 state = set(hbisect.get(repo, status))
629 return subset & state 644 return subset & state
630 645
631 # Backward-compatibility 646 # Backward-compatibility
632 # - no help entry so that we do not advertise it any more 647 # - no help entry so that we do not advertise it any more
633 @predicate('bisected') 648 @predicate('bisected', safe=True)
634 def bisected(repo, subset, x): 649 def bisected(repo, subset, x):
635 return bisect(repo, subset, x) 650 return bisect(repo, subset, x)
636 651
637 @predicate('bookmark([name])') 652 @predicate('bookmark([name])', safe=True)
638 def bookmark(repo, subset, x): 653 def bookmark(repo, subset, x):
639 """The named bookmark or all bookmarks. 654 """The named bookmark or all bookmarks.
640 655
641 If `name` starts with `re:`, the remainder of the name is treated as 656 If `name` starts with `re:`, the remainder of the name is treated as
642 a regular expression. To match a bookmark that actually starts with `re:`, 657 a regular expression. To match a bookmark that actually starts with `re:`,
670 bms = set([repo[r].rev() 685 bms = set([repo[r].rev()
671 for r in repo._bookmarks.values()]) 686 for r in repo._bookmarks.values()])
672 bms -= set([node.nullrev]) 687 bms -= set([node.nullrev])
673 return subset & bms 688 return subset & bms
674 689
675 @predicate('branch(string or set)') 690 @predicate('branch(string or set)', safe=True)
676 def branch(repo, subset, x): 691 def branch(repo, subset, x):
677 """ 692 """
678 All changesets belonging to the given branch or the branches of the given 693 All changesets belonging to the given branch or the branches of the given
679 changesets. 694 changesets.
680 695
707 for r in s: 722 for r in s:
708 b.add(getbi(r)[0]) 723 b.add(getbi(r)[0])
709 c = s.__contains__ 724 c = s.__contains__
710 return subset.filter(lambda r: c(r) or getbi(r)[0] in b) 725 return subset.filter(lambda r: c(r) or getbi(r)[0] in b)
711 726
712 @predicate('bumped()') 727 @predicate('bumped()', safe=True)
713 def bumped(repo, subset, x): 728 def bumped(repo, subset, x):
714 """Mutable changesets marked as successors of public changesets. 729 """Mutable changesets marked as successors of public changesets.
715 730
716 Only non-public and non-obsolete changesets can be `bumped`. 731 Only non-public and non-obsolete changesets can be `bumped`.
717 """ 732 """
718 # i18n: "bumped" is a keyword 733 # i18n: "bumped" is a keyword
719 getargs(x, 0, 0, _("bumped takes no arguments")) 734 getargs(x, 0, 0, _("bumped takes no arguments"))
720 bumped = obsmod.getrevs(repo, 'bumped') 735 bumped = obsmod.getrevs(repo, 'bumped')
721 return subset & bumped 736 return subset & bumped
722 737
723 @predicate('bundle()') 738 @predicate('bundle()', safe=True)
724 def bundle(repo, subset, x): 739 def bundle(repo, subset, x):
725 """Changesets in the bundle. 740 """Changesets in the bundle.
726 741
727 Bundle must be specified by the -R option.""" 742 Bundle must be specified by the -R option."""
728 743
778 cs.add(r) 793 cs.add(r)
779 # XXX using a set to feed the baseset is wrong. Sets are not ordered. 794 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
780 # This does not break because of other fullreposet misbehavior. 795 # This does not break because of other fullreposet misbehavior.
781 return baseset(cs) 796 return baseset(cs)
782 797
783 @predicate('children(set)') 798 @predicate('children(set)', safe=True)
784 def children(repo, subset, x): 799 def children(repo, subset, x):
785 """Child changesets of changesets in set. 800 """Child changesets of changesets in set.
786 """ 801 """
787 s = getset(repo, fullreposet(repo), x) 802 s = getset(repo, fullreposet(repo), x)
788 cs = _children(repo, subset, s) 803 cs = _children(repo, subset, s)
789 return subset & cs 804 return subset & cs
790 805
791 @predicate('closed()') 806 @predicate('closed()', safe=True)
792 def closed(repo, subset, x): 807 def closed(repo, subset, x):
793 """Changeset is closed. 808 """Changeset is closed.
794 """ 809 """
795 # i18n: "closed" is a keyword 810 # i18n: "closed" is a keyword
796 getargs(x, 0, 0, _("closed takes no arguments")) 811 getargs(x, 0, 0, _("closed takes no arguments"))
821 return True 836 return True
822 return False 837 return False
823 838
824 return subset.filter(matches) 839 return subset.filter(matches)
825 840
826 @predicate('converted([id])') 841 @predicate('converted([id])', safe=True)
827 def converted(repo, subset, x): 842 def converted(repo, subset, x):
828 """Changesets converted from the given identifier in the old repository if 843 """Changesets converted from the given identifier in the old repository if
829 present, or all converted changesets if no identifier is specified. 844 present, or all converted changesets if no identifier is specified.
830 """ 845 """
831 846
843 source = repo[r].extra().get('convert_revision', None) 858 source = repo[r].extra().get('convert_revision', None)
844 return source is not None and (rev is None or source.startswith(rev)) 859 return source is not None and (rev is None or source.startswith(rev))
845 860
846 return subset.filter(lambda r: _matchvalue(r)) 861 return subset.filter(lambda r: _matchvalue(r))
847 862
848 @predicate('date(interval)') 863 @predicate('date(interval)', safe=True)
849 def date(repo, subset, x): 864 def date(repo, subset, x):
850 """Changesets within the interval, see :hg:`help dates`. 865 """Changesets within the interval, see :hg:`help dates`.
851 """ 866 """
852 # i18n: "date" is a keyword 867 # i18n: "date" is a keyword
853 ds = getstring(x, _("date requires a string")) 868 ds = getstring(x, _("date requires a string"))
854 dm = util.matchdate(ds) 869 dm = util.matchdate(ds)
855 return subset.filter(lambda x: dm(repo[x].date()[0])) 870 return subset.filter(lambda x: dm(repo[x].date()[0]))
856 871
857 @predicate('desc(string)') 872 @predicate('desc(string)', safe=True)
858 def desc(repo, subset, x): 873 def desc(repo, subset, x):
859 """Search commit message for string. The match is case-insensitive. 874 """Search commit message for string. The match is case-insensitive.
860 """ 875 """
861 # i18n: "desc" is a keyword 876 # i18n: "desc" is a keyword
862 ds = encoding.lower(getstring(x, _("desc requires a string"))) 877 ds = encoding.lower(getstring(x, _("desc requires a string")))
884 result.sort(reverse=True) 899 result.sort(reverse=True)
885 else: 900 else:
886 result = subset & result 901 result = subset & result
887 return result 902 return result
888 903
889 @predicate('descendants(set)') 904 @predicate('descendants(set)', safe=True)
890 def descendants(repo, subset, x): 905 def descendants(repo, subset, x):
891 """Changesets which are descendants of changesets in set. 906 """Changesets which are descendants of changesets in set.
892 """ 907 """
893 return _descendants(repo, subset, x) 908 return _descendants(repo, subset, x)
894 909
895 @predicate('_firstdescendants') 910 @predicate('_firstdescendants', safe=True)
896 def _firstdescendants(repo, subset, x): 911 def _firstdescendants(repo, subset, x):
897 # ``_firstdescendants(set)`` 912 # ``_firstdescendants(set)``
898 # Like ``descendants(set)`` but follows only the first parents. 913 # Like ``descendants(set)`` but follows only the first parents.
899 return _descendants(repo, subset, x, followfirst=True) 914 return _descendants(repo, subset, x, followfirst=True)
900 915
901 @predicate('destination([set])') 916 @predicate('destination([set])', safe=True)
902 def destination(repo, subset, x): 917 def destination(repo, subset, x):
903 """Changesets that were created by a graft, transplant or rebase operation, 918 """Changesets that were created by a graft, transplant or rebase operation,
904 with the given revisions specified as the source. Omitting the optional set 919 with the given revisions specified as the source. Omitting the optional set
905 is the same as passing all(). 920 is the same as passing all().
906 """ 921 """
940 r = src 955 r = src
941 src = _getrevsource(repo, r) 956 src = _getrevsource(repo, r)
942 957
943 return subset.filter(dests.__contains__) 958 return subset.filter(dests.__contains__)
944 959
945 @predicate('divergent()') 960 @predicate('divergent()', safe=True)
946 def divergent(repo, subset, x): 961 def divergent(repo, subset, x):
947 """ 962 """
948 Final successors of changesets with an alternative set of final successors. 963 Final successors of changesets with an alternative set of final successors.
949 """ 964 """
950 # i18n: "divergent" is a keyword 965 # i18n: "divergent" is a keyword
951 getargs(x, 0, 0, _("divergent takes no arguments")) 966 getargs(x, 0, 0, _("divergent takes no arguments"))
952 divergent = obsmod.getrevs(repo, 'divergent') 967 divergent = obsmod.getrevs(repo, 'divergent')
953 return subset & divergent 968 return subset & divergent
954 969
955 @predicate('extinct()') 970 @predicate('extinct()', safe=True)
956 def extinct(repo, subset, x): 971 def extinct(repo, subset, x):
957 """Obsolete changesets with obsolete descendants only. 972 """Obsolete changesets with obsolete descendants only.
958 """ 973 """
959 # i18n: "extinct" is a keyword 974 # i18n: "extinct" is a keyword
960 getargs(x, 0, 0, _("extinct takes no arguments")) 975 getargs(x, 0, 0, _("extinct takes no arguments"))
961 extincts = obsmod.getrevs(repo, 'extinct') 976 extincts = obsmod.getrevs(repo, 'extinct')
962 return subset & extincts 977 return subset & extincts
963 978
964 @predicate('extra(label, [value])') 979 @predicate('extra(label, [value])', safe=True)
965 def extra(repo, subset, x): 980 def extra(repo, subset, x):
966 """Changesets with the given label in the extra metadata, with the given 981 """Changesets with the given label in the extra metadata, with the given
967 optional value. 982 optional value.
968 983
969 If `value` starts with `re:`, the remainder of the value is treated as 984 If `value` starts with `re:`, the remainder of the value is treated as
989 extra = repo[r].extra() 1004 extra = repo[r].extra()
990 return label in extra and (value is None or matcher(extra[label])) 1005 return label in extra and (value is None or matcher(extra[label]))
991 1006
992 return subset.filter(lambda r: _matchvalue(r)) 1007 return subset.filter(lambda r: _matchvalue(r))
993 1008
994 @predicate('filelog(pattern)') 1009 @predicate('filelog(pattern)', safe=True)
995 def filelog(repo, subset, x): 1010 def filelog(repo, subset, x):
996 """Changesets connected to the specified filelog. 1011 """Changesets connected to the specified filelog.
997 1012
998 For performance reasons, visits only revisions mentioned in the file-level 1013 For performance reasons, visits only revisions mentioned in the file-level
999 filelog, rather than filtering through all changesets (much faster, but 1014 filelog, rather than filtering through all changesets (much faster, but
1104 backrevref[fr] = rev 1119 backrevref[fr] = rev
1105 s.add(rev) 1120 s.add(rev)
1106 1121
1107 return subset & s 1122 return subset & s
1108 1123
1109 @predicate('first(set, [n])') 1124 @predicate('first(set, [n])', safe=True)
1110 def first(repo, subset, x): 1125 def first(repo, subset, x):
1111 """An alias for limit(). 1126 """An alias for limit().
1112 """ 1127 """
1113 return limit(repo, subset, x) 1128 return limit(repo, subset, x)
1114 1129
1130 else: 1145 else:
1131 s = _revancestors(repo, baseset([c.rev()]), followfirst) 1146 s = _revancestors(repo, baseset([c.rev()]), followfirst)
1132 1147
1133 return subset & s 1148 return subset & s
1134 1149
1135 @predicate('follow([pattern])') 1150 @predicate('follow([pattern])', safe=True)
1136 def follow(repo, subset, x): 1151 def follow(repo, subset, x):
1137 """ 1152 """
1138 An alias for ``::.`` (ancestors of the working directory's first parent). 1153 An alias for ``::.`` (ancestors of the working directory's first parent).
1139 If pattern is specified, the histories of files matching given 1154 If pattern is specified, the histories of files matching given
1140 pattern is followed, including copies. 1155 pattern is followed, including copies.
1141 """ 1156 """
1142 return _follow(repo, subset, x, 'follow') 1157 return _follow(repo, subset, x, 'follow')
1143 1158
1144 @predicate('_followfirst') 1159 @predicate('_followfirst', safe=True)
1145 def _followfirst(repo, subset, x): 1160 def _followfirst(repo, subset, x):
1146 # ``followfirst([pattern])`` 1161 # ``followfirst([pattern])``
1147 # Like ``follow([pattern])`` but follows only the first parent of 1162 # Like ``follow([pattern])`` but follows only the first parent of
1148 # every revisions or files revisions. 1163 # every revisions or files revisions.
1149 return _follow(repo, subset, x, '_followfirst', followfirst=True) 1164 return _follow(repo, subset, x, '_followfirst', followfirst=True)
1150 1165
1151 @predicate('all()') 1166 @predicate('all()', safe=True)
1152 def getall(repo, subset, x): 1167 def getall(repo, subset, x):
1153 """All changesets, the same as ``0:tip``. 1168 """All changesets, the same as ``0:tip``.
1154 """ 1169 """
1155 # i18n: "all" is a keyword 1170 # i18n: "all" is a keyword
1156 getargs(x, 0, 0, _("all takes no arguments")) 1171 getargs(x, 0, 0, _("all takes no arguments"))
1175 return True 1190 return True
1176 return False 1191 return False
1177 1192
1178 return subset.filter(matches) 1193 return subset.filter(matches)
1179 1194
1180 @predicate('_matchfiles') 1195 @predicate('_matchfiles', safe=True)
1181 def _matchfiles(repo, subset, x): 1196 def _matchfiles(repo, subset, x):
1182 # _matchfiles takes a revset list of prefixed arguments: 1197 # _matchfiles takes a revset list of prefixed arguments:
1183 # 1198 #
1184 # [p:foo, i:bar, x:baz] 1199 # [p:foo, i:bar, x:baz]
1185 # 1200 #
1241 return True 1256 return True
1242 return False 1257 return False
1243 1258
1244 return subset.filter(matches) 1259 return subset.filter(matches)
1245 1260
1246 @predicate('file(pattern)') 1261 @predicate('file(pattern)', safe=True)
1247 def hasfile(repo, subset, x): 1262 def hasfile(repo, subset, x):
1248 """Changesets affecting files matched by pattern. 1263 """Changesets affecting files matched by pattern.
1249 1264
1250 For a faster but less accurate result, consider using ``filelog()`` 1265 For a faster but less accurate result, consider using ``filelog()``
1251 instead. 1266 instead.
1254 """ 1269 """
1255 # i18n: "file" is a keyword 1270 # i18n: "file" is a keyword
1256 pat = getstring(x, _("file requires a pattern")) 1271 pat = getstring(x, _("file requires a pattern"))
1257 return _matchfiles(repo, subset, ('string', 'p:' + pat)) 1272 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1258 1273
1259 @predicate('head()') 1274 @predicate('head()', safe=True)
1260 def head(repo, subset, x): 1275 def head(repo, subset, x):
1261 """Changeset is a named branch head. 1276 """Changeset is a named branch head.
1262 """ 1277 """
1263 # i18n: "head" is a keyword 1278 # i18n: "head" is a keyword
1264 getargs(x, 0, 0, _("head takes no arguments")) 1279 getargs(x, 0, 0, _("head takes no arguments"))
1270 # This does not break because of other fullreposet misbehavior. 1285 # This does not break because of other fullreposet misbehavior.
1271 # XXX We should combine with subset first: 'subset & baseset(...)'. This is 1286 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
1272 # necessary to ensure we preserve the order in subset. 1287 # necessary to ensure we preserve the order in subset.
1273 return baseset(hs) & subset 1288 return baseset(hs) & subset
1274 1289
1275 @predicate('heads(set)') 1290 @predicate('heads(set)', safe=True)
1276 def heads(repo, subset, x): 1291 def heads(repo, subset, x):
1277 """Members of set with no children in set. 1292 """Members of set with no children in set.
1278 """ 1293 """
1279 s = getset(repo, subset, x) 1294 s = getset(repo, subset, x)
1280 ps = parents(repo, subset, x) 1295 ps = parents(repo, subset, x)
1281 return s - ps 1296 return s - ps
1282 1297
1283 @predicate('hidden()') 1298 @predicate('hidden()', safe=True)
1284 def hidden(repo, subset, x): 1299 def hidden(repo, subset, x):
1285 """Hidden changesets. 1300 """Hidden changesets.
1286 """ 1301 """
1287 # i18n: "hidden" is a keyword 1302 # i18n: "hidden" is a keyword
1288 getargs(x, 0, 0, _("hidden takes no arguments")) 1303 getargs(x, 0, 0, _("hidden takes no arguments"))
1289 hiddenrevs = repoview.filterrevs(repo, 'visible') 1304 hiddenrevs = repoview.filterrevs(repo, 'visible')
1290 return subset & hiddenrevs 1305 return subset & hiddenrevs
1291 1306
1292 @predicate('keyword(string)') 1307 @predicate('keyword(string)', safe=True)
1293 def keyword(repo, subset, x): 1308 def keyword(repo, subset, x):
1294 """Search commit message, user name, and names of changed files for 1309 """Search commit message, user name, and names of changed files for
1295 string. The match is case-insensitive. 1310 string. The match is case-insensitive.
1296 """ 1311 """
1297 # i18n: "keyword" is a keyword 1312 # i18n: "keyword" is a keyword
1302 return any(kw in encoding.lower(t) 1317 return any(kw in encoding.lower(t)
1303 for t in c.files() + [c.user(), c.description()]) 1318 for t in c.files() + [c.user(), c.description()])
1304 1319
1305 return subset.filter(matches) 1320 return subset.filter(matches)
1306 1321
1307 @predicate('limit(set[, n[, offset]])') 1322 @predicate('limit(set[, n[, offset]])', safe=True)
1308 def limit(repo, subset, x): 1323 def limit(repo, subset, x):
1309 """First n members of set, defaulting to 1, starting from offset. 1324 """First n members of set, defaulting to 1, starting from offset.
1310 """ 1325 """
1311 args = getargsdict(x, 'limit', 'set n offset') 1326 args = getargsdict(x, 'limit', 'set n offset')
1312 if 'set' not in args: 1327 if 'set' not in args:
1338 break 1353 break
1339 elif y in subset: 1354 elif y in subset:
1340 result.append(y) 1355 result.append(y)
1341 return baseset(result) 1356 return baseset(result)
1342 1357
1343 @predicate('last(set, [n])') 1358 @predicate('last(set, [n])', safe=True)
1344 def last(repo, subset, x): 1359 def last(repo, subset, x):
1345 """Last n members of set, defaulting to 1. 1360 """Last n members of set, defaulting to 1.
1346 """ 1361 """
1347 # i18n: "last" is a keyword 1362 # i18n: "last" is a keyword
1348 l = getargs(x, 1, 2, _("last requires one or two arguments")) 1363 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1364 break 1379 break
1365 elif y in subset: 1380 elif y in subset:
1366 result.append(y) 1381 result.append(y)
1367 return baseset(result) 1382 return baseset(result)
1368 1383
1369 @predicate('max(set)') 1384 @predicate('max(set)', safe=True)
1370 def maxrev(repo, subset, x): 1385 def maxrev(repo, subset, x):
1371 """Changeset with highest revision number in set. 1386 """Changeset with highest revision number in set.
1372 """ 1387 """
1373 os = getset(repo, fullreposet(repo), x) 1388 os = getset(repo, fullreposet(repo), x)
1374 try: 1389 try:
1379 # os.max() throws a ValueError when the collection is empty. 1394 # os.max() throws a ValueError when the collection is empty.
1380 # Same as python's max(). 1395 # Same as python's max().
1381 pass 1396 pass
1382 return baseset() 1397 return baseset()
1383 1398
1384 @predicate('merge()') 1399 @predicate('merge()', safe=True)
1385 def merge(repo, subset, x): 1400 def merge(repo, subset, x):
1386 """Changeset is a merge changeset. 1401 """Changeset is a merge changeset.
1387 """ 1402 """
1388 # i18n: "merge" is a keyword 1403 # i18n: "merge" is a keyword
1389 getargs(x, 0, 0, _("merge takes no arguments")) 1404 getargs(x, 0, 0, _("merge takes no arguments"))
1390 cl = repo.changelog 1405 cl = repo.changelog
1391 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1) 1406 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1392 1407
1393 @predicate('branchpoint()') 1408 @predicate('branchpoint()', safe=True)
1394 def branchpoint(repo, subset, x): 1409 def branchpoint(repo, subset, x):
1395 """Changesets with more than one child. 1410 """Changesets with more than one child.
1396 """ 1411 """
1397 # i18n: "branchpoint" is a keyword 1412 # i18n: "branchpoint" is a keyword
1398 getargs(x, 0, 0, _("branchpoint takes no arguments")) 1413 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1407 for p in cl.parentrevs(r): 1422 for p in cl.parentrevs(r):
1408 if p >= baserev: 1423 if p >= baserev:
1409 parentscount[p - baserev] += 1 1424 parentscount[p - baserev] += 1
1410 return subset.filter(lambda r: parentscount[r - baserev] > 1) 1425 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1411 1426
1412 @predicate('min(set)') 1427 @predicate('min(set)', safe=True)
1413 def minrev(repo, subset, x): 1428 def minrev(repo, subset, x):
1414 """Changeset with lowest revision number in set. 1429 """Changeset with lowest revision number in set.
1415 """ 1430 """
1416 os = getset(repo, fullreposet(repo), x) 1431 os = getset(repo, fullreposet(repo), x)
1417 try: 1432 try:
1422 # os.min() throws a ValueError when the collection is empty. 1437 # os.min() throws a ValueError when the collection is empty.
1423 # Same as python's min(). 1438 # Same as python's min().
1424 pass 1439 pass
1425 return baseset() 1440 return baseset()
1426 1441
1427 @predicate('modifies(pattern)') 1442 @predicate('modifies(pattern)', safe=True)
1428 def modifies(repo, subset, x): 1443 def modifies(repo, subset, x):
1429 """Changesets modifying files matched by pattern. 1444 """Changesets modifying files matched by pattern.
1430 1445
1431 The pattern without explicit kind like ``glob:`` is expected to be 1446 The pattern without explicit kind like ``glob:`` is expected to be
1432 relative to the current directory and match against a file or a 1447 relative to the current directory and match against a file or a
1472 names.update(repo[n].rev() for n in ns.nodes(repo, name)) 1487 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1473 1488
1474 names -= set([node.nullrev]) 1489 names -= set([node.nullrev])
1475 return subset & names 1490 return subset & names
1476 1491
1477 @predicate('id(string)') 1492 @predicate('id(string)', safe=True)
1478 def node_(repo, subset, x): 1493 def node_(repo, subset, x):
1479 """Revision non-ambiguously specified by the given hex string prefix. 1494 """Revision non-ambiguously specified by the given hex string prefix.
1480 """ 1495 """
1481 # i18n: "id" is a keyword 1496 # i18n: "id" is a keyword
1482 l = getargs(x, 1, 1, _("id requires one argument")) 1497 l = getargs(x, 1, 1, _("id requires one argument"))
1496 if rn is None: 1511 if rn is None:
1497 return baseset() 1512 return baseset()
1498 result = baseset([rn]) 1513 result = baseset([rn])
1499 return result & subset 1514 return result & subset
1500 1515
1501 @predicate('obsolete()') 1516 @predicate('obsolete()', safe=True)
1502 def obsolete(repo, subset, x): 1517 def obsolete(repo, subset, x):
1503 """Mutable changeset with a newer version.""" 1518 """Mutable changeset with a newer version."""
1504 # i18n: "obsolete" is a keyword 1519 # i18n: "obsolete" is a keyword
1505 getargs(x, 0, 0, _("obsolete takes no arguments")) 1520 getargs(x, 0, 0, _("obsolete takes no arguments"))
1506 obsoletes = obsmod.getrevs(repo, 'obsolete') 1521 obsoletes = obsmod.getrevs(repo, 'obsolete')
1507 return subset & obsoletes 1522 return subset & obsoletes
1508 1523
1509 @predicate('only(set, [set])') 1524 @predicate('only(set, [set])', safe=True)
1510 def only(repo, subset, x): 1525 def only(repo, subset, x):
1511 """Changesets that are ancestors of the first set that are not ancestors 1526 """Changesets that are ancestors of the first set that are not ancestors
1512 of any other head in the repo. If a second set is specified, the result 1527 of any other head in the repo. If a second set is specified, the result
1513 is ancestors of the first set that are not ancestors of the second set 1528 is ancestors of the first set that are not ancestors of the second set
1514 (i.e. ::<set1> - ::<set2>). 1529 (i.e. ::<set1> - ::<set2>).
1530 results = set(cl.findmissingrevs(common=exclude, heads=include)) 1545 results = set(cl.findmissingrevs(common=exclude, heads=include))
1531 # XXX we should turn this into a baseset instead of a set, smartset may do 1546 # XXX we should turn this into a baseset instead of a set, smartset may do
1532 # some optimisations from the fact this is a baseset. 1547 # some optimisations from the fact this is a baseset.
1533 return subset & results 1548 return subset & results
1534 1549
1535 @predicate('origin([set])') 1550 @predicate('origin([set])', safe=True)
1536 def origin(repo, subset, x): 1551 def origin(repo, subset, x):
1537 """ 1552 """
1538 Changesets that were specified as a source for the grafts, transplants or 1553 Changesets that were specified as a source for the grafts, transplants or
1539 rebases that created the given revisions. Omitting the optional set is the 1554 rebases that created the given revisions. Omitting the optional set is the
1540 same as passing all(). If a changeset created by these operations is itself 1555 same as passing all(). If a changeset created by these operations is itself
1562 o -= set([None]) 1577 o -= set([None])
1563 # XXX we should turn this into a baseset instead of a set, smartset may do 1578 # XXX we should turn this into a baseset instead of a set, smartset may do
1564 # some optimisations from the fact this is a baseset. 1579 # some optimisations from the fact this is a baseset.
1565 return subset & o 1580 return subset & o
1566 1581
1567 @predicate('outgoing([path])') 1582 @predicate('outgoing([path])', safe=True)
1568 def outgoing(repo, subset, x): 1583 def outgoing(repo, subset, x):
1569 """Changesets not found in the specified destination repository, or the 1584 """Changesets not found in the specified destination repository, or the
1570 default push location. 1585 default push location.
1571 """ 1586 """
1572 # Avoid cycles. 1587 # Avoid cycles.
1589 repo.ui.popbuffer() 1604 repo.ui.popbuffer()
1590 cl = repo.changelog 1605 cl = repo.changelog
1591 o = set([cl.rev(r) for r in outgoing.missing]) 1606 o = set([cl.rev(r) for r in outgoing.missing])
1592 return subset & o 1607 return subset & o
1593 1608
1594 @predicate('p1([set])') 1609 @predicate('p1([set])', safe=True)
1595 def p1(repo, subset, x): 1610 def p1(repo, subset, x):
1596 """First parent of changesets in set, or the working directory. 1611 """First parent of changesets in set, or the working directory.
1597 """ 1612 """
1598 if x is None: 1613 if x is None:
1599 p = repo[x].p1().rev() 1614 p = repo[x].p1().rev()
1608 ps -= set([node.nullrev]) 1623 ps -= set([node.nullrev])
1609 # XXX we should turn this into a baseset instead of a set, smartset may do 1624 # XXX we should turn this into a baseset instead of a set, smartset may do
1610 # some optimisations from the fact this is a baseset. 1625 # some optimisations from the fact this is a baseset.
1611 return subset & ps 1626 return subset & ps
1612 1627
1613 @predicate('p2([set])') 1628 @predicate('p2([set])', safe=True)
1614 def p2(repo, subset, x): 1629 def p2(repo, subset, x):
1615 """Second parent of changesets in set, or the working directory. 1630 """Second parent of changesets in set, or the working directory.
1616 """ 1631 """
1617 if x is None: 1632 if x is None:
1618 ps = repo[x].parents() 1633 ps = repo[x].parents()
1631 ps -= set([node.nullrev]) 1646 ps -= set([node.nullrev])
1632 # XXX we should turn this into a baseset instead of a set, smartset may do 1647 # XXX we should turn this into a baseset instead of a set, smartset may do
1633 # some optimisations from the fact this is a baseset. 1648 # some optimisations from the fact this is a baseset.
1634 return subset & ps 1649 return subset & ps
1635 1650
1636 @predicate('parents([set])') 1651 @predicate('parents([set])', safe=True)
1637 def parents(repo, subset, x): 1652 def parents(repo, subset, x):
1638 """ 1653 """
1639 The set of all parents for all changesets in set, or the working directory. 1654 The set of all parents for all changesets in set, or the working directory.
1640 """ 1655 """
1641 if x is None: 1656 if x is None:
1664 else: 1679 else:
1665 phase = repo._phasecache.phase 1680 phase = repo._phasecache.phase
1666 condition = lambda r: phase(repo, r) == target 1681 condition = lambda r: phase(repo, r) == target
1667 return subset.filter(condition, cache=False) 1682 return subset.filter(condition, cache=False)
1668 1683
1669 @predicate('draft()') 1684 @predicate('draft()', safe=True)
1670 def draft(repo, subset, x): 1685 def draft(repo, subset, x):
1671 """Changeset in draft phase.""" 1686 """Changeset in draft phase."""
1672 # i18n: "draft" is a keyword 1687 # i18n: "draft" is a keyword
1673 getargs(x, 0, 0, _("draft takes no arguments")) 1688 getargs(x, 0, 0, _("draft takes no arguments"))
1674 target = phases.draft 1689 target = phases.draft
1675 return _phase(repo, subset, target) 1690 return _phase(repo, subset, target)
1676 1691
1677 @predicate('secret()') 1692 @predicate('secret()', safe=True)
1678 def secret(repo, subset, x): 1693 def secret(repo, subset, x):
1679 """Changeset in secret phase.""" 1694 """Changeset in secret phase."""
1680 # i18n: "secret" is a keyword 1695 # i18n: "secret" is a keyword
1681 getargs(x, 0, 0, _("secret takes no arguments")) 1696 getargs(x, 0, 0, _("secret takes no arguments"))
1682 target = phases.secret 1697 target = phases.secret
1705 parents = cl.parentrevs(r) 1720 parents = cl.parentrevs(r)
1706 if len(parents) > 1: 1721 if len(parents) > 1:
1707 ps.add(parents[1]) 1722 ps.add(parents[1])
1708 return subset & ps 1723 return subset & ps
1709 1724
1710 @predicate('present(set)') 1725 @predicate('present(set)', safe=True)
1711 def present(repo, subset, x): 1726 def present(repo, subset, x):
1712 """An empty set, if any revision in set isn't found; otherwise, 1727 """An empty set, if any revision in set isn't found; otherwise,
1713 all revisions in set. 1728 all revisions in set.
1714 1729
1715 If any of specified revisions is not present in the local repository, 1730 If any of specified revisions is not present in the local repository,
1720 return getset(repo, subset, x) 1735 return getset(repo, subset, x)
1721 except error.RepoLookupError: 1736 except error.RepoLookupError:
1722 return baseset() 1737 return baseset()
1723 1738
1724 # for internal use 1739 # for internal use
1725 @predicate('_notpublic') 1740 @predicate('_notpublic', safe=True)
1726 def _notpublic(repo, subset, x): 1741 def _notpublic(repo, subset, x):
1727 getargs(x, 0, 0, "_notpublic takes no arguments") 1742 getargs(x, 0, 0, "_notpublic takes no arguments")
1728 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded 1743 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1729 if repo._phasecache._phasesets: 1744 if repo._phasecache._phasesets:
1730 s = set() 1745 s = set()
1737 phase = repo._phasecache.phase 1752 phase = repo._phasecache.phase
1738 target = phases.public 1753 target = phases.public
1739 condition = lambda r: phase(repo, r) != target 1754 condition = lambda r: phase(repo, r) != target
1740 return subset.filter(condition, cache=False) 1755 return subset.filter(condition, cache=False)
1741 1756
1742 @predicate('public()') 1757 @predicate('public()', safe=True)
1743 def public(repo, subset, x): 1758 def public(repo, subset, x):
1744 """Changeset in public phase.""" 1759 """Changeset in public phase."""
1745 # i18n: "public" is a keyword 1760 # i18n: "public" is a keyword
1746 getargs(x, 0, 0, _("public takes no arguments")) 1761 getargs(x, 0, 0, _("public takes no arguments"))
1747 phase = repo._phasecache.phase 1762 phase = repo._phasecache.phase
1748 target = phases.public 1763 target = phases.public
1749 condition = lambda r: phase(repo, r) == target 1764 condition = lambda r: phase(repo, r) == target
1750 return subset.filter(condition, cache=False) 1765 return subset.filter(condition, cache=False)
1751 1766
1752 @predicate('remote([id [,path]])') 1767 @predicate('remote([id [,path]])', safe=True)
1753 def remote(repo, subset, x): 1768 def remote(repo, subset, x):
1754 """Local revision that corresponds to the given identifier in a 1769 """Local revision that corresponds to the given identifier in a
1755 remote repository, if present. Here, the '.' identifier is a 1770 remote repository, if present. Here, the '.' identifier is a
1756 synonym for the current local branch. 1771 synonym for the current local branch.
1757 """ 1772 """
1782 r = repo[n].rev() 1797 r = repo[n].rev()
1783 if r in subset: 1798 if r in subset:
1784 return baseset([r]) 1799 return baseset([r])
1785 return baseset() 1800 return baseset()
1786 1801
1787 @predicate('removes(pattern)') 1802 @predicate('removes(pattern)', safe=True)
1788 def removes(repo, subset, x): 1803 def removes(repo, subset, x):
1789 """Changesets which remove files matching pattern. 1804 """Changesets which remove files matching pattern.
1790 1805
1791 The pattern without explicit kind like ``glob:`` is expected to be 1806 The pattern without explicit kind like ``glob:`` is expected to be
1792 relative to the current directory and match against a file or a 1807 relative to the current directory and match against a file or a
1794 """ 1809 """
1795 # i18n: "removes" is a keyword 1810 # i18n: "removes" is a keyword
1796 pat = getstring(x, _("removes requires a pattern")) 1811 pat = getstring(x, _("removes requires a pattern"))
1797 return checkstatus(repo, subset, pat, 2) 1812 return checkstatus(repo, subset, pat, 2)
1798 1813
1799 @predicate('rev(number)') 1814 @predicate('rev(number)', safe=True)
1800 def rev(repo, subset, x): 1815 def rev(repo, subset, x):
1801 """Revision with the given numeric identifier. 1816 """Revision with the given numeric identifier.
1802 """ 1817 """
1803 # i18n: "rev" is a keyword 1818 # i18n: "rev" is a keyword
1804 l = getargs(x, 1, 1, _("rev requires one argument")) 1819 l = getargs(x, 1, 1, _("rev requires one argument"))
1810 raise error.ParseError(_("rev expects a number")) 1825 raise error.ParseError(_("rev expects a number"))
1811 if l not in repo.changelog and l != node.nullrev: 1826 if l not in repo.changelog and l != node.nullrev:
1812 return baseset() 1827 return baseset()
1813 return subset & baseset([l]) 1828 return subset & baseset([l])
1814 1829
1815 @predicate('matching(revision [, field])') 1830 @predicate('matching(revision [, field])', safe=True)
1816 def matching(repo, subset, x): 1831 def matching(repo, subset, x):
1817 """Changesets in which a given set of fields match the set of fields in the 1832 """Changesets in which a given set of fields match the set of fields in the
1818 selected revision or set. 1833 selected revision or set.
1819 1834
1820 To match more than one field pass the list of fields to match separated 1835 To match more than one field pass the list of fields to match separated
1922 return True 1937 return True
1923 return False 1938 return False
1924 1939
1925 return subset.filter(matches) 1940 return subset.filter(matches)
1926 1941
1927 @predicate('reverse(set)') 1942 @predicate('reverse(set)', safe=True)
1928 def reverse(repo, subset, x): 1943 def reverse(repo, subset, x):
1929 """Reverse order of set. 1944 """Reverse order of set.
1930 """ 1945 """
1931 l = getset(repo, subset, x) 1946 l = getset(repo, subset, x)
1932 l.reverse() 1947 l.reverse()
1933 return l 1948 return l
1934 1949
1935 @predicate('roots(set)') 1950 @predicate('roots(set)', safe=True)
1936 def roots(repo, subset, x): 1951 def roots(repo, subset, x):
1937 """Changesets in set with no parent changeset in set. 1952 """Changesets in set with no parent changeset in set.
1938 """ 1953 """
1939 s = getset(repo, fullreposet(repo), x) 1954 s = getset(repo, fullreposet(repo), x)
1940 parents = repo.changelog.parentrevs 1955 parents = repo.changelog.parentrevs
1943 if 0 <= p and p in s: 1958 if 0 <= p and p in s:
1944 return False 1959 return False
1945 return True 1960 return True
1946 return subset & s.filter(filter) 1961 return subset & s.filter(filter)
1947 1962
1948 @predicate('sort(set[, [-]key...])') 1963 @predicate('sort(set[, [-]key...])', safe=True)
1949 def sort(repo, subset, x): 1964 def sort(repo, subset, x):
1950 """Sort set by keys. The default sort order is ascending, specify a key 1965 """Sort set by keys. The default sort order is ascending, specify a key
1951 as ``-key`` to sort in descending order. 1966 as ``-key`` to sort in descending order.
1952 1967
1953 The keys can be: 1968 The keys can be:
2055 kind, pattern, matcher = util.stringmatcher(pattern) 2070 kind, pattern, matcher = util.stringmatcher(pattern)
2056 if kind == 'literal': 2071 if kind == 'literal':
2057 matcher = lambda s: pattern in s 2072 matcher = lambda s: pattern in s
2058 return kind, pattern, matcher 2073 return kind, pattern, matcher
2059 2074
2060 @predicate('tag([name])') 2075 @predicate('tag([name])', safe=True)
2061 def tag(repo, subset, x): 2076 def tag(repo, subset, x):
2062 """The specified tag by name, or all tagged revisions if no name is given. 2077 """The specified tag by name, or all tagged revisions if no name is given.
2063 2078
2064 If `name` starts with `re:`, the remainder of the name is treated as 2079 If `name` starts with `re:`, the remainder of the name is treated as
2065 a regular expression. To match a tag that actually starts with `re:`, 2080 a regular expression. To match a tag that actually starts with `re:`,
2084 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)]) 2099 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
2085 else: 2100 else:
2086 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip']) 2101 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
2087 return subset & s 2102 return subset & s
2088 2103
2089 @predicate('tagged') 2104 @predicate('tagged', safe=True)
2090 def tagged(repo, subset, x): 2105 def tagged(repo, subset, x):
2091 return tag(repo, subset, x) 2106 return tag(repo, subset, x)
2092 2107
2093 @predicate('unstable()') 2108 @predicate('unstable()', safe=True)
2094 def unstable(repo, subset, x): 2109 def unstable(repo, subset, x):
2095 """Non-obsolete changesets with obsolete ancestors. 2110 """Non-obsolete changesets with obsolete ancestors.
2096 """ 2111 """
2097 # i18n: "unstable" is a keyword 2112 # i18n: "unstable" is a keyword
2098 getargs(x, 0, 0, _("unstable takes no arguments")) 2113 getargs(x, 0, 0, _("unstable takes no arguments"))
2099 unstables = obsmod.getrevs(repo, 'unstable') 2114 unstables = obsmod.getrevs(repo, 'unstable')
2100 return subset & unstables 2115 return subset & unstables
2101 2116
2102 2117
2103 @predicate('user(string)') 2118 @predicate('user(string)', safe=True)
2104 def user(repo, subset, x): 2119 def user(repo, subset, x):
2105 """User name contains string. The match is case-insensitive. 2120 """User name contains string. The match is case-insensitive.
2106 2121
2107 If `string` starts with `re:`, the remainder of the string is treated as 2122 If `string` starts with `re:`, the remainder of the string is treated as
2108 a regular expression. To match a user that actually contains `re:`, use 2123 a regular expression. To match a user that actually contains `re:`, use
2109 the prefix `literal:`. 2124 the prefix `literal:`.
2110 """ 2125 """
2111 return author(repo, subset, x) 2126 return author(repo, subset, x)
2112 2127
2113 # experimental 2128 # experimental
2114 @predicate('wdir') 2129 @predicate('wdir', safe=True)
2115 def wdir(repo, subset, x): 2130 def wdir(repo, subset, x):
2116 # i18n: "wdir" is a keyword 2131 # i18n: "wdir" is a keyword
2117 getargs(x, 0, 0, _("wdir takes no arguments")) 2132 getargs(x, 0, 0, _("wdir takes no arguments"))
2118 if node.wdirrev in subset or isinstance(subset, fullreposet): 2133 if node.wdirrev in subset or isinstance(subset, fullreposet):
2119 return baseset([node.wdirrev]) 2134 return baseset([node.wdirrev])
2120 return baseset() 2135 return baseset()
2121 2136
2122 # for internal use 2137 # for internal use
2123 @predicate('_list') 2138 @predicate('_list', safe=True)
2124 def _list(repo, subset, x): 2139 def _list(repo, subset, x):
2125 s = getstring(x, "internal error") 2140 s = getstring(x, "internal error")
2126 if not s: 2141 if not s:
2127 return baseset() 2142 return baseset()
2128 # remove duplicates here. it's difficult for caller to deduplicate sets 2143 # remove duplicates here. it's difficult for caller to deduplicate sets
2148 ls.append(r) 2163 ls.append(r)
2149 seen.add(r) 2164 seen.add(r)
2150 return baseset(ls) 2165 return baseset(ls)
2151 2166
2152 # for internal use 2167 # for internal use
2153 @predicate('_intlist') 2168 @predicate('_intlist', safe=True)
2154 def _intlist(repo, subset, x): 2169 def _intlist(repo, subset, x):
2155 s = getstring(x, "internal error") 2170 s = getstring(x, "internal error")
2156 if not s: 2171 if not s:
2157 return baseset() 2172 return baseset()
2158 ls = [int(r) for r in s.split('\0')] 2173 ls = [int(r) for r in s.split('\0')]
2159 s = subset 2174 s = subset
2160 return baseset([r for r in ls if r in s]) 2175 return baseset([r for r in ls if r in s])
2161 2176
2162 # for internal use 2177 # for internal use
2163 @predicate('_hexlist') 2178 @predicate('_hexlist', safe=True)
2164 def _hexlist(repo, subset, x): 2179 def _hexlist(repo, subset, x):
2165 s = getstring(x, "internal error") 2180 s = getstring(x, "internal error")
2166 if not s: 2181 if not s:
2167 return baseset() 2182 return baseset()
2168 cl = repo.changelog 2183 cl = repo.changelog
2169 ls = [cl.rev(node.bin(r)) for r in s.split('\0')] 2184 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2170 s = subset 2185 s = subset
2171 return baseset([r for r in ls if r in s]) 2186 return baseset([r for r in ls if r in s])
2172
2173 # symbols which can't be used for a DoS attack for any given input
2174 # (e.g. those which accept regexes as plain strings shouldn't be included)
2175 # functions that just return a lot of changesets (like all) don't count here
2176 safesymbols = set([
2177 "adds",
2178 "all",
2179 "ancestor",
2180 "ancestors",
2181 "_firstancestors",
2182 "author",
2183 "bisect",
2184 "bisected",
2185 "bookmark",
2186 "branch",
2187 "branchpoint",
2188 "bumped",
2189 "bundle",
2190 "children",
2191 "closed",
2192 "converted",
2193 "date",
2194 "desc",
2195 "descendants",
2196 "_firstdescendants",
2197 "destination",
2198 "divergent",
2199 "draft",
2200 "extinct",
2201 "extra",
2202 "file",
2203 "filelog",
2204 "first",
2205 "follow",
2206 "_followfirst",
2207 "head",
2208 "heads",
2209 "hidden",
2210 "id",
2211 "keyword",
2212 "last",
2213 "limit",
2214 "_matchfiles",
2215 "max",
2216 "merge",
2217 "min",
2218 "modifies",
2219 "obsolete",
2220 "only",
2221 "origin",
2222 "outgoing",
2223 "p1",
2224 "p2",
2225 "parents",
2226 "present",
2227 "public",
2228 "_notpublic",
2229 "remote",
2230 "removes",
2231 "rev",
2232 "reverse",
2233 "roots",
2234 "sort",
2235 "secret",
2236 "matching",
2237 "tag",
2238 "tagged",
2239 "user",
2240 "unstable",
2241 "wdir",
2242 "_list",
2243 "_intlist",
2244 "_hexlist",
2245 ])
2246 2187
2247 methods = { 2188 methods = {
2248 "range": rangeset, 2189 "range": rangeset,
2249 "dagrange": dagrange, 2190 "dagrange": dagrange,
2250 "string": stringset, 2191 "string": stringset,