##// END OF EJS Templates
revset: make use of natively-computed set for 'draft()' and 'secret()'...
Pierre-Yves David -
r25622:85294076 default
parent child Browse files
Show More
@@ -1,126 +1,130 b''
1 # All revsets ever used with revsetbenchmarks.py script
1 # All revsets ever used with revsetbenchmarks.py script
2 #
2 #
3 # The goal of this file is to gather all revsets ever used for benchmarking
3 # The goal of this file is to gather all revsets ever used for benchmarking
4 # revset's performance. It should be used to gather revsets that test a
4 # revset's performance. It should be used to gather revsets that test a
5 # specific usecase or a specific implementation of revset predicates.
5 # specific usecase or a specific implementation of revset predicates.
6 # If you are working on the smartset implementation itself, check
6 # If you are working on the smartset implementation itself, check
7 # 'base-revsets.txt'.
7 # 'base-revsets.txt'.
8 #
8 #
9 # Please update this file with any revsets you use for benchmarking a change so
9 # Please update this file with any revsets you use for benchmarking a change so
10 # that future contributors can easily find and retest it when doing further
10 # that future contributors can easily find and retest it when doing further
11 # modification. Feel free to highlight interresting variants if needed.
11 # modification. Feel free to highlight interresting variants if needed.
12
12
13
13
14 ## Revset from this section are all extracted from changelog when this file was
14 ## Revset from this section are all extracted from changelog when this file was
15 # created. Feel free to dig and improve documentation.
15 # created. Feel free to dig and improve documentation.
16
16
17 # Used in revision da05fe01170b
17 # Used in revision da05fe01170b
18 (20000::) - (20000)
18 (20000::) - (20000)
19 # Used in revision 95af98616aa7
19 # Used in revision 95af98616aa7
20 parents(20000)
20 parents(20000)
21 # Used in revision 186fd06283b4
21 # Used in revision 186fd06283b4
22 (_intlist('20000\x0020001')) and merge()
22 (_intlist('20000\x0020001')) and merge()
23 # Used in revision 911f5a6579d1
23 # Used in revision 911f5a6579d1
24 p1(20000)
24 p1(20000)
25 p2(10000)
25 p2(10000)
26 # Used in revision f140d6207cca
26 # Used in revision f140d6207cca
27 roots(0:tip)
27 roots(0:tip)
28 # Used in revision b6dc3b79bb25
28 # Used in revision b6dc3b79bb25
29 0::
29 0::
30 # Used in revision faf4f63533ff
30 # Used in revision faf4f63533ff
31 bookmark()
31 bookmark()
32 # Used in revision 22ba2c0825da
32 # Used in revision 22ba2c0825da
33 tip~25
33 tip~25
34 # Used in revision 0cf46b8298fe
34 # Used in revision 0cf46b8298fe
35 bisect(range)
35 bisect(range)
36 # Used in revision 5b65429721d5
36 # Used in revision 5b65429721d5
37 divergent()
37 divergent()
38 # Used in revision 6261b9c549a2
38 # Used in revision 6261b9c549a2
39 file(COPYING)
39 file(COPYING)
40 # Used in revision 44f471102f3a
40 # Used in revision 44f471102f3a
41 follow(COPYING)
41 follow(COPYING)
42 # Used in revision 8040a44aab1c
42 # Used in revision 8040a44aab1c
43 origin(tip)
43 origin(tip)
44 # Used in revision bbf4f3dfd700
44 # Used in revision bbf4f3dfd700
45 rev(25)
45 rev(25)
46 # Used in revision a428db9ab61d
46 # Used in revision a428db9ab61d
47 p1()
47 p1()
48 # Used in revision c1546d7400ef
48 # Used in revision c1546d7400ef
49 min(0::)
49 min(0::)
50 # Used in revision 546fa6576815
50 # Used in revision 546fa6576815
51 author(lmoscovicz) or author(mpm)
51 author(lmoscovicz) or author(mpm)
52 author(mpm) or author(lmoscovicz)
52 author(mpm) or author(lmoscovicz)
53 # Used in revision 9bfe68357c01
53 # Used in revision 9bfe68357c01
54 public() and id("d82e2223f132")
54 public() and id("d82e2223f132")
55 # Used in revision ba89f7b542c9
55 # Used in revision ba89f7b542c9
56 rev(25)
56 rev(25)
57 # Used in revision eb763217152a
57 # Used in revision eb763217152a
58 rev(210000)
58 rev(210000)
59 # Used in revision 69524a05a7fa
59 # Used in revision 69524a05a7fa
60 10:100
60 10:100
61 parents(10):parents(100)
61 parents(10):parents(100)
62 # Used in revision 6f1b8b3f12fd
62 # Used in revision 6f1b8b3f12fd
63 100~5
63 100~5
64 parents(100)~5
64 parents(100)~5
65 (100~5)~5
65 (100~5)~5
66 # Used in revision 7a42e5d4c418
66 # Used in revision 7a42e5d4c418
67 children(tip~100)
67 children(tip~100)
68 # Used in revision 7e8737e6ab08
68 # Used in revision 7e8737e6ab08
69 100^1
69 100^1
70 parents(100)^1
70 parents(100)^1
71 (100^1)^1
71 (100^1)^1
72 # Used in revision 30e0dcd7c5ff
72 # Used in revision 30e0dcd7c5ff
73 matching(100)
73 matching(100)
74 matching(parents(100))
74 matching(parents(100))
75 # Used in revision aafeaba22826
75 # Used in revision aafeaba22826
76 0|1|2|3|4|5|6|7|8|9
76 0|1|2|3|4|5|6|7|8|9
77 # Used in revision 33c7a94d4dd0
77 # Used in revision 33c7a94d4dd0
78 tip:0
78 tip:0
79 # Used in revision 7d369fae098e
79 # Used in revision 7d369fae098e
80 (0:100000)
80 (0:100000)
81 # Used in revision b333ca94403d
81 # Used in revision b333ca94403d
82 0 + 1 + 2 + ... + 200
82 0 + 1 + 2 + ... + 200
83 0 + 1 + 2 + ... + 1000
83 0 + 1 + 2 + ... + 1000
84 sort(0 + 1 + 2 + ... + 200)
84 sort(0 + 1 + 2 + ... + 200)
85 sort(0 + 1 + 2 + ... + 1000)
85 sort(0 + 1 + 2 + ... + 1000)
86 # Used in revision 7fbef7932af9
86 # Used in revision 7fbef7932af9
87 first(0 + 1 + 2 + ... + 1000)
87 first(0 + 1 + 2 + ... + 1000)
88 # Used in revision ceaf04bb14ff
88 # Used in revision ceaf04bb14ff
89 0:1000
89 0:1000
90 # Used in revision 262e6ad93885
90 # Used in revision 262e6ad93885
91 not public()
91 not public()
92 (tip~1000::) - public()
92 (tip~1000::) - public()
93 not public() and branch("default")
93 not public() and branch("default")
94 # Used in revision 15412bba5a68
94 # Used in revision 15412bba5a68
95 0::tip
95 0::tip
96
96
97 ## all the revsets from this section have been taken from the former central file
97 ## all the revsets from this section have been taken from the former central file
98 # for revset's benchmarking, they are undocumented for this reason.
98 # for revset's benchmarking, they are undocumented for this reason.
99 all()
99 all()
100 draft()
100 draft()
101 ::tip
101 ::tip
102 draft() and ::tip
102 draft() and ::tip
103 ::tip and draft()
103 ::tip and draft()
104 roots(0::tip)
104 roots(0::tip)
105 author(lmoscovicz)
105 author(lmoscovicz)
106 author(mpm)
106 author(mpm)
107 42:68 and roots(42:tip)
107 42:68 and roots(42:tip)
108 ::p1(p1(tip))::
108 ::p1(p1(tip))::
109 public()
109 public()
110 :10000 and public()
110 :10000 and public()
111 :10000 and draft()
111 :10000 and draft()
112 roots((0:tip)::)
112 roots((0:tip)::)
113 (not public() - obsolete())
113 (not public() - obsolete())
114
114
115 # The one below is used by rebase
115 # The one below is used by rebase
116 (children(ancestor(tip~5, tip)) and ::(tip~5))::
116 (children(ancestor(tip~5, tip)) and ::(tip~5))::
117
117
118 # those two `roots(...)` inputs are close to what phase movement use.
118 # those two `roots(...)` inputs are close to what phase movement use.
119 roots((tip~100::) - (tip~100::tip))
119 roots((tip~100::) - (tip~100::tip))
120 roots((0::) - (0::tip))
120 roots((0::) - (0::tip))
121
121
122 # Testing the behavior of "head()" in various situations
122 # Testing the behavior of "head()" in various situations
123 head()
123 head()
124 head() - public()
124 head() - public()
125 draft() and head()
125 draft() and head()
126 head() and author("mpm")
126 head() and author("mpm")
127
128 # testing the mutable phases set
129 draft()
130 secret()
@@ -1,3611 +1,3618 b''
1 # revset.py - revision set queries for mercurial
1 # revset.py - revision set queries for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re
8 import re
9 import parser, util, error, hbisect, phases
9 import parser, util, error, hbisect, phases
10 import node
10 import node
11 import heapq
11 import heapq
12 import match as matchmod
12 import match as matchmod
13 from i18n import _
13 from i18n import _
14 import encoding
14 import encoding
15 import obsolete as obsmod
15 import obsolete as obsmod
16 import pathutil
16 import pathutil
17 import repoview
17 import repoview
18
18
19 def _revancestors(repo, revs, followfirst):
19 def _revancestors(repo, revs, followfirst):
20 """Like revlog.ancestors(), but supports followfirst."""
20 """Like revlog.ancestors(), but supports followfirst."""
21 if followfirst:
21 if followfirst:
22 cut = 1
22 cut = 1
23 else:
23 else:
24 cut = None
24 cut = None
25 cl = repo.changelog
25 cl = repo.changelog
26
26
27 def iterate():
27 def iterate():
28 revs.sort(reverse=True)
28 revs.sort(reverse=True)
29 irevs = iter(revs)
29 irevs = iter(revs)
30 h = []
30 h = []
31
31
32 inputrev = next(irevs, None)
32 inputrev = next(irevs, None)
33 if inputrev is not None:
33 if inputrev is not None:
34 heapq.heappush(h, -inputrev)
34 heapq.heappush(h, -inputrev)
35
35
36 seen = set()
36 seen = set()
37 while h:
37 while h:
38 current = -heapq.heappop(h)
38 current = -heapq.heappop(h)
39 if current == inputrev:
39 if current == inputrev:
40 inputrev = next(irevs, None)
40 inputrev = next(irevs, None)
41 if inputrev is not None:
41 if inputrev is not None:
42 heapq.heappush(h, -inputrev)
42 heapq.heappush(h, -inputrev)
43 if current not in seen:
43 if current not in seen:
44 seen.add(current)
44 seen.add(current)
45 yield current
45 yield current
46 for parent in cl.parentrevs(current)[:cut]:
46 for parent in cl.parentrevs(current)[:cut]:
47 if parent != node.nullrev:
47 if parent != node.nullrev:
48 heapq.heappush(h, -parent)
48 heapq.heappush(h, -parent)
49
49
50 return generatorset(iterate(), iterasc=False)
50 return generatorset(iterate(), iterasc=False)
51
51
52 def _revdescendants(repo, revs, followfirst):
52 def _revdescendants(repo, revs, followfirst):
53 """Like revlog.descendants() but supports followfirst."""
53 """Like revlog.descendants() but supports followfirst."""
54 if followfirst:
54 if followfirst:
55 cut = 1
55 cut = 1
56 else:
56 else:
57 cut = None
57 cut = None
58
58
59 def iterate():
59 def iterate():
60 cl = repo.changelog
60 cl = repo.changelog
61 # XXX this should be 'parentset.min()' assuming 'parentset' is a
61 # XXX this should be 'parentset.min()' assuming 'parentset' is a
62 # smartset (and if it is not, it should.)
62 # smartset (and if it is not, it should.)
63 first = min(revs)
63 first = min(revs)
64 nullrev = node.nullrev
64 nullrev = node.nullrev
65 if first == nullrev:
65 if first == nullrev:
66 # Are there nodes with a null first parent and a non-null
66 # Are there nodes with a null first parent and a non-null
67 # second one? Maybe. Do we care? Probably not.
67 # second one? Maybe. Do we care? Probably not.
68 for i in cl:
68 for i in cl:
69 yield i
69 yield i
70 else:
70 else:
71 seen = set(revs)
71 seen = set(revs)
72 for i in cl.revs(first + 1):
72 for i in cl.revs(first + 1):
73 for x in cl.parentrevs(i)[:cut]:
73 for x in cl.parentrevs(i)[:cut]:
74 if x != nullrev and x in seen:
74 if x != nullrev and x in seen:
75 seen.add(i)
75 seen.add(i)
76 yield i
76 yield i
77 break
77 break
78
78
79 return generatorset(iterate(), iterasc=True)
79 return generatorset(iterate(), iterasc=True)
80
80
81 def _revsbetween(repo, roots, heads):
81 def _revsbetween(repo, roots, heads):
82 """Return all paths between roots and heads, inclusive of both endpoint
82 """Return all paths between roots and heads, inclusive of both endpoint
83 sets."""
83 sets."""
84 if not roots:
84 if not roots:
85 return baseset()
85 return baseset()
86 parentrevs = repo.changelog.parentrevs
86 parentrevs = repo.changelog.parentrevs
87 visit = list(heads)
87 visit = list(heads)
88 reachable = set()
88 reachable = set()
89 seen = {}
89 seen = {}
90 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
90 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
91 # (and if it is not, it should.)
91 # (and if it is not, it should.)
92 minroot = min(roots)
92 minroot = min(roots)
93 roots = set(roots)
93 roots = set(roots)
94 # prefetch all the things! (because python is slow)
94 # prefetch all the things! (because python is slow)
95 reached = reachable.add
95 reached = reachable.add
96 dovisit = visit.append
96 dovisit = visit.append
97 nextvisit = visit.pop
97 nextvisit = visit.pop
98 # open-code the post-order traversal due to the tiny size of
98 # open-code the post-order traversal due to the tiny size of
99 # sys.getrecursionlimit()
99 # sys.getrecursionlimit()
100 while visit:
100 while visit:
101 rev = nextvisit()
101 rev = nextvisit()
102 if rev in roots:
102 if rev in roots:
103 reached(rev)
103 reached(rev)
104 parents = parentrevs(rev)
104 parents = parentrevs(rev)
105 seen[rev] = parents
105 seen[rev] = parents
106 for parent in parents:
106 for parent in parents:
107 if parent >= minroot and parent not in seen:
107 if parent >= minroot and parent not in seen:
108 dovisit(parent)
108 dovisit(parent)
109 if not reachable:
109 if not reachable:
110 return baseset()
110 return baseset()
111 for rev in sorted(seen):
111 for rev in sorted(seen):
112 for parent in seen[rev]:
112 for parent in seen[rev]:
113 if parent in reachable:
113 if parent in reachable:
114 reached(rev)
114 reached(rev)
115 return baseset(sorted(reachable))
115 return baseset(sorted(reachable))
116
116
117 elements = {
117 elements = {
118 "(": (21, ("group", 1, ")"), ("func", 1, ")")),
118 "(": (21, ("group", 1, ")"), ("func", 1, ")")),
119 "##": (20, None, ("_concat", 20)),
119 "##": (20, None, ("_concat", 20)),
120 "~": (18, None, ("ancestor", 18)),
120 "~": (18, None, ("ancestor", 18)),
121 "^": (18, None, ("parent", 18), ("parentpost", 18)),
121 "^": (18, None, ("parent", 18), ("parentpost", 18)),
122 "-": (5, ("negate", 19), ("minus", 5)),
122 "-": (5, ("negate", 19), ("minus", 5)),
123 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
123 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
124 ("dagrangepost", 17)),
124 ("dagrangepost", 17)),
125 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
125 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
126 ("dagrangepost", 17)),
126 ("dagrangepost", 17)),
127 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
127 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
128 "not": (10, ("not", 10)),
128 "not": (10, ("not", 10)),
129 "!": (10, ("not", 10)),
129 "!": (10, ("not", 10)),
130 "and": (5, None, ("and", 5)),
130 "and": (5, None, ("and", 5)),
131 "&": (5, None, ("and", 5)),
131 "&": (5, None, ("and", 5)),
132 "%": (5, None, ("only", 5), ("onlypost", 5)),
132 "%": (5, None, ("only", 5), ("onlypost", 5)),
133 "or": (4, None, ("or", 4)),
133 "or": (4, None, ("or", 4)),
134 "|": (4, None, ("or", 4)),
134 "|": (4, None, ("or", 4)),
135 "+": (4, None, ("or", 4)),
135 "+": (4, None, ("or", 4)),
136 ",": (2, None, ("list", 2)),
136 ",": (2, None, ("list", 2)),
137 ")": (0, None, None),
137 ")": (0, None, None),
138 "symbol": (0, ("symbol",), None),
138 "symbol": (0, ("symbol",), None),
139 "string": (0, ("string",), None),
139 "string": (0, ("string",), None),
140 "end": (0, None, None),
140 "end": (0, None, None),
141 }
141 }
142
142
143 keywords = set(['and', 'or', 'not'])
143 keywords = set(['and', 'or', 'not'])
144
144
145 # default set of valid characters for the initial letter of symbols
145 # default set of valid characters for the initial letter of symbols
146 _syminitletters = set(c for c in [chr(i) for i in xrange(256)]
146 _syminitletters = set(c for c in [chr(i) for i in xrange(256)]
147 if c.isalnum() or c in '._@' or ord(c) > 127)
147 if c.isalnum() or c in '._@' or ord(c) > 127)
148
148
149 # default set of valid characters for non-initial letters of symbols
149 # default set of valid characters for non-initial letters of symbols
150 _symletters = set(c for c in [chr(i) for i in xrange(256)]
150 _symletters = set(c for c in [chr(i) for i in xrange(256)]
151 if c.isalnum() or c in '-._/@' or ord(c) > 127)
151 if c.isalnum() or c in '-._/@' or ord(c) > 127)
152
152
153 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
153 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
154 '''
154 '''
155 Parse a revset statement into a stream of tokens
155 Parse a revset statement into a stream of tokens
156
156
157 ``syminitletters`` is the set of valid characters for the initial
157 ``syminitletters`` is the set of valid characters for the initial
158 letter of symbols.
158 letter of symbols.
159
159
160 By default, character ``c`` is recognized as valid for initial
160 By default, character ``c`` is recognized as valid for initial
161 letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
161 letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
162
162
163 ``symletters`` is the set of valid characters for non-initial
163 ``symletters`` is the set of valid characters for non-initial
164 letters of symbols.
164 letters of symbols.
165
165
166 By default, character ``c`` is recognized as valid for non-initial
166 By default, character ``c`` is recognized as valid for non-initial
167 letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
167 letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
168
168
169 Check that @ is a valid unquoted token character (issue3686):
169 Check that @ is a valid unquoted token character (issue3686):
170 >>> list(tokenize("@::"))
170 >>> list(tokenize("@::"))
171 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
171 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
172
172
173 '''
173 '''
174 if syminitletters is None:
174 if syminitletters is None:
175 syminitletters = _syminitletters
175 syminitletters = _syminitletters
176 if symletters is None:
176 if symletters is None:
177 symletters = _symletters
177 symletters = _symletters
178
178
179 pos, l = 0, len(program)
179 pos, l = 0, len(program)
180 while pos < l:
180 while pos < l:
181 c = program[pos]
181 c = program[pos]
182 if c.isspace(): # skip inter-token whitespace
182 if c.isspace(): # skip inter-token whitespace
183 pass
183 pass
184 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
184 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
185 yield ('::', None, pos)
185 yield ('::', None, pos)
186 pos += 1 # skip ahead
186 pos += 1 # skip ahead
187 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
187 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
188 yield ('..', None, pos)
188 yield ('..', None, pos)
189 pos += 1 # skip ahead
189 pos += 1 # skip ahead
190 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
190 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
191 yield ('##', None, pos)
191 yield ('##', None, pos)
192 pos += 1 # skip ahead
192 pos += 1 # skip ahead
193 elif c in "():,-|&+!~^%": # handle simple operators
193 elif c in "():,-|&+!~^%": # handle simple operators
194 yield (c, None, pos)
194 yield (c, None, pos)
195 elif (c in '"\'' or c == 'r' and
195 elif (c in '"\'' or c == 'r' and
196 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
196 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
197 if c == 'r':
197 if c == 'r':
198 pos += 1
198 pos += 1
199 c = program[pos]
199 c = program[pos]
200 decode = lambda x: x
200 decode = lambda x: x
201 else:
201 else:
202 decode = lambda x: x.decode('string-escape')
202 decode = lambda x: x.decode('string-escape')
203 pos += 1
203 pos += 1
204 s = pos
204 s = pos
205 while pos < l: # find closing quote
205 while pos < l: # find closing quote
206 d = program[pos]
206 d = program[pos]
207 if d == '\\': # skip over escaped characters
207 if d == '\\': # skip over escaped characters
208 pos += 2
208 pos += 2
209 continue
209 continue
210 if d == c:
210 if d == c:
211 yield ('string', decode(program[s:pos]), s)
211 yield ('string', decode(program[s:pos]), s)
212 break
212 break
213 pos += 1
213 pos += 1
214 else:
214 else:
215 raise error.ParseError(_("unterminated string"), s)
215 raise error.ParseError(_("unterminated string"), s)
216 # gather up a symbol/keyword
216 # gather up a symbol/keyword
217 elif c in syminitletters:
217 elif c in syminitletters:
218 s = pos
218 s = pos
219 pos += 1
219 pos += 1
220 while pos < l: # find end of symbol
220 while pos < l: # find end of symbol
221 d = program[pos]
221 d = program[pos]
222 if d not in symletters:
222 if d not in symletters:
223 break
223 break
224 if d == '.' and program[pos - 1] == '.': # special case for ..
224 if d == '.' and program[pos - 1] == '.': # special case for ..
225 pos -= 1
225 pos -= 1
226 break
226 break
227 pos += 1
227 pos += 1
228 sym = program[s:pos]
228 sym = program[s:pos]
229 if sym in keywords: # operator keywords
229 if sym in keywords: # operator keywords
230 yield (sym, None, s)
230 yield (sym, None, s)
231 elif '-' in sym:
231 elif '-' in sym:
232 # some jerk gave us foo-bar-baz, try to check if it's a symbol
232 # some jerk gave us foo-bar-baz, try to check if it's a symbol
233 if lookup and lookup(sym):
233 if lookup and lookup(sym):
234 # looks like a real symbol
234 # looks like a real symbol
235 yield ('symbol', sym, s)
235 yield ('symbol', sym, s)
236 else:
236 else:
237 # looks like an expression
237 # looks like an expression
238 parts = sym.split('-')
238 parts = sym.split('-')
239 for p in parts[:-1]:
239 for p in parts[:-1]:
240 if p: # possible consecutive -
240 if p: # possible consecutive -
241 yield ('symbol', p, s)
241 yield ('symbol', p, s)
242 s += len(p)
242 s += len(p)
243 yield ('-', None, pos)
243 yield ('-', None, pos)
244 s += 1
244 s += 1
245 if parts[-1]: # possible trailing -
245 if parts[-1]: # possible trailing -
246 yield ('symbol', parts[-1], s)
246 yield ('symbol', parts[-1], s)
247 else:
247 else:
248 yield ('symbol', sym, s)
248 yield ('symbol', sym, s)
249 pos -= 1
249 pos -= 1
250 else:
250 else:
251 raise error.ParseError(_("syntax error in revset '%s'") %
251 raise error.ParseError(_("syntax error in revset '%s'") %
252 program, pos)
252 program, pos)
253 pos += 1
253 pos += 1
254 yield ('end', None, pos)
254 yield ('end', None, pos)
255
255
256 def parseerrordetail(inst):
256 def parseerrordetail(inst):
257 """Compose error message from specified ParseError object
257 """Compose error message from specified ParseError object
258 """
258 """
259 if len(inst.args) > 1:
259 if len(inst.args) > 1:
260 return _('at %s: %s') % (inst.args[1], inst.args[0])
260 return _('at %s: %s') % (inst.args[1], inst.args[0])
261 else:
261 else:
262 return inst.args[0]
262 return inst.args[0]
263
263
264 # helpers
264 # helpers
265
265
266 def getstring(x, err):
266 def getstring(x, err):
267 if x and (x[0] == 'string' or x[0] == 'symbol'):
267 if x and (x[0] == 'string' or x[0] == 'symbol'):
268 return x[1]
268 return x[1]
269 raise error.ParseError(err)
269 raise error.ParseError(err)
270
270
271 def getlist(x):
271 def getlist(x):
272 if not x:
272 if not x:
273 return []
273 return []
274 if x[0] == 'list':
274 if x[0] == 'list':
275 return getlist(x[1]) + [x[2]]
275 return getlist(x[1]) + [x[2]]
276 return [x]
276 return [x]
277
277
278 def getargs(x, min, max, err):
278 def getargs(x, min, max, err):
279 l = getlist(x)
279 l = getlist(x)
280 if len(l) < min or (max >= 0 and len(l) > max):
280 if len(l) < min or (max >= 0 and len(l) > max):
281 raise error.ParseError(err)
281 raise error.ParseError(err)
282 return l
282 return l
283
283
284 def isvalidsymbol(tree):
284 def isvalidsymbol(tree):
285 """Examine whether specified ``tree`` is valid ``symbol`` or not
285 """Examine whether specified ``tree`` is valid ``symbol`` or not
286 """
286 """
287 return tree[0] == 'symbol' and len(tree) > 1
287 return tree[0] == 'symbol' and len(tree) > 1
288
288
289 def getsymbol(tree):
289 def getsymbol(tree):
290 """Get symbol name from valid ``symbol`` in ``tree``
290 """Get symbol name from valid ``symbol`` in ``tree``
291
291
292 This assumes that ``tree`` is already examined by ``isvalidsymbol``.
292 This assumes that ``tree`` is already examined by ``isvalidsymbol``.
293 """
293 """
294 return tree[1]
294 return tree[1]
295
295
296 def isvalidfunc(tree):
296 def isvalidfunc(tree):
297 """Examine whether specified ``tree`` is valid ``func`` or not
297 """Examine whether specified ``tree`` is valid ``func`` or not
298 """
298 """
299 return tree[0] == 'func' and len(tree) > 1 and isvalidsymbol(tree[1])
299 return tree[0] == 'func' and len(tree) > 1 and isvalidsymbol(tree[1])
300
300
301 def getfuncname(tree):
301 def getfuncname(tree):
302 """Get function name from valid ``func`` in ``tree``
302 """Get function name from valid ``func`` in ``tree``
303
303
304 This assumes that ``tree`` is already examined by ``isvalidfunc``.
304 This assumes that ``tree`` is already examined by ``isvalidfunc``.
305 """
305 """
306 return getsymbol(tree[1])
306 return getsymbol(tree[1])
307
307
308 def getfuncargs(tree):
308 def getfuncargs(tree):
309 """Get list of function arguments from valid ``func`` in ``tree``
309 """Get list of function arguments from valid ``func`` in ``tree``
310
310
311 This assumes that ``tree`` is already examined by ``isvalidfunc``.
311 This assumes that ``tree`` is already examined by ``isvalidfunc``.
312 """
312 """
313 if len(tree) > 2:
313 if len(tree) > 2:
314 return getlist(tree[2])
314 return getlist(tree[2])
315 else:
315 else:
316 return []
316 return []
317
317
318 def getset(repo, subset, x):
318 def getset(repo, subset, x):
319 if not x:
319 if not x:
320 raise error.ParseError(_("missing argument"))
320 raise error.ParseError(_("missing argument"))
321 s = methods[x[0]](repo, subset, *x[1:])
321 s = methods[x[0]](repo, subset, *x[1:])
322 if util.safehasattr(s, 'isascending'):
322 if util.safehasattr(s, 'isascending'):
323 return s
323 return s
324 return baseset(s)
324 return baseset(s)
325
325
326 def _getrevsource(repo, r):
326 def _getrevsource(repo, r):
327 extra = repo[r].extra()
327 extra = repo[r].extra()
328 for label in ('source', 'transplant_source', 'rebase_source'):
328 for label in ('source', 'transplant_source', 'rebase_source'):
329 if label in extra:
329 if label in extra:
330 try:
330 try:
331 return repo[extra[label]].rev()
331 return repo[extra[label]].rev()
332 except error.RepoLookupError:
332 except error.RepoLookupError:
333 pass
333 pass
334 return None
334 return None
335
335
336 # operator methods
336 # operator methods
337
337
338 def stringset(repo, subset, x):
338 def stringset(repo, subset, x):
339 x = repo[x].rev()
339 x = repo[x].rev()
340 if (x in subset
340 if (x in subset
341 or x == node.nullrev and isinstance(subset, fullreposet)):
341 or x == node.nullrev and isinstance(subset, fullreposet)):
342 return baseset([x])
342 return baseset([x])
343 return baseset()
343 return baseset()
344
344
345 def rangeset(repo, subset, x, y):
345 def rangeset(repo, subset, x, y):
346 m = getset(repo, fullreposet(repo), x)
346 m = getset(repo, fullreposet(repo), x)
347 n = getset(repo, fullreposet(repo), y)
347 n = getset(repo, fullreposet(repo), y)
348
348
349 if not m or not n:
349 if not m or not n:
350 return baseset()
350 return baseset()
351 m, n = m.first(), n.last()
351 m, n = m.first(), n.last()
352
352
353 if m < n:
353 if m < n:
354 r = spanset(repo, m, n + 1)
354 r = spanset(repo, m, n + 1)
355 else:
355 else:
356 r = spanset(repo, m, n - 1)
356 r = spanset(repo, m, n - 1)
357 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
357 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
358 # necessary to ensure we preserve the order in subset.
358 # necessary to ensure we preserve the order in subset.
359 #
359 #
360 # This has performance implication, carrying the sorting over when possible
360 # This has performance implication, carrying the sorting over when possible
361 # would be more efficient.
361 # would be more efficient.
362 return r & subset
362 return r & subset
363
363
364 def dagrange(repo, subset, x, y):
364 def dagrange(repo, subset, x, y):
365 r = fullreposet(repo)
365 r = fullreposet(repo)
366 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
366 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
367 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
367 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
368 # necessary to ensure we preserve the order in subset.
368 # necessary to ensure we preserve the order in subset.
369 return xs & subset
369 return xs & subset
370
370
371 def andset(repo, subset, x, y):
371 def andset(repo, subset, x, y):
372 return getset(repo, getset(repo, subset, x), y)
372 return getset(repo, getset(repo, subset, x), y)
373
373
374 def orset(repo, subset, *xs):
374 def orset(repo, subset, *xs):
375 rs = [getset(repo, subset, x) for x in xs]
375 rs = [getset(repo, subset, x) for x in xs]
376 return _combinesets(rs)
376 return _combinesets(rs)
377
377
378 def notset(repo, subset, x):
378 def notset(repo, subset, x):
379 return subset - getset(repo, subset, x)
379 return subset - getset(repo, subset, x)
380
380
381 def listset(repo, subset, a, b):
381 def listset(repo, subset, a, b):
382 raise error.ParseError(_("can't use a list in this context"))
382 raise error.ParseError(_("can't use a list in this context"))
383
383
384 def func(repo, subset, a, b):
384 def func(repo, subset, a, b):
385 if a[0] == 'symbol' and a[1] in symbols:
385 if a[0] == 'symbol' and a[1] in symbols:
386 return symbols[a[1]](repo, subset, b)
386 return symbols[a[1]](repo, subset, b)
387 raise error.UnknownIdentifier(a[1], symbols.keys())
387 raise error.UnknownIdentifier(a[1], symbols.keys())
388
388
389 # functions
389 # functions
390
390
391 def adds(repo, subset, x):
391 def adds(repo, subset, x):
392 """``adds(pattern)``
392 """``adds(pattern)``
393 Changesets that add a file matching pattern.
393 Changesets that add a file matching pattern.
394
394
395 The pattern without explicit kind like ``glob:`` is expected to be
395 The pattern without explicit kind like ``glob:`` is expected to be
396 relative to the current directory and match against a file or a
396 relative to the current directory and match against a file or a
397 directory.
397 directory.
398 """
398 """
399 # i18n: "adds" is a keyword
399 # i18n: "adds" is a keyword
400 pat = getstring(x, _("adds requires a pattern"))
400 pat = getstring(x, _("adds requires a pattern"))
401 return checkstatus(repo, subset, pat, 1)
401 return checkstatus(repo, subset, pat, 1)
402
402
403 def ancestor(repo, subset, x):
403 def ancestor(repo, subset, x):
404 """``ancestor(*changeset)``
404 """``ancestor(*changeset)``
405 A greatest common ancestor of the changesets.
405 A greatest common ancestor of the changesets.
406
406
407 Accepts 0 or more changesets.
407 Accepts 0 or more changesets.
408 Will return empty list when passed no args.
408 Will return empty list when passed no args.
409 Greatest common ancestor of a single changeset is that changeset.
409 Greatest common ancestor of a single changeset is that changeset.
410 """
410 """
411 # i18n: "ancestor" is a keyword
411 # i18n: "ancestor" is a keyword
412 l = getlist(x)
412 l = getlist(x)
413 rl = fullreposet(repo)
413 rl = fullreposet(repo)
414 anc = None
414 anc = None
415
415
416 # (getset(repo, rl, i) for i in l) generates a list of lists
416 # (getset(repo, rl, i) for i in l) generates a list of lists
417 for revs in (getset(repo, rl, i) for i in l):
417 for revs in (getset(repo, rl, i) for i in l):
418 for r in revs:
418 for r in revs:
419 if anc is None:
419 if anc is None:
420 anc = repo[r]
420 anc = repo[r]
421 else:
421 else:
422 anc = anc.ancestor(repo[r])
422 anc = anc.ancestor(repo[r])
423
423
424 if anc is not None and anc.rev() in subset:
424 if anc is not None and anc.rev() in subset:
425 return baseset([anc.rev()])
425 return baseset([anc.rev()])
426 return baseset()
426 return baseset()
427
427
428 def _ancestors(repo, subset, x, followfirst=False):
428 def _ancestors(repo, subset, x, followfirst=False):
429 heads = getset(repo, fullreposet(repo), x)
429 heads = getset(repo, fullreposet(repo), x)
430 if not heads:
430 if not heads:
431 return baseset()
431 return baseset()
432 s = _revancestors(repo, heads, followfirst)
432 s = _revancestors(repo, heads, followfirst)
433 return subset & s
433 return subset & s
434
434
435 def ancestors(repo, subset, x):
435 def ancestors(repo, subset, x):
436 """``ancestors(set)``
436 """``ancestors(set)``
437 Changesets that are ancestors of a changeset in set.
437 Changesets that are ancestors of a changeset in set.
438 """
438 """
439 return _ancestors(repo, subset, x)
439 return _ancestors(repo, subset, x)
440
440
441 def _firstancestors(repo, subset, x):
441 def _firstancestors(repo, subset, x):
442 # ``_firstancestors(set)``
442 # ``_firstancestors(set)``
443 # Like ``ancestors(set)`` but follows only the first parents.
443 # Like ``ancestors(set)`` but follows only the first parents.
444 return _ancestors(repo, subset, x, followfirst=True)
444 return _ancestors(repo, subset, x, followfirst=True)
445
445
446 def ancestorspec(repo, subset, x, n):
446 def ancestorspec(repo, subset, x, n):
447 """``set~n``
447 """``set~n``
448 Changesets that are the Nth ancestor (first parents only) of a changeset
448 Changesets that are the Nth ancestor (first parents only) of a changeset
449 in set.
449 in set.
450 """
450 """
451 try:
451 try:
452 n = int(n[1])
452 n = int(n[1])
453 except (TypeError, ValueError):
453 except (TypeError, ValueError):
454 raise error.ParseError(_("~ expects a number"))
454 raise error.ParseError(_("~ expects a number"))
455 ps = set()
455 ps = set()
456 cl = repo.changelog
456 cl = repo.changelog
457 for r in getset(repo, fullreposet(repo), x):
457 for r in getset(repo, fullreposet(repo), x):
458 for i in range(n):
458 for i in range(n):
459 r = cl.parentrevs(r)[0]
459 r = cl.parentrevs(r)[0]
460 ps.add(r)
460 ps.add(r)
461 return subset & ps
461 return subset & ps
462
462
463 def author(repo, subset, x):
463 def author(repo, subset, x):
464 """``author(string)``
464 """``author(string)``
465 Alias for ``user(string)``.
465 Alias for ``user(string)``.
466 """
466 """
467 # i18n: "author" is a keyword
467 # i18n: "author" is a keyword
468 n = encoding.lower(getstring(x, _("author requires a string")))
468 n = encoding.lower(getstring(x, _("author requires a string")))
469 kind, pattern, matcher = _substringmatcher(n)
469 kind, pattern, matcher = _substringmatcher(n)
470 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
470 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
471
471
472 def bisect(repo, subset, x):
472 def bisect(repo, subset, x):
473 """``bisect(string)``
473 """``bisect(string)``
474 Changesets marked in the specified bisect status:
474 Changesets marked in the specified bisect status:
475
475
476 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
476 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
477 - ``goods``, ``bads`` : csets topologically good/bad
477 - ``goods``, ``bads`` : csets topologically good/bad
478 - ``range`` : csets taking part in the bisection
478 - ``range`` : csets taking part in the bisection
479 - ``pruned`` : csets that are goods, bads or skipped
479 - ``pruned`` : csets that are goods, bads or skipped
480 - ``untested`` : csets whose fate is yet unknown
480 - ``untested`` : csets whose fate is yet unknown
481 - ``ignored`` : csets ignored due to DAG topology
481 - ``ignored`` : csets ignored due to DAG topology
482 - ``current`` : the cset currently being bisected
482 - ``current`` : the cset currently being bisected
483 """
483 """
484 # i18n: "bisect" is a keyword
484 # i18n: "bisect" is a keyword
485 status = getstring(x, _("bisect requires a string")).lower()
485 status = getstring(x, _("bisect requires a string")).lower()
486 state = set(hbisect.get(repo, status))
486 state = set(hbisect.get(repo, status))
487 return subset & state
487 return subset & state
488
488
489 # Backward-compatibility
489 # Backward-compatibility
490 # - no help entry so that we do not advertise it any more
490 # - no help entry so that we do not advertise it any more
491 def bisected(repo, subset, x):
491 def bisected(repo, subset, x):
492 return bisect(repo, subset, x)
492 return bisect(repo, subset, x)
493
493
494 def bookmark(repo, subset, x):
494 def bookmark(repo, subset, x):
495 """``bookmark([name])``
495 """``bookmark([name])``
496 The named bookmark or all bookmarks.
496 The named bookmark or all bookmarks.
497
497
498 If `name` starts with `re:`, the remainder of the name is treated as
498 If `name` starts with `re:`, the remainder of the name is treated as
499 a regular expression. To match a bookmark that actually starts with `re:`,
499 a regular expression. To match a bookmark that actually starts with `re:`,
500 use the prefix `literal:`.
500 use the prefix `literal:`.
501 """
501 """
502 # i18n: "bookmark" is a keyword
502 # i18n: "bookmark" is a keyword
503 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
503 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
504 if args:
504 if args:
505 bm = getstring(args[0],
505 bm = getstring(args[0],
506 # i18n: "bookmark" is a keyword
506 # i18n: "bookmark" is a keyword
507 _('the argument to bookmark must be a string'))
507 _('the argument to bookmark must be a string'))
508 kind, pattern, matcher = _stringmatcher(bm)
508 kind, pattern, matcher = _stringmatcher(bm)
509 bms = set()
509 bms = set()
510 if kind == 'literal':
510 if kind == 'literal':
511 bmrev = repo._bookmarks.get(pattern, None)
511 bmrev = repo._bookmarks.get(pattern, None)
512 if not bmrev:
512 if not bmrev:
513 raise error.RepoLookupError(_("bookmark '%s' does not exist")
513 raise error.RepoLookupError(_("bookmark '%s' does not exist")
514 % bm)
514 % bm)
515 bms.add(repo[bmrev].rev())
515 bms.add(repo[bmrev].rev())
516 else:
516 else:
517 matchrevs = set()
517 matchrevs = set()
518 for name, bmrev in repo._bookmarks.iteritems():
518 for name, bmrev in repo._bookmarks.iteritems():
519 if matcher(name):
519 if matcher(name):
520 matchrevs.add(bmrev)
520 matchrevs.add(bmrev)
521 if not matchrevs:
521 if not matchrevs:
522 raise error.RepoLookupError(_("no bookmarks exist"
522 raise error.RepoLookupError(_("no bookmarks exist"
523 " that match '%s'") % pattern)
523 " that match '%s'") % pattern)
524 for bmrev in matchrevs:
524 for bmrev in matchrevs:
525 bms.add(repo[bmrev].rev())
525 bms.add(repo[bmrev].rev())
526 else:
526 else:
527 bms = set([repo[r].rev()
527 bms = set([repo[r].rev()
528 for r in repo._bookmarks.values()])
528 for r in repo._bookmarks.values()])
529 bms -= set([node.nullrev])
529 bms -= set([node.nullrev])
530 return subset & bms
530 return subset & bms
531
531
532 def branch(repo, subset, x):
532 def branch(repo, subset, x):
533 """``branch(string or set)``
533 """``branch(string or set)``
534 All changesets belonging to the given branch or the branches of the given
534 All changesets belonging to the given branch or the branches of the given
535 changesets.
535 changesets.
536
536
537 If `string` starts with `re:`, the remainder of the name is treated as
537 If `string` starts with `re:`, the remainder of the name is treated as
538 a regular expression. To match a branch that actually starts with `re:`,
538 a regular expression. To match a branch that actually starts with `re:`,
539 use the prefix `literal:`.
539 use the prefix `literal:`.
540 """
540 """
541 getbi = repo.revbranchcache().branchinfo
541 getbi = repo.revbranchcache().branchinfo
542
542
543 try:
543 try:
544 b = getstring(x, '')
544 b = getstring(x, '')
545 except error.ParseError:
545 except error.ParseError:
546 # not a string, but another revspec, e.g. tip()
546 # not a string, but another revspec, e.g. tip()
547 pass
547 pass
548 else:
548 else:
549 kind, pattern, matcher = _stringmatcher(b)
549 kind, pattern, matcher = _stringmatcher(b)
550 if kind == 'literal':
550 if kind == 'literal':
551 # note: falls through to the revspec case if no branch with
551 # note: falls through to the revspec case if no branch with
552 # this name exists
552 # this name exists
553 if pattern in repo.branchmap():
553 if pattern in repo.branchmap():
554 return subset.filter(lambda r: matcher(getbi(r)[0]))
554 return subset.filter(lambda r: matcher(getbi(r)[0]))
555 else:
555 else:
556 return subset.filter(lambda r: matcher(getbi(r)[0]))
556 return subset.filter(lambda r: matcher(getbi(r)[0]))
557
557
558 s = getset(repo, fullreposet(repo), x)
558 s = getset(repo, fullreposet(repo), x)
559 b = set()
559 b = set()
560 for r in s:
560 for r in s:
561 b.add(getbi(r)[0])
561 b.add(getbi(r)[0])
562 c = s.__contains__
562 c = s.__contains__
563 return subset.filter(lambda r: c(r) or getbi(r)[0] in b)
563 return subset.filter(lambda r: c(r) or getbi(r)[0] in b)
564
564
565 def bumped(repo, subset, x):
565 def bumped(repo, subset, x):
566 """``bumped()``
566 """``bumped()``
567 Mutable changesets marked as successors of public changesets.
567 Mutable changesets marked as successors of public changesets.
568
568
569 Only non-public and non-obsolete changesets can be `bumped`.
569 Only non-public and non-obsolete changesets can be `bumped`.
570 """
570 """
571 # i18n: "bumped" is a keyword
571 # i18n: "bumped" is a keyword
572 getargs(x, 0, 0, _("bumped takes no arguments"))
572 getargs(x, 0, 0, _("bumped takes no arguments"))
573 bumped = obsmod.getrevs(repo, 'bumped')
573 bumped = obsmod.getrevs(repo, 'bumped')
574 return subset & bumped
574 return subset & bumped
575
575
576 def bundle(repo, subset, x):
576 def bundle(repo, subset, x):
577 """``bundle()``
577 """``bundle()``
578 Changesets in the bundle.
578 Changesets in the bundle.
579
579
580 Bundle must be specified by the -R option."""
580 Bundle must be specified by the -R option."""
581
581
582 try:
582 try:
583 bundlerevs = repo.changelog.bundlerevs
583 bundlerevs = repo.changelog.bundlerevs
584 except AttributeError:
584 except AttributeError:
585 raise util.Abort(_("no bundle provided - specify with -R"))
585 raise util.Abort(_("no bundle provided - specify with -R"))
586 return subset & bundlerevs
586 return subset & bundlerevs
587
587
588 def checkstatus(repo, subset, pat, field):
588 def checkstatus(repo, subset, pat, field):
589 hasset = matchmod.patkind(pat) == 'set'
589 hasset = matchmod.patkind(pat) == 'set'
590
590
591 mcache = [None]
591 mcache = [None]
592 def matches(x):
592 def matches(x):
593 c = repo[x]
593 c = repo[x]
594 if not mcache[0] or hasset:
594 if not mcache[0] or hasset:
595 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
595 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
596 m = mcache[0]
596 m = mcache[0]
597 fname = None
597 fname = None
598 if not m.anypats() and len(m.files()) == 1:
598 if not m.anypats() and len(m.files()) == 1:
599 fname = m.files()[0]
599 fname = m.files()[0]
600 if fname is not None:
600 if fname is not None:
601 if fname not in c.files():
601 if fname not in c.files():
602 return False
602 return False
603 else:
603 else:
604 for f in c.files():
604 for f in c.files():
605 if m(f):
605 if m(f):
606 break
606 break
607 else:
607 else:
608 return False
608 return False
609 files = repo.status(c.p1().node(), c.node())[field]
609 files = repo.status(c.p1().node(), c.node())[field]
610 if fname is not None:
610 if fname is not None:
611 if fname in files:
611 if fname in files:
612 return True
612 return True
613 else:
613 else:
614 for f in files:
614 for f in files:
615 if m(f):
615 if m(f):
616 return True
616 return True
617
617
618 return subset.filter(matches)
618 return subset.filter(matches)
619
619
620 def _children(repo, narrow, parentset):
620 def _children(repo, narrow, parentset):
621 if not parentset:
621 if not parentset:
622 return baseset()
622 return baseset()
623 cs = set()
623 cs = set()
624 pr = repo.changelog.parentrevs
624 pr = repo.changelog.parentrevs
625 minrev = parentset.min()
625 minrev = parentset.min()
626 for r in narrow:
626 for r in narrow:
627 if r <= minrev:
627 if r <= minrev:
628 continue
628 continue
629 for p in pr(r):
629 for p in pr(r):
630 if p in parentset:
630 if p in parentset:
631 cs.add(r)
631 cs.add(r)
632 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
632 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
633 # This does not break because of other fullreposet misbehavior.
633 # This does not break because of other fullreposet misbehavior.
634 return baseset(cs)
634 return baseset(cs)
635
635
636 def children(repo, subset, x):
636 def children(repo, subset, x):
637 """``children(set)``
637 """``children(set)``
638 Child changesets of changesets in set.
638 Child changesets of changesets in set.
639 """
639 """
640 s = getset(repo, fullreposet(repo), x)
640 s = getset(repo, fullreposet(repo), x)
641 cs = _children(repo, subset, s)
641 cs = _children(repo, subset, s)
642 return subset & cs
642 return subset & cs
643
643
644 def closed(repo, subset, x):
644 def closed(repo, subset, x):
645 """``closed()``
645 """``closed()``
646 Changeset is closed.
646 Changeset is closed.
647 """
647 """
648 # i18n: "closed" is a keyword
648 # i18n: "closed" is a keyword
649 getargs(x, 0, 0, _("closed takes no arguments"))
649 getargs(x, 0, 0, _("closed takes no arguments"))
650 return subset.filter(lambda r: repo[r].closesbranch())
650 return subset.filter(lambda r: repo[r].closesbranch())
651
651
652 def contains(repo, subset, x):
652 def contains(repo, subset, x):
653 """``contains(pattern)``
653 """``contains(pattern)``
654 The revision's manifest contains a file matching pattern (but might not
654 The revision's manifest contains a file matching pattern (but might not
655 modify it). See :hg:`help patterns` for information about file patterns.
655 modify it). See :hg:`help patterns` for information about file patterns.
656
656
657 The pattern without explicit kind like ``glob:`` is expected to be
657 The pattern without explicit kind like ``glob:`` is expected to be
658 relative to the current directory and match against a file exactly
658 relative to the current directory and match against a file exactly
659 for efficiency.
659 for efficiency.
660 """
660 """
661 # i18n: "contains" is a keyword
661 # i18n: "contains" is a keyword
662 pat = getstring(x, _("contains requires a pattern"))
662 pat = getstring(x, _("contains requires a pattern"))
663
663
664 def matches(x):
664 def matches(x):
665 if not matchmod.patkind(pat):
665 if not matchmod.patkind(pat):
666 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
666 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
667 if pats in repo[x]:
667 if pats in repo[x]:
668 return True
668 return True
669 else:
669 else:
670 c = repo[x]
670 c = repo[x]
671 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
671 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
672 for f in c.manifest():
672 for f in c.manifest():
673 if m(f):
673 if m(f):
674 return True
674 return True
675 return False
675 return False
676
676
677 return subset.filter(matches)
677 return subset.filter(matches)
678
678
679 def converted(repo, subset, x):
679 def converted(repo, subset, x):
680 """``converted([id])``
680 """``converted([id])``
681 Changesets converted from the given identifier in the old repository if
681 Changesets converted from the given identifier in the old repository if
682 present, or all converted changesets if no identifier is specified.
682 present, or all converted changesets if no identifier is specified.
683 """
683 """
684
684
685 # There is exactly no chance of resolving the revision, so do a simple
685 # There is exactly no chance of resolving the revision, so do a simple
686 # string compare and hope for the best
686 # string compare and hope for the best
687
687
688 rev = None
688 rev = None
689 # i18n: "converted" is a keyword
689 # i18n: "converted" is a keyword
690 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
690 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
691 if l:
691 if l:
692 # i18n: "converted" is a keyword
692 # i18n: "converted" is a keyword
693 rev = getstring(l[0], _('converted requires a revision'))
693 rev = getstring(l[0], _('converted requires a revision'))
694
694
695 def _matchvalue(r):
695 def _matchvalue(r):
696 source = repo[r].extra().get('convert_revision', None)
696 source = repo[r].extra().get('convert_revision', None)
697 return source is not None and (rev is None or source.startswith(rev))
697 return source is not None and (rev is None or source.startswith(rev))
698
698
699 return subset.filter(lambda r: _matchvalue(r))
699 return subset.filter(lambda r: _matchvalue(r))
700
700
701 def date(repo, subset, x):
701 def date(repo, subset, x):
702 """``date(interval)``
702 """``date(interval)``
703 Changesets within the interval, see :hg:`help dates`.
703 Changesets within the interval, see :hg:`help dates`.
704 """
704 """
705 # i18n: "date" is a keyword
705 # i18n: "date" is a keyword
706 ds = getstring(x, _("date requires a string"))
706 ds = getstring(x, _("date requires a string"))
707 dm = util.matchdate(ds)
707 dm = util.matchdate(ds)
708 return subset.filter(lambda x: dm(repo[x].date()[0]))
708 return subset.filter(lambda x: dm(repo[x].date()[0]))
709
709
710 def desc(repo, subset, x):
710 def desc(repo, subset, x):
711 """``desc(string)``
711 """``desc(string)``
712 Search commit message for string. The match is case-insensitive.
712 Search commit message for string. The match is case-insensitive.
713 """
713 """
714 # i18n: "desc" is a keyword
714 # i18n: "desc" is a keyword
715 ds = encoding.lower(getstring(x, _("desc requires a string")))
715 ds = encoding.lower(getstring(x, _("desc requires a string")))
716
716
717 def matches(x):
717 def matches(x):
718 c = repo[x]
718 c = repo[x]
719 return ds in encoding.lower(c.description())
719 return ds in encoding.lower(c.description())
720
720
721 return subset.filter(matches)
721 return subset.filter(matches)
722
722
723 def _descendants(repo, subset, x, followfirst=False):
723 def _descendants(repo, subset, x, followfirst=False):
724 roots = getset(repo, fullreposet(repo), x)
724 roots = getset(repo, fullreposet(repo), x)
725 if not roots:
725 if not roots:
726 return baseset()
726 return baseset()
727 s = _revdescendants(repo, roots, followfirst)
727 s = _revdescendants(repo, roots, followfirst)
728
728
729 # Both sets need to be ascending in order to lazily return the union
729 # Both sets need to be ascending in order to lazily return the union
730 # in the correct order.
730 # in the correct order.
731 base = subset & roots
731 base = subset & roots
732 desc = subset & s
732 desc = subset & s
733 result = base + desc
733 result = base + desc
734 if subset.isascending():
734 if subset.isascending():
735 result.sort()
735 result.sort()
736 elif subset.isdescending():
736 elif subset.isdescending():
737 result.sort(reverse=True)
737 result.sort(reverse=True)
738 else:
738 else:
739 result = subset & result
739 result = subset & result
740 return result
740 return result
741
741
742 def descendants(repo, subset, x):
742 def descendants(repo, subset, x):
743 """``descendants(set)``
743 """``descendants(set)``
744 Changesets which are descendants of changesets in set.
744 Changesets which are descendants of changesets in set.
745 """
745 """
746 return _descendants(repo, subset, x)
746 return _descendants(repo, subset, x)
747
747
748 def _firstdescendants(repo, subset, x):
748 def _firstdescendants(repo, subset, x):
749 # ``_firstdescendants(set)``
749 # ``_firstdescendants(set)``
750 # Like ``descendants(set)`` but follows only the first parents.
750 # Like ``descendants(set)`` but follows only the first parents.
751 return _descendants(repo, subset, x, followfirst=True)
751 return _descendants(repo, subset, x, followfirst=True)
752
752
753 def destination(repo, subset, x):
753 def destination(repo, subset, x):
754 """``destination([set])``
754 """``destination([set])``
755 Changesets that were created by a graft, transplant or rebase operation,
755 Changesets that were created by a graft, transplant or rebase operation,
756 with the given revisions specified as the source. Omitting the optional set
756 with the given revisions specified as the source. Omitting the optional set
757 is the same as passing all().
757 is the same as passing all().
758 """
758 """
759 if x is not None:
759 if x is not None:
760 sources = getset(repo, fullreposet(repo), x)
760 sources = getset(repo, fullreposet(repo), x)
761 else:
761 else:
762 sources = fullreposet(repo)
762 sources = fullreposet(repo)
763
763
764 dests = set()
764 dests = set()
765
765
766 # subset contains all of the possible destinations that can be returned, so
766 # subset contains all of the possible destinations that can be returned, so
767 # iterate over them and see if their source(s) were provided in the arg set.
767 # iterate over them and see if their source(s) were provided in the arg set.
768 # Even if the immediate src of r is not in the arg set, src's source (or
768 # Even if the immediate src of r is not in the arg set, src's source (or
769 # further back) may be. Scanning back further than the immediate src allows
769 # further back) may be. Scanning back further than the immediate src allows
770 # transitive transplants and rebases to yield the same results as transitive
770 # transitive transplants and rebases to yield the same results as transitive
771 # grafts.
771 # grafts.
772 for r in subset:
772 for r in subset:
773 src = _getrevsource(repo, r)
773 src = _getrevsource(repo, r)
774 lineage = None
774 lineage = None
775
775
776 while src is not None:
776 while src is not None:
777 if lineage is None:
777 if lineage is None:
778 lineage = list()
778 lineage = list()
779
779
780 lineage.append(r)
780 lineage.append(r)
781
781
782 # The visited lineage is a match if the current source is in the arg
782 # The visited lineage is a match if the current source is in the arg
783 # set. Since every candidate dest is visited by way of iterating
783 # set. Since every candidate dest is visited by way of iterating
784 # subset, any dests further back in the lineage will be tested by a
784 # subset, any dests further back in the lineage will be tested by a
785 # different iteration over subset. Likewise, if the src was already
785 # different iteration over subset. Likewise, if the src was already
786 # selected, the current lineage can be selected without going back
786 # selected, the current lineage can be selected without going back
787 # further.
787 # further.
788 if src in sources or src in dests:
788 if src in sources or src in dests:
789 dests.update(lineage)
789 dests.update(lineage)
790 break
790 break
791
791
792 r = src
792 r = src
793 src = _getrevsource(repo, r)
793 src = _getrevsource(repo, r)
794
794
795 return subset.filter(dests.__contains__)
795 return subset.filter(dests.__contains__)
796
796
797 def divergent(repo, subset, x):
797 def divergent(repo, subset, x):
798 """``divergent()``
798 """``divergent()``
799 Final successors of changesets with an alternative set of final successors.
799 Final successors of changesets with an alternative set of final successors.
800 """
800 """
801 # i18n: "divergent" is a keyword
801 # i18n: "divergent" is a keyword
802 getargs(x, 0, 0, _("divergent takes no arguments"))
802 getargs(x, 0, 0, _("divergent takes no arguments"))
803 divergent = obsmod.getrevs(repo, 'divergent')
803 divergent = obsmod.getrevs(repo, 'divergent')
804 return subset & divergent
804 return subset & divergent
805
805
806 def extinct(repo, subset, x):
806 def extinct(repo, subset, x):
807 """``extinct()``
807 """``extinct()``
808 Obsolete changesets with obsolete descendants only.
808 Obsolete changesets with obsolete descendants only.
809 """
809 """
810 # i18n: "extinct" is a keyword
810 # i18n: "extinct" is a keyword
811 getargs(x, 0, 0, _("extinct takes no arguments"))
811 getargs(x, 0, 0, _("extinct takes no arguments"))
812 extincts = obsmod.getrevs(repo, 'extinct')
812 extincts = obsmod.getrevs(repo, 'extinct')
813 return subset & extincts
813 return subset & extincts
814
814
815 def extra(repo, subset, x):
815 def extra(repo, subset, x):
816 """``extra(label, [value])``
816 """``extra(label, [value])``
817 Changesets with the given label in the extra metadata, with the given
817 Changesets with the given label in the extra metadata, with the given
818 optional value.
818 optional value.
819
819
820 If `value` starts with `re:`, the remainder of the value is treated as
820 If `value` starts with `re:`, the remainder of the value is treated as
821 a regular expression. To match a value that actually starts with `re:`,
821 a regular expression. To match a value that actually starts with `re:`,
822 use the prefix `literal:`.
822 use the prefix `literal:`.
823 """
823 """
824
824
825 # i18n: "extra" is a keyword
825 # i18n: "extra" is a keyword
826 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
826 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
827 # i18n: "extra" is a keyword
827 # i18n: "extra" is a keyword
828 label = getstring(l[0], _('first argument to extra must be a string'))
828 label = getstring(l[0], _('first argument to extra must be a string'))
829 value = None
829 value = None
830
830
831 if len(l) > 1:
831 if len(l) > 1:
832 # i18n: "extra" is a keyword
832 # i18n: "extra" is a keyword
833 value = getstring(l[1], _('second argument to extra must be a string'))
833 value = getstring(l[1], _('second argument to extra must be a string'))
834 kind, value, matcher = _stringmatcher(value)
834 kind, value, matcher = _stringmatcher(value)
835
835
836 def _matchvalue(r):
836 def _matchvalue(r):
837 extra = repo[r].extra()
837 extra = repo[r].extra()
838 return label in extra and (value is None or matcher(extra[label]))
838 return label in extra and (value is None or matcher(extra[label]))
839
839
840 return subset.filter(lambda r: _matchvalue(r))
840 return subset.filter(lambda r: _matchvalue(r))
841
841
842 def filelog(repo, subset, x):
842 def filelog(repo, subset, x):
843 """``filelog(pattern)``
843 """``filelog(pattern)``
844 Changesets connected to the specified filelog.
844 Changesets connected to the specified filelog.
845
845
846 For performance reasons, visits only revisions mentioned in the file-level
846 For performance reasons, visits only revisions mentioned in the file-level
847 filelog, rather than filtering through all changesets (much faster, but
847 filelog, rather than filtering through all changesets (much faster, but
848 doesn't include deletes or duplicate changes). For a slower, more accurate
848 doesn't include deletes or duplicate changes). For a slower, more accurate
849 result, use ``file()``.
849 result, use ``file()``.
850
850
851 The pattern without explicit kind like ``glob:`` is expected to be
851 The pattern without explicit kind like ``glob:`` is expected to be
852 relative to the current directory and match against a file exactly
852 relative to the current directory and match against a file exactly
853 for efficiency.
853 for efficiency.
854
854
855 If some linkrev points to revisions filtered by the current repoview, we'll
855 If some linkrev points to revisions filtered by the current repoview, we'll
856 work around it to return a non-filtered value.
856 work around it to return a non-filtered value.
857 """
857 """
858
858
859 # i18n: "filelog" is a keyword
859 # i18n: "filelog" is a keyword
860 pat = getstring(x, _("filelog requires a pattern"))
860 pat = getstring(x, _("filelog requires a pattern"))
861 s = set()
861 s = set()
862 cl = repo.changelog
862 cl = repo.changelog
863
863
864 if not matchmod.patkind(pat):
864 if not matchmod.patkind(pat):
865 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
865 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
866 files = [f]
866 files = [f]
867 else:
867 else:
868 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
868 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
869 files = (f for f in repo[None] if m(f))
869 files = (f for f in repo[None] if m(f))
870
870
871 for f in files:
871 for f in files:
872 backrevref = {} # final value for: filerev -> changerev
872 backrevref = {} # final value for: filerev -> changerev
873 lowestchild = {} # lowest known filerev child of a filerev
873 lowestchild = {} # lowest known filerev child of a filerev
874 delayed = [] # filerev with filtered linkrev, for post-processing
874 delayed = [] # filerev with filtered linkrev, for post-processing
875 lowesthead = None # cache for manifest content of all head revisions
875 lowesthead = None # cache for manifest content of all head revisions
876 fl = repo.file(f)
876 fl = repo.file(f)
877 for fr in list(fl):
877 for fr in list(fl):
878 rev = fl.linkrev(fr)
878 rev = fl.linkrev(fr)
879 if rev not in cl:
879 if rev not in cl:
880 # changerev pointed in linkrev is filtered
880 # changerev pointed in linkrev is filtered
881 # record it for post processing.
881 # record it for post processing.
882 delayed.append((fr, rev))
882 delayed.append((fr, rev))
883 continue
883 continue
884 for p in fl.parentrevs(fr):
884 for p in fl.parentrevs(fr):
885 if 0 <= p and p not in lowestchild:
885 if 0 <= p and p not in lowestchild:
886 lowestchild[p] = fr
886 lowestchild[p] = fr
887 backrevref[fr] = rev
887 backrevref[fr] = rev
888 s.add(rev)
888 s.add(rev)
889
889
890 # Post-processing of all filerevs we skipped because they were
890 # Post-processing of all filerevs we skipped because they were
891 # filtered. If such filerevs have known and unfiltered children, this
891 # filtered. If such filerevs have known and unfiltered children, this
892 # means they have an unfiltered appearance out there. We'll use linkrev
892 # means they have an unfiltered appearance out there. We'll use linkrev
893 # adjustment to find one of these appearances. The lowest known child
893 # adjustment to find one of these appearances. The lowest known child
894 # will be used as a starting point because it is the best upper-bound we
894 # will be used as a starting point because it is the best upper-bound we
895 # have.
895 # have.
896 #
896 #
897 # This approach will fail when an unfiltered but linkrev-shadowed
897 # This approach will fail when an unfiltered but linkrev-shadowed
898 # appearance exists in a head changeset without unfiltered filerev
898 # appearance exists in a head changeset without unfiltered filerev
899 # children anywhere.
899 # children anywhere.
900 while delayed:
900 while delayed:
901 # must be a descending iteration. To slowly fill lowest child
901 # must be a descending iteration. To slowly fill lowest child
902 # information that is of potential use by the next item.
902 # information that is of potential use by the next item.
903 fr, rev = delayed.pop()
903 fr, rev = delayed.pop()
904 lkr = rev
904 lkr = rev
905
905
906 child = lowestchild.get(fr)
906 child = lowestchild.get(fr)
907
907
908 if child is None:
908 if child is None:
909 # search for existence of this file revision in a head revision.
909 # search for existence of this file revision in a head revision.
910 # There are three possibilities:
910 # There are three possibilities:
911 # - the revision exists in a head and we can find an
911 # - the revision exists in a head and we can find an
912 # introduction from there,
912 # introduction from there,
913 # - the revision does not exist in a head because it has been
913 # - the revision does not exist in a head because it has been
914 # changed since its introduction: we would have found a child
914 # changed since its introduction: we would have found a child
915 # and be in the other 'else' clause,
915 # and be in the other 'else' clause,
916 # - all versions of the revision are hidden.
916 # - all versions of the revision are hidden.
917 if lowesthead is None:
917 if lowesthead is None:
918 lowesthead = {}
918 lowesthead = {}
919 for h in repo.heads():
919 for h in repo.heads():
920 fnode = repo[h].manifest().get(f)
920 fnode = repo[h].manifest().get(f)
921 if fnode is not None:
921 if fnode is not None:
922 lowesthead[fl.rev(fnode)] = h
922 lowesthead[fl.rev(fnode)] = h
923 headrev = lowesthead.get(fr)
923 headrev = lowesthead.get(fr)
924 if headrev is None:
924 if headrev is None:
925 # content is nowhere unfiltered
925 # content is nowhere unfiltered
926 continue
926 continue
927 rev = repo[headrev][f].introrev()
927 rev = repo[headrev][f].introrev()
928 else:
928 else:
929 # the lowest known child is a good upper bound
929 # the lowest known child is a good upper bound
930 childcrev = backrevref[child]
930 childcrev = backrevref[child]
931 # XXX this does not guarantee returning the lowest
931 # XXX this does not guarantee returning the lowest
932 # introduction of this revision, but this gives a
932 # introduction of this revision, but this gives a
933 # result which is a good start and will fit in most
933 # result which is a good start and will fit in most
934 # cases. We probably need to fix the multiple
934 # cases. We probably need to fix the multiple
935 # introductions case properly (report each
935 # introductions case properly (report each
936 # introduction, even for identical file revisions)
936 # introduction, even for identical file revisions)
937 # once and for all at some point anyway.
937 # once and for all at some point anyway.
938 for p in repo[childcrev][f].parents():
938 for p in repo[childcrev][f].parents():
939 if p.filerev() == fr:
939 if p.filerev() == fr:
940 rev = p.rev()
940 rev = p.rev()
941 break
941 break
942 if rev == lkr: # no shadowed entry found
942 if rev == lkr: # no shadowed entry found
943 # XXX This should never happen unless some manifest points
943 # XXX This should never happen unless some manifest points
944 # to biggish file revisions (like a revision that uses a
944 # to biggish file revisions (like a revision that uses a
945 # parent that never appears in the manifest ancestors)
945 # parent that never appears in the manifest ancestors)
946 continue
946 continue
947
947
948 # Fill the data for the next iteration.
948 # Fill the data for the next iteration.
949 for p in fl.parentrevs(fr):
949 for p in fl.parentrevs(fr):
950 if 0 <= p and p not in lowestchild:
950 if 0 <= p and p not in lowestchild:
951 lowestchild[p] = fr
951 lowestchild[p] = fr
952 backrevref[fr] = rev
952 backrevref[fr] = rev
953 s.add(rev)
953 s.add(rev)
954
954
955 return subset & s
955 return subset & s
956
956
957 def first(repo, subset, x):
957 def first(repo, subset, x):
958 """``first(set, [n])``
958 """``first(set, [n])``
959 An alias for limit().
959 An alias for limit().
960 """
960 """
961 return limit(repo, subset, x)
961 return limit(repo, subset, x)
962
962
963 def _follow(repo, subset, x, name, followfirst=False):
963 def _follow(repo, subset, x, name, followfirst=False):
964 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
964 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
965 c = repo['.']
965 c = repo['.']
966 if l:
966 if l:
967 x = getstring(l[0], _("%s expected a filename") % name)
967 x = getstring(l[0], _("%s expected a filename") % name)
968 if x in c:
968 if x in c:
969 cx = c[x]
969 cx = c[x]
970 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
970 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
971 # include the revision responsible for the most recent version
971 # include the revision responsible for the most recent version
972 s.add(cx.introrev())
972 s.add(cx.introrev())
973 else:
973 else:
974 return baseset()
974 return baseset()
975 else:
975 else:
976 s = _revancestors(repo, baseset([c.rev()]), followfirst)
976 s = _revancestors(repo, baseset([c.rev()]), followfirst)
977
977
978 return subset & s
978 return subset & s
979
979
980 def follow(repo, subset, x):
980 def follow(repo, subset, x):
981 """``follow([file])``
981 """``follow([file])``
982 An alias for ``::.`` (ancestors of the working directory's first parent).
982 An alias for ``::.`` (ancestors of the working directory's first parent).
983 If a filename is specified, the history of the given file is followed,
983 If a filename is specified, the history of the given file is followed,
984 including copies.
984 including copies.
985 """
985 """
986 return _follow(repo, subset, x, 'follow')
986 return _follow(repo, subset, x, 'follow')
987
987
988 def _followfirst(repo, subset, x):
988 def _followfirst(repo, subset, x):
989 # ``followfirst([file])``
989 # ``followfirst([file])``
990 # Like ``follow([file])`` but follows only the first parent of
990 # Like ``follow([file])`` but follows only the first parent of
991 # every revision or file revision.
991 # every revision or file revision.
992 return _follow(repo, subset, x, '_followfirst', followfirst=True)
992 return _follow(repo, subset, x, '_followfirst', followfirst=True)
993
993
994 def getall(repo, subset, x):
994 def getall(repo, subset, x):
995 """``all()``
995 """``all()``
996 All changesets, the same as ``0:tip``.
996 All changesets, the same as ``0:tip``.
997 """
997 """
998 # i18n: "all" is a keyword
998 # i18n: "all" is a keyword
999 getargs(x, 0, 0, _("all takes no arguments"))
999 getargs(x, 0, 0, _("all takes no arguments"))
1000 return subset & spanset(repo) # drop "null" if any
1000 return subset & spanset(repo) # drop "null" if any
1001
1001
1002 def grep(repo, subset, x):
1002 def grep(repo, subset, x):
1003 """``grep(regex)``
1003 """``grep(regex)``
1004 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1004 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1005 to ensure special escape characters are handled correctly. Unlike
1005 to ensure special escape characters are handled correctly. Unlike
1006 ``keyword(string)``, the match is case-sensitive.
1006 ``keyword(string)``, the match is case-sensitive.
1007 """
1007 """
1008 try:
1008 try:
1009 # i18n: "grep" is a keyword
1009 # i18n: "grep" is a keyword
1010 gr = re.compile(getstring(x, _("grep requires a string")))
1010 gr = re.compile(getstring(x, _("grep requires a string")))
1011 except re.error, e:
1011 except re.error, e:
1012 raise error.ParseError(_('invalid match pattern: %s') % e)
1012 raise error.ParseError(_('invalid match pattern: %s') % e)
1013
1013
1014 def matches(x):
1014 def matches(x):
1015 c = repo[x]
1015 c = repo[x]
1016 for e in c.files() + [c.user(), c.description()]:
1016 for e in c.files() + [c.user(), c.description()]:
1017 if gr.search(e):
1017 if gr.search(e):
1018 return True
1018 return True
1019 return False
1019 return False
1020
1020
1021 return subset.filter(matches)
1021 return subset.filter(matches)
1022
1022
1023 def _matchfiles(repo, subset, x):
1023 def _matchfiles(repo, subset, x):
1024 # _matchfiles takes a revset list of prefixed arguments:
1024 # _matchfiles takes a revset list of prefixed arguments:
1025 #
1025 #
1026 # [p:foo, i:bar, x:baz]
1026 # [p:foo, i:bar, x:baz]
1027 #
1027 #
1028 # builds a match object from them and filters subset. Allowed
1028 # builds a match object from them and filters subset. Allowed
1029 # prefixes are 'p:' for regular patterns, 'i:' for include
1029 # prefixes are 'p:' for regular patterns, 'i:' for include
1030 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1030 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1031 # a revision identifier, or the empty string to reference the
1031 # a revision identifier, or the empty string to reference the
1032 # working directory, from which the match object is
1032 # working directory, from which the match object is
1033 # initialized. Use 'd:' to set the default matching mode, default
1033 # initialized. Use 'd:' to set the default matching mode, default
1034 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1034 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1035
1035
1036 # i18n: "_matchfiles" is a keyword
1036 # i18n: "_matchfiles" is a keyword
1037 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
1037 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
1038 pats, inc, exc = [], [], []
1038 pats, inc, exc = [], [], []
1039 rev, default = None, None
1039 rev, default = None, None
1040 for arg in l:
1040 for arg in l:
1041 # i18n: "_matchfiles" is a keyword
1041 # i18n: "_matchfiles" is a keyword
1042 s = getstring(arg, _("_matchfiles requires string arguments"))
1042 s = getstring(arg, _("_matchfiles requires string arguments"))
1043 prefix, value = s[:2], s[2:]
1043 prefix, value = s[:2], s[2:]
1044 if prefix == 'p:':
1044 if prefix == 'p:':
1045 pats.append(value)
1045 pats.append(value)
1046 elif prefix == 'i:':
1046 elif prefix == 'i:':
1047 inc.append(value)
1047 inc.append(value)
1048 elif prefix == 'x:':
1048 elif prefix == 'x:':
1049 exc.append(value)
1049 exc.append(value)
1050 elif prefix == 'r:':
1050 elif prefix == 'r:':
1051 if rev is not None:
1051 if rev is not None:
1052 # i18n: "_matchfiles" is a keyword
1052 # i18n: "_matchfiles" is a keyword
1053 raise error.ParseError(_('_matchfiles expected at most one '
1053 raise error.ParseError(_('_matchfiles expected at most one '
1054 'revision'))
1054 'revision'))
1055 if value != '': # empty means working directory; leave rev as None
1055 if value != '': # empty means working directory; leave rev as None
1056 rev = value
1056 rev = value
1057 elif prefix == 'd:':
1057 elif prefix == 'd:':
1058 if default is not None:
1058 if default is not None:
1059 # i18n: "_matchfiles" is a keyword
1059 # i18n: "_matchfiles" is a keyword
1060 raise error.ParseError(_('_matchfiles expected at most one '
1060 raise error.ParseError(_('_matchfiles expected at most one '
1061 'default mode'))
1061 'default mode'))
1062 default = value
1062 default = value
1063 else:
1063 else:
1064 # i18n: "_matchfiles" is a keyword
1064 # i18n: "_matchfiles" is a keyword
1065 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
1065 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
1066 if not default:
1066 if not default:
1067 default = 'glob'
1067 default = 'glob'
1068
1068
1069 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
1069 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
1070 exclude=exc, ctx=repo[rev], default=default)
1070 exclude=exc, ctx=repo[rev], default=default)
1071
1071
1072 def matches(x):
1072 def matches(x):
1073 for f in repo[x].files():
1073 for f in repo[x].files():
1074 if m(f):
1074 if m(f):
1075 return True
1075 return True
1076 return False
1076 return False
1077
1077
1078 return subset.filter(matches)
1078 return subset.filter(matches)
1079
1079
1080 def hasfile(repo, subset, x):
1080 def hasfile(repo, subset, x):
1081 """``file(pattern)``
1081 """``file(pattern)``
1082 Changesets affecting files matched by pattern.
1082 Changesets affecting files matched by pattern.
1083
1083
1084 For a faster but less accurate result, consider using ``filelog()``
1084 For a faster but less accurate result, consider using ``filelog()``
1085 instead.
1085 instead.
1086
1086
1087 This predicate uses ``glob:`` as the default kind of pattern.
1087 This predicate uses ``glob:`` as the default kind of pattern.
1088 """
1088 """
1089 # i18n: "file" is a keyword
1089 # i18n: "file" is a keyword
1090 pat = getstring(x, _("file requires a pattern"))
1090 pat = getstring(x, _("file requires a pattern"))
1091 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1091 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1092
1092
1093 def head(repo, subset, x):
1093 def head(repo, subset, x):
1094 """``head()``
1094 """``head()``
1095 Changeset is a named branch head.
1095 Changeset is a named branch head.
1096 """
1096 """
1097 # i18n: "head" is a keyword
1097 # i18n: "head" is a keyword
1098 getargs(x, 0, 0, _("head takes no arguments"))
1098 getargs(x, 0, 0, _("head takes no arguments"))
1099 hs = set()
1099 hs = set()
1100 cl = repo.changelog
1100 cl = repo.changelog
1101 for b, ls in repo.branchmap().iteritems():
1101 for b, ls in repo.branchmap().iteritems():
1102 hs.update(cl.rev(h) for h in ls)
1102 hs.update(cl.rev(h) for h in ls)
1103 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
1103 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
1104 # This does not break because of other fullreposet misbehavior.
1104 # This does not break because of other fullreposet misbehavior.
1105 # XXX We should not be using '.filter' here, but combines subset with '&'
1105 # XXX We should not be using '.filter' here, but combines subset with '&'
1106 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
1106 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
1107 # necessary to ensure we preserve the order in subset.
1107 # necessary to ensure we preserve the order in subset.
1108 return baseset(hs).filter(subset.__contains__)
1108 return baseset(hs).filter(subset.__contains__)
1109
1109
1110 def heads(repo, subset, x):
1110 def heads(repo, subset, x):
1111 """``heads(set)``
1111 """``heads(set)``
1112 Members of set with no children in set.
1112 Members of set with no children in set.
1113 """
1113 """
1114 s = getset(repo, subset, x)
1114 s = getset(repo, subset, x)
1115 ps = parents(repo, subset, x)
1115 ps = parents(repo, subset, x)
1116 return s - ps
1116 return s - ps
1117
1117
1118 def hidden(repo, subset, x):
1118 def hidden(repo, subset, x):
1119 """``hidden()``
1119 """``hidden()``
1120 Hidden changesets.
1120 Hidden changesets.
1121 """
1121 """
1122 # i18n: "hidden" is a keyword
1122 # i18n: "hidden" is a keyword
1123 getargs(x, 0, 0, _("hidden takes no arguments"))
1123 getargs(x, 0, 0, _("hidden takes no arguments"))
1124 hiddenrevs = repoview.filterrevs(repo, 'visible')
1124 hiddenrevs = repoview.filterrevs(repo, 'visible')
1125 return subset & hiddenrevs
1125 return subset & hiddenrevs
1126
1126
1127 def keyword(repo, subset, x):
1127 def keyword(repo, subset, x):
1128 """``keyword(string)``
1128 """``keyword(string)``
1129 Search commit message, user name, and names of changed files for
1129 Search commit message, user name, and names of changed files for
1130 string. The match is case-insensitive.
1130 string. The match is case-insensitive.
1131 """
1131 """
1132 # i18n: "keyword" is a keyword
1132 # i18n: "keyword" is a keyword
1133 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1133 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1134
1134
1135 def matches(r):
1135 def matches(r):
1136 c = repo[r]
1136 c = repo[r]
1137 return any(kw in encoding.lower(t)
1137 return any(kw in encoding.lower(t)
1138 for t in c.files() + [c.user(), c.description()])
1138 for t in c.files() + [c.user(), c.description()])
1139
1139
1140 return subset.filter(matches)
1140 return subset.filter(matches)
1141
1141
1142 def limit(repo, subset, x):
1142 def limit(repo, subset, x):
1143 """``limit(set, [n])``
1143 """``limit(set, [n])``
1144 First n members of set, defaulting to 1.
1144 First n members of set, defaulting to 1.
1145 """
1145 """
1146 # i18n: "limit" is a keyword
1146 # i18n: "limit" is a keyword
1147 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
1147 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
1148 try:
1148 try:
1149 lim = 1
1149 lim = 1
1150 if len(l) == 2:
1150 if len(l) == 2:
1151 # i18n: "limit" is a keyword
1151 # i18n: "limit" is a keyword
1152 lim = int(getstring(l[1], _("limit requires a number")))
1152 lim = int(getstring(l[1], _("limit requires a number")))
1153 except (TypeError, ValueError):
1153 except (TypeError, ValueError):
1154 # i18n: "limit" is a keyword
1154 # i18n: "limit" is a keyword
1155 raise error.ParseError(_("limit expects a number"))
1155 raise error.ParseError(_("limit expects a number"))
1156 ss = subset
1156 ss = subset
1157 os = getset(repo, fullreposet(repo), l[0])
1157 os = getset(repo, fullreposet(repo), l[0])
1158 result = []
1158 result = []
1159 it = iter(os)
1159 it = iter(os)
1160 for x in xrange(lim):
1160 for x in xrange(lim):
1161 y = next(it, None)
1161 y = next(it, None)
1162 if y is None:
1162 if y is None:
1163 break
1163 break
1164 elif y in ss:
1164 elif y in ss:
1165 result.append(y)
1165 result.append(y)
1166 return baseset(result)
1166 return baseset(result)
1167
1167
1168 def last(repo, subset, x):
1168 def last(repo, subset, x):
1169 """``last(set, [n])``
1169 """``last(set, [n])``
1170 Last n members of set, defaulting to 1.
1170 Last n members of set, defaulting to 1.
1171 """
1171 """
1172 # i18n: "last" is a keyword
1172 # i18n: "last" is a keyword
1173 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1173 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1174 try:
1174 try:
1175 lim = 1
1175 lim = 1
1176 if len(l) == 2:
1176 if len(l) == 2:
1177 # i18n: "last" is a keyword
1177 # i18n: "last" is a keyword
1178 lim = int(getstring(l[1], _("last requires a number")))
1178 lim = int(getstring(l[1], _("last requires a number")))
1179 except (TypeError, ValueError):
1179 except (TypeError, ValueError):
1180 # i18n: "last" is a keyword
1180 # i18n: "last" is a keyword
1181 raise error.ParseError(_("last expects a number"))
1181 raise error.ParseError(_("last expects a number"))
1182 ss = subset
1182 ss = subset
1183 os = getset(repo, fullreposet(repo), l[0])
1183 os = getset(repo, fullreposet(repo), l[0])
1184 os.reverse()
1184 os.reverse()
1185 result = []
1185 result = []
1186 it = iter(os)
1186 it = iter(os)
1187 for x in xrange(lim):
1187 for x in xrange(lim):
1188 y = next(it, None)
1188 y = next(it, None)
1189 if y is None:
1189 if y is None:
1190 break
1190 break
1191 elif y in ss:
1191 elif y in ss:
1192 result.append(y)
1192 result.append(y)
1193 return baseset(result)
1193 return baseset(result)
1194
1194
1195 def maxrev(repo, subset, x):
1195 def maxrev(repo, subset, x):
1196 """``max(set)``
1196 """``max(set)``
1197 Changeset with highest revision number in set.
1197 Changeset with highest revision number in set.
1198 """
1198 """
1199 os = getset(repo, fullreposet(repo), x)
1199 os = getset(repo, fullreposet(repo), x)
1200 if os:
1200 if os:
1201 m = os.max()
1201 m = os.max()
1202 if m in subset:
1202 if m in subset:
1203 return baseset([m])
1203 return baseset([m])
1204 return baseset()
1204 return baseset()
1205
1205
1206 def merge(repo, subset, x):
1206 def merge(repo, subset, x):
1207 """``merge()``
1207 """``merge()``
1208 Changeset is a merge changeset.
1208 Changeset is a merge changeset.
1209 """
1209 """
1210 # i18n: "merge" is a keyword
1210 # i18n: "merge" is a keyword
1211 getargs(x, 0, 0, _("merge takes no arguments"))
1211 getargs(x, 0, 0, _("merge takes no arguments"))
1212 cl = repo.changelog
1212 cl = repo.changelog
1213 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1213 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1214
1214
1215 def branchpoint(repo, subset, x):
1215 def branchpoint(repo, subset, x):
1216 """``branchpoint()``
1216 """``branchpoint()``
1217 Changesets with more than one child.
1217 Changesets with more than one child.
1218 """
1218 """
1219 # i18n: "branchpoint" is a keyword
1219 # i18n: "branchpoint" is a keyword
1220 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1220 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1221 cl = repo.changelog
1221 cl = repo.changelog
1222 if not subset:
1222 if not subset:
1223 return baseset()
1223 return baseset()
1224 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1224 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1225 # (and if it is not, it should.)
1225 # (and if it is not, it should.)
1226 baserev = min(subset)
1226 baserev = min(subset)
1227 parentscount = [0]*(len(repo) - baserev)
1227 parentscount = [0]*(len(repo) - baserev)
1228 for r in cl.revs(start=baserev + 1):
1228 for r in cl.revs(start=baserev + 1):
1229 for p in cl.parentrevs(r):
1229 for p in cl.parentrevs(r):
1230 if p >= baserev:
1230 if p >= baserev:
1231 parentscount[p - baserev] += 1
1231 parentscount[p - baserev] += 1
1232 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1232 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1233
1233
1234 def minrev(repo, subset, x):
1234 def minrev(repo, subset, x):
1235 """``min(set)``
1235 """``min(set)``
1236 Changeset with lowest revision number in set.
1236 Changeset with lowest revision number in set.
1237 """
1237 """
1238 os = getset(repo, fullreposet(repo), x)
1238 os = getset(repo, fullreposet(repo), x)
1239 if os:
1239 if os:
1240 m = os.min()
1240 m = os.min()
1241 if m in subset:
1241 if m in subset:
1242 return baseset([m])
1242 return baseset([m])
1243 return baseset()
1243 return baseset()
1244
1244
1245 def modifies(repo, subset, x):
1245 def modifies(repo, subset, x):
1246 """``modifies(pattern)``
1246 """``modifies(pattern)``
1247 Changesets modifying files matched by pattern.
1247 Changesets modifying files matched by pattern.
1248
1248
1249 The pattern without explicit kind like ``glob:`` is expected to be
1249 The pattern without explicit kind like ``glob:`` is expected to be
1250 relative to the current directory and match against a file or a
1250 relative to the current directory and match against a file or a
1251 directory.
1251 directory.
1252 """
1252 """
1253 # i18n: "modifies" is a keyword
1253 # i18n: "modifies" is a keyword
1254 pat = getstring(x, _("modifies requires a pattern"))
1254 pat = getstring(x, _("modifies requires a pattern"))
1255 return checkstatus(repo, subset, pat, 0)
1255 return checkstatus(repo, subset, pat, 0)
1256
1256
1257 def named(repo, subset, x):
1257 def named(repo, subset, x):
1258 """``named(namespace)``
1258 """``named(namespace)``
1259 The changesets in a given namespace.
1259 The changesets in a given namespace.
1260
1260
1261 If `namespace` starts with `re:`, the remainder of the string is treated as
1261 If `namespace` starts with `re:`, the remainder of the string is treated as
1262 a regular expression. To match a namespace that actually starts with `re:`,
1262 a regular expression. To match a namespace that actually starts with `re:`,
1263 use the prefix `literal:`.
1263 use the prefix `literal:`.
1264 """
1264 """
1265 # i18n: "named" is a keyword
1265 # i18n: "named" is a keyword
1266 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1266 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1267
1267
1268 ns = getstring(args[0],
1268 ns = getstring(args[0],
1269 # i18n: "named" is a keyword
1269 # i18n: "named" is a keyword
1270 _('the argument to named must be a string'))
1270 _('the argument to named must be a string'))
1271 kind, pattern, matcher = _stringmatcher(ns)
1271 kind, pattern, matcher = _stringmatcher(ns)
1272 namespaces = set()
1272 namespaces = set()
1273 if kind == 'literal':
1273 if kind == 'literal':
1274 if pattern not in repo.names:
1274 if pattern not in repo.names:
1275 raise error.RepoLookupError(_("namespace '%s' does not exist")
1275 raise error.RepoLookupError(_("namespace '%s' does not exist")
1276 % ns)
1276 % ns)
1277 namespaces.add(repo.names[pattern])
1277 namespaces.add(repo.names[pattern])
1278 else:
1278 else:
1279 for name, ns in repo.names.iteritems():
1279 for name, ns in repo.names.iteritems():
1280 if matcher(name):
1280 if matcher(name):
1281 namespaces.add(ns)
1281 namespaces.add(ns)
1282 if not namespaces:
1282 if not namespaces:
1283 raise error.RepoLookupError(_("no namespace exists"
1283 raise error.RepoLookupError(_("no namespace exists"
1284 " that match '%s'") % pattern)
1284 " that match '%s'") % pattern)
1285
1285
1286 names = set()
1286 names = set()
1287 for ns in namespaces:
1287 for ns in namespaces:
1288 for name in ns.listnames(repo):
1288 for name in ns.listnames(repo):
1289 if name not in ns.deprecated:
1289 if name not in ns.deprecated:
1290 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1290 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1291
1291
1292 names -= set([node.nullrev])
1292 names -= set([node.nullrev])
1293 return subset & names
1293 return subset & names
1294
1294
1295 def node_(repo, subset, x):
1295 def node_(repo, subset, x):
1296 """``id(string)``
1296 """``id(string)``
1297 Revision non-ambiguously specified by the given hex string prefix.
1297 Revision non-ambiguously specified by the given hex string prefix.
1298 """
1298 """
1299 # i18n: "id" is a keyword
1299 # i18n: "id" is a keyword
1300 l = getargs(x, 1, 1, _("id requires one argument"))
1300 l = getargs(x, 1, 1, _("id requires one argument"))
1301 # i18n: "id" is a keyword
1301 # i18n: "id" is a keyword
1302 n = getstring(l[0], _("id requires a string"))
1302 n = getstring(l[0], _("id requires a string"))
1303 if len(n) == 40:
1303 if len(n) == 40:
1304 try:
1304 try:
1305 rn = repo.changelog.rev(node.bin(n))
1305 rn = repo.changelog.rev(node.bin(n))
1306 except (LookupError, TypeError):
1306 except (LookupError, TypeError):
1307 rn = None
1307 rn = None
1308 else:
1308 else:
1309 rn = None
1309 rn = None
1310 pm = repo.changelog._partialmatch(n)
1310 pm = repo.changelog._partialmatch(n)
1311 if pm is not None:
1311 if pm is not None:
1312 rn = repo.changelog.rev(pm)
1312 rn = repo.changelog.rev(pm)
1313
1313
1314 if rn is None:
1314 if rn is None:
1315 return baseset()
1315 return baseset()
1316 result = baseset([rn])
1316 result = baseset([rn])
1317 return result & subset
1317 return result & subset
1318
1318
1319 def obsolete(repo, subset, x):
1319 def obsolete(repo, subset, x):
1320 """``obsolete()``
1320 """``obsolete()``
1321 Mutable changeset with a newer version."""
1321 Mutable changeset with a newer version."""
1322 # i18n: "obsolete" is a keyword
1322 # i18n: "obsolete" is a keyword
1323 getargs(x, 0, 0, _("obsolete takes no arguments"))
1323 getargs(x, 0, 0, _("obsolete takes no arguments"))
1324 obsoletes = obsmod.getrevs(repo, 'obsolete')
1324 obsoletes = obsmod.getrevs(repo, 'obsolete')
1325 return subset & obsoletes
1325 return subset & obsoletes
1326
1326
1327 def only(repo, subset, x):
1327 def only(repo, subset, x):
1328 """``only(set, [set])``
1328 """``only(set, [set])``
1329 Changesets that are ancestors of the first set that are not ancestors
1329 Changesets that are ancestors of the first set that are not ancestors
1330 of any other head in the repo. If a second set is specified, the result
1330 of any other head in the repo. If a second set is specified, the result
1331 is ancestors of the first set that are not ancestors of the second set
1331 is ancestors of the first set that are not ancestors of the second set
1332 (i.e. ::<set1> - ::<set2>).
1332 (i.e. ::<set1> - ::<set2>).
1333 """
1333 """
1334 cl = repo.changelog
1334 cl = repo.changelog
1335 # i18n: "only" is a keyword
1335 # i18n: "only" is a keyword
1336 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1336 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1337 include = getset(repo, fullreposet(repo), args[0])
1337 include = getset(repo, fullreposet(repo), args[0])
1338 if len(args) == 1:
1338 if len(args) == 1:
1339 if not include:
1339 if not include:
1340 return baseset()
1340 return baseset()
1341
1341
1342 descendants = set(_revdescendants(repo, include, False))
1342 descendants = set(_revdescendants(repo, include, False))
1343 exclude = [rev for rev in cl.headrevs()
1343 exclude = [rev for rev in cl.headrevs()
1344 if not rev in descendants and not rev in include]
1344 if not rev in descendants and not rev in include]
1345 else:
1345 else:
1346 exclude = getset(repo, fullreposet(repo), args[1])
1346 exclude = getset(repo, fullreposet(repo), args[1])
1347
1347
1348 results = set(cl.findmissingrevs(common=exclude, heads=include))
1348 results = set(cl.findmissingrevs(common=exclude, heads=include))
1349 # XXX we should turn this into a baseset instead of a set, smartset may do
1349 # XXX we should turn this into a baseset instead of a set, smartset may do
1350 # some optimisations from the fact this is a baseset.
1350 # some optimisations from the fact this is a baseset.
1351 return subset & results
1351 return subset & results
1352
1352
1353 def origin(repo, subset, x):
1353 def origin(repo, subset, x):
1354 """``origin([set])``
1354 """``origin([set])``
1355 Changesets that were specified as a source for the grafts, transplants or
1355 Changesets that were specified as a source for the grafts, transplants or
1356 rebases that created the given revisions. Omitting the optional set is the
1356 rebases that created the given revisions. Omitting the optional set is the
1357 same as passing all(). If a changeset created by these operations is itself
1357 same as passing all(). If a changeset created by these operations is itself
1358 specified as a source for one of these operations, only the source changeset
1358 specified as a source for one of these operations, only the source changeset
1359 for the first operation is selected.
1359 for the first operation is selected.
1360 """
1360 """
1361 if x is not None:
1361 if x is not None:
1362 dests = getset(repo, fullreposet(repo), x)
1362 dests = getset(repo, fullreposet(repo), x)
1363 else:
1363 else:
1364 dests = fullreposet(repo)
1364 dests = fullreposet(repo)
1365
1365
1366 def _firstsrc(rev):
1366 def _firstsrc(rev):
1367 src = _getrevsource(repo, rev)
1367 src = _getrevsource(repo, rev)
1368 if src is None:
1368 if src is None:
1369 return None
1369 return None
1370
1370
1371 while True:
1371 while True:
1372 prev = _getrevsource(repo, src)
1372 prev = _getrevsource(repo, src)
1373
1373
1374 if prev is None:
1374 if prev is None:
1375 return src
1375 return src
1376 src = prev
1376 src = prev
1377
1377
1378 o = set([_firstsrc(r) for r in dests])
1378 o = set([_firstsrc(r) for r in dests])
1379 o -= set([None])
1379 o -= set([None])
1380 # XXX we should turn this into a baseset instead of a set, smartset may do
1380 # XXX we should turn this into a baseset instead of a set, smartset may do
1381 # some optimisations from the fact this is a baseset.
1381 # some optimisations from the fact this is a baseset.
1382 return subset & o
1382 return subset & o
1383
1383
1384 def outgoing(repo, subset, x):
1384 def outgoing(repo, subset, x):
1385 """``outgoing([path])``
1385 """``outgoing([path])``
1386 Changesets not found in the specified destination repository, or the
1386 Changesets not found in the specified destination repository, or the
1387 default push location.
1387 default push location.
1388 """
1388 """
1389 # Avoid cycles.
1389 # Avoid cycles.
1390 import discovery
1390 import discovery
1391 import hg
1391 import hg
1392 # i18n: "outgoing" is a keyword
1392 # i18n: "outgoing" is a keyword
1393 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1393 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1394 # i18n: "outgoing" is a keyword
1394 # i18n: "outgoing" is a keyword
1395 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1395 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1396 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1396 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1397 dest, branches = hg.parseurl(dest)
1397 dest, branches = hg.parseurl(dest)
1398 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1398 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1399 if revs:
1399 if revs:
1400 revs = [repo.lookup(rev) for rev in revs]
1400 revs = [repo.lookup(rev) for rev in revs]
1401 other = hg.peer(repo, {}, dest)
1401 other = hg.peer(repo, {}, dest)
1402 repo.ui.pushbuffer()
1402 repo.ui.pushbuffer()
1403 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1403 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1404 repo.ui.popbuffer()
1404 repo.ui.popbuffer()
1405 cl = repo.changelog
1405 cl = repo.changelog
1406 o = set([cl.rev(r) for r in outgoing.missing])
1406 o = set([cl.rev(r) for r in outgoing.missing])
1407 return subset & o
1407 return subset & o
1408
1408
1409 def p1(repo, subset, x):
1409 def p1(repo, subset, x):
1410 """``p1([set])``
1410 """``p1([set])``
1411 First parent of changesets in set, or the working directory.
1411 First parent of changesets in set, or the working directory.
1412 """
1412 """
1413 if x is None:
1413 if x is None:
1414 p = repo[x].p1().rev()
1414 p = repo[x].p1().rev()
1415 if p >= 0:
1415 if p >= 0:
1416 return subset & baseset([p])
1416 return subset & baseset([p])
1417 return baseset()
1417 return baseset()
1418
1418
1419 ps = set()
1419 ps = set()
1420 cl = repo.changelog
1420 cl = repo.changelog
1421 for r in getset(repo, fullreposet(repo), x):
1421 for r in getset(repo, fullreposet(repo), x):
1422 ps.add(cl.parentrevs(r)[0])
1422 ps.add(cl.parentrevs(r)[0])
1423 ps -= set([node.nullrev])
1423 ps -= set([node.nullrev])
1424 # XXX we should turn this into a baseset instead of a set, smartset may do
1424 # XXX we should turn this into a baseset instead of a set, smartset may do
1425 # some optimisations from the fact this is a baseset.
1425 # some optimisations from the fact this is a baseset.
1426 return subset & ps
1426 return subset & ps
1427
1427
1428 def p2(repo, subset, x):
1428 def p2(repo, subset, x):
1429 """``p2([set])``
1429 """``p2([set])``
1430 Second parent of changesets in set, or the working directory.
1430 Second parent of changesets in set, or the working directory.
1431 """
1431 """
1432 if x is None:
1432 if x is None:
1433 ps = repo[x].parents()
1433 ps = repo[x].parents()
1434 try:
1434 try:
1435 p = ps[1].rev()
1435 p = ps[1].rev()
1436 if p >= 0:
1436 if p >= 0:
1437 return subset & baseset([p])
1437 return subset & baseset([p])
1438 return baseset()
1438 return baseset()
1439 except IndexError:
1439 except IndexError:
1440 return baseset()
1440 return baseset()
1441
1441
1442 ps = set()
1442 ps = set()
1443 cl = repo.changelog
1443 cl = repo.changelog
1444 for r in getset(repo, fullreposet(repo), x):
1444 for r in getset(repo, fullreposet(repo), x):
1445 ps.add(cl.parentrevs(r)[1])
1445 ps.add(cl.parentrevs(r)[1])
1446 ps -= set([node.nullrev])
1446 ps -= set([node.nullrev])
1447 # XXX we should turn this into a baseset instead of a set, smartset may do
1447 # XXX we should turn this into a baseset instead of a set, smartset may do
1448 # some optimisations from the fact this is a baseset.
1448 # some optimisations from the fact this is a baseset.
1449 return subset & ps
1449 return subset & ps
1450
1450
1451 def parents(repo, subset, x):
1451 def parents(repo, subset, x):
1452 """``parents([set])``
1452 """``parents([set])``
1453 The set of all parents for all changesets in set, or the working directory.
1453 The set of all parents for all changesets in set, or the working directory.
1454 """
1454 """
1455 if x is None:
1455 if x is None:
1456 ps = set(p.rev() for p in repo[x].parents())
1456 ps = set(p.rev() for p in repo[x].parents())
1457 else:
1457 else:
1458 ps = set()
1458 ps = set()
1459 cl = repo.changelog
1459 cl = repo.changelog
1460 for r in getset(repo, fullreposet(repo), x):
1460 for r in getset(repo, fullreposet(repo), x):
1461 ps.update(cl.parentrevs(r))
1461 ps.update(cl.parentrevs(r))
1462 ps -= set([node.nullrev])
1462 ps -= set([node.nullrev])
1463 return subset & ps
1463 return subset & ps
1464
1464
1465 def _phase(repo, subset, target):
1465 def _phase(repo, subset, target):
1466 """helper to select all rev in phase <target>"""
1466 """helper to select all rev in phase <target>"""
1467 phase = repo._phasecache.phase
1467 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1468 condition = lambda r: phase(repo, r) == target
1468 if repo._phasecache._phasesets:
1469 return subset.filter(condition, cache=False)
1469 s = repo._phasecache._phasesets[target] - repo.changelog.filteredrevs
1470 s = baseset(s)
1471 s.sort() # set are non ordered, so we enforce ascending
1472 return subset & s
1473 else:
1474 phase = repo._phasecache.phase
1475 condition = lambda r: phase(repo, r) == target
1476 return subset.filter(condition, cache=False)
1470
1477
1471 def draft(repo, subset, x):
1478 def draft(repo, subset, x):
1472 """``draft()``
1479 """``draft()``
1473 Changeset in draft phase."""
1480 Changeset in draft phase."""
1474 # i18n: "draft" is a keyword
1481 # i18n: "draft" is a keyword
1475 getargs(x, 0, 0, _("draft takes no arguments"))
1482 getargs(x, 0, 0, _("draft takes no arguments"))
1476 target = phases.draft
1483 target = phases.draft
1477 return _phase(repo, subset, target)
1484 return _phase(repo, subset, target)
1478
1485
1479 def secret(repo, subset, x):
1486 def secret(repo, subset, x):
1480 """``secret()``
1487 """``secret()``
1481 Changeset in secret phase."""
1488 Changeset in secret phase."""
1482 # i18n: "secret" is a keyword
1489 # i18n: "secret" is a keyword
1483 getargs(x, 0, 0, _("secret takes no arguments"))
1490 getargs(x, 0, 0, _("secret takes no arguments"))
1484 target = phases.secret
1491 target = phases.secret
1485 return _phase(repo, subset, target)
1492 return _phase(repo, subset, target)
1486
1493
1487 def parentspec(repo, subset, x, n):
1494 def parentspec(repo, subset, x, n):
1488 """``set^0``
1495 """``set^0``
1489 The set.
1496 The set.
1490 ``set^1`` (or ``set^``), ``set^2``
1497 ``set^1`` (or ``set^``), ``set^2``
1491 First or second parent, respectively, of all changesets in set.
1498 First or second parent, respectively, of all changesets in set.
1492 """
1499 """
1493 try:
1500 try:
1494 n = int(n[1])
1501 n = int(n[1])
1495 if n not in (0, 1, 2):
1502 if n not in (0, 1, 2):
1496 raise ValueError
1503 raise ValueError
1497 except (TypeError, ValueError):
1504 except (TypeError, ValueError):
1498 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1505 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1499 ps = set()
1506 ps = set()
1500 cl = repo.changelog
1507 cl = repo.changelog
1501 for r in getset(repo, fullreposet(repo), x):
1508 for r in getset(repo, fullreposet(repo), x):
1502 if n == 0:
1509 if n == 0:
1503 ps.add(r)
1510 ps.add(r)
1504 elif n == 1:
1511 elif n == 1:
1505 ps.add(cl.parentrevs(r)[0])
1512 ps.add(cl.parentrevs(r)[0])
1506 elif n == 2:
1513 elif n == 2:
1507 parents = cl.parentrevs(r)
1514 parents = cl.parentrevs(r)
1508 if len(parents) > 1:
1515 if len(parents) > 1:
1509 ps.add(parents[1])
1516 ps.add(parents[1])
1510 return subset & ps
1517 return subset & ps
1511
1518
1512 def present(repo, subset, x):
1519 def present(repo, subset, x):
1513 """``present(set)``
1520 """``present(set)``
1514 An empty set, if any revision in set isn't found; otherwise,
1521 An empty set, if any revision in set isn't found; otherwise,
1515 all revisions in set.
1522 all revisions in set.
1516
1523
1517 If any of specified revisions is not present in the local repository,
1524 If any of specified revisions is not present in the local repository,
1518 the query is normally aborted. But this predicate allows the query
1525 the query is normally aborted. But this predicate allows the query
1519 to continue even in such cases.
1526 to continue even in such cases.
1520 """
1527 """
1521 try:
1528 try:
1522 return getset(repo, subset, x)
1529 return getset(repo, subset, x)
1523 except error.RepoLookupError:
1530 except error.RepoLookupError:
1524 return baseset()
1531 return baseset()
1525
1532
1526 # for internal use
1533 # for internal use
1527 def _notpublic(repo, subset, x):
1534 def _notpublic(repo, subset, x):
1528 getargs(x, 0, 0, "_notpublic takes no arguments")
1535 getargs(x, 0, 0, "_notpublic takes no arguments")
1529 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1536 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1530 if repo._phasecache._phasesets:
1537 if repo._phasecache._phasesets:
1531 s = set()
1538 s = set()
1532 for u in repo._phasecache._phasesets[1:]:
1539 for u in repo._phasecache._phasesets[1:]:
1533 s.update(u)
1540 s.update(u)
1534 s = baseset(s - repo.changelog.filteredrevs)
1541 s = baseset(s - repo.changelog.filteredrevs)
1535 s.sort()
1542 s.sort()
1536 return subset & s
1543 return subset & s
1537 else:
1544 else:
1538 phase = repo._phasecache.phase
1545 phase = repo._phasecache.phase
1539 target = phases.public
1546 target = phases.public
1540 condition = lambda r: phase(repo, r) != target
1547 condition = lambda r: phase(repo, r) != target
1541 return subset.filter(condition, cache=False)
1548 return subset.filter(condition, cache=False)
1542
1549
1543 def public(repo, subset, x):
1550 def public(repo, subset, x):
1544 """``public()``
1551 """``public()``
1545 Changeset in public phase."""
1552 Changeset in public phase."""
1546 # i18n: "public" is a keyword
1553 # i18n: "public" is a keyword
1547 getargs(x, 0, 0, _("public takes no arguments"))
1554 getargs(x, 0, 0, _("public takes no arguments"))
1548 phase = repo._phasecache.phase
1555 phase = repo._phasecache.phase
1549 target = phases.public
1556 target = phases.public
1550 condition = lambda r: phase(repo, r) == target
1557 condition = lambda r: phase(repo, r) == target
1551 return subset.filter(condition, cache=False)
1558 return subset.filter(condition, cache=False)
1552
1559
1553 def remote(repo, subset, x):
1560 def remote(repo, subset, x):
1554 """``remote([id [,path]])``
1561 """``remote([id [,path]])``
1555 Local revision that corresponds to the given identifier in a
1562 Local revision that corresponds to the given identifier in a
1556 remote repository, if present. Here, the '.' identifier is a
1563 remote repository, if present. Here, the '.' identifier is a
1557 synonym for the current local branch.
1564 synonym for the current local branch.
1558 """
1565 """
1559
1566
1560 import hg # avoid start-up nasties
1567 import hg # avoid start-up nasties
1561 # i18n: "remote" is a keyword
1568 # i18n: "remote" is a keyword
1562 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1569 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1563
1570
1564 q = '.'
1571 q = '.'
1565 if len(l) > 0:
1572 if len(l) > 0:
1566 # i18n: "remote" is a keyword
1573 # i18n: "remote" is a keyword
1567 q = getstring(l[0], _("remote requires a string id"))
1574 q = getstring(l[0], _("remote requires a string id"))
1568 if q == '.':
1575 if q == '.':
1569 q = repo['.'].branch()
1576 q = repo['.'].branch()
1570
1577
1571 dest = ''
1578 dest = ''
1572 if len(l) > 1:
1579 if len(l) > 1:
1573 # i18n: "remote" is a keyword
1580 # i18n: "remote" is a keyword
1574 dest = getstring(l[1], _("remote requires a repository path"))
1581 dest = getstring(l[1], _("remote requires a repository path"))
1575 dest = repo.ui.expandpath(dest or 'default')
1582 dest = repo.ui.expandpath(dest or 'default')
1576 dest, branches = hg.parseurl(dest)
1583 dest, branches = hg.parseurl(dest)
1577 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1584 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1578 if revs:
1585 if revs:
1579 revs = [repo.lookup(rev) for rev in revs]
1586 revs = [repo.lookup(rev) for rev in revs]
1580 other = hg.peer(repo, {}, dest)
1587 other = hg.peer(repo, {}, dest)
1581 n = other.lookup(q)
1588 n = other.lookup(q)
1582 if n in repo:
1589 if n in repo:
1583 r = repo[n].rev()
1590 r = repo[n].rev()
1584 if r in subset:
1591 if r in subset:
1585 return baseset([r])
1592 return baseset([r])
1586 return baseset()
1593 return baseset()
1587
1594
1588 def removes(repo, subset, x):
1595 def removes(repo, subset, x):
1589 """``removes(pattern)``
1596 """``removes(pattern)``
1590 Changesets which remove files matching pattern.
1597 Changesets which remove files matching pattern.
1591
1598
1592 The pattern without explicit kind like ``glob:`` is expected to be
1599 The pattern without explicit kind like ``glob:`` is expected to be
1593 relative to the current directory and match against a file or a
1600 relative to the current directory and match against a file or a
1594 directory.
1601 directory.
1595 """
1602 """
1596 # i18n: "removes" is a keyword
1603 # i18n: "removes" is a keyword
1597 pat = getstring(x, _("removes requires a pattern"))
1604 pat = getstring(x, _("removes requires a pattern"))
1598 return checkstatus(repo, subset, pat, 2)
1605 return checkstatus(repo, subset, pat, 2)
1599
1606
1600 def rev(repo, subset, x):
1607 def rev(repo, subset, x):
1601 """``rev(number)``
1608 """``rev(number)``
1602 Revision with the given numeric identifier.
1609 Revision with the given numeric identifier.
1603 """
1610 """
1604 # i18n: "rev" is a keyword
1611 # i18n: "rev" is a keyword
1605 l = getargs(x, 1, 1, _("rev requires one argument"))
1612 l = getargs(x, 1, 1, _("rev requires one argument"))
1606 try:
1613 try:
1607 # i18n: "rev" is a keyword
1614 # i18n: "rev" is a keyword
1608 l = int(getstring(l[0], _("rev requires a number")))
1615 l = int(getstring(l[0], _("rev requires a number")))
1609 except (TypeError, ValueError):
1616 except (TypeError, ValueError):
1610 # i18n: "rev" is a keyword
1617 # i18n: "rev" is a keyword
1611 raise error.ParseError(_("rev expects a number"))
1618 raise error.ParseError(_("rev expects a number"))
1612 if l not in repo.changelog and l != node.nullrev:
1619 if l not in repo.changelog and l != node.nullrev:
1613 return baseset()
1620 return baseset()
1614 return subset & baseset([l])
1621 return subset & baseset([l])
1615
1622
1616 def matching(repo, subset, x):
1623 def matching(repo, subset, x):
1617 """``matching(revision [, field])``
1624 """``matching(revision [, field])``
1618 Changesets in which a given set of fields match the set of fields in the
1625 Changesets in which a given set of fields match the set of fields in the
1619 selected revision or set.
1626 selected revision or set.
1620
1627
1621 To match more than one field pass the list of fields to match separated
1628 To match more than one field pass the list of fields to match separated
1622 by spaces (e.g. ``author description``).
1629 by spaces (e.g. ``author description``).
1623
1630
1624 Valid fields are most regular revision fields and some special fields.
1631 Valid fields are most regular revision fields and some special fields.
1625
1632
1626 Regular revision fields are ``description``, ``author``, ``branch``,
1633 Regular revision fields are ``description``, ``author``, ``branch``,
1627 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1634 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1628 and ``diff``.
1635 and ``diff``.
1629 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1636 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1630 contents of the revision. Two revisions matching their ``diff`` will
1637 contents of the revision. Two revisions matching their ``diff`` will
1631 also match their ``files``.
1638 also match their ``files``.
1632
1639
1633 Special fields are ``summary`` and ``metadata``:
1640 Special fields are ``summary`` and ``metadata``:
1634 ``summary`` matches the first line of the description.
1641 ``summary`` matches the first line of the description.
1635 ``metadata`` is equivalent to matching ``description user date``
1642 ``metadata`` is equivalent to matching ``description user date``
1636 (i.e. it matches the main metadata fields).
1643 (i.e. it matches the main metadata fields).
1637
1644
1638 ``metadata`` is the default field which is used when no fields are
1645 ``metadata`` is the default field which is used when no fields are
1639 specified. You can match more than one field at a time.
1646 specified. You can match more than one field at a time.
1640 """
1647 """
1641 # i18n: "matching" is a keyword
1648 # i18n: "matching" is a keyword
1642 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1649 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1643
1650
1644 revs = getset(repo, fullreposet(repo), l[0])
1651 revs = getset(repo, fullreposet(repo), l[0])
1645
1652
1646 fieldlist = ['metadata']
1653 fieldlist = ['metadata']
1647 if len(l) > 1:
1654 if len(l) > 1:
1648 fieldlist = getstring(l[1],
1655 fieldlist = getstring(l[1],
1649 # i18n: "matching" is a keyword
1656 # i18n: "matching" is a keyword
1650 _("matching requires a string "
1657 _("matching requires a string "
1651 "as its second argument")).split()
1658 "as its second argument")).split()
1652
1659
1653 # Make sure that there are no repeated fields,
1660 # Make sure that there are no repeated fields,
1654 # expand the 'special' 'metadata' field type
1661 # expand the 'special' 'metadata' field type
1655 # and check the 'files' whenever we check the 'diff'
1662 # and check the 'files' whenever we check the 'diff'
1656 fields = []
1663 fields = []
1657 for field in fieldlist:
1664 for field in fieldlist:
1658 if field == 'metadata':
1665 if field == 'metadata':
1659 fields += ['user', 'description', 'date']
1666 fields += ['user', 'description', 'date']
1660 elif field == 'diff':
1667 elif field == 'diff':
1661 # a revision matching the diff must also match the files
1668 # a revision matching the diff must also match the files
1662 # since matching the diff is very costly, make sure to
1669 # since matching the diff is very costly, make sure to
1663 # also match the files first
1670 # also match the files first
1664 fields += ['files', 'diff']
1671 fields += ['files', 'diff']
1665 else:
1672 else:
1666 if field == 'author':
1673 if field == 'author':
1667 field = 'user'
1674 field = 'user'
1668 fields.append(field)
1675 fields.append(field)
1669 fields = set(fields)
1676 fields = set(fields)
1670 if 'summary' in fields and 'description' in fields:
1677 if 'summary' in fields and 'description' in fields:
1671 # If a revision matches its description it also matches its summary
1678 # If a revision matches its description it also matches its summary
1672 fields.discard('summary')
1679 fields.discard('summary')
1673
1680
1674 # We may want to match more than one field
1681 # We may want to match more than one field
1675 # Not all fields take the same amount of time to be matched
1682 # Not all fields take the same amount of time to be matched
1676 # Sort the selected fields in order of increasing matching cost
1683 # Sort the selected fields in order of increasing matching cost
1677 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1684 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1678 'files', 'description', 'substate', 'diff']
1685 'files', 'description', 'substate', 'diff']
1679 def fieldkeyfunc(f):
1686 def fieldkeyfunc(f):
1680 try:
1687 try:
1681 return fieldorder.index(f)
1688 return fieldorder.index(f)
1682 except ValueError:
1689 except ValueError:
1683 # assume an unknown field is very costly
1690 # assume an unknown field is very costly
1684 return len(fieldorder)
1691 return len(fieldorder)
1685 fields = list(fields)
1692 fields = list(fields)
1686 fields.sort(key=fieldkeyfunc)
1693 fields.sort(key=fieldkeyfunc)
1687
1694
1688 # Each field will be matched with its own "getfield" function
1695 # Each field will be matched with its own "getfield" function
1689 # which will be added to the getfieldfuncs array of functions
1696 # which will be added to the getfieldfuncs array of functions
1690 getfieldfuncs = []
1697 getfieldfuncs = []
1691 _funcs = {
1698 _funcs = {
1692 'user': lambda r: repo[r].user(),
1699 'user': lambda r: repo[r].user(),
1693 'branch': lambda r: repo[r].branch(),
1700 'branch': lambda r: repo[r].branch(),
1694 'date': lambda r: repo[r].date(),
1701 'date': lambda r: repo[r].date(),
1695 'description': lambda r: repo[r].description(),
1702 'description': lambda r: repo[r].description(),
1696 'files': lambda r: repo[r].files(),
1703 'files': lambda r: repo[r].files(),
1697 'parents': lambda r: repo[r].parents(),
1704 'parents': lambda r: repo[r].parents(),
1698 'phase': lambda r: repo[r].phase(),
1705 'phase': lambda r: repo[r].phase(),
1699 'substate': lambda r: repo[r].substate,
1706 'substate': lambda r: repo[r].substate,
1700 'summary': lambda r: repo[r].description().splitlines()[0],
1707 'summary': lambda r: repo[r].description().splitlines()[0],
1701 'diff': lambda r: list(repo[r].diff(git=True),)
1708 'diff': lambda r: list(repo[r].diff(git=True),)
1702 }
1709 }
1703 for info in fields:
1710 for info in fields:
1704 getfield = _funcs.get(info, None)
1711 getfield = _funcs.get(info, None)
1705 if getfield is None:
1712 if getfield is None:
1706 raise error.ParseError(
1713 raise error.ParseError(
1707 # i18n: "matching" is a keyword
1714 # i18n: "matching" is a keyword
1708 _("unexpected field name passed to matching: %s") % info)
1715 _("unexpected field name passed to matching: %s") % info)
1709 getfieldfuncs.append(getfield)
1716 getfieldfuncs.append(getfield)
1710 # convert the getfield array of functions into a "getinfo" function
1717 # convert the getfield array of functions into a "getinfo" function
1711 # which returns an array of field values (or a single value if there
1718 # which returns an array of field values (or a single value if there
1712 # is only one field to match)
1719 # is only one field to match)
1713 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1720 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1714
1721
1715 def matches(x):
1722 def matches(x):
1716 for rev in revs:
1723 for rev in revs:
1717 target = getinfo(rev)
1724 target = getinfo(rev)
1718 match = True
1725 match = True
1719 for n, f in enumerate(getfieldfuncs):
1726 for n, f in enumerate(getfieldfuncs):
1720 if target[n] != f(x):
1727 if target[n] != f(x):
1721 match = False
1728 match = False
1722 if match:
1729 if match:
1723 return True
1730 return True
1724 return False
1731 return False
1725
1732
1726 return subset.filter(matches)
1733 return subset.filter(matches)
1727
1734
1728 def reverse(repo, subset, x):
1735 def reverse(repo, subset, x):
1729 """``reverse(set)``
1736 """``reverse(set)``
1730 Reverse order of set.
1737 Reverse order of set.
1731 """
1738 """
1732 l = getset(repo, subset, x)
1739 l = getset(repo, subset, x)
1733 l.reverse()
1740 l.reverse()
1734 return l
1741 return l
1735
1742
1736 def roots(repo, subset, x):
1743 def roots(repo, subset, x):
1737 """``roots(set)``
1744 """``roots(set)``
1738 Changesets in set with no parent changeset in set.
1745 Changesets in set with no parent changeset in set.
1739 """
1746 """
1740 s = getset(repo, fullreposet(repo), x)
1747 s = getset(repo, fullreposet(repo), x)
1741 subset = subset & s# baseset([r for r in s if r in subset])
1748 subset = subset & s# baseset([r for r in s if r in subset])
1742 cs = _children(repo, subset, s)
1749 cs = _children(repo, subset, s)
1743 return subset - cs
1750 return subset - cs
1744
1751
1745 def sort(repo, subset, x):
1752 def sort(repo, subset, x):
1746 """``sort(set[, [-]key...])``
1753 """``sort(set[, [-]key...])``
1747 Sort set by keys. The default sort order is ascending, specify a key
1754 Sort set by keys. The default sort order is ascending, specify a key
1748 as ``-key`` to sort in descending order.
1755 as ``-key`` to sort in descending order.
1749
1756
1750 The keys can be:
1757 The keys can be:
1751
1758
1752 - ``rev`` for the revision number,
1759 - ``rev`` for the revision number,
1753 - ``branch`` for the branch name,
1760 - ``branch`` for the branch name,
1754 - ``desc`` for the commit message (description),
1761 - ``desc`` for the commit message (description),
1755 - ``user`` for user name (``author`` can be used as an alias),
1762 - ``user`` for user name (``author`` can be used as an alias),
1756 - ``date`` for the commit date
1763 - ``date`` for the commit date
1757 """
1764 """
1758 # i18n: "sort" is a keyword
1765 # i18n: "sort" is a keyword
1759 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1766 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1760 keys = "rev"
1767 keys = "rev"
1761 if len(l) == 2:
1768 if len(l) == 2:
1762 # i18n: "sort" is a keyword
1769 # i18n: "sort" is a keyword
1763 keys = getstring(l[1], _("sort spec must be a string"))
1770 keys = getstring(l[1], _("sort spec must be a string"))
1764
1771
1765 s = l[0]
1772 s = l[0]
1766 keys = keys.split()
1773 keys = keys.split()
1767 l = []
1774 l = []
1768 def invert(s):
1775 def invert(s):
1769 return "".join(chr(255 - ord(c)) for c in s)
1776 return "".join(chr(255 - ord(c)) for c in s)
1770 revs = getset(repo, subset, s)
1777 revs = getset(repo, subset, s)
1771 if keys == ["rev"]:
1778 if keys == ["rev"]:
1772 revs.sort()
1779 revs.sort()
1773 return revs
1780 return revs
1774 elif keys == ["-rev"]:
1781 elif keys == ["-rev"]:
1775 revs.sort(reverse=True)
1782 revs.sort(reverse=True)
1776 return revs
1783 return revs
1777 for r in revs:
1784 for r in revs:
1778 c = repo[r]
1785 c = repo[r]
1779 e = []
1786 e = []
1780 for k in keys:
1787 for k in keys:
1781 if k == 'rev':
1788 if k == 'rev':
1782 e.append(r)
1789 e.append(r)
1783 elif k == '-rev':
1790 elif k == '-rev':
1784 e.append(-r)
1791 e.append(-r)
1785 elif k == 'branch':
1792 elif k == 'branch':
1786 e.append(c.branch())
1793 e.append(c.branch())
1787 elif k == '-branch':
1794 elif k == '-branch':
1788 e.append(invert(c.branch()))
1795 e.append(invert(c.branch()))
1789 elif k == 'desc':
1796 elif k == 'desc':
1790 e.append(c.description())
1797 e.append(c.description())
1791 elif k == '-desc':
1798 elif k == '-desc':
1792 e.append(invert(c.description()))
1799 e.append(invert(c.description()))
1793 elif k in 'user author':
1800 elif k in 'user author':
1794 e.append(c.user())
1801 e.append(c.user())
1795 elif k in '-user -author':
1802 elif k in '-user -author':
1796 e.append(invert(c.user()))
1803 e.append(invert(c.user()))
1797 elif k == 'date':
1804 elif k == 'date':
1798 e.append(c.date()[0])
1805 e.append(c.date()[0])
1799 elif k == '-date':
1806 elif k == '-date':
1800 e.append(-c.date()[0])
1807 e.append(-c.date()[0])
1801 else:
1808 else:
1802 raise error.ParseError(_("unknown sort key %r") % k)
1809 raise error.ParseError(_("unknown sort key %r") % k)
1803 e.append(r)
1810 e.append(r)
1804 l.append(e)
1811 l.append(e)
1805 l.sort()
1812 l.sort()
1806 return baseset([e[-1] for e in l])
1813 return baseset([e[-1] for e in l])
1807
1814
1808 def subrepo(repo, subset, x):
1815 def subrepo(repo, subset, x):
1809 """``subrepo([pattern])``
1816 """``subrepo([pattern])``
1810 Changesets that add, modify or remove the given subrepo. If no subrepo
1817 Changesets that add, modify or remove the given subrepo. If no subrepo
1811 pattern is named, any subrepo changes are returned.
1818 pattern is named, any subrepo changes are returned.
1812 """
1819 """
1813 # i18n: "subrepo" is a keyword
1820 # i18n: "subrepo" is a keyword
1814 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
1821 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
1815 if len(args) != 0:
1822 if len(args) != 0:
1816 pat = getstring(args[0], _("subrepo requires a pattern"))
1823 pat = getstring(args[0], _("subrepo requires a pattern"))
1817
1824
1818 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
1825 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
1819
1826
1820 def submatches(names):
1827 def submatches(names):
1821 k, p, m = _stringmatcher(pat)
1828 k, p, m = _stringmatcher(pat)
1822 for name in names:
1829 for name in names:
1823 if m(name):
1830 if m(name):
1824 yield name
1831 yield name
1825
1832
1826 def matches(x):
1833 def matches(x):
1827 c = repo[x]
1834 c = repo[x]
1828 s = repo.status(c.p1().node(), c.node(), match=m)
1835 s = repo.status(c.p1().node(), c.node(), match=m)
1829
1836
1830 if len(args) == 0:
1837 if len(args) == 0:
1831 return s.added or s.modified or s.removed
1838 return s.added or s.modified or s.removed
1832
1839
1833 if s.added:
1840 if s.added:
1834 return any(submatches(c.substate.keys()))
1841 return any(submatches(c.substate.keys()))
1835
1842
1836 if s.modified:
1843 if s.modified:
1837 subs = set(c.p1().substate.keys())
1844 subs = set(c.p1().substate.keys())
1838 subs.update(c.substate.keys())
1845 subs.update(c.substate.keys())
1839
1846
1840 for path in submatches(subs):
1847 for path in submatches(subs):
1841 if c.p1().substate.get(path) != c.substate.get(path):
1848 if c.p1().substate.get(path) != c.substate.get(path):
1842 return True
1849 return True
1843
1850
1844 if s.removed:
1851 if s.removed:
1845 return any(submatches(c.p1().substate.keys()))
1852 return any(submatches(c.p1().substate.keys()))
1846
1853
1847 return False
1854 return False
1848
1855
1849 return subset.filter(matches)
1856 return subset.filter(matches)
1850
1857
1851 def _stringmatcher(pattern):
1858 def _stringmatcher(pattern):
1852 """
1859 """
1853 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1860 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1854 returns the matcher name, pattern, and matcher function.
1861 returns the matcher name, pattern, and matcher function.
1855 missing or unknown prefixes are treated as literal matches.
1862 missing or unknown prefixes are treated as literal matches.
1856
1863
1857 helper for tests:
1864 helper for tests:
1858 >>> def test(pattern, *tests):
1865 >>> def test(pattern, *tests):
1859 ... kind, pattern, matcher = _stringmatcher(pattern)
1866 ... kind, pattern, matcher = _stringmatcher(pattern)
1860 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1867 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1861
1868
1862 exact matching (no prefix):
1869 exact matching (no prefix):
1863 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1870 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1864 ('literal', 'abcdefg', [False, False, True])
1871 ('literal', 'abcdefg', [False, False, True])
1865
1872
1866 regex matching ('re:' prefix)
1873 regex matching ('re:' prefix)
1867 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1874 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1868 ('re', 'a.+b', [False, False, True])
1875 ('re', 'a.+b', [False, False, True])
1869
1876
1870 force exact matches ('literal:' prefix)
1877 force exact matches ('literal:' prefix)
1871 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1878 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1872 ('literal', 're:foobar', [False, True])
1879 ('literal', 're:foobar', [False, True])
1873
1880
1874 unknown prefixes are ignored and treated as literals
1881 unknown prefixes are ignored and treated as literals
1875 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1882 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1876 ('literal', 'foo:bar', [False, False, True])
1883 ('literal', 'foo:bar', [False, False, True])
1877 """
1884 """
1878 if pattern.startswith('re:'):
1885 if pattern.startswith('re:'):
1879 pattern = pattern[3:]
1886 pattern = pattern[3:]
1880 try:
1887 try:
1881 regex = re.compile(pattern)
1888 regex = re.compile(pattern)
1882 except re.error, e:
1889 except re.error, e:
1883 raise error.ParseError(_('invalid regular expression: %s')
1890 raise error.ParseError(_('invalid regular expression: %s')
1884 % e)
1891 % e)
1885 return 're', pattern, regex.search
1892 return 're', pattern, regex.search
1886 elif pattern.startswith('literal:'):
1893 elif pattern.startswith('literal:'):
1887 pattern = pattern[8:]
1894 pattern = pattern[8:]
1888 return 'literal', pattern, pattern.__eq__
1895 return 'literal', pattern, pattern.__eq__
1889
1896
1890 def _substringmatcher(pattern):
1897 def _substringmatcher(pattern):
1891 kind, pattern, matcher = _stringmatcher(pattern)
1898 kind, pattern, matcher = _stringmatcher(pattern)
1892 if kind == 'literal':
1899 if kind == 'literal':
1893 matcher = lambda s: pattern in s
1900 matcher = lambda s: pattern in s
1894 return kind, pattern, matcher
1901 return kind, pattern, matcher
1895
1902
1896 def tag(repo, subset, x):
1903 def tag(repo, subset, x):
1897 """``tag([name])``
1904 """``tag([name])``
1898 The specified tag by name, or all tagged revisions if no name is given.
1905 The specified tag by name, or all tagged revisions if no name is given.
1899
1906
1900 If `name` starts with `re:`, the remainder of the name is treated as
1907 If `name` starts with `re:`, the remainder of the name is treated as
1901 a regular expression. To match a tag that actually starts with `re:`,
1908 a regular expression. To match a tag that actually starts with `re:`,
1902 use the prefix `literal:`.
1909 use the prefix `literal:`.
1903 """
1910 """
1904 # i18n: "tag" is a keyword
1911 # i18n: "tag" is a keyword
1905 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1912 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1906 cl = repo.changelog
1913 cl = repo.changelog
1907 if args:
1914 if args:
1908 pattern = getstring(args[0],
1915 pattern = getstring(args[0],
1909 # i18n: "tag" is a keyword
1916 # i18n: "tag" is a keyword
1910 _('the argument to tag must be a string'))
1917 _('the argument to tag must be a string'))
1911 kind, pattern, matcher = _stringmatcher(pattern)
1918 kind, pattern, matcher = _stringmatcher(pattern)
1912 if kind == 'literal':
1919 if kind == 'literal':
1913 # avoid resolving all tags
1920 # avoid resolving all tags
1914 tn = repo._tagscache.tags.get(pattern, None)
1921 tn = repo._tagscache.tags.get(pattern, None)
1915 if tn is None:
1922 if tn is None:
1916 raise error.RepoLookupError(_("tag '%s' does not exist")
1923 raise error.RepoLookupError(_("tag '%s' does not exist")
1917 % pattern)
1924 % pattern)
1918 s = set([repo[tn].rev()])
1925 s = set([repo[tn].rev()])
1919 else:
1926 else:
1920 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1927 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1921 else:
1928 else:
1922 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1929 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1923 return subset & s
1930 return subset & s
1924
1931
1925 def tagged(repo, subset, x):
1932 def tagged(repo, subset, x):
1926 return tag(repo, subset, x)
1933 return tag(repo, subset, x)
1927
1934
1928 def unstable(repo, subset, x):
1935 def unstable(repo, subset, x):
1929 """``unstable()``
1936 """``unstable()``
1930 Non-obsolete changesets with obsolete ancestors.
1937 Non-obsolete changesets with obsolete ancestors.
1931 """
1938 """
1932 # i18n: "unstable" is a keyword
1939 # i18n: "unstable" is a keyword
1933 getargs(x, 0, 0, _("unstable takes no arguments"))
1940 getargs(x, 0, 0, _("unstable takes no arguments"))
1934 unstables = obsmod.getrevs(repo, 'unstable')
1941 unstables = obsmod.getrevs(repo, 'unstable')
1935 return subset & unstables
1942 return subset & unstables
1936
1943
1937
1944
1938 def user(repo, subset, x):
1945 def user(repo, subset, x):
1939 """``user(string)``
1946 """``user(string)``
1940 User name contains string. The match is case-insensitive.
1947 User name contains string. The match is case-insensitive.
1941
1948
1942 If `string` starts with `re:`, the remainder of the string is treated as
1949 If `string` starts with `re:`, the remainder of the string is treated as
1943 a regular expression. To match a user that actually contains `re:`, use
1950 a regular expression. To match a user that actually contains `re:`, use
1944 the prefix `literal:`.
1951 the prefix `literal:`.
1945 """
1952 """
1946 return author(repo, subset, x)
1953 return author(repo, subset, x)
1947
1954
1948 # experimental
1955 # experimental
1949 def wdir(repo, subset, x):
1956 def wdir(repo, subset, x):
1950 # i18n: "wdir" is a keyword
1957 # i18n: "wdir" is a keyword
1951 getargs(x, 0, 0, _("wdir takes no arguments"))
1958 getargs(x, 0, 0, _("wdir takes no arguments"))
1952 if None in subset or isinstance(subset, fullreposet):
1959 if None in subset or isinstance(subset, fullreposet):
1953 return baseset([None])
1960 return baseset([None])
1954 return baseset()
1961 return baseset()
1955
1962
1956 # for internal use
1963 # for internal use
1957 def _list(repo, subset, x):
1964 def _list(repo, subset, x):
1958 s = getstring(x, "internal error")
1965 s = getstring(x, "internal error")
1959 if not s:
1966 if not s:
1960 return baseset()
1967 return baseset()
1961 # remove duplicates here. it's difficult for caller to deduplicate sets
1968 # remove duplicates here. it's difficult for caller to deduplicate sets
1962 # because different symbols can point to the same rev.
1969 # because different symbols can point to the same rev.
1963 cl = repo.changelog
1970 cl = repo.changelog
1964 ls = []
1971 ls = []
1965 seen = set()
1972 seen = set()
1966 for t in s.split('\0'):
1973 for t in s.split('\0'):
1967 try:
1974 try:
1968 # fast path for integer revision
1975 # fast path for integer revision
1969 r = int(t)
1976 r = int(t)
1970 if str(r) != t or r not in cl:
1977 if str(r) != t or r not in cl:
1971 raise ValueError
1978 raise ValueError
1972 except ValueError:
1979 except ValueError:
1973 r = repo[t].rev()
1980 r = repo[t].rev()
1974 if r in seen:
1981 if r in seen:
1975 continue
1982 continue
1976 if (r in subset
1983 if (r in subset
1977 or r == node.nullrev and isinstance(subset, fullreposet)):
1984 or r == node.nullrev and isinstance(subset, fullreposet)):
1978 ls.append(r)
1985 ls.append(r)
1979 seen.add(r)
1986 seen.add(r)
1980 return baseset(ls)
1987 return baseset(ls)
1981
1988
1982 # for internal use
1989 # for internal use
1983 def _intlist(repo, subset, x):
1990 def _intlist(repo, subset, x):
1984 s = getstring(x, "internal error")
1991 s = getstring(x, "internal error")
1985 if not s:
1992 if not s:
1986 return baseset()
1993 return baseset()
1987 ls = [int(r) for r in s.split('\0')]
1994 ls = [int(r) for r in s.split('\0')]
1988 s = subset
1995 s = subset
1989 return baseset([r for r in ls if r in s])
1996 return baseset([r for r in ls if r in s])
1990
1997
1991 # for internal use
1998 # for internal use
1992 def _hexlist(repo, subset, x):
1999 def _hexlist(repo, subset, x):
1993 s = getstring(x, "internal error")
2000 s = getstring(x, "internal error")
1994 if not s:
2001 if not s:
1995 return baseset()
2002 return baseset()
1996 cl = repo.changelog
2003 cl = repo.changelog
1997 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2004 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
1998 s = subset
2005 s = subset
1999 return baseset([r for r in ls if r in s])
2006 return baseset([r for r in ls if r in s])
2000
2007
2001 symbols = {
2008 symbols = {
2002 "adds": adds,
2009 "adds": adds,
2003 "all": getall,
2010 "all": getall,
2004 "ancestor": ancestor,
2011 "ancestor": ancestor,
2005 "ancestors": ancestors,
2012 "ancestors": ancestors,
2006 "_firstancestors": _firstancestors,
2013 "_firstancestors": _firstancestors,
2007 "author": author,
2014 "author": author,
2008 "bisect": bisect,
2015 "bisect": bisect,
2009 "bisected": bisected,
2016 "bisected": bisected,
2010 "bookmark": bookmark,
2017 "bookmark": bookmark,
2011 "branch": branch,
2018 "branch": branch,
2012 "branchpoint": branchpoint,
2019 "branchpoint": branchpoint,
2013 "bumped": bumped,
2020 "bumped": bumped,
2014 "bundle": bundle,
2021 "bundle": bundle,
2015 "children": children,
2022 "children": children,
2016 "closed": closed,
2023 "closed": closed,
2017 "contains": contains,
2024 "contains": contains,
2018 "converted": converted,
2025 "converted": converted,
2019 "date": date,
2026 "date": date,
2020 "desc": desc,
2027 "desc": desc,
2021 "descendants": descendants,
2028 "descendants": descendants,
2022 "_firstdescendants": _firstdescendants,
2029 "_firstdescendants": _firstdescendants,
2023 "destination": destination,
2030 "destination": destination,
2024 "divergent": divergent,
2031 "divergent": divergent,
2025 "draft": draft,
2032 "draft": draft,
2026 "extinct": extinct,
2033 "extinct": extinct,
2027 "extra": extra,
2034 "extra": extra,
2028 "file": hasfile,
2035 "file": hasfile,
2029 "filelog": filelog,
2036 "filelog": filelog,
2030 "first": first,
2037 "first": first,
2031 "follow": follow,
2038 "follow": follow,
2032 "_followfirst": _followfirst,
2039 "_followfirst": _followfirst,
2033 "grep": grep,
2040 "grep": grep,
2034 "head": head,
2041 "head": head,
2035 "heads": heads,
2042 "heads": heads,
2036 "hidden": hidden,
2043 "hidden": hidden,
2037 "id": node_,
2044 "id": node_,
2038 "keyword": keyword,
2045 "keyword": keyword,
2039 "last": last,
2046 "last": last,
2040 "limit": limit,
2047 "limit": limit,
2041 "_matchfiles": _matchfiles,
2048 "_matchfiles": _matchfiles,
2042 "max": maxrev,
2049 "max": maxrev,
2043 "merge": merge,
2050 "merge": merge,
2044 "min": minrev,
2051 "min": minrev,
2045 "modifies": modifies,
2052 "modifies": modifies,
2046 "named": named,
2053 "named": named,
2047 "obsolete": obsolete,
2054 "obsolete": obsolete,
2048 "only": only,
2055 "only": only,
2049 "origin": origin,
2056 "origin": origin,
2050 "outgoing": outgoing,
2057 "outgoing": outgoing,
2051 "p1": p1,
2058 "p1": p1,
2052 "p2": p2,
2059 "p2": p2,
2053 "parents": parents,
2060 "parents": parents,
2054 "present": present,
2061 "present": present,
2055 "public": public,
2062 "public": public,
2056 "_notpublic": _notpublic,
2063 "_notpublic": _notpublic,
2057 "remote": remote,
2064 "remote": remote,
2058 "removes": removes,
2065 "removes": removes,
2059 "rev": rev,
2066 "rev": rev,
2060 "reverse": reverse,
2067 "reverse": reverse,
2061 "roots": roots,
2068 "roots": roots,
2062 "sort": sort,
2069 "sort": sort,
2063 "secret": secret,
2070 "secret": secret,
2064 "subrepo": subrepo,
2071 "subrepo": subrepo,
2065 "matching": matching,
2072 "matching": matching,
2066 "tag": tag,
2073 "tag": tag,
2067 "tagged": tagged,
2074 "tagged": tagged,
2068 "user": user,
2075 "user": user,
2069 "unstable": unstable,
2076 "unstable": unstable,
2070 "wdir": wdir,
2077 "wdir": wdir,
2071 "_list": _list,
2078 "_list": _list,
2072 "_intlist": _intlist,
2079 "_intlist": _intlist,
2073 "_hexlist": _hexlist,
2080 "_hexlist": _hexlist,
2074 }
2081 }
2075
2082
2076 # symbols which can't be used for a DoS attack for any given input
2083 # symbols which can't be used for a DoS attack for any given input
2077 # (e.g. those which accept regexes as plain strings shouldn't be included)
2084 # (e.g. those which accept regexes as plain strings shouldn't be included)
2078 # functions that just return a lot of changesets (like all) don't count here
2085 # functions that just return a lot of changesets (like all) don't count here
2079 safesymbols = set([
2086 safesymbols = set([
2080 "adds",
2087 "adds",
2081 "all",
2088 "all",
2082 "ancestor",
2089 "ancestor",
2083 "ancestors",
2090 "ancestors",
2084 "_firstancestors",
2091 "_firstancestors",
2085 "author",
2092 "author",
2086 "bisect",
2093 "bisect",
2087 "bisected",
2094 "bisected",
2088 "bookmark",
2095 "bookmark",
2089 "branch",
2096 "branch",
2090 "branchpoint",
2097 "branchpoint",
2091 "bumped",
2098 "bumped",
2092 "bundle",
2099 "bundle",
2093 "children",
2100 "children",
2094 "closed",
2101 "closed",
2095 "converted",
2102 "converted",
2096 "date",
2103 "date",
2097 "desc",
2104 "desc",
2098 "descendants",
2105 "descendants",
2099 "_firstdescendants",
2106 "_firstdescendants",
2100 "destination",
2107 "destination",
2101 "divergent",
2108 "divergent",
2102 "draft",
2109 "draft",
2103 "extinct",
2110 "extinct",
2104 "extra",
2111 "extra",
2105 "file",
2112 "file",
2106 "filelog",
2113 "filelog",
2107 "first",
2114 "first",
2108 "follow",
2115 "follow",
2109 "_followfirst",
2116 "_followfirst",
2110 "head",
2117 "head",
2111 "heads",
2118 "heads",
2112 "hidden",
2119 "hidden",
2113 "id",
2120 "id",
2114 "keyword",
2121 "keyword",
2115 "last",
2122 "last",
2116 "limit",
2123 "limit",
2117 "_matchfiles",
2124 "_matchfiles",
2118 "max",
2125 "max",
2119 "merge",
2126 "merge",
2120 "min",
2127 "min",
2121 "modifies",
2128 "modifies",
2122 "obsolete",
2129 "obsolete",
2123 "only",
2130 "only",
2124 "origin",
2131 "origin",
2125 "outgoing",
2132 "outgoing",
2126 "p1",
2133 "p1",
2127 "p2",
2134 "p2",
2128 "parents",
2135 "parents",
2129 "present",
2136 "present",
2130 "public",
2137 "public",
2131 "_notpublic",
2138 "_notpublic",
2132 "remote",
2139 "remote",
2133 "removes",
2140 "removes",
2134 "rev",
2141 "rev",
2135 "reverse",
2142 "reverse",
2136 "roots",
2143 "roots",
2137 "sort",
2144 "sort",
2138 "secret",
2145 "secret",
2139 "matching",
2146 "matching",
2140 "tag",
2147 "tag",
2141 "tagged",
2148 "tagged",
2142 "user",
2149 "user",
2143 "unstable",
2150 "unstable",
2144 "wdir",
2151 "wdir",
2145 "_list",
2152 "_list",
2146 "_intlist",
2153 "_intlist",
2147 "_hexlist",
2154 "_hexlist",
2148 ])
2155 ])
2149
2156
2150 methods = {
2157 methods = {
2151 "range": rangeset,
2158 "range": rangeset,
2152 "dagrange": dagrange,
2159 "dagrange": dagrange,
2153 "string": stringset,
2160 "string": stringset,
2154 "symbol": stringset,
2161 "symbol": stringset,
2155 "and": andset,
2162 "and": andset,
2156 "or": orset,
2163 "or": orset,
2157 "not": notset,
2164 "not": notset,
2158 "list": listset,
2165 "list": listset,
2159 "func": func,
2166 "func": func,
2160 "ancestor": ancestorspec,
2167 "ancestor": ancestorspec,
2161 "parent": parentspec,
2168 "parent": parentspec,
2162 "parentpost": p1,
2169 "parentpost": p1,
2163 }
2170 }
2164
2171
2165 def optimize(x, small):
2172 def optimize(x, small):
2166 if x is None:
2173 if x is None:
2167 return 0, x
2174 return 0, x
2168
2175
2169 smallbonus = 1
2176 smallbonus = 1
2170 if small:
2177 if small:
2171 smallbonus = .5
2178 smallbonus = .5
2172
2179
2173 op = x[0]
2180 op = x[0]
2174 if op == 'minus':
2181 if op == 'minus':
2175 return optimize(('and', x[1], ('not', x[2])), small)
2182 return optimize(('and', x[1], ('not', x[2])), small)
2176 elif op == 'only':
2183 elif op == 'only':
2177 return optimize(('func', ('symbol', 'only'),
2184 return optimize(('func', ('symbol', 'only'),
2178 ('list', x[1], x[2])), small)
2185 ('list', x[1], x[2])), small)
2179 elif op == 'onlypost':
2186 elif op == 'onlypost':
2180 return optimize(('func', ('symbol', 'only'), x[1]), small)
2187 return optimize(('func', ('symbol', 'only'), x[1]), small)
2181 elif op == 'dagrangepre':
2188 elif op == 'dagrangepre':
2182 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2189 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2183 elif op == 'dagrangepost':
2190 elif op == 'dagrangepost':
2184 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2191 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2185 elif op == 'rangepre':
2192 elif op == 'rangepre':
2186 return optimize(('range', ('string', '0'), x[1]), small)
2193 return optimize(('range', ('string', '0'), x[1]), small)
2187 elif op == 'rangepost':
2194 elif op == 'rangepost':
2188 return optimize(('range', x[1], ('string', 'tip')), small)
2195 return optimize(('range', x[1], ('string', 'tip')), small)
2189 elif op == 'negate':
2196 elif op == 'negate':
2190 return optimize(('string',
2197 return optimize(('string',
2191 '-' + getstring(x[1], _("can't negate that"))), small)
2198 '-' + getstring(x[1], _("can't negate that"))), small)
2192 elif op in 'string symbol negate':
2199 elif op in 'string symbol negate':
2193 return smallbonus, x # single revisions are small
2200 return smallbonus, x # single revisions are small
2194 elif op == 'and':
2201 elif op == 'and':
2195 wa, ta = optimize(x[1], True)
2202 wa, ta = optimize(x[1], True)
2196 wb, tb = optimize(x[2], True)
2203 wb, tb = optimize(x[2], True)
2197
2204
2198 # (::x and not ::y)/(not ::y and ::x) have a fast path
2205 # (::x and not ::y)/(not ::y and ::x) have a fast path
2199 def isonly(revs, bases):
2206 def isonly(revs, bases):
2200 return (
2207 return (
2201 revs[0] == 'func'
2208 revs[0] == 'func'
2202 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2209 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2203 and bases[0] == 'not'
2210 and bases[0] == 'not'
2204 and bases[1][0] == 'func'
2211 and bases[1][0] == 'func'
2205 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2212 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2206
2213
2207 w = min(wa, wb)
2214 w = min(wa, wb)
2208 if isonly(ta, tb):
2215 if isonly(ta, tb):
2209 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2216 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2210 if isonly(tb, ta):
2217 if isonly(tb, ta):
2211 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2218 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2212
2219
2213 if wa > wb:
2220 if wa > wb:
2214 return w, (op, tb, ta)
2221 return w, (op, tb, ta)
2215 return w, (op, ta, tb)
2222 return w, (op, ta, tb)
2216 elif op == 'or':
2223 elif op == 'or':
2217 # fast path for machine-generated expression, that is likely to have
2224 # fast path for machine-generated expression, that is likely to have
2218 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
2225 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
2219 ws, ts, ss = [], [], []
2226 ws, ts, ss = [], [], []
2220 def flushss():
2227 def flushss():
2221 if not ss:
2228 if not ss:
2222 return
2229 return
2223 if len(ss) == 1:
2230 if len(ss) == 1:
2224 w, t = ss[0]
2231 w, t = ss[0]
2225 else:
2232 else:
2226 s = '\0'.join(t[1] for w, t in ss)
2233 s = '\0'.join(t[1] for w, t in ss)
2227 y = ('func', ('symbol', '_list'), ('string', s))
2234 y = ('func', ('symbol', '_list'), ('string', s))
2228 w, t = optimize(y, False)
2235 w, t = optimize(y, False)
2229 ws.append(w)
2236 ws.append(w)
2230 ts.append(t)
2237 ts.append(t)
2231 del ss[:]
2238 del ss[:]
2232 for y in x[1:]:
2239 for y in x[1:]:
2233 w, t = optimize(y, False)
2240 w, t = optimize(y, False)
2234 if t[0] == 'string' or t[0] == 'symbol':
2241 if t[0] == 'string' or t[0] == 'symbol':
2235 ss.append((w, t))
2242 ss.append((w, t))
2236 continue
2243 continue
2237 flushss()
2244 flushss()
2238 ws.append(w)
2245 ws.append(w)
2239 ts.append(t)
2246 ts.append(t)
2240 flushss()
2247 flushss()
2241 if len(ts) == 1:
2248 if len(ts) == 1:
2242 return ws[0], ts[0] # 'or' operation is fully optimized out
2249 return ws[0], ts[0] # 'or' operation is fully optimized out
2243 # we can't reorder trees by weight because it would change the order.
2250 # we can't reorder trees by weight because it would change the order.
2244 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
2251 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
2245 # ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0]))
2252 # ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0]))
2246 return max(ws), (op,) + tuple(ts)
2253 return max(ws), (op,) + tuple(ts)
2247 elif op == 'not':
2254 elif op == 'not':
2248 # Optimize not public() to _notpublic() because we have a fast version
2255 # Optimize not public() to _notpublic() because we have a fast version
2249 if x[1] == ('func', ('symbol', 'public'), None):
2256 if x[1] == ('func', ('symbol', 'public'), None):
2250 newsym = ('func', ('symbol', '_notpublic'), None)
2257 newsym = ('func', ('symbol', '_notpublic'), None)
2251 o = optimize(newsym, not small)
2258 o = optimize(newsym, not small)
2252 return o[0], o[1]
2259 return o[0], o[1]
2253 else:
2260 else:
2254 o = optimize(x[1], not small)
2261 o = optimize(x[1], not small)
2255 return o[0], (op, o[1])
2262 return o[0], (op, o[1])
2256 elif op == 'parentpost':
2263 elif op == 'parentpost':
2257 o = optimize(x[1], small)
2264 o = optimize(x[1], small)
2258 return o[0], (op, o[1])
2265 return o[0], (op, o[1])
2259 elif op == 'group':
2266 elif op == 'group':
2260 return optimize(x[1], small)
2267 return optimize(x[1], small)
2261 elif op in 'dagrange range list parent ancestorspec':
2268 elif op in 'dagrange range list parent ancestorspec':
2262 if op == 'parent':
2269 if op == 'parent':
2263 # x^:y means (x^) : y, not x ^ (:y)
2270 # x^:y means (x^) : y, not x ^ (:y)
2264 post = ('parentpost', x[1])
2271 post = ('parentpost', x[1])
2265 if x[2][0] == 'dagrangepre':
2272 if x[2][0] == 'dagrangepre':
2266 return optimize(('dagrange', post, x[2][1]), small)
2273 return optimize(('dagrange', post, x[2][1]), small)
2267 elif x[2][0] == 'rangepre':
2274 elif x[2][0] == 'rangepre':
2268 return optimize(('range', post, x[2][1]), small)
2275 return optimize(('range', post, x[2][1]), small)
2269
2276
2270 wa, ta = optimize(x[1], small)
2277 wa, ta = optimize(x[1], small)
2271 wb, tb = optimize(x[2], small)
2278 wb, tb = optimize(x[2], small)
2272 return wa + wb, (op, ta, tb)
2279 return wa + wb, (op, ta, tb)
2273 elif op == 'func':
2280 elif op == 'func':
2274 f = getstring(x[1], _("not a symbol"))
2281 f = getstring(x[1], _("not a symbol"))
2275 wa, ta = optimize(x[2], small)
2282 wa, ta = optimize(x[2], small)
2276 if f in ("author branch closed date desc file grep keyword "
2283 if f in ("author branch closed date desc file grep keyword "
2277 "outgoing user"):
2284 "outgoing user"):
2278 w = 10 # slow
2285 w = 10 # slow
2279 elif f in "modifies adds removes":
2286 elif f in "modifies adds removes":
2280 w = 30 # slower
2287 w = 30 # slower
2281 elif f == "contains":
2288 elif f == "contains":
2282 w = 100 # very slow
2289 w = 100 # very slow
2283 elif f == "ancestor":
2290 elif f == "ancestor":
2284 w = 1 * smallbonus
2291 w = 1 * smallbonus
2285 elif f in "reverse limit first _intlist":
2292 elif f in "reverse limit first _intlist":
2286 w = 0
2293 w = 0
2287 elif f in "sort":
2294 elif f in "sort":
2288 w = 10 # assume most sorts look at changelog
2295 w = 10 # assume most sorts look at changelog
2289 else:
2296 else:
2290 w = 1
2297 w = 1
2291 return w + wa, (op, x[1], ta)
2298 return w + wa, (op, x[1], ta)
2292 return 1, x
2299 return 1, x
2293
2300
2294 _aliasarg = ('func', ('symbol', '_aliasarg'))
2301 _aliasarg = ('func', ('symbol', '_aliasarg'))
2295 def _getaliasarg(tree):
2302 def _getaliasarg(tree):
2296 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
2303 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
2297 return X, None otherwise.
2304 return X, None otherwise.
2298 """
2305 """
2299 if (len(tree) == 3 and tree[:2] == _aliasarg
2306 if (len(tree) == 3 and tree[:2] == _aliasarg
2300 and tree[2][0] == 'string'):
2307 and tree[2][0] == 'string'):
2301 return tree[2][1]
2308 return tree[2][1]
2302 return None
2309 return None
2303
2310
2304 def _checkaliasarg(tree, known=None):
2311 def _checkaliasarg(tree, known=None):
2305 """Check tree contains no _aliasarg construct or only ones which
2312 """Check tree contains no _aliasarg construct or only ones which
2306 value is in known. Used to avoid alias placeholders injection.
2313 value is in known. Used to avoid alias placeholders injection.
2307 """
2314 """
2308 if isinstance(tree, tuple):
2315 if isinstance(tree, tuple):
2309 arg = _getaliasarg(tree)
2316 arg = _getaliasarg(tree)
2310 if arg is not None and (not known or arg not in known):
2317 if arg is not None and (not known or arg not in known):
2311 raise error.UnknownIdentifier('_aliasarg', [])
2318 raise error.UnknownIdentifier('_aliasarg', [])
2312 for t in tree:
2319 for t in tree:
2313 _checkaliasarg(t, known)
2320 _checkaliasarg(t, known)
2314
2321
2315 # the set of valid characters for the initial letter of symbols in
2322 # the set of valid characters for the initial letter of symbols in
2316 # alias declarations and definitions
2323 # alias declarations and definitions
2317 _aliassyminitletters = set(c for c in [chr(i) for i in xrange(256)]
2324 _aliassyminitletters = set(c for c in [chr(i) for i in xrange(256)]
2318 if c.isalnum() or c in '._@$' or ord(c) > 127)
2325 if c.isalnum() or c in '._@$' or ord(c) > 127)
2319
2326
2320 def _tokenizealias(program, lookup=None):
2327 def _tokenizealias(program, lookup=None):
2321 """Parse alias declaration/definition into a stream of tokens
2328 """Parse alias declaration/definition into a stream of tokens
2322
2329
2323 This allows symbol names to use also ``$`` as an initial letter
2330 This allows symbol names to use also ``$`` as an initial letter
2324 (for backward compatibility), and callers of this function should
2331 (for backward compatibility), and callers of this function should
2325 examine whether ``$`` is used also for unexpected symbols or not.
2332 examine whether ``$`` is used also for unexpected symbols or not.
2326 """
2333 """
2327 return tokenize(program, lookup=lookup,
2334 return tokenize(program, lookup=lookup,
2328 syminitletters=_aliassyminitletters)
2335 syminitletters=_aliassyminitletters)
2329
2336
2330 def _parsealiasdecl(decl):
2337 def _parsealiasdecl(decl):
2331 """Parse alias declaration ``decl``
2338 """Parse alias declaration ``decl``
2332
2339
2333 This returns ``(name, tree, args, errorstr)`` tuple:
2340 This returns ``(name, tree, args, errorstr)`` tuple:
2334
2341
2335 - ``name``: of declared alias (may be ``decl`` itself at error)
2342 - ``name``: of declared alias (may be ``decl`` itself at error)
2336 - ``tree``: parse result (or ``None`` at error)
2343 - ``tree``: parse result (or ``None`` at error)
2337 - ``args``: list of alias argument names (or None for symbol declaration)
2344 - ``args``: list of alias argument names (or None for symbol declaration)
2338 - ``errorstr``: detail about detected error (or None)
2345 - ``errorstr``: detail about detected error (or None)
2339
2346
2340 >>> _parsealiasdecl('foo')
2347 >>> _parsealiasdecl('foo')
2341 ('foo', ('symbol', 'foo'), None, None)
2348 ('foo', ('symbol', 'foo'), None, None)
2342 >>> _parsealiasdecl('$foo')
2349 >>> _parsealiasdecl('$foo')
2343 ('$foo', None, None, "'$' not for alias arguments")
2350 ('$foo', None, None, "'$' not for alias arguments")
2344 >>> _parsealiasdecl('foo::bar')
2351 >>> _parsealiasdecl('foo::bar')
2345 ('foo::bar', None, None, 'invalid format')
2352 ('foo::bar', None, None, 'invalid format')
2346 >>> _parsealiasdecl('foo bar')
2353 >>> _parsealiasdecl('foo bar')
2347 ('foo bar', None, None, 'at 4: invalid token')
2354 ('foo bar', None, None, 'at 4: invalid token')
2348 >>> _parsealiasdecl('foo()')
2355 >>> _parsealiasdecl('foo()')
2349 ('foo', ('func', ('symbol', 'foo')), [], None)
2356 ('foo', ('func', ('symbol', 'foo')), [], None)
2350 >>> _parsealiasdecl('$foo()')
2357 >>> _parsealiasdecl('$foo()')
2351 ('$foo()', None, None, "'$' not for alias arguments")
2358 ('$foo()', None, None, "'$' not for alias arguments")
2352 >>> _parsealiasdecl('foo($1, $2)')
2359 >>> _parsealiasdecl('foo($1, $2)')
2353 ('foo', ('func', ('symbol', 'foo')), ['$1', '$2'], None)
2360 ('foo', ('func', ('symbol', 'foo')), ['$1', '$2'], None)
2354 >>> _parsealiasdecl('foo(bar_bar, baz.baz)')
2361 >>> _parsealiasdecl('foo(bar_bar, baz.baz)')
2355 ('foo', ('func', ('symbol', 'foo')), ['bar_bar', 'baz.baz'], None)
2362 ('foo', ('func', ('symbol', 'foo')), ['bar_bar', 'baz.baz'], None)
2356 >>> _parsealiasdecl('foo($1, $2, nested($1, $2))')
2363 >>> _parsealiasdecl('foo($1, $2, nested($1, $2))')
2357 ('foo($1, $2, nested($1, $2))', None, None, 'invalid argument list')
2364 ('foo($1, $2, nested($1, $2))', None, None, 'invalid argument list')
2358 >>> _parsealiasdecl('foo(bar($1, $2))')
2365 >>> _parsealiasdecl('foo(bar($1, $2))')
2359 ('foo(bar($1, $2))', None, None, 'invalid argument list')
2366 ('foo(bar($1, $2))', None, None, 'invalid argument list')
2360 >>> _parsealiasdecl('foo("string")')
2367 >>> _parsealiasdecl('foo("string")')
2361 ('foo("string")', None, None, 'invalid argument list')
2368 ('foo("string")', None, None, 'invalid argument list')
2362 >>> _parsealiasdecl('foo($1, $2')
2369 >>> _parsealiasdecl('foo($1, $2')
2363 ('foo($1, $2', None, None, 'at 10: unexpected token: end')
2370 ('foo($1, $2', None, None, 'at 10: unexpected token: end')
2364 >>> _parsealiasdecl('foo("string')
2371 >>> _parsealiasdecl('foo("string')
2365 ('foo("string', None, None, 'at 5: unterminated string')
2372 ('foo("string', None, None, 'at 5: unterminated string')
2366 >>> _parsealiasdecl('foo($1, $2, $1)')
2373 >>> _parsealiasdecl('foo($1, $2, $1)')
2367 ('foo', None, None, 'argument names collide with each other')
2374 ('foo', None, None, 'argument names collide with each other')
2368 """
2375 """
2369 p = parser.parser(_tokenizealias, elements)
2376 p = parser.parser(_tokenizealias, elements)
2370 try:
2377 try:
2371 tree, pos = p.parse(decl)
2378 tree, pos = p.parse(decl)
2372 if (pos != len(decl)):
2379 if (pos != len(decl)):
2373 raise error.ParseError(_('invalid token'), pos)
2380 raise error.ParseError(_('invalid token'), pos)
2374
2381
2375 if isvalidsymbol(tree):
2382 if isvalidsymbol(tree):
2376 # "name = ...." style
2383 # "name = ...." style
2377 name = getsymbol(tree)
2384 name = getsymbol(tree)
2378 if name.startswith('$'):
2385 if name.startswith('$'):
2379 return (decl, None, None, _("'$' not for alias arguments"))
2386 return (decl, None, None, _("'$' not for alias arguments"))
2380 return (name, ('symbol', name), None, None)
2387 return (name, ('symbol', name), None, None)
2381
2388
2382 if isvalidfunc(tree):
2389 if isvalidfunc(tree):
2383 # "name(arg, ....) = ...." style
2390 # "name(arg, ....) = ...." style
2384 name = getfuncname(tree)
2391 name = getfuncname(tree)
2385 if name.startswith('$'):
2392 if name.startswith('$'):
2386 return (decl, None, None, _("'$' not for alias arguments"))
2393 return (decl, None, None, _("'$' not for alias arguments"))
2387 args = []
2394 args = []
2388 for arg in getfuncargs(tree):
2395 for arg in getfuncargs(tree):
2389 if not isvalidsymbol(arg):
2396 if not isvalidsymbol(arg):
2390 return (decl, None, None, _("invalid argument list"))
2397 return (decl, None, None, _("invalid argument list"))
2391 args.append(getsymbol(arg))
2398 args.append(getsymbol(arg))
2392 if len(args) != len(set(args)):
2399 if len(args) != len(set(args)):
2393 return (name, None, None,
2400 return (name, None, None,
2394 _("argument names collide with each other"))
2401 _("argument names collide with each other"))
2395 return (name, ('func', ('symbol', name)), args, None)
2402 return (name, ('func', ('symbol', name)), args, None)
2396
2403
2397 return (decl, None, None, _("invalid format"))
2404 return (decl, None, None, _("invalid format"))
2398 except error.ParseError, inst:
2405 except error.ParseError, inst:
2399 return (decl, None, None, parseerrordetail(inst))
2406 return (decl, None, None, parseerrordetail(inst))
2400
2407
2401 def _parsealiasdefn(defn, args):
2408 def _parsealiasdefn(defn, args):
2402 """Parse alias definition ``defn``
2409 """Parse alias definition ``defn``
2403
2410
2404 This function also replaces alias argument references in the
2411 This function also replaces alias argument references in the
2405 specified definition by ``_aliasarg(ARGNAME)``.
2412 specified definition by ``_aliasarg(ARGNAME)``.
2406
2413
2407 ``args`` is a list of alias argument names, or None if the alias
2414 ``args`` is a list of alias argument names, or None if the alias
2408 is declared as a symbol.
2415 is declared as a symbol.
2409
2416
2410 This returns "tree" as parsing result.
2417 This returns "tree" as parsing result.
2411
2418
2412 >>> args = ['$1', '$2', 'foo']
2419 >>> args = ['$1', '$2', 'foo']
2413 >>> print prettyformat(_parsealiasdefn('$1 or foo', args))
2420 >>> print prettyformat(_parsealiasdefn('$1 or foo', args))
2414 (or
2421 (or
2415 (func
2422 (func
2416 ('symbol', '_aliasarg')
2423 ('symbol', '_aliasarg')
2417 ('string', '$1'))
2424 ('string', '$1'))
2418 (func
2425 (func
2419 ('symbol', '_aliasarg')
2426 ('symbol', '_aliasarg')
2420 ('string', 'foo')))
2427 ('string', 'foo')))
2421 >>> try:
2428 >>> try:
2422 ... _parsealiasdefn('$1 or $bar', args)
2429 ... _parsealiasdefn('$1 or $bar', args)
2423 ... except error.ParseError, inst:
2430 ... except error.ParseError, inst:
2424 ... print parseerrordetail(inst)
2431 ... print parseerrordetail(inst)
2425 at 6: '$' not for alias arguments
2432 at 6: '$' not for alias arguments
2426 >>> args = ['$1', '$10', 'foo']
2433 >>> args = ['$1', '$10', 'foo']
2427 >>> print prettyformat(_parsealiasdefn('$10 or foobar', args))
2434 >>> print prettyformat(_parsealiasdefn('$10 or foobar', args))
2428 (or
2435 (or
2429 (func
2436 (func
2430 ('symbol', '_aliasarg')
2437 ('symbol', '_aliasarg')
2431 ('string', '$10'))
2438 ('string', '$10'))
2432 ('symbol', 'foobar'))
2439 ('symbol', 'foobar'))
2433 >>> print prettyformat(_parsealiasdefn('"$1" or "foo"', args))
2440 >>> print prettyformat(_parsealiasdefn('"$1" or "foo"', args))
2434 (or
2441 (or
2435 ('string', '$1')
2442 ('string', '$1')
2436 ('string', 'foo'))
2443 ('string', 'foo'))
2437 """
2444 """
2438 def tokenizedefn(program, lookup=None):
2445 def tokenizedefn(program, lookup=None):
2439 if args:
2446 if args:
2440 argset = set(args)
2447 argset = set(args)
2441 else:
2448 else:
2442 argset = set()
2449 argset = set()
2443
2450
2444 for t, value, pos in _tokenizealias(program, lookup=lookup):
2451 for t, value, pos in _tokenizealias(program, lookup=lookup):
2445 if t == 'symbol':
2452 if t == 'symbol':
2446 if value in argset:
2453 if value in argset:
2447 # emulate tokenization of "_aliasarg('ARGNAME')":
2454 # emulate tokenization of "_aliasarg('ARGNAME')":
2448 # "_aliasarg()" is an unknown symbol only used separate
2455 # "_aliasarg()" is an unknown symbol only used separate
2449 # alias argument placeholders from regular strings.
2456 # alias argument placeholders from regular strings.
2450 yield ('symbol', '_aliasarg', pos)
2457 yield ('symbol', '_aliasarg', pos)
2451 yield ('(', None, pos)
2458 yield ('(', None, pos)
2452 yield ('string', value, pos)
2459 yield ('string', value, pos)
2453 yield (')', None, pos)
2460 yield (')', None, pos)
2454 continue
2461 continue
2455 elif value.startswith('$'):
2462 elif value.startswith('$'):
2456 raise error.ParseError(_("'$' not for alias arguments"),
2463 raise error.ParseError(_("'$' not for alias arguments"),
2457 pos)
2464 pos)
2458 yield (t, value, pos)
2465 yield (t, value, pos)
2459
2466
2460 p = parser.parser(tokenizedefn, elements)
2467 p = parser.parser(tokenizedefn, elements)
2461 tree, pos = p.parse(defn)
2468 tree, pos = p.parse(defn)
2462 if pos != len(defn):
2469 if pos != len(defn):
2463 raise error.ParseError(_('invalid token'), pos)
2470 raise error.ParseError(_('invalid token'), pos)
2464 return parser.simplifyinfixops(tree, ('or',))
2471 return parser.simplifyinfixops(tree, ('or',))
2465
2472
2466 class revsetalias(object):
2473 class revsetalias(object):
2467 # whether own `error` information is already shown or not.
2474 # whether own `error` information is already shown or not.
2468 # this avoids showing same warning multiple times at each `findaliases`.
2475 # this avoids showing same warning multiple times at each `findaliases`.
2469 warned = False
2476 warned = False
2470
2477
2471 def __init__(self, name, value):
2478 def __init__(self, name, value):
2472 '''Aliases like:
2479 '''Aliases like:
2473
2480
2474 h = heads(default)
2481 h = heads(default)
2475 b($1) = ancestors($1) - ancestors(default)
2482 b($1) = ancestors($1) - ancestors(default)
2476 '''
2483 '''
2477 self.name, self.tree, self.args, self.error = _parsealiasdecl(name)
2484 self.name, self.tree, self.args, self.error = _parsealiasdecl(name)
2478 if self.error:
2485 if self.error:
2479 self.error = _('failed to parse the declaration of revset alias'
2486 self.error = _('failed to parse the declaration of revset alias'
2480 ' "%s": %s') % (self.name, self.error)
2487 ' "%s": %s') % (self.name, self.error)
2481 return
2488 return
2482
2489
2483 try:
2490 try:
2484 self.replacement = _parsealiasdefn(value, self.args)
2491 self.replacement = _parsealiasdefn(value, self.args)
2485 # Check for placeholder injection
2492 # Check for placeholder injection
2486 _checkaliasarg(self.replacement, self.args)
2493 _checkaliasarg(self.replacement, self.args)
2487 except error.ParseError, inst:
2494 except error.ParseError, inst:
2488 self.error = _('failed to parse the definition of revset alias'
2495 self.error = _('failed to parse the definition of revset alias'
2489 ' "%s": %s') % (self.name, parseerrordetail(inst))
2496 ' "%s": %s') % (self.name, parseerrordetail(inst))
2490
2497
2491 def _getalias(aliases, tree):
2498 def _getalias(aliases, tree):
2492 """If tree looks like an unexpanded alias, return it. Return None
2499 """If tree looks like an unexpanded alias, return it. Return None
2493 otherwise.
2500 otherwise.
2494 """
2501 """
2495 if isinstance(tree, tuple) and tree:
2502 if isinstance(tree, tuple) and tree:
2496 if tree[0] == 'symbol' and len(tree) == 2:
2503 if tree[0] == 'symbol' and len(tree) == 2:
2497 name = tree[1]
2504 name = tree[1]
2498 alias = aliases.get(name)
2505 alias = aliases.get(name)
2499 if alias and alias.args is None and alias.tree == tree:
2506 if alias and alias.args is None and alias.tree == tree:
2500 return alias
2507 return alias
2501 if tree[0] == 'func' and len(tree) > 1:
2508 if tree[0] == 'func' and len(tree) > 1:
2502 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
2509 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
2503 name = tree[1][1]
2510 name = tree[1][1]
2504 alias = aliases.get(name)
2511 alias = aliases.get(name)
2505 if alias and alias.args is not None and alias.tree == tree[:2]:
2512 if alias and alias.args is not None and alias.tree == tree[:2]:
2506 return alias
2513 return alias
2507 return None
2514 return None
2508
2515
2509 def _expandargs(tree, args):
2516 def _expandargs(tree, args):
2510 """Replace _aliasarg instances with the substitution value of the
2517 """Replace _aliasarg instances with the substitution value of the
2511 same name in args, recursively.
2518 same name in args, recursively.
2512 """
2519 """
2513 if not tree or not isinstance(tree, tuple):
2520 if not tree or not isinstance(tree, tuple):
2514 return tree
2521 return tree
2515 arg = _getaliasarg(tree)
2522 arg = _getaliasarg(tree)
2516 if arg is not None:
2523 if arg is not None:
2517 return args[arg]
2524 return args[arg]
2518 return tuple(_expandargs(t, args) for t in tree)
2525 return tuple(_expandargs(t, args) for t in tree)
2519
2526
2520 def _expandaliases(aliases, tree, expanding, cache):
2527 def _expandaliases(aliases, tree, expanding, cache):
2521 """Expand aliases in tree, recursively.
2528 """Expand aliases in tree, recursively.
2522
2529
2523 'aliases' is a dictionary mapping user defined aliases to
2530 'aliases' is a dictionary mapping user defined aliases to
2524 revsetalias objects.
2531 revsetalias objects.
2525 """
2532 """
2526 if not isinstance(tree, tuple):
2533 if not isinstance(tree, tuple):
2527 # Do not expand raw strings
2534 # Do not expand raw strings
2528 return tree
2535 return tree
2529 alias = _getalias(aliases, tree)
2536 alias = _getalias(aliases, tree)
2530 if alias is not None:
2537 if alias is not None:
2531 if alias.error:
2538 if alias.error:
2532 raise util.Abort(alias.error)
2539 raise util.Abort(alias.error)
2533 if alias in expanding:
2540 if alias in expanding:
2534 raise error.ParseError(_('infinite expansion of revset alias "%s" '
2541 raise error.ParseError(_('infinite expansion of revset alias "%s" '
2535 'detected') % alias.name)
2542 'detected') % alias.name)
2536 expanding.append(alias)
2543 expanding.append(alias)
2537 if alias.name not in cache:
2544 if alias.name not in cache:
2538 cache[alias.name] = _expandaliases(aliases, alias.replacement,
2545 cache[alias.name] = _expandaliases(aliases, alias.replacement,
2539 expanding, cache)
2546 expanding, cache)
2540 result = cache[alias.name]
2547 result = cache[alias.name]
2541 expanding.pop()
2548 expanding.pop()
2542 if alias.args is not None:
2549 if alias.args is not None:
2543 l = getlist(tree[2])
2550 l = getlist(tree[2])
2544 if len(l) != len(alias.args):
2551 if len(l) != len(alias.args):
2545 raise error.ParseError(
2552 raise error.ParseError(
2546 _('invalid number of arguments: %s') % len(l))
2553 _('invalid number of arguments: %s') % len(l))
2547 l = [_expandaliases(aliases, a, [], cache) for a in l]
2554 l = [_expandaliases(aliases, a, [], cache) for a in l]
2548 result = _expandargs(result, dict(zip(alias.args, l)))
2555 result = _expandargs(result, dict(zip(alias.args, l)))
2549 else:
2556 else:
2550 result = tuple(_expandaliases(aliases, t, expanding, cache)
2557 result = tuple(_expandaliases(aliases, t, expanding, cache)
2551 for t in tree)
2558 for t in tree)
2552 return result
2559 return result
2553
2560
2554 def findaliases(ui, tree, showwarning=None):
2561 def findaliases(ui, tree, showwarning=None):
2555 _checkaliasarg(tree)
2562 _checkaliasarg(tree)
2556 aliases = {}
2563 aliases = {}
2557 for k, v in ui.configitems('revsetalias'):
2564 for k, v in ui.configitems('revsetalias'):
2558 alias = revsetalias(k, v)
2565 alias = revsetalias(k, v)
2559 aliases[alias.name] = alias
2566 aliases[alias.name] = alias
2560 tree = _expandaliases(aliases, tree, [], {})
2567 tree = _expandaliases(aliases, tree, [], {})
2561 if showwarning:
2568 if showwarning:
2562 # warn about problematic (but not referred) aliases
2569 # warn about problematic (but not referred) aliases
2563 for name, alias in sorted(aliases.iteritems()):
2570 for name, alias in sorted(aliases.iteritems()):
2564 if alias.error and not alias.warned:
2571 if alias.error and not alias.warned:
2565 showwarning(_('warning: %s\n') % (alias.error))
2572 showwarning(_('warning: %s\n') % (alias.error))
2566 alias.warned = True
2573 alias.warned = True
2567 return tree
2574 return tree
2568
2575
2569 def foldconcat(tree):
2576 def foldconcat(tree):
2570 """Fold elements to be concatenated by `##`
2577 """Fold elements to be concatenated by `##`
2571 """
2578 """
2572 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2579 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2573 return tree
2580 return tree
2574 if tree[0] == '_concat':
2581 if tree[0] == '_concat':
2575 pending = [tree]
2582 pending = [tree]
2576 l = []
2583 l = []
2577 while pending:
2584 while pending:
2578 e = pending.pop()
2585 e = pending.pop()
2579 if e[0] == '_concat':
2586 if e[0] == '_concat':
2580 pending.extend(reversed(e[1:]))
2587 pending.extend(reversed(e[1:]))
2581 elif e[0] in ('string', 'symbol'):
2588 elif e[0] in ('string', 'symbol'):
2582 l.append(e[1])
2589 l.append(e[1])
2583 else:
2590 else:
2584 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2591 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2585 raise error.ParseError(msg)
2592 raise error.ParseError(msg)
2586 return ('string', ''.join(l))
2593 return ('string', ''.join(l))
2587 else:
2594 else:
2588 return tuple(foldconcat(t) for t in tree)
2595 return tuple(foldconcat(t) for t in tree)
2589
2596
2590 def parse(spec, lookup=None):
2597 def parse(spec, lookup=None):
2591 p = parser.parser(tokenize, elements)
2598 p = parser.parser(tokenize, elements)
2592 tree, pos = p.parse(spec, lookup=lookup)
2599 tree, pos = p.parse(spec, lookup=lookup)
2593 if pos != len(spec):
2600 if pos != len(spec):
2594 raise error.ParseError(_("invalid token"), pos)
2601 raise error.ParseError(_("invalid token"), pos)
2595 return parser.simplifyinfixops(tree, ('or',))
2602 return parser.simplifyinfixops(tree, ('or',))
2596
2603
2597 def posttreebuilthook(tree, repo):
2604 def posttreebuilthook(tree, repo):
2598 # hook for extensions to execute code on the optimized tree
2605 # hook for extensions to execute code on the optimized tree
2599 pass
2606 pass
2600
2607
2601 def match(ui, spec, repo=None):
2608 def match(ui, spec, repo=None):
2602 if not spec:
2609 if not spec:
2603 raise error.ParseError(_("empty query"))
2610 raise error.ParseError(_("empty query"))
2604 lookup = None
2611 lookup = None
2605 if repo:
2612 if repo:
2606 lookup = repo.__contains__
2613 lookup = repo.__contains__
2607 tree = parse(spec, lookup)
2614 tree = parse(spec, lookup)
2608 if ui:
2615 if ui:
2609 tree = findaliases(ui, tree, showwarning=ui.warn)
2616 tree = findaliases(ui, tree, showwarning=ui.warn)
2610 tree = foldconcat(tree)
2617 tree = foldconcat(tree)
2611 weight, tree = optimize(tree, True)
2618 weight, tree = optimize(tree, True)
2612 posttreebuilthook(tree, repo)
2619 posttreebuilthook(tree, repo)
2613 def mfunc(repo, subset=None):
2620 def mfunc(repo, subset=None):
2614 if subset is None:
2621 if subset is None:
2615 subset = fullreposet(repo)
2622 subset = fullreposet(repo)
2616 if util.safehasattr(subset, 'isascending'):
2623 if util.safehasattr(subset, 'isascending'):
2617 result = getset(repo, subset, tree)
2624 result = getset(repo, subset, tree)
2618 else:
2625 else:
2619 result = getset(repo, baseset(subset), tree)
2626 result = getset(repo, baseset(subset), tree)
2620 return result
2627 return result
2621 return mfunc
2628 return mfunc
2622
2629
2623 def formatspec(expr, *args):
2630 def formatspec(expr, *args):
2624 '''
2631 '''
2625 This is a convenience function for using revsets internally, and
2632 This is a convenience function for using revsets internally, and
2626 escapes arguments appropriately. Aliases are intentionally ignored
2633 escapes arguments appropriately. Aliases are intentionally ignored
2627 so that intended expression behavior isn't accidentally subverted.
2634 so that intended expression behavior isn't accidentally subverted.
2628
2635
2629 Supported arguments:
2636 Supported arguments:
2630
2637
2631 %r = revset expression, parenthesized
2638 %r = revset expression, parenthesized
2632 %d = int(arg), no quoting
2639 %d = int(arg), no quoting
2633 %s = string(arg), escaped and single-quoted
2640 %s = string(arg), escaped and single-quoted
2634 %b = arg.branch(), escaped and single-quoted
2641 %b = arg.branch(), escaped and single-quoted
2635 %n = hex(arg), single-quoted
2642 %n = hex(arg), single-quoted
2636 %% = a literal '%'
2643 %% = a literal '%'
2637
2644
2638 Prefixing the type with 'l' specifies a parenthesized list of that type.
2645 Prefixing the type with 'l' specifies a parenthesized list of that type.
2639
2646
2640 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2647 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2641 '(10 or 11):: and ((this()) or (that()))'
2648 '(10 or 11):: and ((this()) or (that()))'
2642 >>> formatspec('%d:: and not %d::', 10, 20)
2649 >>> formatspec('%d:: and not %d::', 10, 20)
2643 '10:: and not 20::'
2650 '10:: and not 20::'
2644 >>> formatspec('%ld or %ld', [], [1])
2651 >>> formatspec('%ld or %ld', [], [1])
2645 "_list('') or 1"
2652 "_list('') or 1"
2646 >>> formatspec('keyword(%s)', 'foo\\xe9')
2653 >>> formatspec('keyword(%s)', 'foo\\xe9')
2647 "keyword('foo\\\\xe9')"
2654 "keyword('foo\\\\xe9')"
2648 >>> b = lambda: 'default'
2655 >>> b = lambda: 'default'
2649 >>> b.branch = b
2656 >>> b.branch = b
2650 >>> formatspec('branch(%b)', b)
2657 >>> formatspec('branch(%b)', b)
2651 "branch('default')"
2658 "branch('default')"
2652 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2659 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2653 "root(_list('a\\x00b\\x00c\\x00d'))"
2660 "root(_list('a\\x00b\\x00c\\x00d'))"
2654 '''
2661 '''
2655
2662
2656 def quote(s):
2663 def quote(s):
2657 return repr(str(s))
2664 return repr(str(s))
2658
2665
2659 def argtype(c, arg):
2666 def argtype(c, arg):
2660 if c == 'd':
2667 if c == 'd':
2661 return str(int(arg))
2668 return str(int(arg))
2662 elif c == 's':
2669 elif c == 's':
2663 return quote(arg)
2670 return quote(arg)
2664 elif c == 'r':
2671 elif c == 'r':
2665 parse(arg) # make sure syntax errors are confined
2672 parse(arg) # make sure syntax errors are confined
2666 return '(%s)' % arg
2673 return '(%s)' % arg
2667 elif c == 'n':
2674 elif c == 'n':
2668 return quote(node.hex(arg))
2675 return quote(node.hex(arg))
2669 elif c == 'b':
2676 elif c == 'b':
2670 return quote(arg.branch())
2677 return quote(arg.branch())
2671
2678
2672 def listexp(s, t):
2679 def listexp(s, t):
2673 l = len(s)
2680 l = len(s)
2674 if l == 0:
2681 if l == 0:
2675 return "_list('')"
2682 return "_list('')"
2676 elif l == 1:
2683 elif l == 1:
2677 return argtype(t, s[0])
2684 return argtype(t, s[0])
2678 elif t == 'd':
2685 elif t == 'd':
2679 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2686 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2680 elif t == 's':
2687 elif t == 's':
2681 return "_list('%s')" % "\0".join(s)
2688 return "_list('%s')" % "\0".join(s)
2682 elif t == 'n':
2689 elif t == 'n':
2683 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2690 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2684 elif t == 'b':
2691 elif t == 'b':
2685 return "_list('%s')" % "\0".join(a.branch() for a in s)
2692 return "_list('%s')" % "\0".join(a.branch() for a in s)
2686
2693
2687 m = l // 2
2694 m = l // 2
2688 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2695 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2689
2696
2690 ret = ''
2697 ret = ''
2691 pos = 0
2698 pos = 0
2692 arg = 0
2699 arg = 0
2693 while pos < len(expr):
2700 while pos < len(expr):
2694 c = expr[pos]
2701 c = expr[pos]
2695 if c == '%':
2702 if c == '%':
2696 pos += 1
2703 pos += 1
2697 d = expr[pos]
2704 d = expr[pos]
2698 if d == '%':
2705 if d == '%':
2699 ret += d
2706 ret += d
2700 elif d in 'dsnbr':
2707 elif d in 'dsnbr':
2701 ret += argtype(d, args[arg])
2708 ret += argtype(d, args[arg])
2702 arg += 1
2709 arg += 1
2703 elif d == 'l':
2710 elif d == 'l':
2704 # a list of some type
2711 # a list of some type
2705 pos += 1
2712 pos += 1
2706 d = expr[pos]
2713 d = expr[pos]
2707 ret += listexp(list(args[arg]), d)
2714 ret += listexp(list(args[arg]), d)
2708 arg += 1
2715 arg += 1
2709 else:
2716 else:
2710 raise util.Abort('unexpected revspec format character %s' % d)
2717 raise util.Abort('unexpected revspec format character %s' % d)
2711 else:
2718 else:
2712 ret += c
2719 ret += c
2713 pos += 1
2720 pos += 1
2714
2721
2715 return ret
2722 return ret
2716
2723
2717 def prettyformat(tree):
2724 def prettyformat(tree):
2718 return parser.prettyformat(tree, ('string', 'symbol'))
2725 return parser.prettyformat(tree, ('string', 'symbol'))
2719
2726
2720 def depth(tree):
2727 def depth(tree):
2721 if isinstance(tree, tuple):
2728 if isinstance(tree, tuple):
2722 return max(map(depth, tree)) + 1
2729 return max(map(depth, tree)) + 1
2723 else:
2730 else:
2724 return 0
2731 return 0
2725
2732
2726 def funcsused(tree):
2733 def funcsused(tree):
2727 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2734 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2728 return set()
2735 return set()
2729 else:
2736 else:
2730 funcs = set()
2737 funcs = set()
2731 for s in tree[1:]:
2738 for s in tree[1:]:
2732 funcs |= funcsused(s)
2739 funcs |= funcsused(s)
2733 if tree[0] == 'func':
2740 if tree[0] == 'func':
2734 funcs.add(tree[1][1])
2741 funcs.add(tree[1][1])
2735 return funcs
2742 return funcs
2736
2743
2737 class abstractsmartset(object):
2744 class abstractsmartset(object):
2738
2745
2739 def __nonzero__(self):
2746 def __nonzero__(self):
2740 """True if the smartset is not empty"""
2747 """True if the smartset is not empty"""
2741 raise NotImplementedError()
2748 raise NotImplementedError()
2742
2749
2743 def __contains__(self, rev):
2750 def __contains__(self, rev):
2744 """provide fast membership testing"""
2751 """provide fast membership testing"""
2745 raise NotImplementedError()
2752 raise NotImplementedError()
2746
2753
2747 def __iter__(self):
2754 def __iter__(self):
2748 """iterate the set in the order it is supposed to be iterated"""
2755 """iterate the set in the order it is supposed to be iterated"""
2749 raise NotImplementedError()
2756 raise NotImplementedError()
2750
2757
2751 # Attributes containing a function to perform a fast iteration in a given
2758 # Attributes containing a function to perform a fast iteration in a given
2752 # direction. A smartset can have none, one, or both defined.
2759 # direction. A smartset can have none, one, or both defined.
2753 #
2760 #
2754 # Default value is None instead of a function returning None to avoid
2761 # Default value is None instead of a function returning None to avoid
2755 # initializing an iterator just for testing if a fast method exists.
2762 # initializing an iterator just for testing if a fast method exists.
2756 fastasc = None
2763 fastasc = None
2757 fastdesc = None
2764 fastdesc = None
2758
2765
2759 def isascending(self):
2766 def isascending(self):
2760 """True if the set will iterate in ascending order"""
2767 """True if the set will iterate in ascending order"""
2761 raise NotImplementedError()
2768 raise NotImplementedError()
2762
2769
2763 def isdescending(self):
2770 def isdescending(self):
2764 """True if the set will iterate in descending order"""
2771 """True if the set will iterate in descending order"""
2765 raise NotImplementedError()
2772 raise NotImplementedError()
2766
2773
2767 def min(self):
2774 def min(self):
2768 """return the minimum element in the set"""
2775 """return the minimum element in the set"""
2769 if self.fastasc is not None:
2776 if self.fastasc is not None:
2770 for r in self.fastasc():
2777 for r in self.fastasc():
2771 return r
2778 return r
2772 raise ValueError('arg is an empty sequence')
2779 raise ValueError('arg is an empty sequence')
2773 return min(self)
2780 return min(self)
2774
2781
2775 def max(self):
2782 def max(self):
2776 """return the maximum element in the set"""
2783 """return the maximum element in the set"""
2777 if self.fastdesc is not None:
2784 if self.fastdesc is not None:
2778 for r in self.fastdesc():
2785 for r in self.fastdesc():
2779 return r
2786 return r
2780 raise ValueError('arg is an empty sequence')
2787 raise ValueError('arg is an empty sequence')
2781 return max(self)
2788 return max(self)
2782
2789
2783 def first(self):
2790 def first(self):
2784 """return the first element in the set (user iteration perspective)
2791 """return the first element in the set (user iteration perspective)
2785
2792
2786 Return None if the set is empty"""
2793 Return None if the set is empty"""
2787 raise NotImplementedError()
2794 raise NotImplementedError()
2788
2795
2789 def last(self):
2796 def last(self):
2790 """return the last element in the set (user iteration perspective)
2797 """return the last element in the set (user iteration perspective)
2791
2798
2792 Return None if the set is empty"""
2799 Return None if the set is empty"""
2793 raise NotImplementedError()
2800 raise NotImplementedError()
2794
2801
2795 def __len__(self):
2802 def __len__(self):
2796 """return the length of the smartsets
2803 """return the length of the smartsets
2797
2804
2798 This can be expensive on smartset that could be lazy otherwise."""
2805 This can be expensive on smartset that could be lazy otherwise."""
2799 raise NotImplementedError()
2806 raise NotImplementedError()
2800
2807
2801 def reverse(self):
2808 def reverse(self):
2802 """reverse the expected iteration order"""
2809 """reverse the expected iteration order"""
2803 raise NotImplementedError()
2810 raise NotImplementedError()
2804
2811
2805 def sort(self, reverse=True):
2812 def sort(self, reverse=True):
2806 """get the set to iterate in an ascending or descending order"""
2813 """get the set to iterate in an ascending or descending order"""
2807 raise NotImplementedError()
2814 raise NotImplementedError()
2808
2815
2809 def __and__(self, other):
2816 def __and__(self, other):
2810 """Returns a new object with the intersection of the two collections.
2817 """Returns a new object with the intersection of the two collections.
2811
2818
2812 This is part of the mandatory API for smartset."""
2819 This is part of the mandatory API for smartset."""
2813 if isinstance(other, fullreposet):
2820 if isinstance(other, fullreposet):
2814 return self
2821 return self
2815 return self.filter(other.__contains__, cache=False)
2822 return self.filter(other.__contains__, cache=False)
2816
2823
2817 def __add__(self, other):
2824 def __add__(self, other):
2818 """Returns a new object with the union of the two collections.
2825 """Returns a new object with the union of the two collections.
2819
2826
2820 This is part of the mandatory API for smartset."""
2827 This is part of the mandatory API for smartset."""
2821 return addset(self, other)
2828 return addset(self, other)
2822
2829
2823 def __sub__(self, other):
2830 def __sub__(self, other):
2824 """Returns a new object with the substraction of the two collections.
2831 """Returns a new object with the substraction of the two collections.
2825
2832
2826 This is part of the mandatory API for smartset."""
2833 This is part of the mandatory API for smartset."""
2827 c = other.__contains__
2834 c = other.__contains__
2828 return self.filter(lambda r: not c(r), cache=False)
2835 return self.filter(lambda r: not c(r), cache=False)
2829
2836
2830 def filter(self, condition, cache=True):
2837 def filter(self, condition, cache=True):
2831 """Returns this smartset filtered by condition as a new smartset.
2838 """Returns this smartset filtered by condition as a new smartset.
2832
2839
2833 `condition` is a callable which takes a revision number and returns a
2840 `condition` is a callable which takes a revision number and returns a
2834 boolean.
2841 boolean.
2835
2842
2836 This is part of the mandatory API for smartset."""
2843 This is part of the mandatory API for smartset."""
2837 # builtin cannot be cached. but do not needs to
2844 # builtin cannot be cached. but do not needs to
2838 if cache and util.safehasattr(condition, 'func_code'):
2845 if cache and util.safehasattr(condition, 'func_code'):
2839 condition = util.cachefunc(condition)
2846 condition = util.cachefunc(condition)
2840 return filteredset(self, condition)
2847 return filteredset(self, condition)
2841
2848
2842 class baseset(abstractsmartset):
2849 class baseset(abstractsmartset):
2843 """Basic data structure that represents a revset and contains the basic
2850 """Basic data structure that represents a revset and contains the basic
2844 operation that it should be able to perform.
2851 operation that it should be able to perform.
2845
2852
2846 Every method in this class should be implemented by any smartset class.
2853 Every method in this class should be implemented by any smartset class.
2847 """
2854 """
2848 def __init__(self, data=()):
2855 def __init__(self, data=()):
2849 if not isinstance(data, list):
2856 if not isinstance(data, list):
2850 data = list(data)
2857 data = list(data)
2851 self._list = data
2858 self._list = data
2852 self._ascending = None
2859 self._ascending = None
2853
2860
2854 @util.propertycache
2861 @util.propertycache
2855 def _set(self):
2862 def _set(self):
2856 return set(self._list)
2863 return set(self._list)
2857
2864
2858 @util.propertycache
2865 @util.propertycache
2859 def _asclist(self):
2866 def _asclist(self):
2860 asclist = self._list[:]
2867 asclist = self._list[:]
2861 asclist.sort()
2868 asclist.sort()
2862 return asclist
2869 return asclist
2863
2870
2864 def __iter__(self):
2871 def __iter__(self):
2865 if self._ascending is None:
2872 if self._ascending is None:
2866 return iter(self._list)
2873 return iter(self._list)
2867 elif self._ascending:
2874 elif self._ascending:
2868 return iter(self._asclist)
2875 return iter(self._asclist)
2869 else:
2876 else:
2870 return reversed(self._asclist)
2877 return reversed(self._asclist)
2871
2878
2872 def fastasc(self):
2879 def fastasc(self):
2873 return iter(self._asclist)
2880 return iter(self._asclist)
2874
2881
2875 def fastdesc(self):
2882 def fastdesc(self):
2876 return reversed(self._asclist)
2883 return reversed(self._asclist)
2877
2884
2878 @util.propertycache
2885 @util.propertycache
2879 def __contains__(self):
2886 def __contains__(self):
2880 return self._set.__contains__
2887 return self._set.__contains__
2881
2888
2882 def __nonzero__(self):
2889 def __nonzero__(self):
2883 return bool(self._list)
2890 return bool(self._list)
2884
2891
2885 def sort(self, reverse=False):
2892 def sort(self, reverse=False):
2886 self._ascending = not bool(reverse)
2893 self._ascending = not bool(reverse)
2887
2894
2888 def reverse(self):
2895 def reverse(self):
2889 if self._ascending is None:
2896 if self._ascending is None:
2890 self._list.reverse()
2897 self._list.reverse()
2891 else:
2898 else:
2892 self._ascending = not self._ascending
2899 self._ascending = not self._ascending
2893
2900
2894 def __len__(self):
2901 def __len__(self):
2895 return len(self._list)
2902 return len(self._list)
2896
2903
2897 def isascending(self):
2904 def isascending(self):
2898 """Returns True if the collection is ascending order, False if not.
2905 """Returns True if the collection is ascending order, False if not.
2899
2906
2900 This is part of the mandatory API for smartset."""
2907 This is part of the mandatory API for smartset."""
2901 if len(self) <= 1:
2908 if len(self) <= 1:
2902 return True
2909 return True
2903 return self._ascending is not None and self._ascending
2910 return self._ascending is not None and self._ascending
2904
2911
2905 def isdescending(self):
2912 def isdescending(self):
2906 """Returns True if the collection is descending order, False if not.
2913 """Returns True if the collection is descending order, False if not.
2907
2914
2908 This is part of the mandatory API for smartset."""
2915 This is part of the mandatory API for smartset."""
2909 if len(self) <= 1:
2916 if len(self) <= 1:
2910 return True
2917 return True
2911 return self._ascending is not None and not self._ascending
2918 return self._ascending is not None and not self._ascending
2912
2919
2913 def first(self):
2920 def first(self):
2914 if self:
2921 if self:
2915 if self._ascending is None:
2922 if self._ascending is None:
2916 return self._list[0]
2923 return self._list[0]
2917 elif self._ascending:
2924 elif self._ascending:
2918 return self._asclist[0]
2925 return self._asclist[0]
2919 else:
2926 else:
2920 return self._asclist[-1]
2927 return self._asclist[-1]
2921 return None
2928 return None
2922
2929
2923 def last(self):
2930 def last(self):
2924 if self:
2931 if self:
2925 if self._ascending is None:
2932 if self._ascending is None:
2926 return self._list[-1]
2933 return self._list[-1]
2927 elif self._ascending:
2934 elif self._ascending:
2928 return self._asclist[-1]
2935 return self._asclist[-1]
2929 else:
2936 else:
2930 return self._asclist[0]
2937 return self._asclist[0]
2931 return None
2938 return None
2932
2939
2933 def __repr__(self):
2940 def __repr__(self):
2934 d = {None: '', False: '-', True: '+'}[self._ascending]
2941 d = {None: '', False: '-', True: '+'}[self._ascending]
2935 return '<%s%s %r>' % (type(self).__name__, d, self._list)
2942 return '<%s%s %r>' % (type(self).__name__, d, self._list)
2936
2943
2937 class filteredset(abstractsmartset):
2944 class filteredset(abstractsmartset):
2938 """Duck type for baseset class which iterates lazily over the revisions in
2945 """Duck type for baseset class which iterates lazily over the revisions in
2939 the subset and contains a function which tests for membership in the
2946 the subset and contains a function which tests for membership in the
2940 revset
2947 revset
2941 """
2948 """
2942 def __init__(self, subset, condition=lambda x: True):
2949 def __init__(self, subset, condition=lambda x: True):
2943 """
2950 """
2944 condition: a function that decide whether a revision in the subset
2951 condition: a function that decide whether a revision in the subset
2945 belongs to the revset or not.
2952 belongs to the revset or not.
2946 """
2953 """
2947 self._subset = subset
2954 self._subset = subset
2948 self._condition = condition
2955 self._condition = condition
2949 self._cache = {}
2956 self._cache = {}
2950
2957
2951 def __contains__(self, x):
2958 def __contains__(self, x):
2952 c = self._cache
2959 c = self._cache
2953 if x not in c:
2960 if x not in c:
2954 v = c[x] = x in self._subset and self._condition(x)
2961 v = c[x] = x in self._subset and self._condition(x)
2955 return v
2962 return v
2956 return c[x]
2963 return c[x]
2957
2964
2958 def __iter__(self):
2965 def __iter__(self):
2959 return self._iterfilter(self._subset)
2966 return self._iterfilter(self._subset)
2960
2967
2961 def _iterfilter(self, it):
2968 def _iterfilter(self, it):
2962 cond = self._condition
2969 cond = self._condition
2963 for x in it:
2970 for x in it:
2964 if cond(x):
2971 if cond(x):
2965 yield x
2972 yield x
2966
2973
2967 @property
2974 @property
2968 def fastasc(self):
2975 def fastasc(self):
2969 it = self._subset.fastasc
2976 it = self._subset.fastasc
2970 if it is None:
2977 if it is None:
2971 return None
2978 return None
2972 return lambda: self._iterfilter(it())
2979 return lambda: self._iterfilter(it())
2973
2980
2974 @property
2981 @property
2975 def fastdesc(self):
2982 def fastdesc(self):
2976 it = self._subset.fastdesc
2983 it = self._subset.fastdesc
2977 if it is None:
2984 if it is None:
2978 return None
2985 return None
2979 return lambda: self._iterfilter(it())
2986 return lambda: self._iterfilter(it())
2980
2987
2981 def __nonzero__(self):
2988 def __nonzero__(self):
2982 for r in self:
2989 for r in self:
2983 return True
2990 return True
2984 return False
2991 return False
2985
2992
2986 def __len__(self):
2993 def __len__(self):
2987 # Basic implementation to be changed in future patches.
2994 # Basic implementation to be changed in future patches.
2988 l = baseset([r for r in self])
2995 l = baseset([r for r in self])
2989 return len(l)
2996 return len(l)
2990
2997
2991 def sort(self, reverse=False):
2998 def sort(self, reverse=False):
2992 self._subset.sort(reverse=reverse)
2999 self._subset.sort(reverse=reverse)
2993
3000
2994 def reverse(self):
3001 def reverse(self):
2995 self._subset.reverse()
3002 self._subset.reverse()
2996
3003
2997 def isascending(self):
3004 def isascending(self):
2998 return self._subset.isascending()
3005 return self._subset.isascending()
2999
3006
3000 def isdescending(self):
3007 def isdescending(self):
3001 return self._subset.isdescending()
3008 return self._subset.isdescending()
3002
3009
3003 def first(self):
3010 def first(self):
3004 for x in self:
3011 for x in self:
3005 return x
3012 return x
3006 return None
3013 return None
3007
3014
3008 def last(self):
3015 def last(self):
3009 it = None
3016 it = None
3010 if self._subset.isascending:
3017 if self._subset.isascending:
3011 it = self.fastdesc
3018 it = self.fastdesc
3012 elif self._subset.isdescending:
3019 elif self._subset.isdescending:
3013 it = self.fastdesc
3020 it = self.fastdesc
3014 if it is None:
3021 if it is None:
3015 # slowly consume everything. This needs improvement
3022 # slowly consume everything. This needs improvement
3016 it = lambda: reversed(list(self))
3023 it = lambda: reversed(list(self))
3017 for x in it():
3024 for x in it():
3018 return x
3025 return x
3019 return None
3026 return None
3020
3027
3021 def __repr__(self):
3028 def __repr__(self):
3022 return '<%s %r>' % (type(self).__name__, self._subset)
3029 return '<%s %r>' % (type(self).__name__, self._subset)
3023
3030
3024 # this function will be removed, or merged to addset or orset, when
3031 # this function will be removed, or merged to addset or orset, when
3025 # - scmutil.revrange() can be rewritten to not combine calculated smartsets
3032 # - scmutil.revrange() can be rewritten to not combine calculated smartsets
3026 # - or addset can handle more than two sets without balanced tree
3033 # - or addset can handle more than two sets without balanced tree
3027 def _combinesets(subsets):
3034 def _combinesets(subsets):
3028 """Create balanced tree of addsets representing union of given sets"""
3035 """Create balanced tree of addsets representing union of given sets"""
3029 if not subsets:
3036 if not subsets:
3030 return baseset()
3037 return baseset()
3031 if len(subsets) == 1:
3038 if len(subsets) == 1:
3032 return subsets[0]
3039 return subsets[0]
3033 p = len(subsets) // 2
3040 p = len(subsets) // 2
3034 xs = _combinesets(subsets[:p])
3041 xs = _combinesets(subsets[:p])
3035 ys = _combinesets(subsets[p:])
3042 ys = _combinesets(subsets[p:])
3036 return addset(xs, ys)
3043 return addset(xs, ys)
3037
3044
3038 def _iterordered(ascending, iter1, iter2):
3045 def _iterordered(ascending, iter1, iter2):
3039 """produce an ordered iteration from two iterators with the same order
3046 """produce an ordered iteration from two iterators with the same order
3040
3047
3041 The ascending is used to indicated the iteration direction.
3048 The ascending is used to indicated the iteration direction.
3042 """
3049 """
3043 choice = max
3050 choice = max
3044 if ascending:
3051 if ascending:
3045 choice = min
3052 choice = min
3046
3053
3047 val1 = None
3054 val1 = None
3048 val2 = None
3055 val2 = None
3049 try:
3056 try:
3050 # Consume both iterators in an ordered way until one is empty
3057 # Consume both iterators in an ordered way until one is empty
3051 while True:
3058 while True:
3052 if val1 is None:
3059 if val1 is None:
3053 val1 = iter1.next()
3060 val1 = iter1.next()
3054 if val2 is None:
3061 if val2 is None:
3055 val2 = iter2.next()
3062 val2 = iter2.next()
3056 next = choice(val1, val2)
3063 next = choice(val1, val2)
3057 yield next
3064 yield next
3058 if val1 == next:
3065 if val1 == next:
3059 val1 = None
3066 val1 = None
3060 if val2 == next:
3067 if val2 == next:
3061 val2 = None
3068 val2 = None
3062 except StopIteration:
3069 except StopIteration:
3063 # Flush any remaining values and consume the other one
3070 # Flush any remaining values and consume the other one
3064 it = iter2
3071 it = iter2
3065 if val1 is not None:
3072 if val1 is not None:
3066 yield val1
3073 yield val1
3067 it = iter1
3074 it = iter1
3068 elif val2 is not None:
3075 elif val2 is not None:
3069 # might have been equality and both are empty
3076 # might have been equality and both are empty
3070 yield val2
3077 yield val2
3071 for val in it:
3078 for val in it:
3072 yield val
3079 yield val
3073
3080
3074 class addset(abstractsmartset):
3081 class addset(abstractsmartset):
3075 """Represent the addition of two sets
3082 """Represent the addition of two sets
3076
3083
3077 Wrapper structure for lazily adding two structures without losing much
3084 Wrapper structure for lazily adding two structures without losing much
3078 performance on the __contains__ method
3085 performance on the __contains__ method
3079
3086
3080 If the ascending attribute is set, that means the two structures are
3087 If the ascending attribute is set, that means the two structures are
3081 ordered in either an ascending or descending way. Therefore, we can add
3088 ordered in either an ascending or descending way. Therefore, we can add
3082 them maintaining the order by iterating over both at the same time
3089 them maintaining the order by iterating over both at the same time
3083
3090
3084 >>> xs = baseset([0, 3, 2])
3091 >>> xs = baseset([0, 3, 2])
3085 >>> ys = baseset([5, 2, 4])
3092 >>> ys = baseset([5, 2, 4])
3086
3093
3087 >>> rs = addset(xs, ys)
3094 >>> rs = addset(xs, ys)
3088 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
3095 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
3089 (True, True, False, True, 0, 4)
3096 (True, True, False, True, 0, 4)
3090 >>> rs = addset(xs, baseset([]))
3097 >>> rs = addset(xs, baseset([]))
3091 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
3098 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
3092 (True, True, False, 0, 2)
3099 (True, True, False, 0, 2)
3093 >>> rs = addset(baseset([]), baseset([]))
3100 >>> rs = addset(baseset([]), baseset([]))
3094 >>> bool(rs), 0 in rs, rs.first(), rs.last()
3101 >>> bool(rs), 0 in rs, rs.first(), rs.last()
3095 (False, False, None, None)
3102 (False, False, None, None)
3096
3103
3097 iterate unsorted:
3104 iterate unsorted:
3098 >>> rs = addset(xs, ys)
3105 >>> rs = addset(xs, ys)
3099 >>> [x for x in rs] # without _genlist
3106 >>> [x for x in rs] # without _genlist
3100 [0, 3, 2, 5, 4]
3107 [0, 3, 2, 5, 4]
3101 >>> assert not rs._genlist
3108 >>> assert not rs._genlist
3102 >>> len(rs)
3109 >>> len(rs)
3103 5
3110 5
3104 >>> [x for x in rs] # with _genlist
3111 >>> [x for x in rs] # with _genlist
3105 [0, 3, 2, 5, 4]
3112 [0, 3, 2, 5, 4]
3106 >>> assert rs._genlist
3113 >>> assert rs._genlist
3107
3114
3108 iterate ascending:
3115 iterate ascending:
3109 >>> rs = addset(xs, ys, ascending=True)
3116 >>> rs = addset(xs, ys, ascending=True)
3110 >>> [x for x in rs], [x for x in rs.fastasc()] # without _asclist
3117 >>> [x for x in rs], [x for x in rs.fastasc()] # without _asclist
3111 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3118 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3112 >>> assert not rs._asclist
3119 >>> assert not rs._asclist
3113 >>> len(rs)
3120 >>> len(rs)
3114 5
3121 5
3115 >>> [x for x in rs], [x for x in rs.fastasc()]
3122 >>> [x for x in rs], [x for x in rs.fastasc()]
3116 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3123 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3117 >>> assert rs._asclist
3124 >>> assert rs._asclist
3118
3125
3119 iterate descending:
3126 iterate descending:
3120 >>> rs = addset(xs, ys, ascending=False)
3127 >>> rs = addset(xs, ys, ascending=False)
3121 >>> [x for x in rs], [x for x in rs.fastdesc()] # without _asclist
3128 >>> [x for x in rs], [x for x in rs.fastdesc()] # without _asclist
3122 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3129 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3123 >>> assert not rs._asclist
3130 >>> assert not rs._asclist
3124 >>> len(rs)
3131 >>> len(rs)
3125 5
3132 5
3126 >>> [x for x in rs], [x for x in rs.fastdesc()]
3133 >>> [x for x in rs], [x for x in rs.fastdesc()]
3127 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3134 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3128 >>> assert rs._asclist
3135 >>> assert rs._asclist
3129
3136
3130 iterate ascending without fastasc:
3137 iterate ascending without fastasc:
3131 >>> rs = addset(xs, generatorset(ys), ascending=True)
3138 >>> rs = addset(xs, generatorset(ys), ascending=True)
3132 >>> assert rs.fastasc is None
3139 >>> assert rs.fastasc is None
3133 >>> [x for x in rs]
3140 >>> [x for x in rs]
3134 [0, 2, 3, 4, 5]
3141 [0, 2, 3, 4, 5]
3135
3142
3136 iterate descending without fastdesc:
3143 iterate descending without fastdesc:
3137 >>> rs = addset(generatorset(xs), ys, ascending=False)
3144 >>> rs = addset(generatorset(xs), ys, ascending=False)
3138 >>> assert rs.fastdesc is None
3145 >>> assert rs.fastdesc is None
3139 >>> [x for x in rs]
3146 >>> [x for x in rs]
3140 [5, 4, 3, 2, 0]
3147 [5, 4, 3, 2, 0]
3141 """
3148 """
3142 def __init__(self, revs1, revs2, ascending=None):
3149 def __init__(self, revs1, revs2, ascending=None):
3143 self._r1 = revs1
3150 self._r1 = revs1
3144 self._r2 = revs2
3151 self._r2 = revs2
3145 self._iter = None
3152 self._iter = None
3146 self._ascending = ascending
3153 self._ascending = ascending
3147 self._genlist = None
3154 self._genlist = None
3148 self._asclist = None
3155 self._asclist = None
3149
3156
3150 def __len__(self):
3157 def __len__(self):
3151 return len(self._list)
3158 return len(self._list)
3152
3159
3153 def __nonzero__(self):
3160 def __nonzero__(self):
3154 return bool(self._r1) or bool(self._r2)
3161 return bool(self._r1) or bool(self._r2)
3155
3162
3156 @util.propertycache
3163 @util.propertycache
3157 def _list(self):
3164 def _list(self):
3158 if not self._genlist:
3165 if not self._genlist:
3159 self._genlist = baseset(iter(self))
3166 self._genlist = baseset(iter(self))
3160 return self._genlist
3167 return self._genlist
3161
3168
3162 def __iter__(self):
3169 def __iter__(self):
3163 """Iterate over both collections without repeating elements
3170 """Iterate over both collections without repeating elements
3164
3171
3165 If the ascending attribute is not set, iterate over the first one and
3172 If the ascending attribute is not set, iterate over the first one and
3166 then over the second one checking for membership on the first one so we
3173 then over the second one checking for membership on the first one so we
3167 dont yield any duplicates.
3174 dont yield any duplicates.
3168
3175
3169 If the ascending attribute is set, iterate over both collections at the
3176 If the ascending attribute is set, iterate over both collections at the
3170 same time, yielding only one value at a time in the given order.
3177 same time, yielding only one value at a time in the given order.
3171 """
3178 """
3172 if self._ascending is None:
3179 if self._ascending is None:
3173 if self._genlist:
3180 if self._genlist:
3174 return iter(self._genlist)
3181 return iter(self._genlist)
3175 def arbitraryordergen():
3182 def arbitraryordergen():
3176 for r in self._r1:
3183 for r in self._r1:
3177 yield r
3184 yield r
3178 inr1 = self._r1.__contains__
3185 inr1 = self._r1.__contains__
3179 for r in self._r2:
3186 for r in self._r2:
3180 if not inr1(r):
3187 if not inr1(r):
3181 yield r
3188 yield r
3182 return arbitraryordergen()
3189 return arbitraryordergen()
3183 # try to use our own fast iterator if it exists
3190 # try to use our own fast iterator if it exists
3184 self._trysetasclist()
3191 self._trysetasclist()
3185 if self._ascending:
3192 if self._ascending:
3186 attr = 'fastasc'
3193 attr = 'fastasc'
3187 else:
3194 else:
3188 attr = 'fastdesc'
3195 attr = 'fastdesc'
3189 it = getattr(self, attr)
3196 it = getattr(self, attr)
3190 if it is not None:
3197 if it is not None:
3191 return it()
3198 return it()
3192 # maybe half of the component supports fast
3199 # maybe half of the component supports fast
3193 # get iterator for _r1
3200 # get iterator for _r1
3194 iter1 = getattr(self._r1, attr)
3201 iter1 = getattr(self._r1, attr)
3195 if iter1 is None:
3202 if iter1 is None:
3196 # let's avoid side effect (not sure it matters)
3203 # let's avoid side effect (not sure it matters)
3197 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
3204 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
3198 else:
3205 else:
3199 iter1 = iter1()
3206 iter1 = iter1()
3200 # get iterator for _r2
3207 # get iterator for _r2
3201 iter2 = getattr(self._r2, attr)
3208 iter2 = getattr(self._r2, attr)
3202 if iter2 is None:
3209 if iter2 is None:
3203 # let's avoid side effect (not sure it matters)
3210 # let's avoid side effect (not sure it matters)
3204 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
3211 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
3205 else:
3212 else:
3206 iter2 = iter2()
3213 iter2 = iter2()
3207 return _iterordered(self._ascending, iter1, iter2)
3214 return _iterordered(self._ascending, iter1, iter2)
3208
3215
3209 def _trysetasclist(self):
3216 def _trysetasclist(self):
3210 """populate the _asclist attribute if possible and necessary"""
3217 """populate the _asclist attribute if possible and necessary"""
3211 if self._genlist is not None and self._asclist is None:
3218 if self._genlist is not None and self._asclist is None:
3212 self._asclist = sorted(self._genlist)
3219 self._asclist = sorted(self._genlist)
3213
3220
3214 @property
3221 @property
3215 def fastasc(self):
3222 def fastasc(self):
3216 self._trysetasclist()
3223 self._trysetasclist()
3217 if self._asclist is not None:
3224 if self._asclist is not None:
3218 return self._asclist.__iter__
3225 return self._asclist.__iter__
3219 iter1 = self._r1.fastasc
3226 iter1 = self._r1.fastasc
3220 iter2 = self._r2.fastasc
3227 iter2 = self._r2.fastasc
3221 if None in (iter1, iter2):
3228 if None in (iter1, iter2):
3222 return None
3229 return None
3223 return lambda: _iterordered(True, iter1(), iter2())
3230 return lambda: _iterordered(True, iter1(), iter2())
3224
3231
3225 @property
3232 @property
3226 def fastdesc(self):
3233 def fastdesc(self):
3227 self._trysetasclist()
3234 self._trysetasclist()
3228 if self._asclist is not None:
3235 if self._asclist is not None:
3229 return self._asclist.__reversed__
3236 return self._asclist.__reversed__
3230 iter1 = self._r1.fastdesc
3237 iter1 = self._r1.fastdesc
3231 iter2 = self._r2.fastdesc
3238 iter2 = self._r2.fastdesc
3232 if None in (iter1, iter2):
3239 if None in (iter1, iter2):
3233 return None
3240 return None
3234 return lambda: _iterordered(False, iter1(), iter2())
3241 return lambda: _iterordered(False, iter1(), iter2())
3235
3242
3236 def __contains__(self, x):
3243 def __contains__(self, x):
3237 return x in self._r1 or x in self._r2
3244 return x in self._r1 or x in self._r2
3238
3245
3239 def sort(self, reverse=False):
3246 def sort(self, reverse=False):
3240 """Sort the added set
3247 """Sort the added set
3241
3248
3242 For this we use the cached list with all the generated values and if we
3249 For this we use the cached list with all the generated values and if we
3243 know they are ascending or descending we can sort them in a smart way.
3250 know they are ascending or descending we can sort them in a smart way.
3244 """
3251 """
3245 self._ascending = not reverse
3252 self._ascending = not reverse
3246
3253
3247 def isascending(self):
3254 def isascending(self):
3248 return self._ascending is not None and self._ascending
3255 return self._ascending is not None and self._ascending
3249
3256
3250 def isdescending(self):
3257 def isdescending(self):
3251 return self._ascending is not None and not self._ascending
3258 return self._ascending is not None and not self._ascending
3252
3259
3253 def reverse(self):
3260 def reverse(self):
3254 if self._ascending is None:
3261 if self._ascending is None:
3255 self._list.reverse()
3262 self._list.reverse()
3256 else:
3263 else:
3257 self._ascending = not self._ascending
3264 self._ascending = not self._ascending
3258
3265
3259 def first(self):
3266 def first(self):
3260 for x in self:
3267 for x in self:
3261 return x
3268 return x
3262 return None
3269 return None
3263
3270
3264 def last(self):
3271 def last(self):
3265 self.reverse()
3272 self.reverse()
3266 val = self.first()
3273 val = self.first()
3267 self.reverse()
3274 self.reverse()
3268 return val
3275 return val
3269
3276
3270 def __repr__(self):
3277 def __repr__(self):
3271 d = {None: '', False: '-', True: '+'}[self._ascending]
3278 d = {None: '', False: '-', True: '+'}[self._ascending]
3272 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3279 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3273
3280
3274 class generatorset(abstractsmartset):
3281 class generatorset(abstractsmartset):
3275 """Wrap a generator for lazy iteration
3282 """Wrap a generator for lazy iteration
3276
3283
3277 Wrapper structure for generators that provides lazy membership and can
3284 Wrapper structure for generators that provides lazy membership and can
3278 be iterated more than once.
3285 be iterated more than once.
3279 When asked for membership it generates values until either it finds the
3286 When asked for membership it generates values until either it finds the
3280 requested one or has gone through all the elements in the generator
3287 requested one or has gone through all the elements in the generator
3281 """
3288 """
3282 def __init__(self, gen, iterasc=None):
3289 def __init__(self, gen, iterasc=None):
3283 """
3290 """
3284 gen: a generator producing the values for the generatorset.
3291 gen: a generator producing the values for the generatorset.
3285 """
3292 """
3286 self._gen = gen
3293 self._gen = gen
3287 self._asclist = None
3294 self._asclist = None
3288 self._cache = {}
3295 self._cache = {}
3289 self._genlist = []
3296 self._genlist = []
3290 self._finished = False
3297 self._finished = False
3291 self._ascending = True
3298 self._ascending = True
3292 if iterasc is not None:
3299 if iterasc is not None:
3293 if iterasc:
3300 if iterasc:
3294 self.fastasc = self._iterator
3301 self.fastasc = self._iterator
3295 self.__contains__ = self._asccontains
3302 self.__contains__ = self._asccontains
3296 else:
3303 else:
3297 self.fastdesc = self._iterator
3304 self.fastdesc = self._iterator
3298 self.__contains__ = self._desccontains
3305 self.__contains__ = self._desccontains
3299
3306
3300 def __nonzero__(self):
3307 def __nonzero__(self):
3301 # Do not use 'for r in self' because it will enforce the iteration
3308 # Do not use 'for r in self' because it will enforce the iteration
3302 # order (default ascending), possibly unrolling a whole descending
3309 # order (default ascending), possibly unrolling a whole descending
3303 # iterator.
3310 # iterator.
3304 if self._genlist:
3311 if self._genlist:
3305 return True
3312 return True
3306 for r in self._consumegen():
3313 for r in self._consumegen():
3307 return True
3314 return True
3308 return False
3315 return False
3309
3316
3310 def __contains__(self, x):
3317 def __contains__(self, x):
3311 if x in self._cache:
3318 if x in self._cache:
3312 return self._cache[x]
3319 return self._cache[x]
3313
3320
3314 # Use new values only, as existing values would be cached.
3321 # Use new values only, as existing values would be cached.
3315 for l in self._consumegen():
3322 for l in self._consumegen():
3316 if l == x:
3323 if l == x:
3317 return True
3324 return True
3318
3325
3319 self._cache[x] = False
3326 self._cache[x] = False
3320 return False
3327 return False
3321
3328
3322 def _asccontains(self, x):
3329 def _asccontains(self, x):
3323 """version of contains optimised for ascending generator"""
3330 """version of contains optimised for ascending generator"""
3324 if x in self._cache:
3331 if x in self._cache:
3325 return self._cache[x]
3332 return self._cache[x]
3326
3333
3327 # Use new values only, as existing values would be cached.
3334 # Use new values only, as existing values would be cached.
3328 for l in self._consumegen():
3335 for l in self._consumegen():
3329 if l == x:
3336 if l == x:
3330 return True
3337 return True
3331 if l > x:
3338 if l > x:
3332 break
3339 break
3333
3340
3334 self._cache[x] = False
3341 self._cache[x] = False
3335 return False
3342 return False
3336
3343
3337 def _desccontains(self, x):
3344 def _desccontains(self, x):
3338 """version of contains optimised for descending generator"""
3345 """version of contains optimised for descending generator"""
3339 if x in self._cache:
3346 if x in self._cache:
3340 return self._cache[x]
3347 return self._cache[x]
3341
3348
3342 # Use new values only, as existing values would be cached.
3349 # Use new values only, as existing values would be cached.
3343 for l in self._consumegen():
3350 for l in self._consumegen():
3344 if l == x:
3351 if l == x:
3345 return True
3352 return True
3346 if l < x:
3353 if l < x:
3347 break
3354 break
3348
3355
3349 self._cache[x] = False
3356 self._cache[x] = False
3350 return False
3357 return False
3351
3358
3352 def __iter__(self):
3359 def __iter__(self):
3353 if self._ascending:
3360 if self._ascending:
3354 it = self.fastasc
3361 it = self.fastasc
3355 else:
3362 else:
3356 it = self.fastdesc
3363 it = self.fastdesc
3357 if it is not None:
3364 if it is not None:
3358 return it()
3365 return it()
3359 # we need to consume the iterator
3366 # we need to consume the iterator
3360 for x in self._consumegen():
3367 for x in self._consumegen():
3361 pass
3368 pass
3362 # recall the same code
3369 # recall the same code
3363 return iter(self)
3370 return iter(self)
3364
3371
3365 def _iterator(self):
3372 def _iterator(self):
3366 if self._finished:
3373 if self._finished:
3367 return iter(self._genlist)
3374 return iter(self._genlist)
3368
3375
3369 # We have to use this complex iteration strategy to allow multiple
3376 # We have to use this complex iteration strategy to allow multiple
3370 # iterations at the same time. We need to be able to catch revision
3377 # iterations at the same time. We need to be able to catch revision
3371 # removed from _consumegen and added to genlist in another instance.
3378 # removed from _consumegen and added to genlist in another instance.
3372 #
3379 #
3373 # Getting rid of it would provide an about 15% speed up on this
3380 # Getting rid of it would provide an about 15% speed up on this
3374 # iteration.
3381 # iteration.
3375 genlist = self._genlist
3382 genlist = self._genlist
3376 nextrev = self._consumegen().next
3383 nextrev = self._consumegen().next
3377 _len = len # cache global lookup
3384 _len = len # cache global lookup
3378 def gen():
3385 def gen():
3379 i = 0
3386 i = 0
3380 while True:
3387 while True:
3381 if i < _len(genlist):
3388 if i < _len(genlist):
3382 yield genlist[i]
3389 yield genlist[i]
3383 else:
3390 else:
3384 yield nextrev()
3391 yield nextrev()
3385 i += 1
3392 i += 1
3386 return gen()
3393 return gen()
3387
3394
3388 def _consumegen(self):
3395 def _consumegen(self):
3389 cache = self._cache
3396 cache = self._cache
3390 genlist = self._genlist.append
3397 genlist = self._genlist.append
3391 for item in self._gen:
3398 for item in self._gen:
3392 cache[item] = True
3399 cache[item] = True
3393 genlist(item)
3400 genlist(item)
3394 yield item
3401 yield item
3395 if not self._finished:
3402 if not self._finished:
3396 self._finished = True
3403 self._finished = True
3397 asc = self._genlist[:]
3404 asc = self._genlist[:]
3398 asc.sort()
3405 asc.sort()
3399 self._asclist = asc
3406 self._asclist = asc
3400 self.fastasc = asc.__iter__
3407 self.fastasc = asc.__iter__
3401 self.fastdesc = asc.__reversed__
3408 self.fastdesc = asc.__reversed__
3402
3409
3403 def __len__(self):
3410 def __len__(self):
3404 for x in self._consumegen():
3411 for x in self._consumegen():
3405 pass
3412 pass
3406 return len(self._genlist)
3413 return len(self._genlist)
3407
3414
3408 def sort(self, reverse=False):
3415 def sort(self, reverse=False):
3409 self._ascending = not reverse
3416 self._ascending = not reverse
3410
3417
3411 def reverse(self):
3418 def reverse(self):
3412 self._ascending = not self._ascending
3419 self._ascending = not self._ascending
3413
3420
3414 def isascending(self):
3421 def isascending(self):
3415 return self._ascending
3422 return self._ascending
3416
3423
3417 def isdescending(self):
3424 def isdescending(self):
3418 return not self._ascending
3425 return not self._ascending
3419
3426
3420 def first(self):
3427 def first(self):
3421 if self._ascending:
3428 if self._ascending:
3422 it = self.fastasc
3429 it = self.fastasc
3423 else:
3430 else:
3424 it = self.fastdesc
3431 it = self.fastdesc
3425 if it is None:
3432 if it is None:
3426 # we need to consume all and try again
3433 # we need to consume all and try again
3427 for x in self._consumegen():
3434 for x in self._consumegen():
3428 pass
3435 pass
3429 return self.first()
3436 return self.first()
3430 return next(it(), None)
3437 return next(it(), None)
3431
3438
3432 def last(self):
3439 def last(self):
3433 if self._ascending:
3440 if self._ascending:
3434 it = self.fastdesc
3441 it = self.fastdesc
3435 else:
3442 else:
3436 it = self.fastasc
3443 it = self.fastasc
3437 if it is None:
3444 if it is None:
3438 # we need to consume all and try again
3445 # we need to consume all and try again
3439 for x in self._consumegen():
3446 for x in self._consumegen():
3440 pass
3447 pass
3441 return self.first()
3448 return self.first()
3442 return next(it(), None)
3449 return next(it(), None)
3443
3450
3444 def __repr__(self):
3451 def __repr__(self):
3445 d = {False: '-', True: '+'}[self._ascending]
3452 d = {False: '-', True: '+'}[self._ascending]
3446 return '<%s%s>' % (type(self).__name__, d)
3453 return '<%s%s>' % (type(self).__name__, d)
3447
3454
3448 class spanset(abstractsmartset):
3455 class spanset(abstractsmartset):
3449 """Duck type for baseset class which represents a range of revisions and
3456 """Duck type for baseset class which represents a range of revisions and
3450 can work lazily and without having all the range in memory
3457 can work lazily and without having all the range in memory
3451
3458
3452 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3459 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3453 notable points:
3460 notable points:
3454 - when x < y it will be automatically descending,
3461 - when x < y it will be automatically descending,
3455 - revision filtered with this repoview will be skipped.
3462 - revision filtered with this repoview will be skipped.
3456
3463
3457 """
3464 """
3458 def __init__(self, repo, start=0, end=None):
3465 def __init__(self, repo, start=0, end=None):
3459 """
3466 """
3460 start: first revision included the set
3467 start: first revision included the set
3461 (default to 0)
3468 (default to 0)
3462 end: first revision excluded (last+1)
3469 end: first revision excluded (last+1)
3463 (default to len(repo)
3470 (default to len(repo)
3464
3471
3465 Spanset will be descending if `end` < `start`.
3472 Spanset will be descending if `end` < `start`.
3466 """
3473 """
3467 if end is None:
3474 if end is None:
3468 end = len(repo)
3475 end = len(repo)
3469 self._ascending = start <= end
3476 self._ascending = start <= end
3470 if not self._ascending:
3477 if not self._ascending:
3471 start, end = end + 1, start +1
3478 start, end = end + 1, start +1
3472 self._start = start
3479 self._start = start
3473 self._end = end
3480 self._end = end
3474 self._hiddenrevs = repo.changelog.filteredrevs
3481 self._hiddenrevs = repo.changelog.filteredrevs
3475
3482
3476 def sort(self, reverse=False):
3483 def sort(self, reverse=False):
3477 self._ascending = not reverse
3484 self._ascending = not reverse
3478
3485
3479 def reverse(self):
3486 def reverse(self):
3480 self._ascending = not self._ascending
3487 self._ascending = not self._ascending
3481
3488
3482 def _iterfilter(self, iterrange):
3489 def _iterfilter(self, iterrange):
3483 s = self._hiddenrevs
3490 s = self._hiddenrevs
3484 for r in iterrange:
3491 for r in iterrange:
3485 if r not in s:
3492 if r not in s:
3486 yield r
3493 yield r
3487
3494
3488 def __iter__(self):
3495 def __iter__(self):
3489 if self._ascending:
3496 if self._ascending:
3490 return self.fastasc()
3497 return self.fastasc()
3491 else:
3498 else:
3492 return self.fastdesc()
3499 return self.fastdesc()
3493
3500
3494 def fastasc(self):
3501 def fastasc(self):
3495 iterrange = xrange(self._start, self._end)
3502 iterrange = xrange(self._start, self._end)
3496 if self._hiddenrevs:
3503 if self._hiddenrevs:
3497 return self._iterfilter(iterrange)
3504 return self._iterfilter(iterrange)
3498 return iter(iterrange)
3505 return iter(iterrange)
3499
3506
3500 def fastdesc(self):
3507 def fastdesc(self):
3501 iterrange = xrange(self._end - 1, self._start - 1, -1)
3508 iterrange = xrange(self._end - 1, self._start - 1, -1)
3502 if self._hiddenrevs:
3509 if self._hiddenrevs:
3503 return self._iterfilter(iterrange)
3510 return self._iterfilter(iterrange)
3504 return iter(iterrange)
3511 return iter(iterrange)
3505
3512
3506 def __contains__(self, rev):
3513 def __contains__(self, rev):
3507 hidden = self._hiddenrevs
3514 hidden = self._hiddenrevs
3508 return ((self._start <= rev < self._end)
3515 return ((self._start <= rev < self._end)
3509 and not (hidden and rev in hidden))
3516 and not (hidden and rev in hidden))
3510
3517
3511 def __nonzero__(self):
3518 def __nonzero__(self):
3512 for r in self:
3519 for r in self:
3513 return True
3520 return True
3514 return False
3521 return False
3515
3522
3516 def __len__(self):
3523 def __len__(self):
3517 if not self._hiddenrevs:
3524 if not self._hiddenrevs:
3518 return abs(self._end - self._start)
3525 return abs(self._end - self._start)
3519 else:
3526 else:
3520 count = 0
3527 count = 0
3521 start = self._start
3528 start = self._start
3522 end = self._end
3529 end = self._end
3523 for rev in self._hiddenrevs:
3530 for rev in self._hiddenrevs:
3524 if (end < rev <= start) or (start <= rev < end):
3531 if (end < rev <= start) or (start <= rev < end):
3525 count += 1
3532 count += 1
3526 return abs(self._end - self._start) - count
3533 return abs(self._end - self._start) - count
3527
3534
3528 def isascending(self):
3535 def isascending(self):
3529 return self._ascending
3536 return self._ascending
3530
3537
3531 def isdescending(self):
3538 def isdescending(self):
3532 return not self._ascending
3539 return not self._ascending
3533
3540
3534 def first(self):
3541 def first(self):
3535 if self._ascending:
3542 if self._ascending:
3536 it = self.fastasc
3543 it = self.fastasc
3537 else:
3544 else:
3538 it = self.fastdesc
3545 it = self.fastdesc
3539 for x in it():
3546 for x in it():
3540 return x
3547 return x
3541 return None
3548 return None
3542
3549
3543 def last(self):
3550 def last(self):
3544 if self._ascending:
3551 if self._ascending:
3545 it = self.fastdesc
3552 it = self.fastdesc
3546 else:
3553 else:
3547 it = self.fastasc
3554 it = self.fastasc
3548 for x in it():
3555 for x in it():
3549 return x
3556 return x
3550 return None
3557 return None
3551
3558
3552 def __repr__(self):
3559 def __repr__(self):
3553 d = {False: '-', True: '+'}[self._ascending]
3560 d = {False: '-', True: '+'}[self._ascending]
3554 return '<%s%s %d:%d>' % (type(self).__name__, d,
3561 return '<%s%s %d:%d>' % (type(self).__name__, d,
3555 self._start, self._end - 1)
3562 self._start, self._end - 1)
3556
3563
3557 class fullreposet(spanset):
3564 class fullreposet(spanset):
3558 """a set containing all revisions in the repo
3565 """a set containing all revisions in the repo
3559
3566
3560 This class exists to host special optimization and magic to handle virtual
3567 This class exists to host special optimization and magic to handle virtual
3561 revisions such as "null".
3568 revisions such as "null".
3562 """
3569 """
3563
3570
3564 def __init__(self, repo):
3571 def __init__(self, repo):
3565 super(fullreposet, self).__init__(repo)
3572 super(fullreposet, self).__init__(repo)
3566
3573
3567 def __and__(self, other):
3574 def __and__(self, other):
3568 """As self contains the whole repo, all of the other set should also be
3575 """As self contains the whole repo, all of the other set should also be
3569 in self. Therefore `self & other = other`.
3576 in self. Therefore `self & other = other`.
3570
3577
3571 This boldly assumes the other contains valid revs only.
3578 This boldly assumes the other contains valid revs only.
3572 """
3579 """
3573 # other not a smartset, make is so
3580 # other not a smartset, make is so
3574 if not util.safehasattr(other, 'isascending'):
3581 if not util.safehasattr(other, 'isascending'):
3575 # filter out hidden revision
3582 # filter out hidden revision
3576 # (this boldly assumes all smartset are pure)
3583 # (this boldly assumes all smartset are pure)
3577 #
3584 #
3578 # `other` was used with "&", let's assume this is a set like
3585 # `other` was used with "&", let's assume this is a set like
3579 # object.
3586 # object.
3580 other = baseset(other - self._hiddenrevs)
3587 other = baseset(other - self._hiddenrevs)
3581
3588
3582 # XXX As fullreposet is also used as bootstrap, this is wrong.
3589 # XXX As fullreposet is also used as bootstrap, this is wrong.
3583 #
3590 #
3584 # With a giveme312() revset returning [3,1,2], this makes
3591 # With a giveme312() revset returning [3,1,2], this makes
3585 # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
3592 # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
3586 # We cannot just drop it because other usage still need to sort it:
3593 # We cannot just drop it because other usage still need to sort it:
3587 # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
3594 # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
3588 #
3595 #
3589 # There is also some faulty revset implementations that rely on it
3596 # There is also some faulty revset implementations that rely on it
3590 # (eg: children as of its state in e8075329c5fb)
3597 # (eg: children as of its state in e8075329c5fb)
3591 #
3598 #
3592 # When we fix the two points above we can move this into the if clause
3599 # When we fix the two points above we can move this into the if clause
3593 other.sort(reverse=self.isdescending())
3600 other.sort(reverse=self.isdescending())
3594 return other
3601 return other
3595
3602
3596 def prettyformatset(revs):
3603 def prettyformatset(revs):
3597 lines = []
3604 lines = []
3598 rs = repr(revs)
3605 rs = repr(revs)
3599 p = 0
3606 p = 0
3600 while p < len(rs):
3607 while p < len(rs):
3601 q = rs.find('<', p + 1)
3608 q = rs.find('<', p + 1)
3602 if q < 0:
3609 if q < 0:
3603 q = len(rs)
3610 q = len(rs)
3604 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3611 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3605 assert l >= 0
3612 assert l >= 0
3606 lines.append((l, rs[p:q].rstrip()))
3613 lines.append((l, rs[p:q].rstrip()))
3607 p = q
3614 p = q
3608 return '\n'.join(' ' * l + s for l, s in lines)
3615 return '\n'.join(' ' * l + s for l, s in lines)
3609
3616
3610 # tell hggettext to extract docstrings from these functions:
3617 # tell hggettext to extract docstrings from these functions:
3611 i18nfunctions = symbols.values()
3618 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now