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