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