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