##// END OF EJS Templates
revset: add a whitelist of DoS-safe symbols...
Alexander Plavin -
r19721:d8ca6d96 default
parent child Browse files
Show More
@@ -1,1956 +1,2025 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, discovery, hbisect, phases
9 import parser, util, error, discovery, hbisect, phases
10 import node
10 import node
11 import match as matchmod
11 import match as matchmod
12 from i18n import _
12 from i18n import _
13 import encoding
13 import encoding
14 import obsolete as obsmod
14 import obsolete as obsmod
15 import repoview
15 import repoview
16
16
17 def _revancestors(repo, revs, followfirst):
17 def _revancestors(repo, revs, followfirst):
18 """Like revlog.ancestors(), but supports followfirst."""
18 """Like revlog.ancestors(), but supports followfirst."""
19 cut = followfirst and 1 or None
19 cut = followfirst and 1 or None
20 cl = repo.changelog
20 cl = repo.changelog
21 visit = util.deque(revs)
21 visit = util.deque(revs)
22 seen = set([node.nullrev])
22 seen = set([node.nullrev])
23 while visit:
23 while visit:
24 for parent in cl.parentrevs(visit.popleft())[:cut]:
24 for parent in cl.parentrevs(visit.popleft())[:cut]:
25 if parent not in seen:
25 if parent not in seen:
26 visit.append(parent)
26 visit.append(parent)
27 seen.add(parent)
27 seen.add(parent)
28 yield parent
28 yield parent
29
29
30 def _revdescendants(repo, revs, followfirst):
30 def _revdescendants(repo, revs, followfirst):
31 """Like revlog.descendants() but supports followfirst."""
31 """Like revlog.descendants() but supports followfirst."""
32 cut = followfirst and 1 or None
32 cut = followfirst and 1 or None
33 cl = repo.changelog
33 cl = repo.changelog
34 first = min(revs)
34 first = min(revs)
35 nullrev = node.nullrev
35 nullrev = node.nullrev
36 if first == nullrev:
36 if first == nullrev:
37 # Are there nodes with a null first parent and a non-null
37 # Are there nodes with a null first parent and a non-null
38 # second one? Maybe. Do we care? Probably not.
38 # second one? Maybe. Do we care? Probably not.
39 for i in cl:
39 for i in cl:
40 yield i
40 yield i
41 return
41 return
42
42
43 seen = set(revs)
43 seen = set(revs)
44 for i in cl.revs(first + 1):
44 for i in cl.revs(first + 1):
45 for x in cl.parentrevs(i)[:cut]:
45 for x in cl.parentrevs(i)[:cut]:
46 if x != nullrev and x in seen:
46 if x != nullrev and x in seen:
47 seen.add(i)
47 seen.add(i)
48 yield i
48 yield i
49 break
49 break
50
50
51 def _revsbetween(repo, roots, heads):
51 def _revsbetween(repo, roots, heads):
52 """Return all paths between roots and heads, inclusive of both endpoint
52 """Return all paths between roots and heads, inclusive of both endpoint
53 sets."""
53 sets."""
54 if not roots:
54 if not roots:
55 return []
55 return []
56 parentrevs = repo.changelog.parentrevs
56 parentrevs = repo.changelog.parentrevs
57 visit = heads[:]
57 visit = heads[:]
58 reachable = set()
58 reachable = set()
59 seen = {}
59 seen = {}
60 minroot = min(roots)
60 minroot = min(roots)
61 roots = set(roots)
61 roots = set(roots)
62 # open-code the post-order traversal due to the tiny size of
62 # open-code the post-order traversal due to the tiny size of
63 # sys.getrecursionlimit()
63 # sys.getrecursionlimit()
64 while visit:
64 while visit:
65 rev = visit.pop()
65 rev = visit.pop()
66 if rev in roots:
66 if rev in roots:
67 reachable.add(rev)
67 reachable.add(rev)
68 parents = parentrevs(rev)
68 parents = parentrevs(rev)
69 seen[rev] = parents
69 seen[rev] = parents
70 for parent in parents:
70 for parent in parents:
71 if parent >= minroot and parent not in seen:
71 if parent >= minroot and parent not in seen:
72 visit.append(parent)
72 visit.append(parent)
73 if not reachable:
73 if not reachable:
74 return []
74 return []
75 for rev in sorted(seen):
75 for rev in sorted(seen):
76 for parent in seen[rev]:
76 for parent in seen[rev]:
77 if parent in reachable:
77 if parent in reachable:
78 reachable.add(rev)
78 reachable.add(rev)
79 return sorted(reachable)
79 return sorted(reachable)
80
80
81 elements = {
81 elements = {
82 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
82 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
83 "~": (18, None, ("ancestor", 18)),
83 "~": (18, None, ("ancestor", 18)),
84 "^": (18, None, ("parent", 18), ("parentpost", 18)),
84 "^": (18, None, ("parent", 18), ("parentpost", 18)),
85 "-": (5, ("negate", 19), ("minus", 5)),
85 "-": (5, ("negate", 19), ("minus", 5)),
86 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
86 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
87 ("dagrangepost", 17)),
87 ("dagrangepost", 17)),
88 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
88 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
89 ("dagrangepost", 17)),
89 ("dagrangepost", 17)),
90 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
90 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
91 "not": (10, ("not", 10)),
91 "not": (10, ("not", 10)),
92 "!": (10, ("not", 10)),
92 "!": (10, ("not", 10)),
93 "and": (5, None, ("and", 5)),
93 "and": (5, None, ("and", 5)),
94 "&": (5, None, ("and", 5)),
94 "&": (5, None, ("and", 5)),
95 "or": (4, None, ("or", 4)),
95 "or": (4, None, ("or", 4)),
96 "|": (4, None, ("or", 4)),
96 "|": (4, None, ("or", 4)),
97 "+": (4, None, ("or", 4)),
97 "+": (4, None, ("or", 4)),
98 ",": (2, None, ("list", 2)),
98 ",": (2, None, ("list", 2)),
99 ")": (0, None, None),
99 ")": (0, None, None),
100 "symbol": (0, ("symbol",), None),
100 "symbol": (0, ("symbol",), None),
101 "string": (0, ("string",), None),
101 "string": (0, ("string",), None),
102 "end": (0, None, None),
102 "end": (0, None, None),
103 }
103 }
104
104
105 keywords = set(['and', 'or', 'not'])
105 keywords = set(['and', 'or', 'not'])
106
106
107 def tokenize(program):
107 def tokenize(program):
108 '''
108 '''
109 Parse a revset statement into a stream of tokens
109 Parse a revset statement into a stream of tokens
110
110
111 Check that @ is a valid unquoted token character (issue3686):
111 Check that @ is a valid unquoted token character (issue3686):
112 >>> list(tokenize("@::"))
112 >>> list(tokenize("@::"))
113 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
113 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
114
114
115 '''
115 '''
116
116
117 pos, l = 0, len(program)
117 pos, l = 0, len(program)
118 while pos < l:
118 while pos < l:
119 c = program[pos]
119 c = program[pos]
120 if c.isspace(): # skip inter-token whitespace
120 if c.isspace(): # skip inter-token whitespace
121 pass
121 pass
122 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
122 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
123 yield ('::', None, pos)
123 yield ('::', None, pos)
124 pos += 1 # skip ahead
124 pos += 1 # skip ahead
125 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
125 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
126 yield ('..', None, pos)
126 yield ('..', None, pos)
127 pos += 1 # skip ahead
127 pos += 1 # skip ahead
128 elif c in "():,-|&+!~^": # handle simple operators
128 elif c in "():,-|&+!~^": # handle simple operators
129 yield (c, None, pos)
129 yield (c, None, pos)
130 elif (c in '"\'' or c == 'r' and
130 elif (c in '"\'' or c == 'r' and
131 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
131 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
132 if c == 'r':
132 if c == 'r':
133 pos += 1
133 pos += 1
134 c = program[pos]
134 c = program[pos]
135 decode = lambda x: x
135 decode = lambda x: x
136 else:
136 else:
137 decode = lambda x: x.decode('string-escape')
137 decode = lambda x: x.decode('string-escape')
138 pos += 1
138 pos += 1
139 s = pos
139 s = pos
140 while pos < l: # find closing quote
140 while pos < l: # find closing quote
141 d = program[pos]
141 d = program[pos]
142 if d == '\\': # skip over escaped characters
142 if d == '\\': # skip over escaped characters
143 pos += 2
143 pos += 2
144 continue
144 continue
145 if d == c:
145 if d == c:
146 yield ('string', decode(program[s:pos]), s)
146 yield ('string', decode(program[s:pos]), s)
147 break
147 break
148 pos += 1
148 pos += 1
149 else:
149 else:
150 raise error.ParseError(_("unterminated string"), s)
150 raise error.ParseError(_("unterminated string"), s)
151 # gather up a symbol/keyword
151 # gather up a symbol/keyword
152 elif c.isalnum() or c in '._@' or ord(c) > 127:
152 elif c.isalnum() or c in '._@' or ord(c) > 127:
153 s = pos
153 s = pos
154 pos += 1
154 pos += 1
155 while pos < l: # find end of symbol
155 while pos < l: # find end of symbol
156 d = program[pos]
156 d = program[pos]
157 if not (d.isalnum() or d in "._/@" or ord(d) > 127):
157 if not (d.isalnum() or d in "._/@" or ord(d) > 127):
158 break
158 break
159 if d == '.' and program[pos - 1] == '.': # special case for ..
159 if d == '.' and program[pos - 1] == '.': # special case for ..
160 pos -= 1
160 pos -= 1
161 break
161 break
162 pos += 1
162 pos += 1
163 sym = program[s:pos]
163 sym = program[s:pos]
164 if sym in keywords: # operator keywords
164 if sym in keywords: # operator keywords
165 yield (sym, None, s)
165 yield (sym, None, s)
166 else:
166 else:
167 yield ('symbol', sym, s)
167 yield ('symbol', sym, s)
168 pos -= 1
168 pos -= 1
169 else:
169 else:
170 raise error.ParseError(_("syntax error"), pos)
170 raise error.ParseError(_("syntax error"), pos)
171 pos += 1
171 pos += 1
172 yield ('end', None, pos)
172 yield ('end', None, pos)
173
173
174 # helpers
174 # helpers
175
175
176 def getstring(x, err):
176 def getstring(x, err):
177 if x and (x[0] == 'string' or x[0] == 'symbol'):
177 if x and (x[0] == 'string' or x[0] == 'symbol'):
178 return x[1]
178 return x[1]
179 raise error.ParseError(err)
179 raise error.ParseError(err)
180
180
181 def getlist(x):
181 def getlist(x):
182 if not x:
182 if not x:
183 return []
183 return []
184 if x[0] == 'list':
184 if x[0] == 'list':
185 return getlist(x[1]) + [x[2]]
185 return getlist(x[1]) + [x[2]]
186 return [x]
186 return [x]
187
187
188 def getargs(x, min, max, err):
188 def getargs(x, min, max, err):
189 l = getlist(x)
189 l = getlist(x)
190 if len(l) < min or (max >= 0 and len(l) > max):
190 if len(l) < min or (max >= 0 and len(l) > max):
191 raise error.ParseError(err)
191 raise error.ParseError(err)
192 return l
192 return l
193
193
194 def getset(repo, subset, x):
194 def getset(repo, subset, x):
195 if not x:
195 if not x:
196 raise error.ParseError(_("missing argument"))
196 raise error.ParseError(_("missing argument"))
197 return methods[x[0]](repo, subset, *x[1:])
197 return methods[x[0]](repo, subset, *x[1:])
198
198
199 def _getrevsource(repo, r):
199 def _getrevsource(repo, r):
200 extra = repo[r].extra()
200 extra = repo[r].extra()
201 for label in ('source', 'transplant_source', 'rebase_source'):
201 for label in ('source', 'transplant_source', 'rebase_source'):
202 if label in extra:
202 if label in extra:
203 try:
203 try:
204 return repo[extra[label]].rev()
204 return repo[extra[label]].rev()
205 except error.RepoLookupError:
205 except error.RepoLookupError:
206 pass
206 pass
207 return None
207 return None
208
208
209 # operator methods
209 # operator methods
210
210
211 def stringset(repo, subset, x):
211 def stringset(repo, subset, x):
212 x = repo[x].rev()
212 x = repo[x].rev()
213 if x == -1 and len(subset) == len(repo):
213 if x == -1 and len(subset) == len(repo):
214 return [-1]
214 return [-1]
215 if len(subset) == len(repo) or x in subset:
215 if len(subset) == len(repo) or x in subset:
216 return [x]
216 return [x]
217 return []
217 return []
218
218
219 def symbolset(repo, subset, x):
219 def symbolset(repo, subset, x):
220 if x in symbols:
220 if x in symbols:
221 raise error.ParseError(_("can't use %s here") % x)
221 raise error.ParseError(_("can't use %s here") % x)
222 return stringset(repo, subset, x)
222 return stringset(repo, subset, x)
223
223
224 def rangeset(repo, subset, x, y):
224 def rangeset(repo, subset, x, y):
225 cl = repo.changelog
225 cl = repo.changelog
226 m = getset(repo, cl, x)
226 m = getset(repo, cl, x)
227 n = getset(repo, cl, y)
227 n = getset(repo, cl, y)
228
228
229 if not m or not n:
229 if not m or not n:
230 return []
230 return []
231 m, n = m[0], n[-1]
231 m, n = m[0], n[-1]
232
232
233 if m < n:
233 if m < n:
234 r = range(m, n + 1)
234 r = range(m, n + 1)
235 else:
235 else:
236 r = range(m, n - 1, -1)
236 r = range(m, n - 1, -1)
237 s = set(subset)
237 s = set(subset)
238 return [x for x in r if x in s]
238 return [x for x in r if x in s]
239
239
240 def dagrange(repo, subset, x, y):
240 def dagrange(repo, subset, x, y):
241 r = list(repo)
241 r = list(repo)
242 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
242 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
243 s = set(subset)
243 s = set(subset)
244 return [r for r in xs if r in s]
244 return [r for r in xs if r in s]
245
245
246 def andset(repo, subset, x, y):
246 def andset(repo, subset, x, y):
247 return getset(repo, getset(repo, subset, x), y)
247 return getset(repo, getset(repo, subset, x), y)
248
248
249 def orset(repo, subset, x, y):
249 def orset(repo, subset, x, y):
250 xl = getset(repo, subset, x)
250 xl = getset(repo, subset, x)
251 s = set(xl)
251 s = set(xl)
252 yl = getset(repo, [r for r in subset if r not in s], y)
252 yl = getset(repo, [r for r in subset if r not in s], y)
253 return xl + yl
253 return xl + yl
254
254
255 def notset(repo, subset, x):
255 def notset(repo, subset, x):
256 s = set(getset(repo, subset, x))
256 s = set(getset(repo, subset, x))
257 return [r for r in subset if r not in s]
257 return [r for r in subset if r not in s]
258
258
259 def listset(repo, subset, a, b):
259 def listset(repo, subset, a, b):
260 raise error.ParseError(_("can't use a list in this context"))
260 raise error.ParseError(_("can't use a list in this context"))
261
261
262 def func(repo, subset, a, b):
262 def func(repo, subset, a, b):
263 if a[0] == 'symbol' and a[1] in symbols:
263 if a[0] == 'symbol' and a[1] in symbols:
264 return symbols[a[1]](repo, subset, b)
264 return symbols[a[1]](repo, subset, b)
265 raise error.ParseError(_("not a function: %s") % a[1])
265 raise error.ParseError(_("not a function: %s") % a[1])
266
266
267 # functions
267 # functions
268
268
269 def adds(repo, subset, x):
269 def adds(repo, subset, x):
270 """``adds(pattern)``
270 """``adds(pattern)``
271 Changesets that add a file matching pattern.
271 Changesets that add a file matching pattern.
272 """
272 """
273 # i18n: "adds" is a keyword
273 # i18n: "adds" is a keyword
274 pat = getstring(x, _("adds requires a pattern"))
274 pat = getstring(x, _("adds requires a pattern"))
275 return checkstatus(repo, subset, pat, 1)
275 return checkstatus(repo, subset, pat, 1)
276
276
277 def ancestor(repo, subset, x):
277 def ancestor(repo, subset, x):
278 """``ancestor(*changeset)``
278 """``ancestor(*changeset)``
279 Greatest common ancestor of the changesets.
279 Greatest common ancestor of the changesets.
280
280
281 Accepts 0 or more changesets.
281 Accepts 0 or more changesets.
282 Will return empty list when passed no args.
282 Will return empty list when passed no args.
283 Greatest common ancestor of a single changeset is that changeset.
283 Greatest common ancestor of a single changeset is that changeset.
284 """
284 """
285 # i18n: "ancestor" is a keyword
285 # i18n: "ancestor" is a keyword
286 l = getlist(x)
286 l = getlist(x)
287 rl = list(repo)
287 rl = list(repo)
288 anc = None
288 anc = None
289
289
290 # (getset(repo, rl, i) for i in l) generates a list of lists
290 # (getset(repo, rl, i) for i in l) generates a list of lists
291 rev = repo.changelog.rev
291 rev = repo.changelog.rev
292 ancestor = repo.changelog.ancestor
292 ancestor = repo.changelog.ancestor
293 node = repo.changelog.node
293 node = repo.changelog.node
294 for revs in (getset(repo, rl, i) for i in l):
294 for revs in (getset(repo, rl, i) for i in l):
295 for r in revs:
295 for r in revs:
296 if anc is None:
296 if anc is None:
297 anc = r
297 anc = r
298 else:
298 else:
299 anc = rev(ancestor(node(anc), node(r)))
299 anc = rev(ancestor(node(anc), node(r)))
300
300
301 if anc is not None and anc in subset:
301 if anc is not None and anc in subset:
302 return [anc]
302 return [anc]
303 return []
303 return []
304
304
305 def _ancestors(repo, subset, x, followfirst=False):
305 def _ancestors(repo, subset, x, followfirst=False):
306 args = getset(repo, list(repo), x)
306 args = getset(repo, list(repo), x)
307 if not args:
307 if not args:
308 return []
308 return []
309 s = set(_revancestors(repo, args, followfirst)) | set(args)
309 s = set(_revancestors(repo, args, followfirst)) | set(args)
310 return [r for r in subset if r in s]
310 return [r for r in subset if r in s]
311
311
312 def ancestors(repo, subset, x):
312 def ancestors(repo, subset, x):
313 """``ancestors(set)``
313 """``ancestors(set)``
314 Changesets that are ancestors of a changeset in set.
314 Changesets that are ancestors of a changeset in set.
315 """
315 """
316 return _ancestors(repo, subset, x)
316 return _ancestors(repo, subset, x)
317
317
318 def _firstancestors(repo, subset, x):
318 def _firstancestors(repo, subset, x):
319 # ``_firstancestors(set)``
319 # ``_firstancestors(set)``
320 # Like ``ancestors(set)`` but follows only the first parents.
320 # Like ``ancestors(set)`` but follows only the first parents.
321 return _ancestors(repo, subset, x, followfirst=True)
321 return _ancestors(repo, subset, x, followfirst=True)
322
322
323 def ancestorspec(repo, subset, x, n):
323 def ancestorspec(repo, subset, x, n):
324 """``set~n``
324 """``set~n``
325 Changesets that are the Nth ancestor (first parents only) of a changeset
325 Changesets that are the Nth ancestor (first parents only) of a changeset
326 in set.
326 in set.
327 """
327 """
328 try:
328 try:
329 n = int(n[1])
329 n = int(n[1])
330 except (TypeError, ValueError):
330 except (TypeError, ValueError):
331 raise error.ParseError(_("~ expects a number"))
331 raise error.ParseError(_("~ expects a number"))
332 ps = set()
332 ps = set()
333 cl = repo.changelog
333 cl = repo.changelog
334 for r in getset(repo, cl, x):
334 for r in getset(repo, cl, x):
335 for i in range(n):
335 for i in range(n):
336 r = cl.parentrevs(r)[0]
336 r = cl.parentrevs(r)[0]
337 ps.add(r)
337 ps.add(r)
338 return [r for r in subset if r in ps]
338 return [r for r in subset if r in ps]
339
339
340 def author(repo, subset, x):
340 def author(repo, subset, x):
341 """``author(string)``
341 """``author(string)``
342 Alias for ``user(string)``.
342 Alias for ``user(string)``.
343 """
343 """
344 # i18n: "author" is a keyword
344 # i18n: "author" is a keyword
345 n = encoding.lower(getstring(x, _("author requires a string")))
345 n = encoding.lower(getstring(x, _("author requires a string")))
346 kind, pattern, matcher = _substringmatcher(n)
346 kind, pattern, matcher = _substringmatcher(n)
347 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
347 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
348
348
349 def bisect(repo, subset, x):
349 def bisect(repo, subset, x):
350 """``bisect(string)``
350 """``bisect(string)``
351 Changesets marked in the specified bisect status:
351 Changesets marked in the specified bisect status:
352
352
353 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
353 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
354 - ``goods``, ``bads`` : csets topologically good/bad
354 - ``goods``, ``bads`` : csets topologically good/bad
355 - ``range`` : csets taking part in the bisection
355 - ``range`` : csets taking part in the bisection
356 - ``pruned`` : csets that are goods, bads or skipped
356 - ``pruned`` : csets that are goods, bads or skipped
357 - ``untested`` : csets whose fate is yet unknown
357 - ``untested`` : csets whose fate is yet unknown
358 - ``ignored`` : csets ignored due to DAG topology
358 - ``ignored`` : csets ignored due to DAG topology
359 - ``current`` : the cset currently being bisected
359 - ``current`` : the cset currently being bisected
360 """
360 """
361 # i18n: "bisect" is a keyword
361 # i18n: "bisect" is a keyword
362 status = getstring(x, _("bisect requires a string")).lower()
362 status = getstring(x, _("bisect requires a string")).lower()
363 state = set(hbisect.get(repo, status))
363 state = set(hbisect.get(repo, status))
364 return [r for r in subset if r in state]
364 return [r for r in subset if r in state]
365
365
366 # Backward-compatibility
366 # Backward-compatibility
367 # - no help entry so that we do not advertise it any more
367 # - no help entry so that we do not advertise it any more
368 def bisected(repo, subset, x):
368 def bisected(repo, subset, x):
369 return bisect(repo, subset, x)
369 return bisect(repo, subset, x)
370
370
371 def bookmark(repo, subset, x):
371 def bookmark(repo, subset, x):
372 """``bookmark([name])``
372 """``bookmark([name])``
373 The named bookmark or all bookmarks.
373 The named bookmark or all bookmarks.
374
374
375 If `name` starts with `re:`, the remainder of the name is treated as
375 If `name` starts with `re:`, the remainder of the name is treated as
376 a regular expression. To match a bookmark that actually starts with `re:`,
376 a regular expression. To match a bookmark that actually starts with `re:`,
377 use the prefix `literal:`.
377 use the prefix `literal:`.
378 """
378 """
379 # i18n: "bookmark" is a keyword
379 # i18n: "bookmark" is a keyword
380 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
380 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
381 if args:
381 if args:
382 bm = getstring(args[0],
382 bm = getstring(args[0],
383 # i18n: "bookmark" is a keyword
383 # i18n: "bookmark" is a keyword
384 _('the argument to bookmark must be a string'))
384 _('the argument to bookmark must be a string'))
385 kind, pattern, matcher = _stringmatcher(bm)
385 kind, pattern, matcher = _stringmatcher(bm)
386 if kind == 'literal':
386 if kind == 'literal':
387 bmrev = repo._bookmarks.get(bm, None)
387 bmrev = repo._bookmarks.get(bm, None)
388 if not bmrev:
388 if not bmrev:
389 raise util.Abort(_("bookmark '%s' does not exist") % bm)
389 raise util.Abort(_("bookmark '%s' does not exist") % bm)
390 bmrev = repo[bmrev].rev()
390 bmrev = repo[bmrev].rev()
391 return [r for r in subset if r == bmrev]
391 return [r for r in subset if r == bmrev]
392 else:
392 else:
393 matchrevs = set()
393 matchrevs = set()
394 for name, bmrev in repo._bookmarks.iteritems():
394 for name, bmrev in repo._bookmarks.iteritems():
395 if matcher(name):
395 if matcher(name):
396 matchrevs.add(bmrev)
396 matchrevs.add(bmrev)
397 if not matchrevs:
397 if not matchrevs:
398 raise util.Abort(_("no bookmarks exist that match '%s'")
398 raise util.Abort(_("no bookmarks exist that match '%s'")
399 % pattern)
399 % pattern)
400 bmrevs = set()
400 bmrevs = set()
401 for bmrev in matchrevs:
401 for bmrev in matchrevs:
402 bmrevs.add(repo[bmrev].rev())
402 bmrevs.add(repo[bmrev].rev())
403 return [r for r in subset if r in bmrevs]
403 return [r for r in subset if r in bmrevs]
404
404
405 bms = set([repo[r].rev()
405 bms = set([repo[r].rev()
406 for r in repo._bookmarks.values()])
406 for r in repo._bookmarks.values()])
407 return [r for r in subset if r in bms]
407 return [r for r in subset if r in bms]
408
408
409 def branch(repo, subset, x):
409 def branch(repo, subset, x):
410 """``branch(string or set)``
410 """``branch(string or set)``
411 All changesets belonging to the given branch or the branches of the given
411 All changesets belonging to the given branch or the branches of the given
412 changesets.
412 changesets.
413
413
414 If `string` starts with `re:`, the remainder of the name is treated as
414 If `string` starts with `re:`, the remainder of the name is treated as
415 a regular expression. To match a branch that actually starts with `re:`,
415 a regular expression. To match a branch that actually starts with `re:`,
416 use the prefix `literal:`.
416 use the prefix `literal:`.
417 """
417 """
418 try:
418 try:
419 b = getstring(x, '')
419 b = getstring(x, '')
420 except error.ParseError:
420 except error.ParseError:
421 # not a string, but another revspec, e.g. tip()
421 # not a string, but another revspec, e.g. tip()
422 pass
422 pass
423 else:
423 else:
424 kind, pattern, matcher = _stringmatcher(b)
424 kind, pattern, matcher = _stringmatcher(b)
425 if kind == 'literal':
425 if kind == 'literal':
426 # note: falls through to the revspec case if no branch with
426 # note: falls through to the revspec case if no branch with
427 # this name exists
427 # this name exists
428 if pattern in repo.branchmap():
428 if pattern in repo.branchmap():
429 return [r for r in subset if matcher(repo[r].branch())]
429 return [r for r in subset if matcher(repo[r].branch())]
430 else:
430 else:
431 return [r for r in subset if matcher(repo[r].branch())]
431 return [r for r in subset if matcher(repo[r].branch())]
432
432
433 s = getset(repo, list(repo), x)
433 s = getset(repo, list(repo), x)
434 b = set()
434 b = set()
435 for r in s:
435 for r in s:
436 b.add(repo[r].branch())
436 b.add(repo[r].branch())
437 s = set(s)
437 s = set(s)
438 return [r for r in subset if r in s or repo[r].branch() in b]
438 return [r for r in subset if r in s or repo[r].branch() in b]
439
439
440 def bumped(repo, subset, x):
440 def bumped(repo, subset, x):
441 """``bumped()``
441 """``bumped()``
442 Mutable changesets marked as successors of public changesets.
442 Mutable changesets marked as successors of public changesets.
443
443
444 Only non-public and non-obsolete changesets can be `bumped`.
444 Only non-public and non-obsolete changesets can be `bumped`.
445 """
445 """
446 # i18n: "bumped" is a keyword
446 # i18n: "bumped" is a keyword
447 getargs(x, 0, 0, _("bumped takes no arguments"))
447 getargs(x, 0, 0, _("bumped takes no arguments"))
448 bumped = obsmod.getrevs(repo, 'bumped')
448 bumped = obsmod.getrevs(repo, 'bumped')
449 return [r for r in subset if r in bumped]
449 return [r for r in subset if r in bumped]
450
450
451 def bundle(repo, subset, x):
451 def bundle(repo, subset, x):
452 """``bundle()``
452 """``bundle()``
453 Changesets in the bundle.
453 Changesets in the bundle.
454
454
455 Bundle must be specified by the -R option."""
455 Bundle must be specified by the -R option."""
456
456
457 try:
457 try:
458 bundlerevs = repo.changelog.bundlerevs
458 bundlerevs = repo.changelog.bundlerevs
459 except AttributeError:
459 except AttributeError:
460 raise util.Abort(_("no bundle provided - specify with -R"))
460 raise util.Abort(_("no bundle provided - specify with -R"))
461 return [r for r in subset if r in bundlerevs]
461 return [r for r in subset if r in bundlerevs]
462
462
463 def checkstatus(repo, subset, pat, field):
463 def checkstatus(repo, subset, pat, field):
464 m = None
464 m = None
465 s = []
465 s = []
466 hasset = matchmod.patkind(pat) == 'set'
466 hasset = matchmod.patkind(pat) == 'set'
467 fname = None
467 fname = None
468 for r in subset:
468 for r in subset:
469 c = repo[r]
469 c = repo[r]
470 if not m or hasset:
470 if not m or hasset:
471 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
471 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
472 if not m.anypats() and len(m.files()) == 1:
472 if not m.anypats() and len(m.files()) == 1:
473 fname = m.files()[0]
473 fname = m.files()[0]
474 if fname is not None:
474 if fname is not None:
475 if fname not in c.files():
475 if fname not in c.files():
476 continue
476 continue
477 else:
477 else:
478 for f in c.files():
478 for f in c.files():
479 if m(f):
479 if m(f):
480 break
480 break
481 else:
481 else:
482 continue
482 continue
483 files = repo.status(c.p1().node(), c.node())[field]
483 files = repo.status(c.p1().node(), c.node())[field]
484 if fname is not None:
484 if fname is not None:
485 if fname in files:
485 if fname in files:
486 s.append(r)
486 s.append(r)
487 else:
487 else:
488 for f in files:
488 for f in files:
489 if m(f):
489 if m(f):
490 s.append(r)
490 s.append(r)
491 break
491 break
492 return s
492 return s
493
493
494 def _children(repo, narrow, parentset):
494 def _children(repo, narrow, parentset):
495 cs = set()
495 cs = set()
496 if not parentset:
496 if not parentset:
497 return cs
497 return cs
498 pr = repo.changelog.parentrevs
498 pr = repo.changelog.parentrevs
499 minrev = min(parentset)
499 minrev = min(parentset)
500 for r in narrow:
500 for r in narrow:
501 if r <= minrev:
501 if r <= minrev:
502 continue
502 continue
503 for p in pr(r):
503 for p in pr(r):
504 if p in parentset:
504 if p in parentset:
505 cs.add(r)
505 cs.add(r)
506 return cs
506 return cs
507
507
508 def children(repo, subset, x):
508 def children(repo, subset, x):
509 """``children(set)``
509 """``children(set)``
510 Child changesets of changesets in set.
510 Child changesets of changesets in set.
511 """
511 """
512 s = set(getset(repo, list(repo), x))
512 s = set(getset(repo, list(repo), x))
513 cs = _children(repo, subset, s)
513 cs = _children(repo, subset, s)
514 return [r for r in subset if r in cs]
514 return [r for r in subset if r in cs]
515
515
516 def closed(repo, subset, x):
516 def closed(repo, subset, x):
517 """``closed()``
517 """``closed()``
518 Changeset is closed.
518 Changeset is closed.
519 """
519 """
520 # i18n: "closed" is a keyword
520 # i18n: "closed" is a keyword
521 getargs(x, 0, 0, _("closed takes no arguments"))
521 getargs(x, 0, 0, _("closed takes no arguments"))
522 return [r for r in subset if repo[r].closesbranch()]
522 return [r for r in subset if repo[r].closesbranch()]
523
523
524 def contains(repo, subset, x):
524 def contains(repo, subset, x):
525 """``contains(pattern)``
525 """``contains(pattern)``
526 Revision contains a file matching pattern. See :hg:`help patterns`
526 Revision contains a file matching pattern. See :hg:`help patterns`
527 for information about file patterns.
527 for information about file patterns.
528 """
528 """
529 # i18n: "contains" is a keyword
529 # i18n: "contains" is a keyword
530 pat = getstring(x, _("contains requires a pattern"))
530 pat = getstring(x, _("contains requires a pattern"))
531 m = None
531 m = None
532 s = []
532 s = []
533 if not matchmod.patkind(pat):
533 if not matchmod.patkind(pat):
534 for r in subset:
534 for r in subset:
535 if pat in repo[r]:
535 if pat in repo[r]:
536 s.append(r)
536 s.append(r)
537 else:
537 else:
538 for r in subset:
538 for r in subset:
539 c = repo[r]
539 c = repo[r]
540 if not m or matchmod.patkind(pat) == 'set':
540 if not m or matchmod.patkind(pat) == 'set':
541 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
541 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
542 for f in c.manifest():
542 for f in c.manifest():
543 if m(f):
543 if m(f):
544 s.append(r)
544 s.append(r)
545 break
545 break
546 return s
546 return s
547
547
548 def converted(repo, subset, x):
548 def converted(repo, subset, x):
549 """``converted([id])``
549 """``converted([id])``
550 Changesets converted from the given identifier in the old repository if
550 Changesets converted from the given identifier in the old repository if
551 present, or all converted changesets if no identifier is specified.
551 present, or all converted changesets if no identifier is specified.
552 """
552 """
553
553
554 # There is exactly no chance of resolving the revision, so do a simple
554 # There is exactly no chance of resolving the revision, so do a simple
555 # string compare and hope for the best
555 # string compare and hope for the best
556
556
557 rev = None
557 rev = None
558 # i18n: "converted" is a keyword
558 # i18n: "converted" is a keyword
559 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
559 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
560 if l:
560 if l:
561 # i18n: "converted" is a keyword
561 # i18n: "converted" is a keyword
562 rev = getstring(l[0], _('converted requires a revision'))
562 rev = getstring(l[0], _('converted requires a revision'))
563
563
564 def _matchvalue(r):
564 def _matchvalue(r):
565 source = repo[r].extra().get('convert_revision', None)
565 source = repo[r].extra().get('convert_revision', None)
566 return source is not None and (rev is None or source.startswith(rev))
566 return source is not None and (rev is None or source.startswith(rev))
567
567
568 return [r for r in subset if _matchvalue(r)]
568 return [r for r in subset if _matchvalue(r)]
569
569
570 def date(repo, subset, x):
570 def date(repo, subset, x):
571 """``date(interval)``
571 """``date(interval)``
572 Changesets within the interval, see :hg:`help dates`.
572 Changesets within the interval, see :hg:`help dates`.
573 """
573 """
574 # i18n: "date" is a keyword
574 # i18n: "date" is a keyword
575 ds = getstring(x, _("date requires a string"))
575 ds = getstring(x, _("date requires a string"))
576 dm = util.matchdate(ds)
576 dm = util.matchdate(ds)
577 return [r for r in subset if dm(repo[r].date()[0])]
577 return [r for r in subset if dm(repo[r].date()[0])]
578
578
579 def desc(repo, subset, x):
579 def desc(repo, subset, x):
580 """``desc(string)``
580 """``desc(string)``
581 Search commit message for string. The match is case-insensitive.
581 Search commit message for string. The match is case-insensitive.
582 """
582 """
583 # i18n: "desc" is a keyword
583 # i18n: "desc" is a keyword
584 ds = encoding.lower(getstring(x, _("desc requires a string")))
584 ds = encoding.lower(getstring(x, _("desc requires a string")))
585 l = []
585 l = []
586 for r in subset:
586 for r in subset:
587 c = repo[r]
587 c = repo[r]
588 if ds in encoding.lower(c.description()):
588 if ds in encoding.lower(c.description()):
589 l.append(r)
589 l.append(r)
590 return l
590 return l
591
591
592 def _descendants(repo, subset, x, followfirst=False):
592 def _descendants(repo, subset, x, followfirst=False):
593 args = getset(repo, list(repo), x)
593 args = getset(repo, list(repo), x)
594 if not args:
594 if not args:
595 return []
595 return []
596 s = set(_revdescendants(repo, args, followfirst)) | set(args)
596 s = set(_revdescendants(repo, args, followfirst)) | set(args)
597 return [r for r in subset if r in s]
597 return [r for r in subset if r in s]
598
598
599 def descendants(repo, subset, x):
599 def descendants(repo, subset, x):
600 """``descendants(set)``
600 """``descendants(set)``
601 Changesets which are descendants of changesets in set.
601 Changesets which are descendants of changesets in set.
602 """
602 """
603 return _descendants(repo, subset, x)
603 return _descendants(repo, subset, x)
604
604
605 def _firstdescendants(repo, subset, x):
605 def _firstdescendants(repo, subset, x):
606 # ``_firstdescendants(set)``
606 # ``_firstdescendants(set)``
607 # Like ``descendants(set)`` but follows only the first parents.
607 # Like ``descendants(set)`` but follows only the first parents.
608 return _descendants(repo, subset, x, followfirst=True)
608 return _descendants(repo, subset, x, followfirst=True)
609
609
610 def destination(repo, subset, x):
610 def destination(repo, subset, x):
611 """``destination([set])``
611 """``destination([set])``
612 Changesets that were created by a graft, transplant or rebase operation,
612 Changesets that were created by a graft, transplant or rebase operation,
613 with the given revisions specified as the source. Omitting the optional set
613 with the given revisions specified as the source. Omitting the optional set
614 is the same as passing all().
614 is the same as passing all().
615 """
615 """
616 if x is not None:
616 if x is not None:
617 args = set(getset(repo, list(repo), x))
617 args = set(getset(repo, list(repo), x))
618 else:
618 else:
619 args = set(getall(repo, list(repo), x))
619 args = set(getall(repo, list(repo), x))
620
620
621 dests = set()
621 dests = set()
622
622
623 # subset contains all of the possible destinations that can be returned, so
623 # subset contains all of the possible destinations that can be returned, so
624 # iterate over them and see if their source(s) were provided in the args.
624 # iterate over them and see if their source(s) were provided in the args.
625 # Even if the immediate src of r is not in the args, src's source (or
625 # Even if the immediate src of r is not in the args, src's source (or
626 # further back) may be. Scanning back further than the immediate src allows
626 # further back) may be. Scanning back further than the immediate src allows
627 # transitive transplants and rebases to yield the same results as transitive
627 # transitive transplants and rebases to yield the same results as transitive
628 # grafts.
628 # grafts.
629 for r in subset:
629 for r in subset:
630 src = _getrevsource(repo, r)
630 src = _getrevsource(repo, r)
631 lineage = None
631 lineage = None
632
632
633 while src is not None:
633 while src is not None:
634 if lineage is None:
634 if lineage is None:
635 lineage = list()
635 lineage = list()
636
636
637 lineage.append(r)
637 lineage.append(r)
638
638
639 # The visited lineage is a match if the current source is in the arg
639 # The visited lineage is a match if the current source is in the arg
640 # set. Since every candidate dest is visited by way of iterating
640 # set. Since every candidate dest is visited by way of iterating
641 # subset, any dests further back in the lineage will be tested by a
641 # subset, any dests further back in the lineage will be tested by a
642 # different iteration over subset. Likewise, if the src was already
642 # different iteration over subset. Likewise, if the src was already
643 # selected, the current lineage can be selected without going back
643 # selected, the current lineage can be selected without going back
644 # further.
644 # further.
645 if src in args or src in dests:
645 if src in args or src in dests:
646 dests.update(lineage)
646 dests.update(lineage)
647 break
647 break
648
648
649 r = src
649 r = src
650 src = _getrevsource(repo, r)
650 src = _getrevsource(repo, r)
651
651
652 return [r for r in subset if r in dests]
652 return [r for r in subset if r in dests]
653
653
654 def divergent(repo, subset, x):
654 def divergent(repo, subset, x):
655 """``divergent()``
655 """``divergent()``
656 Final successors of changesets with an alternative set of final successors.
656 Final successors of changesets with an alternative set of final successors.
657 """
657 """
658 # i18n: "divergent" is a keyword
658 # i18n: "divergent" is a keyword
659 getargs(x, 0, 0, _("divergent takes no arguments"))
659 getargs(x, 0, 0, _("divergent takes no arguments"))
660 divergent = obsmod.getrevs(repo, 'divergent')
660 divergent = obsmod.getrevs(repo, 'divergent')
661 return [r for r in subset if r in divergent]
661 return [r for r in subset if r in divergent]
662
662
663 def draft(repo, subset, x):
663 def draft(repo, subset, x):
664 """``draft()``
664 """``draft()``
665 Changeset in draft phase."""
665 Changeset in draft phase."""
666 # i18n: "draft" is a keyword
666 # i18n: "draft" is a keyword
667 getargs(x, 0, 0, _("draft takes no arguments"))
667 getargs(x, 0, 0, _("draft takes no arguments"))
668 pc = repo._phasecache
668 pc = repo._phasecache
669 return [r for r in subset if pc.phase(repo, r) == phases.draft]
669 return [r for r in subset if pc.phase(repo, r) == phases.draft]
670
670
671 def extinct(repo, subset, x):
671 def extinct(repo, subset, x):
672 """``extinct()``
672 """``extinct()``
673 Obsolete changesets with obsolete descendants only.
673 Obsolete changesets with obsolete descendants only.
674 """
674 """
675 # i18n: "extinct" is a keyword
675 # i18n: "extinct" is a keyword
676 getargs(x, 0, 0, _("extinct takes no arguments"))
676 getargs(x, 0, 0, _("extinct takes no arguments"))
677 extincts = obsmod.getrevs(repo, 'extinct')
677 extincts = obsmod.getrevs(repo, 'extinct')
678 return [r for r in subset if r in extincts]
678 return [r for r in subset if r in extincts]
679
679
680 def extra(repo, subset, x):
680 def extra(repo, subset, x):
681 """``extra(label, [value])``
681 """``extra(label, [value])``
682 Changesets with the given label in the extra metadata, with the given
682 Changesets with the given label in the extra metadata, with the given
683 optional value.
683 optional value.
684
684
685 If `value` starts with `re:`, the remainder of the value is treated as
685 If `value` starts with `re:`, the remainder of the value is treated as
686 a regular expression. To match a value that actually starts with `re:`,
686 a regular expression. To match a value that actually starts with `re:`,
687 use the prefix `literal:`.
687 use the prefix `literal:`.
688 """
688 """
689
689
690 # i18n: "extra" is a keyword
690 # i18n: "extra" is a keyword
691 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
691 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
692 # i18n: "extra" is a keyword
692 # i18n: "extra" is a keyword
693 label = getstring(l[0], _('first argument to extra must be a string'))
693 label = getstring(l[0], _('first argument to extra must be a string'))
694 value = None
694 value = None
695
695
696 if len(l) > 1:
696 if len(l) > 1:
697 # i18n: "extra" is a keyword
697 # i18n: "extra" is a keyword
698 value = getstring(l[1], _('second argument to extra must be a string'))
698 value = getstring(l[1], _('second argument to extra must be a string'))
699 kind, value, matcher = _stringmatcher(value)
699 kind, value, matcher = _stringmatcher(value)
700
700
701 def _matchvalue(r):
701 def _matchvalue(r):
702 extra = repo[r].extra()
702 extra = repo[r].extra()
703 return label in extra and (value is None or matcher(extra[label]))
703 return label in extra and (value is None or matcher(extra[label]))
704
704
705 return [r for r in subset if _matchvalue(r)]
705 return [r for r in subset if _matchvalue(r)]
706
706
707 def filelog(repo, subset, x):
707 def filelog(repo, subset, x):
708 """``filelog(pattern)``
708 """``filelog(pattern)``
709 Changesets connected to the specified filelog.
709 Changesets connected to the specified filelog.
710
710
711 For performance reasons, ``filelog()`` does not show every changeset
711 For performance reasons, ``filelog()`` does not show every changeset
712 that affects the requested file(s). See :hg:`help log` for details. For
712 that affects the requested file(s). See :hg:`help log` for details. For
713 a slower, more accurate result, use ``file()``.
713 a slower, more accurate result, use ``file()``.
714 """
714 """
715
715
716 # i18n: "filelog" is a keyword
716 # i18n: "filelog" is a keyword
717 pat = getstring(x, _("filelog requires a pattern"))
717 pat = getstring(x, _("filelog requires a pattern"))
718 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
718 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
719 ctx=repo[None])
719 ctx=repo[None])
720 s = set()
720 s = set()
721
721
722 if not matchmod.patkind(pat):
722 if not matchmod.patkind(pat):
723 for f in m.files():
723 for f in m.files():
724 fl = repo.file(f)
724 fl = repo.file(f)
725 for fr in fl:
725 for fr in fl:
726 s.add(fl.linkrev(fr))
726 s.add(fl.linkrev(fr))
727 else:
727 else:
728 for f in repo[None]:
728 for f in repo[None]:
729 if m(f):
729 if m(f):
730 fl = repo.file(f)
730 fl = repo.file(f)
731 for fr in fl:
731 for fr in fl:
732 s.add(fl.linkrev(fr))
732 s.add(fl.linkrev(fr))
733
733
734 return [r for r in subset if r in s]
734 return [r for r in subset if r in s]
735
735
736 def first(repo, subset, x):
736 def first(repo, subset, x):
737 """``first(set, [n])``
737 """``first(set, [n])``
738 An alias for limit().
738 An alias for limit().
739 """
739 """
740 return limit(repo, subset, x)
740 return limit(repo, subset, x)
741
741
742 def _follow(repo, subset, x, name, followfirst=False):
742 def _follow(repo, subset, x, name, followfirst=False):
743 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
743 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
744 c = repo['.']
744 c = repo['.']
745 if l:
745 if l:
746 x = getstring(l[0], _("%s expected a filename") % name)
746 x = getstring(l[0], _("%s expected a filename") % name)
747 if x in c:
747 if x in c:
748 cx = c[x]
748 cx = c[x]
749 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
749 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
750 # include the revision responsible for the most recent version
750 # include the revision responsible for the most recent version
751 s.add(cx.linkrev())
751 s.add(cx.linkrev())
752 else:
752 else:
753 return []
753 return []
754 else:
754 else:
755 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
755 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
756
756
757 return [r for r in subset if r in s]
757 return [r for r in subset if r in s]
758
758
759 def follow(repo, subset, x):
759 def follow(repo, subset, x):
760 """``follow([file])``
760 """``follow([file])``
761 An alias for ``::.`` (ancestors of the working copy's first parent).
761 An alias for ``::.`` (ancestors of the working copy's first parent).
762 If a filename is specified, the history of the given file is followed,
762 If a filename is specified, the history of the given file is followed,
763 including copies.
763 including copies.
764 """
764 """
765 return _follow(repo, subset, x, 'follow')
765 return _follow(repo, subset, x, 'follow')
766
766
767 def _followfirst(repo, subset, x):
767 def _followfirst(repo, subset, x):
768 # ``followfirst([file])``
768 # ``followfirst([file])``
769 # Like ``follow([file])`` but follows only the first parent of
769 # Like ``follow([file])`` but follows only the first parent of
770 # every revision or file revision.
770 # every revision or file revision.
771 return _follow(repo, subset, x, '_followfirst', followfirst=True)
771 return _follow(repo, subset, x, '_followfirst', followfirst=True)
772
772
773 def getall(repo, subset, x):
773 def getall(repo, subset, x):
774 """``all()``
774 """``all()``
775 All changesets, the same as ``0:tip``.
775 All changesets, the same as ``0:tip``.
776 """
776 """
777 # i18n: "all" is a keyword
777 # i18n: "all" is a keyword
778 getargs(x, 0, 0, _("all takes no arguments"))
778 getargs(x, 0, 0, _("all takes no arguments"))
779 return subset
779 return subset
780
780
781 def grep(repo, subset, x):
781 def grep(repo, subset, x):
782 """``grep(regex)``
782 """``grep(regex)``
783 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
783 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
784 to ensure special escape characters are handled correctly. Unlike
784 to ensure special escape characters are handled correctly. Unlike
785 ``keyword(string)``, the match is case-sensitive.
785 ``keyword(string)``, the match is case-sensitive.
786 """
786 """
787 try:
787 try:
788 # i18n: "grep" is a keyword
788 # i18n: "grep" is a keyword
789 gr = re.compile(getstring(x, _("grep requires a string")))
789 gr = re.compile(getstring(x, _("grep requires a string")))
790 except re.error, e:
790 except re.error, e:
791 raise error.ParseError(_('invalid match pattern: %s') % e)
791 raise error.ParseError(_('invalid match pattern: %s') % e)
792 l = []
792 l = []
793 for r in subset:
793 for r in subset:
794 c = repo[r]
794 c = repo[r]
795 for e in c.files() + [c.user(), c.description()]:
795 for e in c.files() + [c.user(), c.description()]:
796 if gr.search(e):
796 if gr.search(e):
797 l.append(r)
797 l.append(r)
798 break
798 break
799 return l
799 return l
800
800
801 def _matchfiles(repo, subset, x):
801 def _matchfiles(repo, subset, x):
802 # _matchfiles takes a revset list of prefixed arguments:
802 # _matchfiles takes a revset list of prefixed arguments:
803 #
803 #
804 # [p:foo, i:bar, x:baz]
804 # [p:foo, i:bar, x:baz]
805 #
805 #
806 # builds a match object from them and filters subset. Allowed
806 # builds a match object from them and filters subset. Allowed
807 # prefixes are 'p:' for regular patterns, 'i:' for include
807 # prefixes are 'p:' for regular patterns, 'i:' for include
808 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
808 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
809 # a revision identifier, or the empty string to reference the
809 # a revision identifier, or the empty string to reference the
810 # working directory, from which the match object is
810 # working directory, from which the match object is
811 # initialized. Use 'd:' to set the default matching mode, default
811 # initialized. Use 'd:' to set the default matching mode, default
812 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
812 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
813
813
814 # i18n: "_matchfiles" is a keyword
814 # i18n: "_matchfiles" is a keyword
815 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
815 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
816 pats, inc, exc = [], [], []
816 pats, inc, exc = [], [], []
817 hasset = False
817 hasset = False
818 rev, default = None, None
818 rev, default = None, None
819 for arg in l:
819 for arg in l:
820 # i18n: "_matchfiles" is a keyword
820 # i18n: "_matchfiles" is a keyword
821 s = getstring(arg, _("_matchfiles requires string arguments"))
821 s = getstring(arg, _("_matchfiles requires string arguments"))
822 prefix, value = s[:2], s[2:]
822 prefix, value = s[:2], s[2:]
823 if prefix == 'p:':
823 if prefix == 'p:':
824 pats.append(value)
824 pats.append(value)
825 elif prefix == 'i:':
825 elif prefix == 'i:':
826 inc.append(value)
826 inc.append(value)
827 elif prefix == 'x:':
827 elif prefix == 'x:':
828 exc.append(value)
828 exc.append(value)
829 elif prefix == 'r:':
829 elif prefix == 'r:':
830 if rev is not None:
830 if rev is not None:
831 # i18n: "_matchfiles" is a keyword
831 # i18n: "_matchfiles" is a keyword
832 raise error.ParseError(_('_matchfiles expected at most one '
832 raise error.ParseError(_('_matchfiles expected at most one '
833 'revision'))
833 'revision'))
834 rev = value
834 rev = value
835 elif prefix == 'd:':
835 elif prefix == 'd:':
836 if default is not None:
836 if default is not None:
837 # i18n: "_matchfiles" is a keyword
837 # i18n: "_matchfiles" is a keyword
838 raise error.ParseError(_('_matchfiles expected at most one '
838 raise error.ParseError(_('_matchfiles expected at most one '
839 'default mode'))
839 'default mode'))
840 default = value
840 default = value
841 else:
841 else:
842 # i18n: "_matchfiles" is a keyword
842 # i18n: "_matchfiles" is a keyword
843 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
843 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
844 if not hasset and matchmod.patkind(value) == 'set':
844 if not hasset and matchmod.patkind(value) == 'set':
845 hasset = True
845 hasset = True
846 if not default:
846 if not default:
847 default = 'glob'
847 default = 'glob'
848 m = None
848 m = None
849 s = []
849 s = []
850 for r in subset:
850 for r in subset:
851 c = repo[r]
851 c = repo[r]
852 if not m or (hasset and rev is None):
852 if not m or (hasset and rev is None):
853 ctx = c
853 ctx = c
854 if rev is not None:
854 if rev is not None:
855 ctx = repo[rev or None]
855 ctx = repo[rev or None]
856 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
856 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
857 exclude=exc, ctx=ctx, default=default)
857 exclude=exc, ctx=ctx, default=default)
858 for f in c.files():
858 for f in c.files():
859 if m(f):
859 if m(f):
860 s.append(r)
860 s.append(r)
861 break
861 break
862 return s
862 return s
863
863
864 def hasfile(repo, subset, x):
864 def hasfile(repo, subset, x):
865 """``file(pattern)``
865 """``file(pattern)``
866 Changesets affecting files matched by pattern.
866 Changesets affecting files matched by pattern.
867
867
868 For a faster but less accurate result, consider using ``filelog()``
868 For a faster but less accurate result, consider using ``filelog()``
869 instead.
869 instead.
870 """
870 """
871 # i18n: "file" is a keyword
871 # i18n: "file" is a keyword
872 pat = getstring(x, _("file requires a pattern"))
872 pat = getstring(x, _("file requires a pattern"))
873 return _matchfiles(repo, subset, ('string', 'p:' + pat))
873 return _matchfiles(repo, subset, ('string', 'p:' + pat))
874
874
875 def head(repo, subset, x):
875 def head(repo, subset, x):
876 """``head()``
876 """``head()``
877 Changeset is a named branch head.
877 Changeset is a named branch head.
878 """
878 """
879 # i18n: "head" is a keyword
879 # i18n: "head" is a keyword
880 getargs(x, 0, 0, _("head takes no arguments"))
880 getargs(x, 0, 0, _("head takes no arguments"))
881 hs = set()
881 hs = set()
882 for b, ls in repo.branchmap().iteritems():
882 for b, ls in repo.branchmap().iteritems():
883 hs.update(repo[h].rev() for h in ls)
883 hs.update(repo[h].rev() for h in ls)
884 return [r for r in subset if r in hs]
884 return [r for r in subset if r in hs]
885
885
886 def heads(repo, subset, x):
886 def heads(repo, subset, x):
887 """``heads(set)``
887 """``heads(set)``
888 Members of set with no children in set.
888 Members of set with no children in set.
889 """
889 """
890 s = getset(repo, subset, x)
890 s = getset(repo, subset, x)
891 ps = set(parents(repo, subset, x))
891 ps = set(parents(repo, subset, x))
892 return [r for r in s if r not in ps]
892 return [r for r in s if r not in ps]
893
893
894 def hidden(repo, subset, x):
894 def hidden(repo, subset, x):
895 """``hidden()``
895 """``hidden()``
896 Hidden changesets.
896 Hidden changesets.
897 """
897 """
898 # i18n: "hidden" is a keyword
898 # i18n: "hidden" is a keyword
899 getargs(x, 0, 0, _("hidden takes no arguments"))
899 getargs(x, 0, 0, _("hidden takes no arguments"))
900 hiddenrevs = repoview.filterrevs(repo, 'visible')
900 hiddenrevs = repoview.filterrevs(repo, 'visible')
901 return [r for r in subset if r in hiddenrevs]
901 return [r for r in subset if r in hiddenrevs]
902
902
903 def keyword(repo, subset, x):
903 def keyword(repo, subset, x):
904 """``keyword(string)``
904 """``keyword(string)``
905 Search commit message, user name, and names of changed files for
905 Search commit message, user name, and names of changed files for
906 string. The match is case-insensitive.
906 string. The match is case-insensitive.
907 """
907 """
908 # i18n: "keyword" is a keyword
908 # i18n: "keyword" is a keyword
909 kw = encoding.lower(getstring(x, _("keyword requires a string")))
909 kw = encoding.lower(getstring(x, _("keyword requires a string")))
910 l = []
910 l = []
911 for r in subset:
911 for r in subset:
912 c = repo[r]
912 c = repo[r]
913 if util.any(kw in encoding.lower(t)
913 if util.any(kw in encoding.lower(t)
914 for t in c.files() + [c.user(), c.description()]):
914 for t in c.files() + [c.user(), c.description()]):
915 l.append(r)
915 l.append(r)
916 return l
916 return l
917
917
918 def limit(repo, subset, x):
918 def limit(repo, subset, x):
919 """``limit(set, [n])``
919 """``limit(set, [n])``
920 First n members of set, defaulting to 1.
920 First n members of set, defaulting to 1.
921 """
921 """
922 # i18n: "limit" is a keyword
922 # i18n: "limit" is a keyword
923 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
923 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
924 try:
924 try:
925 lim = 1
925 lim = 1
926 if len(l) == 2:
926 if len(l) == 2:
927 # i18n: "limit" is a keyword
927 # i18n: "limit" is a keyword
928 lim = int(getstring(l[1], _("limit requires a number")))
928 lim = int(getstring(l[1], _("limit requires a number")))
929 except (TypeError, ValueError):
929 except (TypeError, ValueError):
930 # i18n: "limit" is a keyword
930 # i18n: "limit" is a keyword
931 raise error.ParseError(_("limit expects a number"))
931 raise error.ParseError(_("limit expects a number"))
932 ss = set(subset)
932 ss = set(subset)
933 os = getset(repo, list(repo), l[0])[:lim]
933 os = getset(repo, list(repo), l[0])[:lim]
934 return [r for r in os if r in ss]
934 return [r for r in os if r in ss]
935
935
936 def last(repo, subset, x):
936 def last(repo, subset, x):
937 """``last(set, [n])``
937 """``last(set, [n])``
938 Last n members of set, defaulting to 1.
938 Last n members of set, defaulting to 1.
939 """
939 """
940 # i18n: "last" is a keyword
940 # i18n: "last" is a keyword
941 l = getargs(x, 1, 2, _("last requires one or two arguments"))
941 l = getargs(x, 1, 2, _("last requires one or two arguments"))
942 try:
942 try:
943 lim = 1
943 lim = 1
944 if len(l) == 2:
944 if len(l) == 2:
945 # i18n: "last" is a keyword
945 # i18n: "last" is a keyword
946 lim = int(getstring(l[1], _("last requires a number")))
946 lim = int(getstring(l[1], _("last requires a number")))
947 except (TypeError, ValueError):
947 except (TypeError, ValueError):
948 # i18n: "last" is a keyword
948 # i18n: "last" is a keyword
949 raise error.ParseError(_("last expects a number"))
949 raise error.ParseError(_("last expects a number"))
950 ss = set(subset)
950 ss = set(subset)
951 os = getset(repo, list(repo), l[0])[-lim:]
951 os = getset(repo, list(repo), l[0])[-lim:]
952 return [r for r in os if r in ss]
952 return [r for r in os if r in ss]
953
953
954 def maxrev(repo, subset, x):
954 def maxrev(repo, subset, x):
955 """``max(set)``
955 """``max(set)``
956 Changeset with highest revision number in set.
956 Changeset with highest revision number in set.
957 """
957 """
958 os = getset(repo, list(repo), x)
958 os = getset(repo, list(repo), x)
959 if os:
959 if os:
960 m = max(os)
960 m = max(os)
961 if m in subset:
961 if m in subset:
962 return [m]
962 return [m]
963 return []
963 return []
964
964
965 def merge(repo, subset, x):
965 def merge(repo, subset, x):
966 """``merge()``
966 """``merge()``
967 Changeset is a merge changeset.
967 Changeset is a merge changeset.
968 """
968 """
969 # i18n: "merge" is a keyword
969 # i18n: "merge" is a keyword
970 getargs(x, 0, 0, _("merge takes no arguments"))
970 getargs(x, 0, 0, _("merge takes no arguments"))
971 cl = repo.changelog
971 cl = repo.changelog
972 return [r for r in subset if cl.parentrevs(r)[1] != -1]
972 return [r for r in subset if cl.parentrevs(r)[1] != -1]
973
973
974 def branchpoint(repo, subset, x):
974 def branchpoint(repo, subset, x):
975 """``branchpoint()``
975 """``branchpoint()``
976 Changesets with more than one child.
976 Changesets with more than one child.
977 """
977 """
978 # i18n: "branchpoint" is a keyword
978 # i18n: "branchpoint" is a keyword
979 getargs(x, 0, 0, _("branchpoint takes no arguments"))
979 getargs(x, 0, 0, _("branchpoint takes no arguments"))
980 cl = repo.changelog
980 cl = repo.changelog
981 if not subset:
981 if not subset:
982 return []
982 return []
983 baserev = min(subset)
983 baserev = min(subset)
984 parentscount = [0]*(len(repo) - baserev)
984 parentscount = [0]*(len(repo) - baserev)
985 for r in cl.revs(start=baserev + 1):
985 for r in cl.revs(start=baserev + 1):
986 for p in cl.parentrevs(r):
986 for p in cl.parentrevs(r):
987 if p >= baserev:
987 if p >= baserev:
988 parentscount[p - baserev] += 1
988 parentscount[p - baserev] += 1
989 return [r for r in subset if (parentscount[r - baserev] > 1)]
989 return [r for r in subset if (parentscount[r - baserev] > 1)]
990
990
991 def minrev(repo, subset, x):
991 def minrev(repo, subset, x):
992 """``min(set)``
992 """``min(set)``
993 Changeset with lowest revision number in set.
993 Changeset with lowest revision number in set.
994 """
994 """
995 os = getset(repo, list(repo), x)
995 os = getset(repo, list(repo), x)
996 if os:
996 if os:
997 m = min(os)
997 m = min(os)
998 if m in subset:
998 if m in subset:
999 return [m]
999 return [m]
1000 return []
1000 return []
1001
1001
1002 def modifies(repo, subset, x):
1002 def modifies(repo, subset, x):
1003 """``modifies(pattern)``
1003 """``modifies(pattern)``
1004 Changesets modifying files matched by pattern.
1004 Changesets modifying files matched by pattern.
1005 """
1005 """
1006 # i18n: "modifies" is a keyword
1006 # i18n: "modifies" is a keyword
1007 pat = getstring(x, _("modifies requires a pattern"))
1007 pat = getstring(x, _("modifies requires a pattern"))
1008 return checkstatus(repo, subset, pat, 0)
1008 return checkstatus(repo, subset, pat, 0)
1009
1009
1010 def node_(repo, subset, x):
1010 def node_(repo, subset, x):
1011 """``id(string)``
1011 """``id(string)``
1012 Revision non-ambiguously specified by the given hex string prefix.
1012 Revision non-ambiguously specified by the given hex string prefix.
1013 """
1013 """
1014 # i18n: "id" is a keyword
1014 # i18n: "id" is a keyword
1015 l = getargs(x, 1, 1, _("id requires one argument"))
1015 l = getargs(x, 1, 1, _("id requires one argument"))
1016 # i18n: "id" is a keyword
1016 # i18n: "id" is a keyword
1017 n = getstring(l[0], _("id requires a string"))
1017 n = getstring(l[0], _("id requires a string"))
1018 if len(n) == 40:
1018 if len(n) == 40:
1019 rn = repo[n].rev()
1019 rn = repo[n].rev()
1020 else:
1020 else:
1021 rn = None
1021 rn = None
1022 pm = repo.changelog._partialmatch(n)
1022 pm = repo.changelog._partialmatch(n)
1023 if pm is not None:
1023 if pm is not None:
1024 rn = repo.changelog.rev(pm)
1024 rn = repo.changelog.rev(pm)
1025
1025
1026 return [r for r in subset if r == rn]
1026 return [r for r in subset if r == rn]
1027
1027
1028 def obsolete(repo, subset, x):
1028 def obsolete(repo, subset, x):
1029 """``obsolete()``
1029 """``obsolete()``
1030 Mutable changeset with a newer version."""
1030 Mutable changeset with a newer version."""
1031 # i18n: "obsolete" is a keyword
1031 # i18n: "obsolete" is a keyword
1032 getargs(x, 0, 0, _("obsolete takes no arguments"))
1032 getargs(x, 0, 0, _("obsolete takes no arguments"))
1033 obsoletes = obsmod.getrevs(repo, 'obsolete')
1033 obsoletes = obsmod.getrevs(repo, 'obsolete')
1034 return [r for r in subset if r in obsoletes]
1034 return [r for r in subset if r in obsoletes]
1035
1035
1036 def origin(repo, subset, x):
1036 def origin(repo, subset, x):
1037 """``origin([set])``
1037 """``origin([set])``
1038 Changesets that were specified as a source for the grafts, transplants or
1038 Changesets that were specified as a source for the grafts, transplants or
1039 rebases that created the given revisions. Omitting the optional set is the
1039 rebases that created the given revisions. Omitting the optional set is the
1040 same as passing all(). If a changeset created by these operations is itself
1040 same as passing all(). If a changeset created by these operations is itself
1041 specified as a source for one of these operations, only the source changeset
1041 specified as a source for one of these operations, only the source changeset
1042 for the first operation is selected.
1042 for the first operation is selected.
1043 """
1043 """
1044 if x is not None:
1044 if x is not None:
1045 args = set(getset(repo, list(repo), x))
1045 args = set(getset(repo, list(repo), x))
1046 else:
1046 else:
1047 args = set(getall(repo, list(repo), x))
1047 args = set(getall(repo, list(repo), x))
1048
1048
1049 def _firstsrc(rev):
1049 def _firstsrc(rev):
1050 src = _getrevsource(repo, rev)
1050 src = _getrevsource(repo, rev)
1051 if src is None:
1051 if src is None:
1052 return None
1052 return None
1053
1053
1054 while True:
1054 while True:
1055 prev = _getrevsource(repo, src)
1055 prev = _getrevsource(repo, src)
1056
1056
1057 if prev is None:
1057 if prev is None:
1058 return src
1058 return src
1059 src = prev
1059 src = prev
1060
1060
1061 o = set([_firstsrc(r) for r in args])
1061 o = set([_firstsrc(r) for r in args])
1062 return [r for r in subset if r in o]
1062 return [r for r in subset if r in o]
1063
1063
1064 def outgoing(repo, subset, x):
1064 def outgoing(repo, subset, x):
1065 """``outgoing([path])``
1065 """``outgoing([path])``
1066 Changesets not found in the specified destination repository, or the
1066 Changesets not found in the specified destination repository, or the
1067 default push location.
1067 default push location.
1068 """
1068 """
1069 import hg # avoid start-up nasties
1069 import hg # avoid start-up nasties
1070 # i18n: "outgoing" is a keyword
1070 # i18n: "outgoing" is a keyword
1071 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1071 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1072 # i18n: "outgoing" is a keyword
1072 # i18n: "outgoing" is a keyword
1073 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1073 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1074 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1074 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1075 dest, branches = hg.parseurl(dest)
1075 dest, branches = hg.parseurl(dest)
1076 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1076 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1077 if revs:
1077 if revs:
1078 revs = [repo.lookup(rev) for rev in revs]
1078 revs = [repo.lookup(rev) for rev in revs]
1079 other = hg.peer(repo, {}, dest)
1079 other = hg.peer(repo, {}, dest)
1080 repo.ui.pushbuffer()
1080 repo.ui.pushbuffer()
1081 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1081 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1082 repo.ui.popbuffer()
1082 repo.ui.popbuffer()
1083 cl = repo.changelog
1083 cl = repo.changelog
1084 o = set([cl.rev(r) for r in outgoing.missing])
1084 o = set([cl.rev(r) for r in outgoing.missing])
1085 return [r for r in subset if r in o]
1085 return [r for r in subset if r in o]
1086
1086
1087 def p1(repo, subset, x):
1087 def p1(repo, subset, x):
1088 """``p1([set])``
1088 """``p1([set])``
1089 First parent of changesets in set, or the working directory.
1089 First parent of changesets in set, or the working directory.
1090 """
1090 """
1091 if x is None:
1091 if x is None:
1092 p = repo[x].p1().rev()
1092 p = repo[x].p1().rev()
1093 return [r for r in subset if r == p]
1093 return [r for r in subset if r == p]
1094
1094
1095 ps = set()
1095 ps = set()
1096 cl = repo.changelog
1096 cl = repo.changelog
1097 for r in getset(repo, list(repo), x):
1097 for r in getset(repo, list(repo), x):
1098 ps.add(cl.parentrevs(r)[0])
1098 ps.add(cl.parentrevs(r)[0])
1099 return [r for r in subset if r in ps]
1099 return [r for r in subset if r in ps]
1100
1100
1101 def p2(repo, subset, x):
1101 def p2(repo, subset, x):
1102 """``p2([set])``
1102 """``p2([set])``
1103 Second parent of changesets in set, or the working directory.
1103 Second parent of changesets in set, or the working directory.
1104 """
1104 """
1105 if x is None:
1105 if x is None:
1106 ps = repo[x].parents()
1106 ps = repo[x].parents()
1107 try:
1107 try:
1108 p = ps[1].rev()
1108 p = ps[1].rev()
1109 return [r for r in subset if r == p]
1109 return [r for r in subset if r == p]
1110 except IndexError:
1110 except IndexError:
1111 return []
1111 return []
1112
1112
1113 ps = set()
1113 ps = set()
1114 cl = repo.changelog
1114 cl = repo.changelog
1115 for r in getset(repo, list(repo), x):
1115 for r in getset(repo, list(repo), x):
1116 ps.add(cl.parentrevs(r)[1])
1116 ps.add(cl.parentrevs(r)[1])
1117 return [r for r in subset if r in ps]
1117 return [r for r in subset if r in ps]
1118
1118
1119 def parents(repo, subset, x):
1119 def parents(repo, subset, x):
1120 """``parents([set])``
1120 """``parents([set])``
1121 The set of all parents for all changesets in set, or the working directory.
1121 The set of all parents for all changesets in set, or the working directory.
1122 """
1122 """
1123 if x is None:
1123 if x is None:
1124 ps = tuple(p.rev() for p in repo[x].parents())
1124 ps = tuple(p.rev() for p in repo[x].parents())
1125 return [r for r in subset if r in ps]
1125 return [r for r in subset if r in ps]
1126
1126
1127 ps = set()
1127 ps = set()
1128 cl = repo.changelog
1128 cl = repo.changelog
1129 for r in getset(repo, list(repo), x):
1129 for r in getset(repo, list(repo), x):
1130 ps.update(cl.parentrevs(r))
1130 ps.update(cl.parentrevs(r))
1131 return [r for r in subset if r in ps]
1131 return [r for r in subset if r in ps]
1132
1132
1133 def parentspec(repo, subset, x, n):
1133 def parentspec(repo, subset, x, n):
1134 """``set^0``
1134 """``set^0``
1135 The set.
1135 The set.
1136 ``set^1`` (or ``set^``), ``set^2``
1136 ``set^1`` (or ``set^``), ``set^2``
1137 First or second parent, respectively, of all changesets in set.
1137 First or second parent, respectively, of all changesets in set.
1138 """
1138 """
1139 try:
1139 try:
1140 n = int(n[1])
1140 n = int(n[1])
1141 if n not in (0, 1, 2):
1141 if n not in (0, 1, 2):
1142 raise ValueError
1142 raise ValueError
1143 except (TypeError, ValueError):
1143 except (TypeError, ValueError):
1144 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1144 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1145 ps = set()
1145 ps = set()
1146 cl = repo.changelog
1146 cl = repo.changelog
1147 for r in getset(repo, cl, x):
1147 for r in getset(repo, cl, x):
1148 if n == 0:
1148 if n == 0:
1149 ps.add(r)
1149 ps.add(r)
1150 elif n == 1:
1150 elif n == 1:
1151 ps.add(cl.parentrevs(r)[0])
1151 ps.add(cl.parentrevs(r)[0])
1152 elif n == 2:
1152 elif n == 2:
1153 parents = cl.parentrevs(r)
1153 parents = cl.parentrevs(r)
1154 if len(parents) > 1:
1154 if len(parents) > 1:
1155 ps.add(parents[1])
1155 ps.add(parents[1])
1156 return [r for r in subset if r in ps]
1156 return [r for r in subset if r in ps]
1157
1157
1158 def present(repo, subset, x):
1158 def present(repo, subset, x):
1159 """``present(set)``
1159 """``present(set)``
1160 An empty set, if any revision in set isn't found; otherwise,
1160 An empty set, if any revision in set isn't found; otherwise,
1161 all revisions in set.
1161 all revisions in set.
1162
1162
1163 If any of specified revisions is not present in the local repository,
1163 If any of specified revisions is not present in the local repository,
1164 the query is normally aborted. But this predicate allows the query
1164 the query is normally aborted. But this predicate allows the query
1165 to continue even in such cases.
1165 to continue even in such cases.
1166 """
1166 """
1167 try:
1167 try:
1168 return getset(repo, subset, x)
1168 return getset(repo, subset, x)
1169 except error.RepoLookupError:
1169 except error.RepoLookupError:
1170 return []
1170 return []
1171
1171
1172 def public(repo, subset, x):
1172 def public(repo, subset, x):
1173 """``public()``
1173 """``public()``
1174 Changeset in public phase."""
1174 Changeset in public phase."""
1175 # i18n: "public" is a keyword
1175 # i18n: "public" is a keyword
1176 getargs(x, 0, 0, _("public takes no arguments"))
1176 getargs(x, 0, 0, _("public takes no arguments"))
1177 pc = repo._phasecache
1177 pc = repo._phasecache
1178 return [r for r in subset if pc.phase(repo, r) == phases.public]
1178 return [r for r in subset if pc.phase(repo, r) == phases.public]
1179
1179
1180 def remote(repo, subset, x):
1180 def remote(repo, subset, x):
1181 """``remote([id [,path]])``
1181 """``remote([id [,path]])``
1182 Local revision that corresponds to the given identifier in a
1182 Local revision that corresponds to the given identifier in a
1183 remote repository, if present. Here, the '.' identifier is a
1183 remote repository, if present. Here, the '.' identifier is a
1184 synonym for the current local branch.
1184 synonym for the current local branch.
1185 """
1185 """
1186
1186
1187 import hg # avoid start-up nasties
1187 import hg # avoid start-up nasties
1188 # i18n: "remote" is a keyword
1188 # i18n: "remote" is a keyword
1189 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1189 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1190
1190
1191 q = '.'
1191 q = '.'
1192 if len(l) > 0:
1192 if len(l) > 0:
1193 # i18n: "remote" is a keyword
1193 # i18n: "remote" is a keyword
1194 q = getstring(l[0], _("remote requires a string id"))
1194 q = getstring(l[0], _("remote requires a string id"))
1195 if q == '.':
1195 if q == '.':
1196 q = repo['.'].branch()
1196 q = repo['.'].branch()
1197
1197
1198 dest = ''
1198 dest = ''
1199 if len(l) > 1:
1199 if len(l) > 1:
1200 # i18n: "remote" is a keyword
1200 # i18n: "remote" is a keyword
1201 dest = getstring(l[1], _("remote requires a repository path"))
1201 dest = getstring(l[1], _("remote requires a repository path"))
1202 dest = repo.ui.expandpath(dest or 'default')
1202 dest = repo.ui.expandpath(dest or 'default')
1203 dest, branches = hg.parseurl(dest)
1203 dest, branches = hg.parseurl(dest)
1204 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1204 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1205 if revs:
1205 if revs:
1206 revs = [repo.lookup(rev) for rev in revs]
1206 revs = [repo.lookup(rev) for rev in revs]
1207 other = hg.peer(repo, {}, dest)
1207 other = hg.peer(repo, {}, dest)
1208 n = other.lookup(q)
1208 n = other.lookup(q)
1209 if n in repo:
1209 if n in repo:
1210 r = repo[n].rev()
1210 r = repo[n].rev()
1211 if r in subset:
1211 if r in subset:
1212 return [r]
1212 return [r]
1213 return []
1213 return []
1214
1214
1215 def removes(repo, subset, x):
1215 def removes(repo, subset, x):
1216 """``removes(pattern)``
1216 """``removes(pattern)``
1217 Changesets which remove files matching pattern.
1217 Changesets which remove files matching pattern.
1218 """
1218 """
1219 # i18n: "removes" is a keyword
1219 # i18n: "removes" is a keyword
1220 pat = getstring(x, _("removes requires a pattern"))
1220 pat = getstring(x, _("removes requires a pattern"))
1221 return checkstatus(repo, subset, pat, 2)
1221 return checkstatus(repo, subset, pat, 2)
1222
1222
1223 def rev(repo, subset, x):
1223 def rev(repo, subset, x):
1224 """``rev(number)``
1224 """``rev(number)``
1225 Revision with the given numeric identifier.
1225 Revision with the given numeric identifier.
1226 """
1226 """
1227 # i18n: "rev" is a keyword
1227 # i18n: "rev" is a keyword
1228 l = getargs(x, 1, 1, _("rev requires one argument"))
1228 l = getargs(x, 1, 1, _("rev requires one argument"))
1229 try:
1229 try:
1230 # i18n: "rev" is a keyword
1230 # i18n: "rev" is a keyword
1231 l = int(getstring(l[0], _("rev requires a number")))
1231 l = int(getstring(l[0], _("rev requires a number")))
1232 except (TypeError, ValueError):
1232 except (TypeError, ValueError):
1233 # i18n: "rev" is a keyword
1233 # i18n: "rev" is a keyword
1234 raise error.ParseError(_("rev expects a number"))
1234 raise error.ParseError(_("rev expects a number"))
1235 return [r for r in subset if r == l]
1235 return [r for r in subset if r == l]
1236
1236
1237 def matching(repo, subset, x):
1237 def matching(repo, subset, x):
1238 """``matching(revision [, field])``
1238 """``matching(revision [, field])``
1239 Changesets in which a given set of fields match the set of fields in the
1239 Changesets in which a given set of fields match the set of fields in the
1240 selected revision or set.
1240 selected revision or set.
1241
1241
1242 To match more than one field pass the list of fields to match separated
1242 To match more than one field pass the list of fields to match separated
1243 by spaces (e.g. ``author description``).
1243 by spaces (e.g. ``author description``).
1244
1244
1245 Valid fields are most regular revision fields and some special fields.
1245 Valid fields are most regular revision fields and some special fields.
1246
1246
1247 Regular revision fields are ``description``, ``author``, ``branch``,
1247 Regular revision fields are ``description``, ``author``, ``branch``,
1248 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1248 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1249 and ``diff``.
1249 and ``diff``.
1250 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1250 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1251 contents of the revision. Two revisions matching their ``diff`` will
1251 contents of the revision. Two revisions matching their ``diff`` will
1252 also match their ``files``.
1252 also match their ``files``.
1253
1253
1254 Special fields are ``summary`` and ``metadata``:
1254 Special fields are ``summary`` and ``metadata``:
1255 ``summary`` matches the first line of the description.
1255 ``summary`` matches the first line of the description.
1256 ``metadata`` is equivalent to matching ``description user date``
1256 ``metadata`` is equivalent to matching ``description user date``
1257 (i.e. it matches the main metadata fields).
1257 (i.e. it matches the main metadata fields).
1258
1258
1259 ``metadata`` is the default field which is used when no fields are
1259 ``metadata`` is the default field which is used when no fields are
1260 specified. You can match more than one field at a time.
1260 specified. You can match more than one field at a time.
1261 """
1261 """
1262 # i18n: "matching" is a keyword
1262 # i18n: "matching" is a keyword
1263 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1263 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1264
1264
1265 revs = getset(repo, repo.changelog, l[0])
1265 revs = getset(repo, repo.changelog, l[0])
1266
1266
1267 fieldlist = ['metadata']
1267 fieldlist = ['metadata']
1268 if len(l) > 1:
1268 if len(l) > 1:
1269 fieldlist = getstring(l[1],
1269 fieldlist = getstring(l[1],
1270 # i18n: "matching" is a keyword
1270 # i18n: "matching" is a keyword
1271 _("matching requires a string "
1271 _("matching requires a string "
1272 "as its second argument")).split()
1272 "as its second argument")).split()
1273
1273
1274 # Make sure that there are no repeated fields,
1274 # Make sure that there are no repeated fields,
1275 # expand the 'special' 'metadata' field type
1275 # expand the 'special' 'metadata' field type
1276 # and check the 'files' whenever we check the 'diff'
1276 # and check the 'files' whenever we check the 'diff'
1277 fields = []
1277 fields = []
1278 for field in fieldlist:
1278 for field in fieldlist:
1279 if field == 'metadata':
1279 if field == 'metadata':
1280 fields += ['user', 'description', 'date']
1280 fields += ['user', 'description', 'date']
1281 elif field == 'diff':
1281 elif field == 'diff':
1282 # a revision matching the diff must also match the files
1282 # a revision matching the diff must also match the files
1283 # since matching the diff is very costly, make sure to
1283 # since matching the diff is very costly, make sure to
1284 # also match the files first
1284 # also match the files first
1285 fields += ['files', 'diff']
1285 fields += ['files', 'diff']
1286 else:
1286 else:
1287 if field == 'author':
1287 if field == 'author':
1288 field = 'user'
1288 field = 'user'
1289 fields.append(field)
1289 fields.append(field)
1290 fields = set(fields)
1290 fields = set(fields)
1291 if 'summary' in fields and 'description' in fields:
1291 if 'summary' in fields and 'description' in fields:
1292 # If a revision matches its description it also matches its summary
1292 # If a revision matches its description it also matches its summary
1293 fields.discard('summary')
1293 fields.discard('summary')
1294
1294
1295 # We may want to match more than one field
1295 # We may want to match more than one field
1296 # Not all fields take the same amount of time to be matched
1296 # Not all fields take the same amount of time to be matched
1297 # Sort the selected fields in order of increasing matching cost
1297 # Sort the selected fields in order of increasing matching cost
1298 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1298 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1299 'files', 'description', 'substate', 'diff']
1299 'files', 'description', 'substate', 'diff']
1300 def fieldkeyfunc(f):
1300 def fieldkeyfunc(f):
1301 try:
1301 try:
1302 return fieldorder.index(f)
1302 return fieldorder.index(f)
1303 except ValueError:
1303 except ValueError:
1304 # assume an unknown field is very costly
1304 # assume an unknown field is very costly
1305 return len(fieldorder)
1305 return len(fieldorder)
1306 fields = list(fields)
1306 fields = list(fields)
1307 fields.sort(key=fieldkeyfunc)
1307 fields.sort(key=fieldkeyfunc)
1308
1308
1309 # Each field will be matched with its own "getfield" function
1309 # Each field will be matched with its own "getfield" function
1310 # which will be added to the getfieldfuncs array of functions
1310 # which will be added to the getfieldfuncs array of functions
1311 getfieldfuncs = []
1311 getfieldfuncs = []
1312 _funcs = {
1312 _funcs = {
1313 'user': lambda r: repo[r].user(),
1313 'user': lambda r: repo[r].user(),
1314 'branch': lambda r: repo[r].branch(),
1314 'branch': lambda r: repo[r].branch(),
1315 'date': lambda r: repo[r].date(),
1315 'date': lambda r: repo[r].date(),
1316 'description': lambda r: repo[r].description(),
1316 'description': lambda r: repo[r].description(),
1317 'files': lambda r: repo[r].files(),
1317 'files': lambda r: repo[r].files(),
1318 'parents': lambda r: repo[r].parents(),
1318 'parents': lambda r: repo[r].parents(),
1319 'phase': lambda r: repo[r].phase(),
1319 'phase': lambda r: repo[r].phase(),
1320 'substate': lambda r: repo[r].substate,
1320 'substate': lambda r: repo[r].substate,
1321 'summary': lambda r: repo[r].description().splitlines()[0],
1321 'summary': lambda r: repo[r].description().splitlines()[0],
1322 'diff': lambda r: list(repo[r].diff(git=True),)
1322 'diff': lambda r: list(repo[r].diff(git=True),)
1323 }
1323 }
1324 for info in fields:
1324 for info in fields:
1325 getfield = _funcs.get(info, None)
1325 getfield = _funcs.get(info, None)
1326 if getfield is None:
1326 if getfield is None:
1327 raise error.ParseError(
1327 raise error.ParseError(
1328 # i18n: "matching" is a keyword
1328 # i18n: "matching" is a keyword
1329 _("unexpected field name passed to matching: %s") % info)
1329 _("unexpected field name passed to matching: %s") % info)
1330 getfieldfuncs.append(getfield)
1330 getfieldfuncs.append(getfield)
1331 # convert the getfield array of functions into a "getinfo" function
1331 # convert the getfield array of functions into a "getinfo" function
1332 # which returns an array of field values (or a single value if there
1332 # which returns an array of field values (or a single value if there
1333 # is only one field to match)
1333 # is only one field to match)
1334 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1334 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1335
1335
1336 matches = set()
1336 matches = set()
1337 for rev in revs:
1337 for rev in revs:
1338 target = getinfo(rev)
1338 target = getinfo(rev)
1339 for r in subset:
1339 for r in subset:
1340 match = True
1340 match = True
1341 for n, f in enumerate(getfieldfuncs):
1341 for n, f in enumerate(getfieldfuncs):
1342 if target[n] != f(r):
1342 if target[n] != f(r):
1343 match = False
1343 match = False
1344 break
1344 break
1345 if match:
1345 if match:
1346 matches.add(r)
1346 matches.add(r)
1347 return [r for r in subset if r in matches]
1347 return [r for r in subset if r in matches]
1348
1348
1349 def reverse(repo, subset, x):
1349 def reverse(repo, subset, x):
1350 """``reverse(set)``
1350 """``reverse(set)``
1351 Reverse order of set.
1351 Reverse order of set.
1352 """
1352 """
1353 l = getset(repo, subset, x)
1353 l = getset(repo, subset, x)
1354 if not isinstance(l, list):
1354 if not isinstance(l, list):
1355 l = list(l)
1355 l = list(l)
1356 l.reverse()
1356 l.reverse()
1357 return l
1357 return l
1358
1358
1359 def roots(repo, subset, x):
1359 def roots(repo, subset, x):
1360 """``roots(set)``
1360 """``roots(set)``
1361 Changesets in set with no parent changeset in set.
1361 Changesets in set with no parent changeset in set.
1362 """
1362 """
1363 s = set(getset(repo, repo.changelog, x))
1363 s = set(getset(repo, repo.changelog, x))
1364 subset = [r for r in subset if r in s]
1364 subset = [r for r in subset if r in s]
1365 cs = _children(repo, subset, s)
1365 cs = _children(repo, subset, s)
1366 return [r for r in subset if r not in cs]
1366 return [r for r in subset if r not in cs]
1367
1367
1368 def secret(repo, subset, x):
1368 def secret(repo, subset, x):
1369 """``secret()``
1369 """``secret()``
1370 Changeset in secret phase."""
1370 Changeset in secret phase."""
1371 # i18n: "secret" is a keyword
1371 # i18n: "secret" is a keyword
1372 getargs(x, 0, 0, _("secret takes no arguments"))
1372 getargs(x, 0, 0, _("secret takes no arguments"))
1373 pc = repo._phasecache
1373 pc = repo._phasecache
1374 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1374 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1375
1375
1376 def sort(repo, subset, x):
1376 def sort(repo, subset, x):
1377 """``sort(set[, [-]key...])``
1377 """``sort(set[, [-]key...])``
1378 Sort set by keys. The default sort order is ascending, specify a key
1378 Sort set by keys. The default sort order is ascending, specify a key
1379 as ``-key`` to sort in descending order.
1379 as ``-key`` to sort in descending order.
1380
1380
1381 The keys can be:
1381 The keys can be:
1382
1382
1383 - ``rev`` for the revision number,
1383 - ``rev`` for the revision number,
1384 - ``branch`` for the branch name,
1384 - ``branch`` for the branch name,
1385 - ``desc`` for the commit message (description),
1385 - ``desc`` for the commit message (description),
1386 - ``user`` for user name (``author`` can be used as an alias),
1386 - ``user`` for user name (``author`` can be used as an alias),
1387 - ``date`` for the commit date
1387 - ``date`` for the commit date
1388 """
1388 """
1389 # i18n: "sort" is a keyword
1389 # i18n: "sort" is a keyword
1390 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1390 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1391 keys = "rev"
1391 keys = "rev"
1392 if len(l) == 2:
1392 if len(l) == 2:
1393 # i18n: "sort" is a keyword
1393 # i18n: "sort" is a keyword
1394 keys = getstring(l[1], _("sort spec must be a string"))
1394 keys = getstring(l[1], _("sort spec must be a string"))
1395
1395
1396 s = l[0]
1396 s = l[0]
1397 keys = keys.split()
1397 keys = keys.split()
1398 l = []
1398 l = []
1399 def invert(s):
1399 def invert(s):
1400 return "".join(chr(255 - ord(c)) for c in s)
1400 return "".join(chr(255 - ord(c)) for c in s)
1401 for r in getset(repo, subset, s):
1401 for r in getset(repo, subset, s):
1402 c = repo[r]
1402 c = repo[r]
1403 e = []
1403 e = []
1404 for k in keys:
1404 for k in keys:
1405 if k == 'rev':
1405 if k == 'rev':
1406 e.append(r)
1406 e.append(r)
1407 elif k == '-rev':
1407 elif k == '-rev':
1408 e.append(-r)
1408 e.append(-r)
1409 elif k == 'branch':
1409 elif k == 'branch':
1410 e.append(c.branch())
1410 e.append(c.branch())
1411 elif k == '-branch':
1411 elif k == '-branch':
1412 e.append(invert(c.branch()))
1412 e.append(invert(c.branch()))
1413 elif k == 'desc':
1413 elif k == 'desc':
1414 e.append(c.description())
1414 e.append(c.description())
1415 elif k == '-desc':
1415 elif k == '-desc':
1416 e.append(invert(c.description()))
1416 e.append(invert(c.description()))
1417 elif k in 'user author':
1417 elif k in 'user author':
1418 e.append(c.user())
1418 e.append(c.user())
1419 elif k in '-user -author':
1419 elif k in '-user -author':
1420 e.append(invert(c.user()))
1420 e.append(invert(c.user()))
1421 elif k == 'date':
1421 elif k == 'date':
1422 e.append(c.date()[0])
1422 e.append(c.date()[0])
1423 elif k == '-date':
1423 elif k == '-date':
1424 e.append(-c.date()[0])
1424 e.append(-c.date()[0])
1425 else:
1425 else:
1426 raise error.ParseError(_("unknown sort key %r") % k)
1426 raise error.ParseError(_("unknown sort key %r") % k)
1427 e.append(r)
1427 e.append(r)
1428 l.append(e)
1428 l.append(e)
1429 l.sort()
1429 l.sort()
1430 return [e[-1] for e in l]
1430 return [e[-1] for e in l]
1431
1431
1432 def _stringmatcher(pattern):
1432 def _stringmatcher(pattern):
1433 """
1433 """
1434 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1434 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1435 returns the matcher name, pattern, and matcher function.
1435 returns the matcher name, pattern, and matcher function.
1436 missing or unknown prefixes are treated as literal matches.
1436 missing or unknown prefixes are treated as literal matches.
1437
1437
1438 helper for tests:
1438 helper for tests:
1439 >>> def test(pattern, *tests):
1439 >>> def test(pattern, *tests):
1440 ... kind, pattern, matcher = _stringmatcher(pattern)
1440 ... kind, pattern, matcher = _stringmatcher(pattern)
1441 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1441 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1442
1442
1443 exact matching (no prefix):
1443 exact matching (no prefix):
1444 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1444 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1445 ('literal', 'abcdefg', [False, False, True])
1445 ('literal', 'abcdefg', [False, False, True])
1446
1446
1447 regex matching ('re:' prefix)
1447 regex matching ('re:' prefix)
1448 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1448 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1449 ('re', 'a.+b', [False, False, True])
1449 ('re', 'a.+b', [False, False, True])
1450
1450
1451 force exact matches ('literal:' prefix)
1451 force exact matches ('literal:' prefix)
1452 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1452 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1453 ('literal', 're:foobar', [False, True])
1453 ('literal', 're:foobar', [False, True])
1454
1454
1455 unknown prefixes are ignored and treated as literals
1455 unknown prefixes are ignored and treated as literals
1456 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1456 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1457 ('literal', 'foo:bar', [False, False, True])
1457 ('literal', 'foo:bar', [False, False, True])
1458 """
1458 """
1459 if pattern.startswith('re:'):
1459 if pattern.startswith('re:'):
1460 pattern = pattern[3:]
1460 pattern = pattern[3:]
1461 try:
1461 try:
1462 regex = re.compile(pattern)
1462 regex = re.compile(pattern)
1463 except re.error, e:
1463 except re.error, e:
1464 raise error.ParseError(_('invalid regular expression: %s')
1464 raise error.ParseError(_('invalid regular expression: %s')
1465 % e)
1465 % e)
1466 return 're', pattern, regex.search
1466 return 're', pattern, regex.search
1467 elif pattern.startswith('literal:'):
1467 elif pattern.startswith('literal:'):
1468 pattern = pattern[8:]
1468 pattern = pattern[8:]
1469 return 'literal', pattern, pattern.__eq__
1469 return 'literal', pattern, pattern.__eq__
1470
1470
1471 def _substringmatcher(pattern):
1471 def _substringmatcher(pattern):
1472 kind, pattern, matcher = _stringmatcher(pattern)
1472 kind, pattern, matcher = _stringmatcher(pattern)
1473 if kind == 'literal':
1473 if kind == 'literal':
1474 matcher = lambda s: pattern in s
1474 matcher = lambda s: pattern in s
1475 return kind, pattern, matcher
1475 return kind, pattern, matcher
1476
1476
1477 def tag(repo, subset, x):
1477 def tag(repo, subset, x):
1478 """``tag([name])``
1478 """``tag([name])``
1479 The specified tag by name, or all tagged revisions if no name is given.
1479 The specified tag by name, or all tagged revisions if no name is given.
1480 """
1480 """
1481 # i18n: "tag" is a keyword
1481 # i18n: "tag" is a keyword
1482 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1482 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1483 cl = repo.changelog
1483 cl = repo.changelog
1484 if args:
1484 if args:
1485 pattern = getstring(args[0],
1485 pattern = getstring(args[0],
1486 # i18n: "tag" is a keyword
1486 # i18n: "tag" is a keyword
1487 _('the argument to tag must be a string'))
1487 _('the argument to tag must be a string'))
1488 kind, pattern, matcher = _stringmatcher(pattern)
1488 kind, pattern, matcher = _stringmatcher(pattern)
1489 if kind == 'literal':
1489 if kind == 'literal':
1490 # avoid resolving all tags
1490 # avoid resolving all tags
1491 tn = repo._tagscache.tags.get(pattern, None)
1491 tn = repo._tagscache.tags.get(pattern, None)
1492 if tn is None:
1492 if tn is None:
1493 raise util.Abort(_("tag '%s' does not exist") % pattern)
1493 raise util.Abort(_("tag '%s' does not exist") % pattern)
1494 s = set([repo[tn].rev()])
1494 s = set([repo[tn].rev()])
1495 else:
1495 else:
1496 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1496 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1497 else:
1497 else:
1498 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1498 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1499 return [r for r in subset if r in s]
1499 return [r for r in subset if r in s]
1500
1500
1501 def tagged(repo, subset, x):
1501 def tagged(repo, subset, x):
1502 return tag(repo, subset, x)
1502 return tag(repo, subset, x)
1503
1503
1504 def unstable(repo, subset, x):
1504 def unstable(repo, subset, x):
1505 """``unstable()``
1505 """``unstable()``
1506 Non-obsolete changesets with obsolete ancestors.
1506 Non-obsolete changesets with obsolete ancestors.
1507 """
1507 """
1508 # i18n: "unstable" is a keyword
1508 # i18n: "unstable" is a keyword
1509 getargs(x, 0, 0, _("unstable takes no arguments"))
1509 getargs(x, 0, 0, _("unstable takes no arguments"))
1510 unstables = obsmod.getrevs(repo, 'unstable')
1510 unstables = obsmod.getrevs(repo, 'unstable')
1511 return [r for r in subset if r in unstables]
1511 return [r for r in subset if r in unstables]
1512
1512
1513
1513
1514 def user(repo, subset, x):
1514 def user(repo, subset, x):
1515 """``user(string)``
1515 """``user(string)``
1516 User name contains string. The match is case-insensitive.
1516 User name contains string. The match is case-insensitive.
1517
1517
1518 If `string` starts with `re:`, the remainder of the string is treated as
1518 If `string` starts with `re:`, the remainder of the string is treated as
1519 a regular expression. To match a user that actually contains `re:`, use
1519 a regular expression. To match a user that actually contains `re:`, use
1520 the prefix `literal:`.
1520 the prefix `literal:`.
1521 """
1521 """
1522 return author(repo, subset, x)
1522 return author(repo, subset, x)
1523
1523
1524 # for internal use
1524 # for internal use
1525 def _list(repo, subset, x):
1525 def _list(repo, subset, x):
1526 s = getstring(x, "internal error")
1526 s = getstring(x, "internal error")
1527 if not s:
1527 if not s:
1528 return []
1528 return []
1529 if not isinstance(subset, set):
1529 if not isinstance(subset, set):
1530 subset = set(subset)
1530 subset = set(subset)
1531 ls = [repo[r].rev() for r in s.split('\0')]
1531 ls = [repo[r].rev() for r in s.split('\0')]
1532 return [r for r in ls if r in subset]
1532 return [r for r in ls if r in subset]
1533
1533
1534 symbols = {
1534 symbols = {
1535 "adds": adds,
1535 "adds": adds,
1536 "all": getall,
1536 "all": getall,
1537 "ancestor": ancestor,
1537 "ancestor": ancestor,
1538 "ancestors": ancestors,
1538 "ancestors": ancestors,
1539 "_firstancestors": _firstancestors,
1539 "_firstancestors": _firstancestors,
1540 "author": author,
1540 "author": author,
1541 "bisect": bisect,
1541 "bisect": bisect,
1542 "bisected": bisected,
1542 "bisected": bisected,
1543 "bookmark": bookmark,
1543 "bookmark": bookmark,
1544 "branch": branch,
1544 "branch": branch,
1545 "branchpoint": branchpoint,
1545 "branchpoint": branchpoint,
1546 "bumped": bumped,
1546 "bumped": bumped,
1547 "bundle": bundle,
1547 "bundle": bundle,
1548 "children": children,
1548 "children": children,
1549 "closed": closed,
1549 "closed": closed,
1550 "contains": contains,
1550 "contains": contains,
1551 "converted": converted,
1551 "converted": converted,
1552 "date": date,
1552 "date": date,
1553 "desc": desc,
1553 "desc": desc,
1554 "descendants": descendants,
1554 "descendants": descendants,
1555 "_firstdescendants": _firstdescendants,
1555 "_firstdescendants": _firstdescendants,
1556 "destination": destination,
1556 "destination": destination,
1557 "divergent": divergent,
1557 "divergent": divergent,
1558 "draft": draft,
1558 "draft": draft,
1559 "extinct": extinct,
1559 "extinct": extinct,
1560 "extra": extra,
1560 "extra": extra,
1561 "file": hasfile,
1561 "file": hasfile,
1562 "filelog": filelog,
1562 "filelog": filelog,
1563 "first": first,
1563 "first": first,
1564 "follow": follow,
1564 "follow": follow,
1565 "_followfirst": _followfirst,
1565 "_followfirst": _followfirst,
1566 "grep": grep,
1566 "grep": grep,
1567 "head": head,
1567 "head": head,
1568 "heads": heads,
1568 "heads": heads,
1569 "hidden": hidden,
1569 "hidden": hidden,
1570 "id": node_,
1570 "id": node_,
1571 "keyword": keyword,
1571 "keyword": keyword,
1572 "last": last,
1572 "last": last,
1573 "limit": limit,
1573 "limit": limit,
1574 "_matchfiles": _matchfiles,
1574 "_matchfiles": _matchfiles,
1575 "max": maxrev,
1575 "max": maxrev,
1576 "merge": merge,
1576 "merge": merge,
1577 "min": minrev,
1577 "min": minrev,
1578 "modifies": modifies,
1578 "modifies": modifies,
1579 "obsolete": obsolete,
1579 "obsolete": obsolete,
1580 "origin": origin,
1580 "origin": origin,
1581 "outgoing": outgoing,
1581 "outgoing": outgoing,
1582 "p1": p1,
1582 "p1": p1,
1583 "p2": p2,
1583 "p2": p2,
1584 "parents": parents,
1584 "parents": parents,
1585 "present": present,
1585 "present": present,
1586 "public": public,
1586 "public": public,
1587 "remote": remote,
1587 "remote": remote,
1588 "removes": removes,
1588 "removes": removes,
1589 "rev": rev,
1589 "rev": rev,
1590 "reverse": reverse,
1590 "reverse": reverse,
1591 "roots": roots,
1591 "roots": roots,
1592 "sort": sort,
1592 "sort": sort,
1593 "secret": secret,
1593 "secret": secret,
1594 "matching": matching,
1594 "matching": matching,
1595 "tag": tag,
1595 "tag": tag,
1596 "tagged": tagged,
1596 "tagged": tagged,
1597 "user": user,
1597 "user": user,
1598 "unstable": unstable,
1598 "unstable": unstable,
1599 "_list": _list,
1599 "_list": _list,
1600 }
1600 }
1601
1601
1602 # symbols which can't be used for a DoS attack for any given input
1603 # (e.g. those which accept regexes as plain strings shouldn't be included)
1604 # functions that just return a lot of changesets (like all) don't count here
1605 safesymbols = set([
1606 "adds",
1607 "all",
1608 "ancestor",
1609 "ancestors",
1610 "_firstancestors",
1611 "author",
1612 "bisect",
1613 "bisected",
1614 "bookmark",
1615 "branch",
1616 "branchpoint",
1617 "bumped",
1618 "bundle",
1619 "children",
1620 "closed",
1621 "converted",
1622 "date",
1623 "desc",
1624 "descendants",
1625 "_firstdescendants",
1626 "destination",
1627 "divergent",
1628 "draft",
1629 "extinct",
1630 "extra",
1631 "file",
1632 "filelog",
1633 "first",
1634 "follow",
1635 "_followfirst",
1636 "head",
1637 "heads",
1638 "hidden",
1639 "id",
1640 "keyword",
1641 "last",
1642 "limit",
1643 "_matchfiles",
1644 "max",
1645 "merge",
1646 "min",
1647 "modifies",
1648 "obsolete",
1649 "origin",
1650 "outgoing",
1651 "p1",
1652 "p2",
1653 "parents",
1654 "present",
1655 "public",
1656 "remote",
1657 "removes",
1658 "rev",
1659 "reverse",
1660 "roots",
1661 "sort",
1662 "secret",
1663 "matching",
1664 "tag",
1665 "tagged",
1666 "user",
1667 "unstable",
1668 "_list",
1669 ])
1670
1602 methods = {
1671 methods = {
1603 "range": rangeset,
1672 "range": rangeset,
1604 "dagrange": dagrange,
1673 "dagrange": dagrange,
1605 "string": stringset,
1674 "string": stringset,
1606 "symbol": symbolset,
1675 "symbol": symbolset,
1607 "and": andset,
1676 "and": andset,
1608 "or": orset,
1677 "or": orset,
1609 "not": notset,
1678 "not": notset,
1610 "list": listset,
1679 "list": listset,
1611 "func": func,
1680 "func": func,
1612 "ancestor": ancestorspec,
1681 "ancestor": ancestorspec,
1613 "parent": parentspec,
1682 "parent": parentspec,
1614 "parentpost": p1,
1683 "parentpost": p1,
1615 }
1684 }
1616
1685
1617 def optimize(x, small):
1686 def optimize(x, small):
1618 if x is None:
1687 if x is None:
1619 return 0, x
1688 return 0, x
1620
1689
1621 smallbonus = 1
1690 smallbonus = 1
1622 if small:
1691 if small:
1623 smallbonus = .5
1692 smallbonus = .5
1624
1693
1625 op = x[0]
1694 op = x[0]
1626 if op == 'minus':
1695 if op == 'minus':
1627 return optimize(('and', x[1], ('not', x[2])), small)
1696 return optimize(('and', x[1], ('not', x[2])), small)
1628 elif op == 'dagrangepre':
1697 elif op == 'dagrangepre':
1629 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1698 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1630 elif op == 'dagrangepost':
1699 elif op == 'dagrangepost':
1631 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1700 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1632 elif op == 'rangepre':
1701 elif op == 'rangepre':
1633 return optimize(('range', ('string', '0'), x[1]), small)
1702 return optimize(('range', ('string', '0'), x[1]), small)
1634 elif op == 'rangepost':
1703 elif op == 'rangepost':
1635 return optimize(('range', x[1], ('string', 'tip')), small)
1704 return optimize(('range', x[1], ('string', 'tip')), small)
1636 elif op == 'negate':
1705 elif op == 'negate':
1637 return optimize(('string',
1706 return optimize(('string',
1638 '-' + getstring(x[1], _("can't negate that"))), small)
1707 '-' + getstring(x[1], _("can't negate that"))), small)
1639 elif op in 'string symbol negate':
1708 elif op in 'string symbol negate':
1640 return smallbonus, x # single revisions are small
1709 return smallbonus, x # single revisions are small
1641 elif op == 'and':
1710 elif op == 'and':
1642 wa, ta = optimize(x[1], True)
1711 wa, ta = optimize(x[1], True)
1643 wb, tb = optimize(x[2], True)
1712 wb, tb = optimize(x[2], True)
1644 w = min(wa, wb)
1713 w = min(wa, wb)
1645 if wa > wb:
1714 if wa > wb:
1646 return w, (op, tb, ta)
1715 return w, (op, tb, ta)
1647 return w, (op, ta, tb)
1716 return w, (op, ta, tb)
1648 elif op == 'or':
1717 elif op == 'or':
1649 wa, ta = optimize(x[1], False)
1718 wa, ta = optimize(x[1], False)
1650 wb, tb = optimize(x[2], False)
1719 wb, tb = optimize(x[2], False)
1651 if wb < wa:
1720 if wb < wa:
1652 wb, wa = wa, wb
1721 wb, wa = wa, wb
1653 return max(wa, wb), (op, ta, tb)
1722 return max(wa, wb), (op, ta, tb)
1654 elif op == 'not':
1723 elif op == 'not':
1655 o = optimize(x[1], not small)
1724 o = optimize(x[1], not small)
1656 return o[0], (op, o[1])
1725 return o[0], (op, o[1])
1657 elif op == 'parentpost':
1726 elif op == 'parentpost':
1658 o = optimize(x[1], small)
1727 o = optimize(x[1], small)
1659 return o[0], (op, o[1])
1728 return o[0], (op, o[1])
1660 elif op == 'group':
1729 elif op == 'group':
1661 return optimize(x[1], small)
1730 return optimize(x[1], small)
1662 elif op in 'dagrange range list parent ancestorspec':
1731 elif op in 'dagrange range list parent ancestorspec':
1663 if op == 'parent':
1732 if op == 'parent':
1664 # x^:y means (x^) : y, not x ^ (:y)
1733 # x^:y means (x^) : y, not x ^ (:y)
1665 post = ('parentpost', x[1])
1734 post = ('parentpost', x[1])
1666 if x[2][0] == 'dagrangepre':
1735 if x[2][0] == 'dagrangepre':
1667 return optimize(('dagrange', post, x[2][1]), small)
1736 return optimize(('dagrange', post, x[2][1]), small)
1668 elif x[2][0] == 'rangepre':
1737 elif x[2][0] == 'rangepre':
1669 return optimize(('range', post, x[2][1]), small)
1738 return optimize(('range', post, x[2][1]), small)
1670
1739
1671 wa, ta = optimize(x[1], small)
1740 wa, ta = optimize(x[1], small)
1672 wb, tb = optimize(x[2], small)
1741 wb, tb = optimize(x[2], small)
1673 return wa + wb, (op, ta, tb)
1742 return wa + wb, (op, ta, tb)
1674 elif op == 'func':
1743 elif op == 'func':
1675 f = getstring(x[1], _("not a symbol"))
1744 f = getstring(x[1], _("not a symbol"))
1676 wa, ta = optimize(x[2], small)
1745 wa, ta = optimize(x[2], small)
1677 if f in ("author branch closed date desc file grep keyword "
1746 if f in ("author branch closed date desc file grep keyword "
1678 "outgoing user"):
1747 "outgoing user"):
1679 w = 10 # slow
1748 w = 10 # slow
1680 elif f in "modifies adds removes":
1749 elif f in "modifies adds removes":
1681 w = 30 # slower
1750 w = 30 # slower
1682 elif f == "contains":
1751 elif f == "contains":
1683 w = 100 # very slow
1752 w = 100 # very slow
1684 elif f == "ancestor":
1753 elif f == "ancestor":
1685 w = 1 * smallbonus
1754 w = 1 * smallbonus
1686 elif f in "reverse limit first":
1755 elif f in "reverse limit first":
1687 w = 0
1756 w = 0
1688 elif f in "sort":
1757 elif f in "sort":
1689 w = 10 # assume most sorts look at changelog
1758 w = 10 # assume most sorts look at changelog
1690 else:
1759 else:
1691 w = 1
1760 w = 1
1692 return w + wa, (op, x[1], ta)
1761 return w + wa, (op, x[1], ta)
1693 return 1, x
1762 return 1, x
1694
1763
1695 _aliasarg = ('func', ('symbol', '_aliasarg'))
1764 _aliasarg = ('func', ('symbol', '_aliasarg'))
1696 def _getaliasarg(tree):
1765 def _getaliasarg(tree):
1697 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1766 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1698 return X, None otherwise.
1767 return X, None otherwise.
1699 """
1768 """
1700 if (len(tree) == 3 and tree[:2] == _aliasarg
1769 if (len(tree) == 3 and tree[:2] == _aliasarg
1701 and tree[2][0] == 'string'):
1770 and tree[2][0] == 'string'):
1702 return tree[2][1]
1771 return tree[2][1]
1703 return None
1772 return None
1704
1773
1705 def _checkaliasarg(tree, known=None):
1774 def _checkaliasarg(tree, known=None):
1706 """Check tree contains no _aliasarg construct or only ones which
1775 """Check tree contains no _aliasarg construct or only ones which
1707 value is in known. Used to avoid alias placeholders injection.
1776 value is in known. Used to avoid alias placeholders injection.
1708 """
1777 """
1709 if isinstance(tree, tuple):
1778 if isinstance(tree, tuple):
1710 arg = _getaliasarg(tree)
1779 arg = _getaliasarg(tree)
1711 if arg is not None and (not known or arg not in known):
1780 if arg is not None and (not known or arg not in known):
1712 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1781 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1713 for t in tree:
1782 for t in tree:
1714 _checkaliasarg(t, known)
1783 _checkaliasarg(t, known)
1715
1784
1716 class revsetalias(object):
1785 class revsetalias(object):
1717 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1786 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1718 args = None
1787 args = None
1719
1788
1720 def __init__(self, name, value):
1789 def __init__(self, name, value):
1721 '''Aliases like:
1790 '''Aliases like:
1722
1791
1723 h = heads(default)
1792 h = heads(default)
1724 b($1) = ancestors($1) - ancestors(default)
1793 b($1) = ancestors($1) - ancestors(default)
1725 '''
1794 '''
1726 m = self.funcre.search(name)
1795 m = self.funcre.search(name)
1727 if m:
1796 if m:
1728 self.name = m.group(1)
1797 self.name = m.group(1)
1729 self.tree = ('func', ('symbol', m.group(1)))
1798 self.tree = ('func', ('symbol', m.group(1)))
1730 self.args = [x.strip() for x in m.group(2).split(',')]
1799 self.args = [x.strip() for x in m.group(2).split(',')]
1731 for arg in self.args:
1800 for arg in self.args:
1732 # _aliasarg() is an unknown symbol only used separate
1801 # _aliasarg() is an unknown symbol only used separate
1733 # alias argument placeholders from regular strings.
1802 # alias argument placeholders from regular strings.
1734 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1803 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1735 else:
1804 else:
1736 self.name = name
1805 self.name = name
1737 self.tree = ('symbol', name)
1806 self.tree = ('symbol', name)
1738
1807
1739 self.replacement, pos = parse(value)
1808 self.replacement, pos = parse(value)
1740 if pos != len(value):
1809 if pos != len(value):
1741 raise error.ParseError(_('invalid token'), pos)
1810 raise error.ParseError(_('invalid token'), pos)
1742 # Check for placeholder injection
1811 # Check for placeholder injection
1743 _checkaliasarg(self.replacement, self.args)
1812 _checkaliasarg(self.replacement, self.args)
1744
1813
1745 def _getalias(aliases, tree):
1814 def _getalias(aliases, tree):
1746 """If tree looks like an unexpanded alias, return it. Return None
1815 """If tree looks like an unexpanded alias, return it. Return None
1747 otherwise.
1816 otherwise.
1748 """
1817 """
1749 if isinstance(tree, tuple) and tree:
1818 if isinstance(tree, tuple) and tree:
1750 if tree[0] == 'symbol' and len(tree) == 2:
1819 if tree[0] == 'symbol' and len(tree) == 2:
1751 name = tree[1]
1820 name = tree[1]
1752 alias = aliases.get(name)
1821 alias = aliases.get(name)
1753 if alias and alias.args is None and alias.tree == tree:
1822 if alias and alias.args is None and alias.tree == tree:
1754 return alias
1823 return alias
1755 if tree[0] == 'func' and len(tree) > 1:
1824 if tree[0] == 'func' and len(tree) > 1:
1756 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1825 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1757 name = tree[1][1]
1826 name = tree[1][1]
1758 alias = aliases.get(name)
1827 alias = aliases.get(name)
1759 if alias and alias.args is not None and alias.tree == tree[:2]:
1828 if alias and alias.args is not None and alias.tree == tree[:2]:
1760 return alias
1829 return alias
1761 return None
1830 return None
1762
1831
1763 def _expandargs(tree, args):
1832 def _expandargs(tree, args):
1764 """Replace _aliasarg instances with the substitution value of the
1833 """Replace _aliasarg instances with the substitution value of the
1765 same name in args, recursively.
1834 same name in args, recursively.
1766 """
1835 """
1767 if not tree or not isinstance(tree, tuple):
1836 if not tree or not isinstance(tree, tuple):
1768 return tree
1837 return tree
1769 arg = _getaliasarg(tree)
1838 arg = _getaliasarg(tree)
1770 if arg is not None:
1839 if arg is not None:
1771 return args[arg]
1840 return args[arg]
1772 return tuple(_expandargs(t, args) for t in tree)
1841 return tuple(_expandargs(t, args) for t in tree)
1773
1842
1774 def _expandaliases(aliases, tree, expanding, cache):
1843 def _expandaliases(aliases, tree, expanding, cache):
1775 """Expand aliases in tree, recursively.
1844 """Expand aliases in tree, recursively.
1776
1845
1777 'aliases' is a dictionary mapping user defined aliases to
1846 'aliases' is a dictionary mapping user defined aliases to
1778 revsetalias objects.
1847 revsetalias objects.
1779 """
1848 """
1780 if not isinstance(tree, tuple):
1849 if not isinstance(tree, tuple):
1781 # Do not expand raw strings
1850 # Do not expand raw strings
1782 return tree
1851 return tree
1783 alias = _getalias(aliases, tree)
1852 alias = _getalias(aliases, tree)
1784 if alias is not None:
1853 if alias is not None:
1785 if alias in expanding:
1854 if alias in expanding:
1786 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1855 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1787 'detected') % alias.name)
1856 'detected') % alias.name)
1788 expanding.append(alias)
1857 expanding.append(alias)
1789 if alias.name not in cache:
1858 if alias.name not in cache:
1790 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1859 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1791 expanding, cache)
1860 expanding, cache)
1792 result = cache[alias.name]
1861 result = cache[alias.name]
1793 expanding.pop()
1862 expanding.pop()
1794 if alias.args is not None:
1863 if alias.args is not None:
1795 l = getlist(tree[2])
1864 l = getlist(tree[2])
1796 if len(l) != len(alias.args):
1865 if len(l) != len(alias.args):
1797 raise error.ParseError(
1866 raise error.ParseError(
1798 _('invalid number of arguments: %s') % len(l))
1867 _('invalid number of arguments: %s') % len(l))
1799 l = [_expandaliases(aliases, a, [], cache) for a in l]
1868 l = [_expandaliases(aliases, a, [], cache) for a in l]
1800 result = _expandargs(result, dict(zip(alias.args, l)))
1869 result = _expandargs(result, dict(zip(alias.args, l)))
1801 else:
1870 else:
1802 result = tuple(_expandaliases(aliases, t, expanding, cache)
1871 result = tuple(_expandaliases(aliases, t, expanding, cache)
1803 for t in tree)
1872 for t in tree)
1804 return result
1873 return result
1805
1874
1806 def findaliases(ui, tree):
1875 def findaliases(ui, tree):
1807 _checkaliasarg(tree)
1876 _checkaliasarg(tree)
1808 aliases = {}
1877 aliases = {}
1809 for k, v in ui.configitems('revsetalias'):
1878 for k, v in ui.configitems('revsetalias'):
1810 alias = revsetalias(k, v)
1879 alias = revsetalias(k, v)
1811 aliases[alias.name] = alias
1880 aliases[alias.name] = alias
1812 return _expandaliases(aliases, tree, [], {})
1881 return _expandaliases(aliases, tree, [], {})
1813
1882
1814 parse = parser.parser(tokenize, elements).parse
1883 parse = parser.parser(tokenize, elements).parse
1815
1884
1816 def match(ui, spec):
1885 def match(ui, spec):
1817 if not spec:
1886 if not spec:
1818 raise error.ParseError(_("empty query"))
1887 raise error.ParseError(_("empty query"))
1819 tree, pos = parse(spec)
1888 tree, pos = parse(spec)
1820 if (pos != len(spec)):
1889 if (pos != len(spec)):
1821 raise error.ParseError(_("invalid token"), pos)
1890 raise error.ParseError(_("invalid token"), pos)
1822 if ui:
1891 if ui:
1823 tree = findaliases(ui, tree)
1892 tree = findaliases(ui, tree)
1824 weight, tree = optimize(tree, True)
1893 weight, tree = optimize(tree, True)
1825 def mfunc(repo, subset):
1894 def mfunc(repo, subset):
1826 return getset(repo, subset, tree)
1895 return getset(repo, subset, tree)
1827 return mfunc
1896 return mfunc
1828
1897
1829 def formatspec(expr, *args):
1898 def formatspec(expr, *args):
1830 '''
1899 '''
1831 This is a convenience function for using revsets internally, and
1900 This is a convenience function for using revsets internally, and
1832 escapes arguments appropriately. Aliases are intentionally ignored
1901 escapes arguments appropriately. Aliases are intentionally ignored
1833 so that intended expression behavior isn't accidentally subverted.
1902 so that intended expression behavior isn't accidentally subverted.
1834
1903
1835 Supported arguments:
1904 Supported arguments:
1836
1905
1837 %r = revset expression, parenthesized
1906 %r = revset expression, parenthesized
1838 %d = int(arg), no quoting
1907 %d = int(arg), no quoting
1839 %s = string(arg), escaped and single-quoted
1908 %s = string(arg), escaped and single-quoted
1840 %b = arg.branch(), escaped and single-quoted
1909 %b = arg.branch(), escaped and single-quoted
1841 %n = hex(arg), single-quoted
1910 %n = hex(arg), single-quoted
1842 %% = a literal '%'
1911 %% = a literal '%'
1843
1912
1844 Prefixing the type with 'l' specifies a parenthesized list of that type.
1913 Prefixing the type with 'l' specifies a parenthesized list of that type.
1845
1914
1846 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1915 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1847 '(10 or 11):: and ((this()) or (that()))'
1916 '(10 or 11):: and ((this()) or (that()))'
1848 >>> formatspec('%d:: and not %d::', 10, 20)
1917 >>> formatspec('%d:: and not %d::', 10, 20)
1849 '10:: and not 20::'
1918 '10:: and not 20::'
1850 >>> formatspec('%ld or %ld', [], [1])
1919 >>> formatspec('%ld or %ld', [], [1])
1851 "_list('') or 1"
1920 "_list('') or 1"
1852 >>> formatspec('keyword(%s)', 'foo\\xe9')
1921 >>> formatspec('keyword(%s)', 'foo\\xe9')
1853 "keyword('foo\\\\xe9')"
1922 "keyword('foo\\\\xe9')"
1854 >>> b = lambda: 'default'
1923 >>> b = lambda: 'default'
1855 >>> b.branch = b
1924 >>> b.branch = b
1856 >>> formatspec('branch(%b)', b)
1925 >>> formatspec('branch(%b)', b)
1857 "branch('default')"
1926 "branch('default')"
1858 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1927 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1859 "root(_list('a\\x00b\\x00c\\x00d'))"
1928 "root(_list('a\\x00b\\x00c\\x00d'))"
1860 '''
1929 '''
1861
1930
1862 def quote(s):
1931 def quote(s):
1863 return repr(str(s))
1932 return repr(str(s))
1864
1933
1865 def argtype(c, arg):
1934 def argtype(c, arg):
1866 if c == 'd':
1935 if c == 'd':
1867 return str(int(arg))
1936 return str(int(arg))
1868 elif c == 's':
1937 elif c == 's':
1869 return quote(arg)
1938 return quote(arg)
1870 elif c == 'r':
1939 elif c == 'r':
1871 parse(arg) # make sure syntax errors are confined
1940 parse(arg) # make sure syntax errors are confined
1872 return '(%s)' % arg
1941 return '(%s)' % arg
1873 elif c == 'n':
1942 elif c == 'n':
1874 return quote(node.hex(arg))
1943 return quote(node.hex(arg))
1875 elif c == 'b':
1944 elif c == 'b':
1876 return quote(arg.branch())
1945 return quote(arg.branch())
1877
1946
1878 def listexp(s, t):
1947 def listexp(s, t):
1879 l = len(s)
1948 l = len(s)
1880 if l == 0:
1949 if l == 0:
1881 return "_list('')"
1950 return "_list('')"
1882 elif l == 1:
1951 elif l == 1:
1883 return argtype(t, s[0])
1952 return argtype(t, s[0])
1884 elif t == 'd':
1953 elif t == 'd':
1885 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1954 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1886 elif t == 's':
1955 elif t == 's':
1887 return "_list('%s')" % "\0".join(s)
1956 return "_list('%s')" % "\0".join(s)
1888 elif t == 'n':
1957 elif t == 'n':
1889 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1958 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1890 elif t == 'b':
1959 elif t == 'b':
1891 return "_list('%s')" % "\0".join(a.branch() for a in s)
1960 return "_list('%s')" % "\0".join(a.branch() for a in s)
1892
1961
1893 m = l // 2
1962 m = l // 2
1894 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1963 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1895
1964
1896 ret = ''
1965 ret = ''
1897 pos = 0
1966 pos = 0
1898 arg = 0
1967 arg = 0
1899 while pos < len(expr):
1968 while pos < len(expr):
1900 c = expr[pos]
1969 c = expr[pos]
1901 if c == '%':
1970 if c == '%':
1902 pos += 1
1971 pos += 1
1903 d = expr[pos]
1972 d = expr[pos]
1904 if d == '%':
1973 if d == '%':
1905 ret += d
1974 ret += d
1906 elif d in 'dsnbr':
1975 elif d in 'dsnbr':
1907 ret += argtype(d, args[arg])
1976 ret += argtype(d, args[arg])
1908 arg += 1
1977 arg += 1
1909 elif d == 'l':
1978 elif d == 'l':
1910 # a list of some type
1979 # a list of some type
1911 pos += 1
1980 pos += 1
1912 d = expr[pos]
1981 d = expr[pos]
1913 ret += listexp(list(args[arg]), d)
1982 ret += listexp(list(args[arg]), d)
1914 arg += 1
1983 arg += 1
1915 else:
1984 else:
1916 raise util.Abort('unexpected revspec format character %s' % d)
1985 raise util.Abort('unexpected revspec format character %s' % d)
1917 else:
1986 else:
1918 ret += c
1987 ret += c
1919 pos += 1
1988 pos += 1
1920
1989
1921 return ret
1990 return ret
1922
1991
1923 def prettyformat(tree):
1992 def prettyformat(tree):
1924 def _prettyformat(tree, level, lines):
1993 def _prettyformat(tree, level, lines):
1925 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1994 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1926 lines.append((level, str(tree)))
1995 lines.append((level, str(tree)))
1927 else:
1996 else:
1928 lines.append((level, '(%s' % tree[0]))
1997 lines.append((level, '(%s' % tree[0]))
1929 for s in tree[1:]:
1998 for s in tree[1:]:
1930 _prettyformat(s, level + 1, lines)
1999 _prettyformat(s, level + 1, lines)
1931 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
2000 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1932
2001
1933 lines = []
2002 lines = []
1934 _prettyformat(tree, 0, lines)
2003 _prettyformat(tree, 0, lines)
1935 output = '\n'.join((' '*l + s) for l, s in lines)
2004 output = '\n'.join((' '*l + s) for l, s in lines)
1936 return output
2005 return output
1937
2006
1938 def depth(tree):
2007 def depth(tree):
1939 if isinstance(tree, tuple):
2008 if isinstance(tree, tuple):
1940 return max(map(depth, tree)) + 1
2009 return max(map(depth, tree)) + 1
1941 else:
2010 else:
1942 return 0
2011 return 0
1943
2012
1944 def funcsused(tree):
2013 def funcsused(tree):
1945 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2014 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1946 return set()
2015 return set()
1947 else:
2016 else:
1948 funcs = set()
2017 funcs = set()
1949 for s in tree[1:]:
2018 for s in tree[1:]:
1950 funcs |= funcsused(s)
2019 funcs |= funcsused(s)
1951 if tree[0] == 'func':
2020 if tree[0] == 'func':
1952 funcs.add(tree[1][1])
2021 funcs.add(tree[1][1])
1953 return funcs
2022 return funcs
1954
2023
1955 # tell hggettext to extract docstrings from these functions:
2024 # tell hggettext to extract docstrings from these functions:
1956 i18nfunctions = symbols.values()
2025 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now