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