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