##// END OF EJS Templates
revset: use appropriate predicate name in error messages...
FUJIWARA Katsunori -
r17258:5822345e stable
parent child Browse files
Show More
@@ -1,1834 +1,1834 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 destination(repo, subset, x):
564 def destination(repo, subset, x):
565 """``destination([set])``
565 """``destination([set])``
566 Changesets that were created by a graft, transplant or rebase operation,
566 Changesets that were created by a graft, transplant or rebase operation,
567 with the given revisions specified as the source. Omitting the optional set
567 with the given revisions specified as the source. Omitting the optional set
568 is the same as passing all().
568 is the same as passing all().
569 """
569 """
570 if x is not None:
570 if x is not None:
571 args = set(getset(repo, range(len(repo)), x))
571 args = set(getset(repo, range(len(repo)), x))
572 else:
572 else:
573 args = set(getall(repo, range(len(repo)), x))
573 args = set(getall(repo, range(len(repo)), x))
574
574
575 dests = set()
575 dests = set()
576
576
577 # subset contains all of the possible destinations that can be returned, so
577 # subset contains all of the possible destinations that can be returned, so
578 # iterate over them and see if their source(s) were provided in the args.
578 # iterate over them and see if their source(s) were provided in the args.
579 # Even if the immediate src of r is not in the args, src's source (or
579 # Even if the immediate src of r is not in the args, src's source (or
580 # further back) may be. Scanning back further than the immediate src allows
580 # further back) may be. Scanning back further than the immediate src allows
581 # transitive transplants and rebases to yield the same results as transitive
581 # transitive transplants and rebases to yield the same results as transitive
582 # grafts.
582 # grafts.
583 for r in subset:
583 for r in subset:
584 src = _getrevsource(repo, r)
584 src = _getrevsource(repo, r)
585 lineage = None
585 lineage = None
586
586
587 while src is not None:
587 while src is not None:
588 if lineage is None:
588 if lineage is None:
589 lineage = list()
589 lineage = list()
590
590
591 lineage.append(r)
591 lineage.append(r)
592
592
593 # The visited lineage is a match if the current source is in the arg
593 # The visited lineage is a match if the current source is in the arg
594 # set. Since every candidate dest is visited by way of iterating
594 # set. Since every candidate dest is visited by way of iterating
595 # subset, any dests futher back in the lineage will be tested by a
595 # subset, any dests futher back in the lineage will be tested by a
596 # different iteration over subset. Likewise, if the src was already
596 # different iteration over subset. Likewise, if the src was already
597 # selected, the current lineage can be selected without going back
597 # selected, the current lineage can be selected without going back
598 # further.
598 # further.
599 if src in args or src in dests:
599 if src in args or src in dests:
600 dests.update(lineage)
600 dests.update(lineage)
601 break
601 break
602
602
603 r = src
603 r = src
604 src = _getrevsource(repo, r)
604 src = _getrevsource(repo, r)
605
605
606 return [r for r in subset if r in dests]
606 return [r for r in subset if r in dests]
607
607
608 def draft(repo, subset, x):
608 def draft(repo, subset, x):
609 """``draft()``
609 """``draft()``
610 Changeset in draft phase."""
610 Changeset in draft phase."""
611 getargs(x, 0, 0, _("draft takes no arguments"))
611 getargs(x, 0, 0, _("draft takes no arguments"))
612 pc = repo._phasecache
612 pc = repo._phasecache
613 return [r for r in subset if pc.phase(repo, r) == phases.draft]
613 return [r for r in subset if pc.phase(repo, r) == phases.draft]
614
614
615 def extinct(repo, subset, x):
615 def extinct(repo, subset, x):
616 """``extinct()``
616 """``extinct()``
617 obsolete changeset with obsolete descendant only."""
617 obsolete changeset with obsolete descendant only."""
618 getargs(x, 0, 0, _("obsolete takes no arguments"))
618 getargs(x, 0, 0, _("extinct takes no arguments"))
619 extinctset = set(repo.revs('(obsolete()::) - (::(not obsolete()))'))
619 extinctset = set(repo.revs('(obsolete()::) - (::(not obsolete()))'))
620 return [r for r in subset if r in extinctset]
620 return [r for r in subset if r in extinctset]
621
621
622 def extra(repo, subset, x):
622 def extra(repo, subset, x):
623 """``extra(label, [value])``
623 """``extra(label, [value])``
624 Changesets with the given label in the extra metadata, with the given
624 Changesets with the given label in the extra metadata, with the given
625 optional value.
625 optional value.
626
626
627 If `value` starts with `re:`, the remainder of the value is treated as
627 If `value` starts with `re:`, the remainder of the value is treated as
628 a regular expression. To match a value that actually starts with `re:`,
628 a regular expression. To match a value that actually starts with `re:`,
629 use the prefix `literal:`.
629 use the prefix `literal:`.
630 """
630 """
631
631
632 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
632 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
633 label = getstring(l[0], _('first argument to extra must be a string'))
633 label = getstring(l[0], _('first argument to extra must be a string'))
634 value = None
634 value = None
635
635
636 if len(l) > 1:
636 if len(l) > 1:
637 value = getstring(l[1], _('second argument to extra must be a string'))
637 value = getstring(l[1], _('second argument to extra must be a string'))
638 kind, value, matcher = _stringmatcher(value)
638 kind, value, matcher = _stringmatcher(value)
639
639
640 def _matchvalue(r):
640 def _matchvalue(r):
641 extra = repo[r].extra()
641 extra = repo[r].extra()
642 return label in extra and (value is None or matcher(extra[label]))
642 return label in extra and (value is None or matcher(extra[label]))
643
643
644 return [r for r in subset if _matchvalue(r)]
644 return [r for r in subset if _matchvalue(r)]
645
645
646 def filelog(repo, subset, x):
646 def filelog(repo, subset, x):
647 """``filelog(pattern)``
647 """``filelog(pattern)``
648 Changesets connected to the specified filelog.
648 Changesets connected to the specified filelog.
649
649
650 If you want to get all changesets affecting matched files exactly,
650 If you want to get all changesets affecting matched files exactly,
651 use ``file()`` predicate, because ``filelog()`` may omit some changesets
651 use ``file()`` predicate, because ``filelog()`` may omit some changesets
652 for performance reasons: see :hg:`help log` for detail.
652 for performance reasons: see :hg:`help log` for detail.
653 """
653 """
654
654
655 pat = getstring(x, _("filelog requires a pattern"))
655 pat = getstring(x, _("filelog requires a pattern"))
656 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
656 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
657 ctx=repo[None])
657 ctx=repo[None])
658 s = set()
658 s = set()
659
659
660 if not matchmod.patkind(pat):
660 if not matchmod.patkind(pat):
661 for f in m.files():
661 for f in m.files():
662 fl = repo.file(f)
662 fl = repo.file(f)
663 for fr in fl:
663 for fr in fl:
664 s.add(fl.linkrev(fr))
664 s.add(fl.linkrev(fr))
665 else:
665 else:
666 for f in repo[None]:
666 for f in repo[None]:
667 if m(f):
667 if m(f):
668 fl = repo.file(f)
668 fl = repo.file(f)
669 for fr in fl:
669 for fr in fl:
670 s.add(fl.linkrev(fr))
670 s.add(fl.linkrev(fr))
671
671
672 return [r for r in subset if r in s]
672 return [r for r in subset if r in s]
673
673
674 def first(repo, subset, x):
674 def first(repo, subset, x):
675 """``first(set, [n])``
675 """``first(set, [n])``
676 An alias for limit().
676 An alias for limit().
677 """
677 """
678 return limit(repo, subset, x)
678 return limit(repo, subset, x)
679
679
680 def _follow(repo, subset, x, name, followfirst=False):
680 def _follow(repo, subset, x, name, followfirst=False):
681 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
681 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
682 c = repo['.']
682 c = repo['.']
683 if l:
683 if l:
684 x = getstring(l[0], _("%s expected a filename") % name)
684 x = getstring(l[0], _("%s expected a filename") % name)
685 if x in c:
685 if x in c:
686 cx = c[x]
686 cx = c[x]
687 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
687 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
688 # include the revision responsible for the most recent version
688 # include the revision responsible for the most recent version
689 s.add(cx.linkrev())
689 s.add(cx.linkrev())
690 else:
690 else:
691 return []
691 return []
692 else:
692 else:
693 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
693 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
694
694
695 return [r for r in subset if r in s]
695 return [r for r in subset if r in s]
696
696
697 def follow(repo, subset, x):
697 def follow(repo, subset, x):
698 """``follow([file])``
698 """``follow([file])``
699 An alias for ``::.`` (ancestors of the working copy's first parent).
699 An alias for ``::.`` (ancestors of the working copy's first parent).
700 If a filename is specified, the history of the given file is followed,
700 If a filename is specified, the history of the given file is followed,
701 including copies.
701 including copies.
702 """
702 """
703 return _follow(repo, subset, x, 'follow')
703 return _follow(repo, subset, x, 'follow')
704
704
705 def _followfirst(repo, subset, x):
705 def _followfirst(repo, subset, x):
706 # ``followfirst([file])``
706 # ``followfirst([file])``
707 # Like ``follow([file])`` but follows only the first parent of
707 # Like ``follow([file])`` but follows only the first parent of
708 # every revision or file revision.
708 # every revision or file revision.
709 return _follow(repo, subset, x, '_followfirst', followfirst=True)
709 return _follow(repo, subset, x, '_followfirst', followfirst=True)
710
710
711 def getall(repo, subset, x):
711 def getall(repo, subset, x):
712 """``all()``
712 """``all()``
713 All changesets, the same as ``0:tip``.
713 All changesets, the same as ``0:tip``.
714 """
714 """
715 # i18n: "all" is a keyword
715 # i18n: "all" is a keyword
716 getargs(x, 0, 0, _("all takes no arguments"))
716 getargs(x, 0, 0, _("all takes no arguments"))
717 return subset
717 return subset
718
718
719 def grep(repo, subset, x):
719 def grep(repo, subset, x):
720 """``grep(regex)``
720 """``grep(regex)``
721 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
721 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
722 to ensure special escape characters are handled correctly. Unlike
722 to ensure special escape characters are handled correctly. Unlike
723 ``keyword(string)``, the match is case-sensitive.
723 ``keyword(string)``, the match is case-sensitive.
724 """
724 """
725 try:
725 try:
726 # i18n: "grep" is a keyword
726 # i18n: "grep" is a keyword
727 gr = re.compile(getstring(x, _("grep requires a string")))
727 gr = re.compile(getstring(x, _("grep requires a string")))
728 except re.error, e:
728 except re.error, e:
729 raise error.ParseError(_('invalid match pattern: %s') % e)
729 raise error.ParseError(_('invalid match pattern: %s') % e)
730 l = []
730 l = []
731 for r in subset:
731 for r in subset:
732 c = repo[r]
732 c = repo[r]
733 for e in c.files() + [c.user(), c.description()]:
733 for e in c.files() + [c.user(), c.description()]:
734 if gr.search(e):
734 if gr.search(e):
735 l.append(r)
735 l.append(r)
736 break
736 break
737 return l
737 return l
738
738
739 def _matchfiles(repo, subset, x):
739 def _matchfiles(repo, subset, x):
740 # _matchfiles takes a revset list of prefixed arguments:
740 # _matchfiles takes a revset list of prefixed arguments:
741 #
741 #
742 # [p:foo, i:bar, x:baz]
742 # [p:foo, i:bar, x:baz]
743 #
743 #
744 # builds a match object from them and filters subset. Allowed
744 # builds a match object from them and filters subset. Allowed
745 # prefixes are 'p:' for regular patterns, 'i:' for include
745 # prefixes are 'p:' for regular patterns, 'i:' for include
746 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
746 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
747 # a revision identifier, or the empty string to reference the
747 # a revision identifier, or the empty string to reference the
748 # working directory, from which the match object is
748 # working directory, from which the match object is
749 # initialized. Use 'd:' to set the default matching mode, default
749 # initialized. Use 'd:' to set the default matching mode, default
750 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
750 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
751
751
752 # i18n: "_matchfiles" is a keyword
752 # i18n: "_matchfiles" is a keyword
753 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
753 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
754 pats, inc, exc = [], [], []
754 pats, inc, exc = [], [], []
755 hasset = False
755 hasset = False
756 rev, default = None, None
756 rev, default = None, None
757 for arg in l:
757 for arg in l:
758 s = getstring(arg, _("_matchfiles requires string arguments"))
758 s = getstring(arg, _("_matchfiles requires string arguments"))
759 prefix, value = s[:2], s[2:]
759 prefix, value = s[:2], s[2:]
760 if prefix == 'p:':
760 if prefix == 'p:':
761 pats.append(value)
761 pats.append(value)
762 elif prefix == 'i:':
762 elif prefix == 'i:':
763 inc.append(value)
763 inc.append(value)
764 elif prefix == 'x:':
764 elif prefix == 'x:':
765 exc.append(value)
765 exc.append(value)
766 elif prefix == 'r:':
766 elif prefix == 'r:':
767 if rev is not None:
767 if rev is not None:
768 raise error.ParseError(_('_matchfiles expected at most one '
768 raise error.ParseError(_('_matchfiles expected at most one '
769 'revision'))
769 'revision'))
770 rev = value
770 rev = value
771 elif prefix == 'd:':
771 elif prefix == 'd:':
772 if default is not None:
772 if default is not None:
773 raise error.ParseError(_('_matchfiles expected at most one '
773 raise error.ParseError(_('_matchfiles expected at most one '
774 'default mode'))
774 'default mode'))
775 default = value
775 default = value
776 else:
776 else:
777 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
777 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
778 if not hasset and matchmod.patkind(value) == 'set':
778 if not hasset and matchmod.patkind(value) == 'set':
779 hasset = True
779 hasset = True
780 if not default:
780 if not default:
781 default = 'glob'
781 default = 'glob'
782 m = None
782 m = None
783 s = []
783 s = []
784 for r in subset:
784 for r in subset:
785 c = repo[r]
785 c = repo[r]
786 if not m or (hasset and rev is None):
786 if not m or (hasset and rev is None):
787 ctx = c
787 ctx = c
788 if rev is not None:
788 if rev is not None:
789 ctx = repo[rev or None]
789 ctx = repo[rev or None]
790 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
790 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
791 exclude=exc, ctx=ctx, default=default)
791 exclude=exc, ctx=ctx, default=default)
792 for f in c.files():
792 for f in c.files():
793 if m(f):
793 if m(f):
794 s.append(r)
794 s.append(r)
795 break
795 break
796 return s
796 return s
797
797
798 def hasfile(repo, subset, x):
798 def hasfile(repo, subset, x):
799 """``file(pattern)``
799 """``file(pattern)``
800 Changesets affecting files matched by pattern.
800 Changesets affecting files matched by pattern.
801
801
802 If you want to pick changesets up fast, consider to
802 If you want to pick changesets up fast, consider to
803 use ``filelog()`` predicate, too.
803 use ``filelog()`` predicate, too.
804 """
804 """
805 # i18n: "file" is a keyword
805 # i18n: "file" is a keyword
806 pat = getstring(x, _("file requires a pattern"))
806 pat = getstring(x, _("file requires a pattern"))
807 return _matchfiles(repo, subset, ('string', 'p:' + pat))
807 return _matchfiles(repo, subset, ('string', 'p:' + pat))
808
808
809 def head(repo, subset, x):
809 def head(repo, subset, x):
810 """``head()``
810 """``head()``
811 Changeset is a named branch head.
811 Changeset is a named branch head.
812 """
812 """
813 # i18n: "head" is a keyword
813 # i18n: "head" is a keyword
814 getargs(x, 0, 0, _("head takes no arguments"))
814 getargs(x, 0, 0, _("head takes no arguments"))
815 hs = set()
815 hs = set()
816 for b, ls in repo.branchmap().iteritems():
816 for b, ls in repo.branchmap().iteritems():
817 hs.update(repo[h].rev() for h in ls)
817 hs.update(repo[h].rev() for h in ls)
818 return [r for r in subset if r in hs]
818 return [r for r in subset if r in hs]
819
819
820 def heads(repo, subset, x):
820 def heads(repo, subset, x):
821 """``heads(set)``
821 """``heads(set)``
822 Members of set with no children in set.
822 Members of set with no children in set.
823 """
823 """
824 s = getset(repo, subset, x)
824 s = getset(repo, subset, x)
825 ps = set(parents(repo, subset, x))
825 ps = set(parents(repo, subset, x))
826 return [r for r in s if r not in ps]
826 return [r for r in s if r not in ps]
827
827
828 def keyword(repo, subset, x):
828 def keyword(repo, subset, x):
829 """``keyword(string)``
829 """``keyword(string)``
830 Search commit message, user name, and names of changed files for
830 Search commit message, user name, and names of changed files for
831 string. The match is case-insensitive.
831 string. The match is case-insensitive.
832 """
832 """
833 # i18n: "keyword" is a keyword
833 # i18n: "keyword" is a keyword
834 kw = encoding.lower(getstring(x, _("keyword requires a string")))
834 kw = encoding.lower(getstring(x, _("keyword requires a string")))
835 l = []
835 l = []
836 for r in subset:
836 for r in subset:
837 c = repo[r]
837 c = repo[r]
838 t = " ".join(c.files() + [c.user(), c.description()])
838 t = " ".join(c.files() + [c.user(), c.description()])
839 if kw in encoding.lower(t):
839 if kw in encoding.lower(t):
840 l.append(r)
840 l.append(r)
841 return l
841 return l
842
842
843 def limit(repo, subset, x):
843 def limit(repo, subset, x):
844 """``limit(set, [n])``
844 """``limit(set, [n])``
845 First n members of set, defaulting to 1.
845 First n members of set, defaulting to 1.
846 """
846 """
847 # i18n: "limit" is a keyword
847 # i18n: "limit" is a keyword
848 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
848 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
849 try:
849 try:
850 lim = 1
850 lim = 1
851 if len(l) == 2:
851 if len(l) == 2:
852 # i18n: "limit" is a keyword
852 # i18n: "limit" is a keyword
853 lim = int(getstring(l[1], _("limit requires a number")))
853 lim = int(getstring(l[1], _("limit requires a number")))
854 except (TypeError, ValueError):
854 except (TypeError, ValueError):
855 # i18n: "limit" is a keyword
855 # i18n: "limit" is a keyword
856 raise error.ParseError(_("limit expects a number"))
856 raise error.ParseError(_("limit expects a number"))
857 ss = set(subset)
857 ss = set(subset)
858 os = getset(repo, range(len(repo)), l[0])[:lim]
858 os = getset(repo, range(len(repo)), l[0])[:lim]
859 return [r for r in os if r in ss]
859 return [r for r in os if r in ss]
860
860
861 def last(repo, subset, x):
861 def last(repo, subset, x):
862 """``last(set, [n])``
862 """``last(set, [n])``
863 Last n members of set, defaulting to 1.
863 Last n members of set, defaulting to 1.
864 """
864 """
865 # i18n: "last" is a keyword
865 # i18n: "last" is a keyword
866 l = getargs(x, 1, 2, _("last requires one or two arguments"))
866 l = getargs(x, 1, 2, _("last requires one or two arguments"))
867 try:
867 try:
868 lim = 1
868 lim = 1
869 if len(l) == 2:
869 if len(l) == 2:
870 # i18n: "last" is a keyword
870 # i18n: "last" is a keyword
871 lim = int(getstring(l[1], _("last requires a number")))
871 lim = int(getstring(l[1], _("last requires a number")))
872 except (TypeError, ValueError):
872 except (TypeError, ValueError):
873 # i18n: "last" is a keyword
873 # i18n: "last" is a keyword
874 raise error.ParseError(_("last expects a number"))
874 raise error.ParseError(_("last expects a number"))
875 ss = set(subset)
875 ss = set(subset)
876 os = getset(repo, range(len(repo)), l[0])[-lim:]
876 os = getset(repo, range(len(repo)), l[0])[-lim:]
877 return [r for r in os if r in ss]
877 return [r for r in os if r in ss]
878
878
879 def maxrev(repo, subset, x):
879 def maxrev(repo, subset, x):
880 """``max(set)``
880 """``max(set)``
881 Changeset with highest revision number in set.
881 Changeset with highest revision number in set.
882 """
882 """
883 os = getset(repo, range(len(repo)), x)
883 os = getset(repo, range(len(repo)), x)
884 if os:
884 if os:
885 m = max(os)
885 m = max(os)
886 if m in subset:
886 if m in subset:
887 return [m]
887 return [m]
888 return []
888 return []
889
889
890 def merge(repo, subset, x):
890 def merge(repo, subset, x):
891 """``merge()``
891 """``merge()``
892 Changeset is a merge changeset.
892 Changeset is a merge changeset.
893 """
893 """
894 # i18n: "merge" is a keyword
894 # i18n: "merge" is a keyword
895 getargs(x, 0, 0, _("merge takes no arguments"))
895 getargs(x, 0, 0, _("merge takes no arguments"))
896 cl = repo.changelog
896 cl = repo.changelog
897 return [r for r in subset if cl.parentrevs(r)[1] != -1]
897 return [r for r in subset if cl.parentrevs(r)[1] != -1]
898
898
899 def minrev(repo, subset, x):
899 def minrev(repo, subset, x):
900 """``min(set)``
900 """``min(set)``
901 Changeset with lowest revision number in set.
901 Changeset with lowest revision number in set.
902 """
902 """
903 os = getset(repo, range(len(repo)), x)
903 os = getset(repo, range(len(repo)), x)
904 if os:
904 if os:
905 m = min(os)
905 m = min(os)
906 if m in subset:
906 if m in subset:
907 return [m]
907 return [m]
908 return []
908 return []
909
909
910 def modifies(repo, subset, x):
910 def modifies(repo, subset, x):
911 """``modifies(pattern)``
911 """``modifies(pattern)``
912 Changesets modifying files matched by pattern.
912 Changesets modifying files matched by pattern.
913 """
913 """
914 # i18n: "modifies" is a keyword
914 # i18n: "modifies" is a keyword
915 pat = getstring(x, _("modifies requires a pattern"))
915 pat = getstring(x, _("modifies requires a pattern"))
916 return checkstatus(repo, subset, pat, 0)
916 return checkstatus(repo, subset, pat, 0)
917
917
918 def node_(repo, subset, x):
918 def node_(repo, subset, x):
919 """``id(string)``
919 """``id(string)``
920 Revision non-ambiguously specified by the given hex string prefix.
920 Revision non-ambiguously specified by the given hex string prefix.
921 """
921 """
922 # i18n: "id" is a keyword
922 # i18n: "id" is a keyword
923 l = getargs(x, 1, 1, _("id requires one argument"))
923 l = getargs(x, 1, 1, _("id requires one argument"))
924 # i18n: "id" is a keyword
924 # i18n: "id" is a keyword
925 n = getstring(l[0], _("id requires a string"))
925 n = getstring(l[0], _("id requires a string"))
926 if len(n) == 40:
926 if len(n) == 40:
927 rn = repo[n].rev()
927 rn = repo[n].rev()
928 else:
928 else:
929 rn = None
929 rn = None
930 pm = repo.changelog._partialmatch(n)
930 pm = repo.changelog._partialmatch(n)
931 if pm is not None:
931 if pm is not None:
932 rn = repo.changelog.rev(pm)
932 rn = repo.changelog.rev(pm)
933
933
934 return [r for r in subset if r == rn]
934 return [r for r in subset if r == rn]
935
935
936 def obsolete(repo, subset, x):
936 def obsolete(repo, subset, x):
937 """``obsolete()``
937 """``obsolete()``
938 Mutable changeset with a newer version."""
938 Mutable changeset with a newer version."""
939 getargs(x, 0, 0, _("obsolete takes no arguments"))
939 getargs(x, 0, 0, _("obsolete takes no arguments"))
940 return [r for r in subset if repo[r].obsolete()]
940 return [r for r in subset if repo[r].obsolete()]
941
941
942 def origin(repo, subset, x):
942 def origin(repo, subset, x):
943 """``origin([set])``
943 """``origin([set])``
944 Changesets that were specified as a source for the grafts, transplants or
944 Changesets that were specified as a source for the grafts, transplants or
945 rebases that created the given revisions. Omitting the optional set is the
945 rebases that created the given revisions. Omitting the optional set is the
946 same as passing all(). If a changeset created by these operations is itself
946 same as passing all(). If a changeset created by these operations is itself
947 specified as a source for one of these operations, only the source changeset
947 specified as a source for one of these operations, only the source changeset
948 for the first operation is selected.
948 for the first operation is selected.
949 """
949 """
950 if x is not None:
950 if x is not None:
951 args = set(getset(repo, range(len(repo)), x))
951 args = set(getset(repo, range(len(repo)), x))
952 else:
952 else:
953 args = set(getall(repo, range(len(repo)), x))
953 args = set(getall(repo, range(len(repo)), x))
954
954
955 def _firstsrc(rev):
955 def _firstsrc(rev):
956 src = _getrevsource(repo, rev)
956 src = _getrevsource(repo, rev)
957 if src is None:
957 if src is None:
958 return None
958 return None
959
959
960 while True:
960 while True:
961 prev = _getrevsource(repo, src)
961 prev = _getrevsource(repo, src)
962
962
963 if prev is None:
963 if prev is None:
964 return src
964 return src
965 src = prev
965 src = prev
966
966
967 o = set([_firstsrc(r) for r in args])
967 o = set([_firstsrc(r) for r in args])
968 return [r for r in subset if r in o]
968 return [r for r in subset if r in o]
969
969
970 def outgoing(repo, subset, x):
970 def outgoing(repo, subset, x):
971 """``outgoing([path])``
971 """``outgoing([path])``
972 Changesets not found in the specified destination repository, or the
972 Changesets not found in the specified destination repository, or the
973 default push location.
973 default push location.
974 """
974 """
975 import hg # avoid start-up nasties
975 import hg # avoid start-up nasties
976 # i18n: "outgoing" is a keyword
976 # i18n: "outgoing" is a keyword
977 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
977 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
978 # i18n: "outgoing" is a keyword
978 # i18n: "outgoing" is a keyword
979 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
979 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
980 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
980 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
981 dest, branches = hg.parseurl(dest)
981 dest, branches = hg.parseurl(dest)
982 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
982 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
983 if revs:
983 if revs:
984 revs = [repo.lookup(rev) for rev in revs]
984 revs = [repo.lookup(rev) for rev in revs]
985 other = hg.peer(repo, {}, dest)
985 other = hg.peer(repo, {}, dest)
986 repo.ui.pushbuffer()
986 repo.ui.pushbuffer()
987 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
987 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
988 repo.ui.popbuffer()
988 repo.ui.popbuffer()
989 cl = repo.changelog
989 cl = repo.changelog
990 o = set([cl.rev(r) for r in outgoing.missing])
990 o = set([cl.rev(r) for r in outgoing.missing])
991 return [r for r in subset if r in o]
991 return [r for r in subset if r in o]
992
992
993 def p1(repo, subset, x):
993 def p1(repo, subset, x):
994 """``p1([set])``
994 """``p1([set])``
995 First parent of changesets in set, or the working directory.
995 First parent of changesets in set, or the working directory.
996 """
996 """
997 if x is None:
997 if x is None:
998 p = repo[x].p1().rev()
998 p = repo[x].p1().rev()
999 return [r for r in subset if r == p]
999 return [r for r in subset if r == p]
1000
1000
1001 ps = set()
1001 ps = set()
1002 cl = repo.changelog
1002 cl = repo.changelog
1003 for r in getset(repo, range(len(repo)), x):
1003 for r in getset(repo, range(len(repo)), x):
1004 ps.add(cl.parentrevs(r)[0])
1004 ps.add(cl.parentrevs(r)[0])
1005 return [r for r in subset if r in ps]
1005 return [r for r in subset if r in ps]
1006
1006
1007 def p2(repo, subset, x):
1007 def p2(repo, subset, x):
1008 """``p2([set])``
1008 """``p2([set])``
1009 Second parent of changesets in set, or the working directory.
1009 Second parent of changesets in set, or the working directory.
1010 """
1010 """
1011 if x is None:
1011 if x is None:
1012 ps = repo[x].parents()
1012 ps = repo[x].parents()
1013 try:
1013 try:
1014 p = ps[1].rev()
1014 p = ps[1].rev()
1015 return [r for r in subset if r == p]
1015 return [r for r in subset if r == p]
1016 except IndexError:
1016 except IndexError:
1017 return []
1017 return []
1018
1018
1019 ps = set()
1019 ps = set()
1020 cl = repo.changelog
1020 cl = repo.changelog
1021 for r in getset(repo, range(len(repo)), x):
1021 for r in getset(repo, range(len(repo)), x):
1022 ps.add(cl.parentrevs(r)[1])
1022 ps.add(cl.parentrevs(r)[1])
1023 return [r for r in subset if r in ps]
1023 return [r for r in subset if r in ps]
1024
1024
1025 def parents(repo, subset, x):
1025 def parents(repo, subset, x):
1026 """``parents([set])``
1026 """``parents([set])``
1027 The set of all parents for all changesets in set, or the working directory.
1027 The set of all parents for all changesets in set, or the working directory.
1028 """
1028 """
1029 if x is None:
1029 if x is None:
1030 ps = tuple(p.rev() for p in repo[x].parents())
1030 ps = tuple(p.rev() for p in repo[x].parents())
1031 return [r for r in subset if r in ps]
1031 return [r for r in subset if r in ps]
1032
1032
1033 ps = set()
1033 ps = set()
1034 cl = repo.changelog
1034 cl = repo.changelog
1035 for r in getset(repo, range(len(repo)), x):
1035 for r in getset(repo, range(len(repo)), x):
1036 ps.update(cl.parentrevs(r))
1036 ps.update(cl.parentrevs(r))
1037 return [r for r in subset if r in ps]
1037 return [r for r in subset if r in ps]
1038
1038
1039 def parentspec(repo, subset, x, n):
1039 def parentspec(repo, subset, x, n):
1040 """``set^0``
1040 """``set^0``
1041 The set.
1041 The set.
1042 ``set^1`` (or ``set^``), ``set^2``
1042 ``set^1`` (or ``set^``), ``set^2``
1043 First or second parent, respectively, of all changesets in set.
1043 First or second parent, respectively, of all changesets in set.
1044 """
1044 """
1045 try:
1045 try:
1046 n = int(n[1])
1046 n = int(n[1])
1047 if n not in (0, 1, 2):
1047 if n not in (0, 1, 2):
1048 raise ValueError
1048 raise ValueError
1049 except (TypeError, ValueError):
1049 except (TypeError, ValueError):
1050 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1050 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1051 ps = set()
1051 ps = set()
1052 cl = repo.changelog
1052 cl = repo.changelog
1053 for r in getset(repo, subset, x):
1053 for r in getset(repo, subset, x):
1054 if n == 0:
1054 if n == 0:
1055 ps.add(r)
1055 ps.add(r)
1056 elif n == 1:
1056 elif n == 1:
1057 ps.add(cl.parentrevs(r)[0])
1057 ps.add(cl.parentrevs(r)[0])
1058 elif n == 2:
1058 elif n == 2:
1059 parents = cl.parentrevs(r)
1059 parents = cl.parentrevs(r)
1060 if len(parents) > 1:
1060 if len(parents) > 1:
1061 ps.add(parents[1])
1061 ps.add(parents[1])
1062 return [r for r in subset if r in ps]
1062 return [r for r in subset if r in ps]
1063
1063
1064 def present(repo, subset, x):
1064 def present(repo, subset, x):
1065 """``present(set)``
1065 """``present(set)``
1066 An empty set, if any revision in set isn't found; otherwise,
1066 An empty set, if any revision in set isn't found; otherwise,
1067 all revisions in set.
1067 all revisions in set.
1068
1068
1069 If any of specified revisions is not present in the local repository,
1069 If any of specified revisions is not present in the local repository,
1070 the query is normally aborted. But this predicate allows the query
1070 the query is normally aborted. But this predicate allows the query
1071 to continue even in such cases.
1071 to continue even in such cases.
1072 """
1072 """
1073 try:
1073 try:
1074 return getset(repo, subset, x)
1074 return getset(repo, subset, x)
1075 except error.RepoLookupError:
1075 except error.RepoLookupError:
1076 return []
1076 return []
1077
1077
1078 def public(repo, subset, x):
1078 def public(repo, subset, x):
1079 """``public()``
1079 """``public()``
1080 Changeset in public phase."""
1080 Changeset in public phase."""
1081 getargs(x, 0, 0, _("public takes no arguments"))
1081 getargs(x, 0, 0, _("public takes no arguments"))
1082 pc = repo._phasecache
1082 pc = repo._phasecache
1083 return [r for r in subset if pc.phase(repo, r) == phases.public]
1083 return [r for r in subset if pc.phase(repo, r) == phases.public]
1084
1084
1085 def remote(repo, subset, x):
1085 def remote(repo, subset, x):
1086 """``remote([id [,path]])``
1086 """``remote([id [,path]])``
1087 Local revision that corresponds to the given identifier in a
1087 Local revision that corresponds to the given identifier in a
1088 remote repository, if present. Here, the '.' identifier is a
1088 remote repository, if present. Here, the '.' identifier is a
1089 synonym for the current local branch.
1089 synonym for the current local branch.
1090 """
1090 """
1091
1091
1092 import hg # avoid start-up nasties
1092 import hg # avoid start-up nasties
1093 # i18n: "remote" is a keyword
1093 # i18n: "remote" is a keyword
1094 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1094 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1095
1095
1096 q = '.'
1096 q = '.'
1097 if len(l) > 0:
1097 if len(l) > 0:
1098 # i18n: "remote" is a keyword
1098 # i18n: "remote" is a keyword
1099 q = getstring(l[0], _("remote requires a string id"))
1099 q = getstring(l[0], _("remote requires a string id"))
1100 if q == '.':
1100 if q == '.':
1101 q = repo['.'].branch()
1101 q = repo['.'].branch()
1102
1102
1103 dest = ''
1103 dest = ''
1104 if len(l) > 1:
1104 if len(l) > 1:
1105 # i18n: "remote" is a keyword
1105 # i18n: "remote" is a keyword
1106 dest = getstring(l[1], _("remote requires a repository path"))
1106 dest = getstring(l[1], _("remote requires a repository path"))
1107 dest = repo.ui.expandpath(dest or 'default')
1107 dest = repo.ui.expandpath(dest or 'default')
1108 dest, branches = hg.parseurl(dest)
1108 dest, branches = hg.parseurl(dest)
1109 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1109 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1110 if revs:
1110 if revs:
1111 revs = [repo.lookup(rev) for rev in revs]
1111 revs = [repo.lookup(rev) for rev in revs]
1112 other = hg.peer(repo, {}, dest)
1112 other = hg.peer(repo, {}, dest)
1113 n = other.lookup(q)
1113 n = other.lookup(q)
1114 if n in repo:
1114 if n in repo:
1115 r = repo[n].rev()
1115 r = repo[n].rev()
1116 if r in subset:
1116 if r in subset:
1117 return [r]
1117 return [r]
1118 return []
1118 return []
1119
1119
1120 def removes(repo, subset, x):
1120 def removes(repo, subset, x):
1121 """``removes(pattern)``
1121 """``removes(pattern)``
1122 Changesets which remove files matching pattern.
1122 Changesets which remove files matching pattern.
1123 """
1123 """
1124 # i18n: "removes" is a keyword
1124 # i18n: "removes" is a keyword
1125 pat = getstring(x, _("removes requires a pattern"))
1125 pat = getstring(x, _("removes requires a pattern"))
1126 return checkstatus(repo, subset, pat, 2)
1126 return checkstatus(repo, subset, pat, 2)
1127
1127
1128 def rev(repo, subset, x):
1128 def rev(repo, subset, x):
1129 """``rev(number)``
1129 """``rev(number)``
1130 Revision with the given numeric identifier.
1130 Revision with the given numeric identifier.
1131 """
1131 """
1132 # i18n: "rev" is a keyword
1132 # i18n: "rev" is a keyword
1133 l = getargs(x, 1, 1, _("rev requires one argument"))
1133 l = getargs(x, 1, 1, _("rev requires one argument"))
1134 try:
1134 try:
1135 # i18n: "rev" is a keyword
1135 # i18n: "rev" is a keyword
1136 l = int(getstring(l[0], _("rev requires a number")))
1136 l = int(getstring(l[0], _("rev requires a number")))
1137 except (TypeError, ValueError):
1137 except (TypeError, ValueError):
1138 # i18n: "rev" is a keyword
1138 # i18n: "rev" is a keyword
1139 raise error.ParseError(_("rev expects a number"))
1139 raise error.ParseError(_("rev expects a number"))
1140 return [r for r in subset if r == l]
1140 return [r for r in subset if r == l]
1141
1141
1142 def matching(repo, subset, x):
1142 def matching(repo, subset, x):
1143 """``matching(revision [, field])``
1143 """``matching(revision [, field])``
1144 Changesets in which a given set of fields match the set of fields in the
1144 Changesets in which a given set of fields match the set of fields in the
1145 selected revision or set.
1145 selected revision or set.
1146
1146
1147 To match more than one field pass the list of fields to match separated
1147 To match more than one field pass the list of fields to match separated
1148 by spaces (e.g. ``author description``).
1148 by spaces (e.g. ``author description``).
1149
1149
1150 Valid fields are most regular revision fields and some special fields.
1150 Valid fields are most regular revision fields and some special fields.
1151
1151
1152 Regular revision fields are ``description``, ``author``, ``branch``,
1152 Regular revision fields are ``description``, ``author``, ``branch``,
1153 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1153 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1154 and ``diff``.
1154 and ``diff``.
1155 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1155 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1156 contents of the revision. Two revisions matching their ``diff`` will
1156 contents of the revision. Two revisions matching their ``diff`` will
1157 also match their ``files``.
1157 also match their ``files``.
1158
1158
1159 Special fields are ``summary`` and ``metadata``:
1159 Special fields are ``summary`` and ``metadata``:
1160 ``summary`` matches the first line of the description.
1160 ``summary`` matches the first line of the description.
1161 ``metadata`` is equivalent to matching ``description user date``
1161 ``metadata`` is equivalent to matching ``description user date``
1162 (i.e. it matches the main metadata fields).
1162 (i.e. it matches the main metadata fields).
1163
1163
1164 ``metadata`` is the default field which is used when no fields are
1164 ``metadata`` is the default field which is used when no fields are
1165 specified. You can match more than one field at a time.
1165 specified. You can match more than one field at a time.
1166 """
1166 """
1167 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1167 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1168
1168
1169 revs = getset(repo, xrange(len(repo)), l[0])
1169 revs = getset(repo, xrange(len(repo)), l[0])
1170
1170
1171 fieldlist = ['metadata']
1171 fieldlist = ['metadata']
1172 if len(l) > 1:
1172 if len(l) > 1:
1173 fieldlist = getstring(l[1],
1173 fieldlist = getstring(l[1],
1174 _("matching requires a string "
1174 _("matching requires a string "
1175 "as its second argument")).split()
1175 "as its second argument")).split()
1176
1176
1177 # Make sure that there are no repeated fields,
1177 # Make sure that there are no repeated fields,
1178 # expand the 'special' 'metadata' field type
1178 # expand the 'special' 'metadata' field type
1179 # and check the 'files' whenever we check the 'diff'
1179 # and check the 'files' whenever we check the 'diff'
1180 fields = []
1180 fields = []
1181 for field in fieldlist:
1181 for field in fieldlist:
1182 if field == 'metadata':
1182 if field == 'metadata':
1183 fields += ['user', 'description', 'date']
1183 fields += ['user', 'description', 'date']
1184 elif field == 'diff':
1184 elif field == 'diff':
1185 # a revision matching the diff must also match the files
1185 # a revision matching the diff must also match the files
1186 # since matching the diff is very costly, make sure to
1186 # since matching the diff is very costly, make sure to
1187 # also match the files first
1187 # also match the files first
1188 fields += ['files', 'diff']
1188 fields += ['files', 'diff']
1189 else:
1189 else:
1190 if field == 'author':
1190 if field == 'author':
1191 field = 'user'
1191 field = 'user'
1192 fields.append(field)
1192 fields.append(field)
1193 fields = set(fields)
1193 fields = set(fields)
1194 if 'summary' in fields and 'description' in fields:
1194 if 'summary' in fields and 'description' in fields:
1195 # If a revision matches its description it also matches its summary
1195 # If a revision matches its description it also matches its summary
1196 fields.discard('summary')
1196 fields.discard('summary')
1197
1197
1198 # We may want to match more than one field
1198 # We may want to match more than one field
1199 # Not all fields take the same amount of time to be matched
1199 # Not all fields take the same amount of time to be matched
1200 # Sort the selected fields in order of increasing matching cost
1200 # Sort the selected fields in order of increasing matching cost
1201 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1201 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1202 'files', 'description', 'substate', 'diff']
1202 'files', 'description', 'substate', 'diff']
1203 def fieldkeyfunc(f):
1203 def fieldkeyfunc(f):
1204 try:
1204 try:
1205 return fieldorder.index(f)
1205 return fieldorder.index(f)
1206 except ValueError:
1206 except ValueError:
1207 # assume an unknown field is very costly
1207 # assume an unknown field is very costly
1208 return len(fieldorder)
1208 return len(fieldorder)
1209 fields = list(fields)
1209 fields = list(fields)
1210 fields.sort(key=fieldkeyfunc)
1210 fields.sort(key=fieldkeyfunc)
1211
1211
1212 # Each field will be matched with its own "getfield" function
1212 # Each field will be matched with its own "getfield" function
1213 # which will be added to the getfieldfuncs array of functions
1213 # which will be added to the getfieldfuncs array of functions
1214 getfieldfuncs = []
1214 getfieldfuncs = []
1215 _funcs = {
1215 _funcs = {
1216 'user': lambda r: repo[r].user(),
1216 'user': lambda r: repo[r].user(),
1217 'branch': lambda r: repo[r].branch(),
1217 'branch': lambda r: repo[r].branch(),
1218 'date': lambda r: repo[r].date(),
1218 'date': lambda r: repo[r].date(),
1219 'description': lambda r: repo[r].description(),
1219 'description': lambda r: repo[r].description(),
1220 'files': lambda r: repo[r].files(),
1220 'files': lambda r: repo[r].files(),
1221 'parents': lambda r: repo[r].parents(),
1221 'parents': lambda r: repo[r].parents(),
1222 'phase': lambda r: repo[r].phase(),
1222 'phase': lambda r: repo[r].phase(),
1223 'substate': lambda r: repo[r].substate,
1223 'substate': lambda r: repo[r].substate,
1224 'summary': lambda r: repo[r].description().splitlines()[0],
1224 'summary': lambda r: repo[r].description().splitlines()[0],
1225 'diff': lambda r: list(repo[r].diff(git=True),)
1225 'diff': lambda r: list(repo[r].diff(git=True),)
1226 }
1226 }
1227 for info in fields:
1227 for info in fields:
1228 getfield = _funcs.get(info, None)
1228 getfield = _funcs.get(info, None)
1229 if getfield is None:
1229 if getfield is None:
1230 raise error.ParseError(
1230 raise error.ParseError(
1231 _("unexpected field name passed to matching: %s") % info)
1231 _("unexpected field name passed to matching: %s") % info)
1232 getfieldfuncs.append(getfield)
1232 getfieldfuncs.append(getfield)
1233 # convert the getfield array of functions into a "getinfo" function
1233 # convert the getfield array of functions into a "getinfo" function
1234 # which returns an array of field values (or a single value if there
1234 # which returns an array of field values (or a single value if there
1235 # is only one field to match)
1235 # is only one field to match)
1236 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1236 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1237
1237
1238 matches = set()
1238 matches = set()
1239 for rev in revs:
1239 for rev in revs:
1240 target = getinfo(rev)
1240 target = getinfo(rev)
1241 for r in subset:
1241 for r in subset:
1242 match = True
1242 match = True
1243 for n, f in enumerate(getfieldfuncs):
1243 for n, f in enumerate(getfieldfuncs):
1244 if target[n] != f(r):
1244 if target[n] != f(r):
1245 match = False
1245 match = False
1246 break
1246 break
1247 if match:
1247 if match:
1248 matches.add(r)
1248 matches.add(r)
1249 return [r for r in subset if r in matches]
1249 return [r for r in subset if r in matches]
1250
1250
1251 def reverse(repo, subset, x):
1251 def reverse(repo, subset, x):
1252 """``reverse(set)``
1252 """``reverse(set)``
1253 Reverse order of set.
1253 Reverse order of set.
1254 """
1254 """
1255 l = getset(repo, subset, x)
1255 l = getset(repo, subset, x)
1256 if not isinstance(l, list):
1256 if not isinstance(l, list):
1257 l = list(l)
1257 l = list(l)
1258 l.reverse()
1258 l.reverse()
1259 return l
1259 return l
1260
1260
1261 def roots(repo, subset, x):
1261 def roots(repo, subset, x):
1262 """``roots(set)``
1262 """``roots(set)``
1263 Changesets in set with no parent changeset in set.
1263 Changesets in set with no parent changeset in set.
1264 """
1264 """
1265 s = set(getset(repo, xrange(len(repo)), x))
1265 s = set(getset(repo, xrange(len(repo)), x))
1266 subset = [r for r in subset if r in s]
1266 subset = [r for r in subset if r in s]
1267 cs = _children(repo, subset, s)
1267 cs = _children(repo, subset, s)
1268 return [r for r in subset if r not in cs]
1268 return [r for r in subset if r not in cs]
1269
1269
1270 def secret(repo, subset, x):
1270 def secret(repo, subset, x):
1271 """``secret()``
1271 """``secret()``
1272 Changeset in secret phase."""
1272 Changeset in secret phase."""
1273 getargs(x, 0, 0, _("secret takes no arguments"))
1273 getargs(x, 0, 0, _("secret takes no arguments"))
1274 pc = repo._phasecache
1274 pc = repo._phasecache
1275 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1275 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1276
1276
1277 def sort(repo, subset, x):
1277 def sort(repo, subset, x):
1278 """``sort(set[, [-]key...])``
1278 """``sort(set[, [-]key...])``
1279 Sort set by keys. The default sort order is ascending, specify a key
1279 Sort set by keys. The default sort order is ascending, specify a key
1280 as ``-key`` to sort in descending order.
1280 as ``-key`` to sort in descending order.
1281
1281
1282 The keys can be:
1282 The keys can be:
1283
1283
1284 - ``rev`` for the revision number,
1284 - ``rev`` for the revision number,
1285 - ``branch`` for the branch name,
1285 - ``branch`` for the branch name,
1286 - ``desc`` for the commit message (description),
1286 - ``desc`` for the commit message (description),
1287 - ``user`` for user name (``author`` can be used as an alias),
1287 - ``user`` for user name (``author`` can be used as an alias),
1288 - ``date`` for the commit date
1288 - ``date`` for the commit date
1289 """
1289 """
1290 # i18n: "sort" is a keyword
1290 # i18n: "sort" is a keyword
1291 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1291 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1292 keys = "rev"
1292 keys = "rev"
1293 if len(l) == 2:
1293 if len(l) == 2:
1294 keys = getstring(l[1], _("sort spec must be a string"))
1294 keys = getstring(l[1], _("sort spec must be a string"))
1295
1295
1296 s = l[0]
1296 s = l[0]
1297 keys = keys.split()
1297 keys = keys.split()
1298 l = []
1298 l = []
1299 def invert(s):
1299 def invert(s):
1300 return "".join(chr(255 - ord(c)) for c in s)
1300 return "".join(chr(255 - ord(c)) for c in s)
1301 for r in getset(repo, subset, s):
1301 for r in getset(repo, subset, s):
1302 c = repo[r]
1302 c = repo[r]
1303 e = []
1303 e = []
1304 for k in keys:
1304 for k in keys:
1305 if k == 'rev':
1305 if k == 'rev':
1306 e.append(r)
1306 e.append(r)
1307 elif k == '-rev':
1307 elif k == '-rev':
1308 e.append(-r)
1308 e.append(-r)
1309 elif k == 'branch':
1309 elif k == 'branch':
1310 e.append(c.branch())
1310 e.append(c.branch())
1311 elif k == '-branch':
1311 elif k == '-branch':
1312 e.append(invert(c.branch()))
1312 e.append(invert(c.branch()))
1313 elif k == 'desc':
1313 elif k == 'desc':
1314 e.append(c.description())
1314 e.append(c.description())
1315 elif k == '-desc':
1315 elif k == '-desc':
1316 e.append(invert(c.description()))
1316 e.append(invert(c.description()))
1317 elif k in 'user author':
1317 elif k in 'user author':
1318 e.append(c.user())
1318 e.append(c.user())
1319 elif k in '-user -author':
1319 elif k in '-user -author':
1320 e.append(invert(c.user()))
1320 e.append(invert(c.user()))
1321 elif k == 'date':
1321 elif k == 'date':
1322 e.append(c.date()[0])
1322 e.append(c.date()[0])
1323 elif k == '-date':
1323 elif k == '-date':
1324 e.append(-c.date()[0])
1324 e.append(-c.date()[0])
1325 else:
1325 else:
1326 raise error.ParseError(_("unknown sort key %r") % k)
1326 raise error.ParseError(_("unknown sort key %r") % k)
1327 e.append(r)
1327 e.append(r)
1328 l.append(e)
1328 l.append(e)
1329 l.sort()
1329 l.sort()
1330 return [e[-1] for e in l]
1330 return [e[-1] for e in l]
1331
1331
1332 def _stringmatcher(pattern):
1332 def _stringmatcher(pattern):
1333 """
1333 """
1334 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1334 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1335 returns the matcher name, pattern, and matcher function.
1335 returns the matcher name, pattern, and matcher function.
1336 missing or unknown prefixes are treated as literal matches.
1336 missing or unknown prefixes are treated as literal matches.
1337
1337
1338 helper for tests:
1338 helper for tests:
1339 >>> def test(pattern, *tests):
1339 >>> def test(pattern, *tests):
1340 ... kind, pattern, matcher = _stringmatcher(pattern)
1340 ... kind, pattern, matcher = _stringmatcher(pattern)
1341 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1341 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1342
1342
1343 exact matching (no prefix):
1343 exact matching (no prefix):
1344 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1344 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1345 ('literal', 'abcdefg', [False, False, True])
1345 ('literal', 'abcdefg', [False, False, True])
1346
1346
1347 regex matching ('re:' prefix)
1347 regex matching ('re:' prefix)
1348 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1348 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1349 ('re', 'a.+b', [False, False, True])
1349 ('re', 'a.+b', [False, False, True])
1350
1350
1351 force exact matches ('literal:' prefix)
1351 force exact matches ('literal:' prefix)
1352 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1352 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1353 ('literal', 're:foobar', [False, True])
1353 ('literal', 're:foobar', [False, True])
1354
1354
1355 unknown prefixes are ignored and treated as literals
1355 unknown prefixes are ignored and treated as literals
1356 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1356 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1357 ('literal', 'foo:bar', [False, False, True])
1357 ('literal', 'foo:bar', [False, False, True])
1358 """
1358 """
1359 if pattern.startswith('re:'):
1359 if pattern.startswith('re:'):
1360 pattern = pattern[3:]
1360 pattern = pattern[3:]
1361 try:
1361 try:
1362 regex = re.compile(pattern)
1362 regex = re.compile(pattern)
1363 except re.error, e:
1363 except re.error, e:
1364 raise error.ParseError(_('invalid regular expression: %s')
1364 raise error.ParseError(_('invalid regular expression: %s')
1365 % e)
1365 % e)
1366 return 're', pattern, regex.search
1366 return 're', pattern, regex.search
1367 elif pattern.startswith('literal:'):
1367 elif pattern.startswith('literal:'):
1368 pattern = pattern[8:]
1368 pattern = pattern[8:]
1369 return 'literal', pattern, pattern.__eq__
1369 return 'literal', pattern, pattern.__eq__
1370
1370
1371 def _substringmatcher(pattern):
1371 def _substringmatcher(pattern):
1372 kind, pattern, matcher = _stringmatcher(pattern)
1372 kind, pattern, matcher = _stringmatcher(pattern)
1373 if kind == 'literal':
1373 if kind == 'literal':
1374 matcher = lambda s: pattern in s
1374 matcher = lambda s: pattern in s
1375 return kind, pattern, matcher
1375 return kind, pattern, matcher
1376
1376
1377 def tag(repo, subset, x):
1377 def tag(repo, subset, x):
1378 """``tag([name])``
1378 """``tag([name])``
1379 The specified tag by name, or all tagged revisions if no name is given.
1379 The specified tag by name, or all tagged revisions if no name is given.
1380 """
1380 """
1381 # i18n: "tag" is a keyword
1381 # i18n: "tag" is a keyword
1382 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1382 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1383 cl = repo.changelog
1383 cl = repo.changelog
1384 if args:
1384 if args:
1385 pattern = getstring(args[0],
1385 pattern = getstring(args[0],
1386 # i18n: "tag" is a keyword
1386 # i18n: "tag" is a keyword
1387 _('the argument to tag must be a string'))
1387 _('the argument to tag must be a string'))
1388 kind, pattern, matcher = _stringmatcher(pattern)
1388 kind, pattern, matcher = _stringmatcher(pattern)
1389 if kind == 'literal':
1389 if kind == 'literal':
1390 # avoid resolving all tags
1390 # avoid resolving all tags
1391 tn = repo._tagscache.tags.get(pattern, None)
1391 tn = repo._tagscache.tags.get(pattern, None)
1392 if tn is None:
1392 if tn is None:
1393 raise util.Abort(_("tag '%s' does not exist") % pattern)
1393 raise util.Abort(_("tag '%s' does not exist") % pattern)
1394 s = set([repo[tn].rev()])
1394 s = set([repo[tn].rev()])
1395 else:
1395 else:
1396 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1396 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1397 if not s:
1397 if not s:
1398 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1398 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1399 else:
1399 else:
1400 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1400 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1401 return [r for r in subset if r in s]
1401 return [r for r in subset if r in s]
1402
1402
1403 def tagged(repo, subset, x):
1403 def tagged(repo, subset, x):
1404 return tag(repo, subset, x)
1404 return tag(repo, subset, x)
1405
1405
1406 def unstable(repo, subset, x):
1406 def unstable(repo, subset, x):
1407 """``unstable()``
1407 """``unstable()``
1408 Unstable changesets are non-obsolete with obsolete descendants."""
1408 Unstable changesets are non-obsolete with obsolete descendants."""
1409 getargs(x, 0, 0, _("obsolete takes no arguments"))
1409 getargs(x, 0, 0, _("unstable takes no arguments"))
1410 unstableset = set(repo.revs('(obsolete()::) - obsolete()'))
1410 unstableset = set(repo.revs('(obsolete()::) - obsolete()'))
1411 return [r for r in subset if r in unstableset]
1411 return [r for r in subset if r in unstableset]
1412
1412
1413
1413
1414 def user(repo, subset, x):
1414 def user(repo, subset, x):
1415 """``user(string)``
1415 """``user(string)``
1416 User name contains string. The match is case-insensitive.
1416 User name contains string. The match is case-insensitive.
1417
1417
1418 If `string` starts with `re:`, the remainder of the string is treated as
1418 If `string` starts with `re:`, the remainder of the string is treated as
1419 a regular expression. To match a user that actually contains `re:`, use
1419 a regular expression. To match a user that actually contains `re:`, use
1420 the prefix `literal:`.
1420 the prefix `literal:`.
1421 """
1421 """
1422 return author(repo, subset, x)
1422 return author(repo, subset, x)
1423
1423
1424 # for internal use
1424 # for internal use
1425 def _list(repo, subset, x):
1425 def _list(repo, subset, x):
1426 s = getstring(x, "internal error")
1426 s = getstring(x, "internal error")
1427 if not s:
1427 if not s:
1428 return []
1428 return []
1429 if not isinstance(subset, set):
1429 if not isinstance(subset, set):
1430 subset = set(subset)
1430 subset = set(subset)
1431 ls = [repo[r].rev() for r in s.split('\0')]
1431 ls = [repo[r].rev() for r in s.split('\0')]
1432 return [r for r in ls if r in subset]
1432 return [r for r in ls if r in subset]
1433
1433
1434 symbols = {
1434 symbols = {
1435 "adds": adds,
1435 "adds": adds,
1436 "all": getall,
1436 "all": getall,
1437 "ancestor": ancestor,
1437 "ancestor": ancestor,
1438 "ancestors": ancestors,
1438 "ancestors": ancestors,
1439 "_firstancestors": _firstancestors,
1439 "_firstancestors": _firstancestors,
1440 "author": author,
1440 "author": author,
1441 "bisect": bisect,
1441 "bisect": bisect,
1442 "bisected": bisected,
1442 "bisected": bisected,
1443 "bookmark": bookmark,
1443 "bookmark": bookmark,
1444 "branch": branch,
1444 "branch": branch,
1445 "children": children,
1445 "children": children,
1446 "closed": closed,
1446 "closed": closed,
1447 "contains": contains,
1447 "contains": contains,
1448 "converted": converted,
1448 "converted": converted,
1449 "date": date,
1449 "date": date,
1450 "desc": desc,
1450 "desc": desc,
1451 "descendants": descendants,
1451 "descendants": descendants,
1452 "_firstdescendants": _firstdescendants,
1452 "_firstdescendants": _firstdescendants,
1453 "destination": destination,
1453 "destination": destination,
1454 "draft": draft,
1454 "draft": draft,
1455 "extinct": extinct,
1455 "extinct": extinct,
1456 "extra": extra,
1456 "extra": extra,
1457 "file": hasfile,
1457 "file": hasfile,
1458 "filelog": filelog,
1458 "filelog": filelog,
1459 "first": first,
1459 "first": first,
1460 "follow": follow,
1460 "follow": follow,
1461 "_followfirst": _followfirst,
1461 "_followfirst": _followfirst,
1462 "grep": grep,
1462 "grep": grep,
1463 "head": head,
1463 "head": head,
1464 "heads": heads,
1464 "heads": heads,
1465 "id": node_,
1465 "id": node_,
1466 "keyword": keyword,
1466 "keyword": keyword,
1467 "last": last,
1467 "last": last,
1468 "limit": limit,
1468 "limit": limit,
1469 "_matchfiles": _matchfiles,
1469 "_matchfiles": _matchfiles,
1470 "max": maxrev,
1470 "max": maxrev,
1471 "merge": merge,
1471 "merge": merge,
1472 "min": minrev,
1472 "min": minrev,
1473 "modifies": modifies,
1473 "modifies": modifies,
1474 "obsolete": obsolete,
1474 "obsolete": obsolete,
1475 "origin": origin,
1475 "origin": origin,
1476 "outgoing": outgoing,
1476 "outgoing": outgoing,
1477 "p1": p1,
1477 "p1": p1,
1478 "p2": p2,
1478 "p2": p2,
1479 "parents": parents,
1479 "parents": parents,
1480 "present": present,
1480 "present": present,
1481 "public": public,
1481 "public": public,
1482 "remote": remote,
1482 "remote": remote,
1483 "removes": removes,
1483 "removes": removes,
1484 "rev": rev,
1484 "rev": rev,
1485 "reverse": reverse,
1485 "reverse": reverse,
1486 "roots": roots,
1486 "roots": roots,
1487 "sort": sort,
1487 "sort": sort,
1488 "secret": secret,
1488 "secret": secret,
1489 "matching": matching,
1489 "matching": matching,
1490 "tag": tag,
1490 "tag": tag,
1491 "tagged": tagged,
1491 "tagged": tagged,
1492 "user": user,
1492 "user": user,
1493 "unstable": unstable,
1493 "unstable": unstable,
1494 "_list": _list,
1494 "_list": _list,
1495 }
1495 }
1496
1496
1497 methods = {
1497 methods = {
1498 "range": rangeset,
1498 "range": rangeset,
1499 "dagrange": dagrange,
1499 "dagrange": dagrange,
1500 "string": stringset,
1500 "string": stringset,
1501 "symbol": symbolset,
1501 "symbol": symbolset,
1502 "and": andset,
1502 "and": andset,
1503 "or": orset,
1503 "or": orset,
1504 "not": notset,
1504 "not": notset,
1505 "list": listset,
1505 "list": listset,
1506 "func": func,
1506 "func": func,
1507 "ancestor": ancestorspec,
1507 "ancestor": ancestorspec,
1508 "parent": parentspec,
1508 "parent": parentspec,
1509 "parentpost": p1,
1509 "parentpost": p1,
1510 }
1510 }
1511
1511
1512 def optimize(x, small):
1512 def optimize(x, small):
1513 if x is None:
1513 if x is None:
1514 return 0, x
1514 return 0, x
1515
1515
1516 smallbonus = 1
1516 smallbonus = 1
1517 if small:
1517 if small:
1518 smallbonus = .5
1518 smallbonus = .5
1519
1519
1520 op = x[0]
1520 op = x[0]
1521 if op == 'minus':
1521 if op == 'minus':
1522 return optimize(('and', x[1], ('not', x[2])), small)
1522 return optimize(('and', x[1], ('not', x[2])), small)
1523 elif op == 'dagrangepre':
1523 elif op == 'dagrangepre':
1524 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1524 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1525 elif op == 'dagrangepost':
1525 elif op == 'dagrangepost':
1526 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1526 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1527 elif op == 'rangepre':
1527 elif op == 'rangepre':
1528 return optimize(('range', ('string', '0'), x[1]), small)
1528 return optimize(('range', ('string', '0'), x[1]), small)
1529 elif op == 'rangepost':
1529 elif op == 'rangepost':
1530 return optimize(('range', x[1], ('string', 'tip')), small)
1530 return optimize(('range', x[1], ('string', 'tip')), small)
1531 elif op == 'negate':
1531 elif op == 'negate':
1532 return optimize(('string',
1532 return optimize(('string',
1533 '-' + getstring(x[1], _("can't negate that"))), small)
1533 '-' + getstring(x[1], _("can't negate that"))), small)
1534 elif op in 'string symbol negate':
1534 elif op in 'string symbol negate':
1535 return smallbonus, x # single revisions are small
1535 return smallbonus, x # single revisions are small
1536 elif op == 'and':
1536 elif op == 'and':
1537 wa, ta = optimize(x[1], True)
1537 wa, ta = optimize(x[1], True)
1538 wb, tb = optimize(x[2], True)
1538 wb, tb = optimize(x[2], True)
1539 w = min(wa, wb)
1539 w = min(wa, wb)
1540 if wa > wb:
1540 if wa > wb:
1541 return w, (op, tb, ta)
1541 return w, (op, tb, ta)
1542 return w, (op, ta, tb)
1542 return w, (op, ta, tb)
1543 elif op == 'or':
1543 elif op == 'or':
1544 wa, ta = optimize(x[1], False)
1544 wa, ta = optimize(x[1], False)
1545 wb, tb = optimize(x[2], False)
1545 wb, tb = optimize(x[2], False)
1546 if wb < wa:
1546 if wb < wa:
1547 wb, wa = wa, wb
1547 wb, wa = wa, wb
1548 return max(wa, wb), (op, ta, tb)
1548 return max(wa, wb), (op, ta, tb)
1549 elif op == 'not':
1549 elif op == 'not':
1550 o = optimize(x[1], not small)
1550 o = optimize(x[1], not small)
1551 return o[0], (op, o[1])
1551 return o[0], (op, o[1])
1552 elif op == 'parentpost':
1552 elif op == 'parentpost':
1553 o = optimize(x[1], small)
1553 o = optimize(x[1], small)
1554 return o[0], (op, o[1])
1554 return o[0], (op, o[1])
1555 elif op == 'group':
1555 elif op == 'group':
1556 return optimize(x[1], small)
1556 return optimize(x[1], small)
1557 elif op in 'dagrange range list parent ancestorspec':
1557 elif op in 'dagrange range list parent ancestorspec':
1558 if op == 'parent':
1558 if op == 'parent':
1559 # x^:y means (x^) : y, not x ^ (:y)
1559 # x^:y means (x^) : y, not x ^ (:y)
1560 post = ('parentpost', x[1])
1560 post = ('parentpost', x[1])
1561 if x[2][0] == 'dagrangepre':
1561 if x[2][0] == 'dagrangepre':
1562 return optimize(('dagrange', post, x[2][1]), small)
1562 return optimize(('dagrange', post, x[2][1]), small)
1563 elif x[2][0] == 'rangepre':
1563 elif x[2][0] == 'rangepre':
1564 return optimize(('range', post, x[2][1]), small)
1564 return optimize(('range', post, x[2][1]), small)
1565
1565
1566 wa, ta = optimize(x[1], small)
1566 wa, ta = optimize(x[1], small)
1567 wb, tb = optimize(x[2], small)
1567 wb, tb = optimize(x[2], small)
1568 return wa + wb, (op, ta, tb)
1568 return wa + wb, (op, ta, tb)
1569 elif op == 'func':
1569 elif op == 'func':
1570 f = getstring(x[1], _("not a symbol"))
1570 f = getstring(x[1], _("not a symbol"))
1571 wa, ta = optimize(x[2], small)
1571 wa, ta = optimize(x[2], small)
1572 if f in ("author branch closed date desc file grep keyword "
1572 if f in ("author branch closed date desc file grep keyword "
1573 "outgoing user"):
1573 "outgoing user"):
1574 w = 10 # slow
1574 w = 10 # slow
1575 elif f in "modifies adds removes":
1575 elif f in "modifies adds removes":
1576 w = 30 # slower
1576 w = 30 # slower
1577 elif f == "contains":
1577 elif f == "contains":
1578 w = 100 # very slow
1578 w = 100 # very slow
1579 elif f == "ancestor":
1579 elif f == "ancestor":
1580 w = 1 * smallbonus
1580 w = 1 * smallbonus
1581 elif f in "reverse limit first":
1581 elif f in "reverse limit first":
1582 w = 0
1582 w = 0
1583 elif f in "sort":
1583 elif f in "sort":
1584 w = 10 # assume most sorts look at changelog
1584 w = 10 # assume most sorts look at changelog
1585 else:
1585 else:
1586 w = 1
1586 w = 1
1587 return w + wa, (op, x[1], ta)
1587 return w + wa, (op, x[1], ta)
1588 return 1, x
1588 return 1, x
1589
1589
1590 _aliasarg = ('func', ('symbol', '_aliasarg'))
1590 _aliasarg = ('func', ('symbol', '_aliasarg'))
1591 def _getaliasarg(tree):
1591 def _getaliasarg(tree):
1592 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1592 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1593 return X, None otherwise.
1593 return X, None otherwise.
1594 """
1594 """
1595 if (len(tree) == 3 and tree[:2] == _aliasarg
1595 if (len(tree) == 3 and tree[:2] == _aliasarg
1596 and tree[2][0] == 'string'):
1596 and tree[2][0] == 'string'):
1597 return tree[2][1]
1597 return tree[2][1]
1598 return None
1598 return None
1599
1599
1600 def _checkaliasarg(tree, known=None):
1600 def _checkaliasarg(tree, known=None):
1601 """Check tree contains no _aliasarg construct or only ones which
1601 """Check tree contains no _aliasarg construct or only ones which
1602 value is in known. Used to avoid alias placeholders injection.
1602 value is in known. Used to avoid alias placeholders injection.
1603 """
1603 """
1604 if isinstance(tree, tuple):
1604 if isinstance(tree, tuple):
1605 arg = _getaliasarg(tree)
1605 arg = _getaliasarg(tree)
1606 if arg is not None and (not known or arg not in known):
1606 if arg is not None and (not known or arg not in known):
1607 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1607 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1608 for t in tree:
1608 for t in tree:
1609 _checkaliasarg(t, known)
1609 _checkaliasarg(t, known)
1610
1610
1611 class revsetalias(object):
1611 class revsetalias(object):
1612 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1612 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1613 args = None
1613 args = None
1614
1614
1615 def __init__(self, name, value):
1615 def __init__(self, name, value):
1616 '''Aliases like:
1616 '''Aliases like:
1617
1617
1618 h = heads(default)
1618 h = heads(default)
1619 b($1) = ancestors($1) - ancestors(default)
1619 b($1) = ancestors($1) - ancestors(default)
1620 '''
1620 '''
1621 m = self.funcre.search(name)
1621 m = self.funcre.search(name)
1622 if m:
1622 if m:
1623 self.name = m.group(1)
1623 self.name = m.group(1)
1624 self.tree = ('func', ('symbol', m.group(1)))
1624 self.tree = ('func', ('symbol', m.group(1)))
1625 self.args = [x.strip() for x in m.group(2).split(',')]
1625 self.args = [x.strip() for x in m.group(2).split(',')]
1626 for arg in self.args:
1626 for arg in self.args:
1627 # _aliasarg() is an unknown symbol only used separate
1627 # _aliasarg() is an unknown symbol only used separate
1628 # alias argument placeholders from regular strings.
1628 # alias argument placeholders from regular strings.
1629 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1629 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1630 else:
1630 else:
1631 self.name = name
1631 self.name = name
1632 self.tree = ('symbol', name)
1632 self.tree = ('symbol', name)
1633
1633
1634 self.replacement, pos = parse(value)
1634 self.replacement, pos = parse(value)
1635 if pos != len(value):
1635 if pos != len(value):
1636 raise error.ParseError(_('invalid token'), pos)
1636 raise error.ParseError(_('invalid token'), pos)
1637 # Check for placeholder injection
1637 # Check for placeholder injection
1638 _checkaliasarg(self.replacement, self.args)
1638 _checkaliasarg(self.replacement, self.args)
1639
1639
1640 def _getalias(aliases, tree):
1640 def _getalias(aliases, tree):
1641 """If tree looks like an unexpanded alias, return it. Return None
1641 """If tree looks like an unexpanded alias, return it. Return None
1642 otherwise.
1642 otherwise.
1643 """
1643 """
1644 if isinstance(tree, tuple) and tree:
1644 if isinstance(tree, tuple) and tree:
1645 if tree[0] == 'symbol' and len(tree) == 2:
1645 if tree[0] == 'symbol' and len(tree) == 2:
1646 name = tree[1]
1646 name = tree[1]
1647 alias = aliases.get(name)
1647 alias = aliases.get(name)
1648 if alias and alias.args is None and alias.tree == tree:
1648 if alias and alias.args is None and alias.tree == tree:
1649 return alias
1649 return alias
1650 if tree[0] == 'func' and len(tree) > 1:
1650 if tree[0] == 'func' and len(tree) > 1:
1651 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1651 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1652 name = tree[1][1]
1652 name = tree[1][1]
1653 alias = aliases.get(name)
1653 alias = aliases.get(name)
1654 if alias and alias.args is not None and alias.tree == tree[:2]:
1654 if alias and alias.args is not None and alias.tree == tree[:2]:
1655 return alias
1655 return alias
1656 return None
1656 return None
1657
1657
1658 def _expandargs(tree, args):
1658 def _expandargs(tree, args):
1659 """Replace _aliasarg instances with the substitution value of the
1659 """Replace _aliasarg instances with the substitution value of the
1660 same name in args, recursively.
1660 same name in args, recursively.
1661 """
1661 """
1662 if not tree or not isinstance(tree, tuple):
1662 if not tree or not isinstance(tree, tuple):
1663 return tree
1663 return tree
1664 arg = _getaliasarg(tree)
1664 arg = _getaliasarg(tree)
1665 if arg is not None:
1665 if arg is not None:
1666 return args[arg]
1666 return args[arg]
1667 return tuple(_expandargs(t, args) for t in tree)
1667 return tuple(_expandargs(t, args) for t in tree)
1668
1668
1669 def _expandaliases(aliases, tree, expanding, cache):
1669 def _expandaliases(aliases, tree, expanding, cache):
1670 """Expand aliases in tree, recursively.
1670 """Expand aliases in tree, recursively.
1671
1671
1672 'aliases' is a dictionary mapping user defined aliases to
1672 'aliases' is a dictionary mapping user defined aliases to
1673 revsetalias objects.
1673 revsetalias objects.
1674 """
1674 """
1675 if not isinstance(tree, tuple):
1675 if not isinstance(tree, tuple):
1676 # Do not expand raw strings
1676 # Do not expand raw strings
1677 return tree
1677 return tree
1678 alias = _getalias(aliases, tree)
1678 alias = _getalias(aliases, tree)
1679 if alias is not None:
1679 if alias is not None:
1680 if alias in expanding:
1680 if alias in expanding:
1681 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1681 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1682 'detected') % alias.name)
1682 'detected') % alias.name)
1683 expanding.append(alias)
1683 expanding.append(alias)
1684 if alias.name not in cache:
1684 if alias.name not in cache:
1685 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1685 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1686 expanding, cache)
1686 expanding, cache)
1687 result = cache[alias.name]
1687 result = cache[alias.name]
1688 expanding.pop()
1688 expanding.pop()
1689 if alias.args is not None:
1689 if alias.args is not None:
1690 l = getlist(tree[2])
1690 l = getlist(tree[2])
1691 if len(l) != len(alias.args):
1691 if len(l) != len(alias.args):
1692 raise error.ParseError(
1692 raise error.ParseError(
1693 _('invalid number of arguments: %s') % len(l))
1693 _('invalid number of arguments: %s') % len(l))
1694 l = [_expandaliases(aliases, a, [], cache) for a in l]
1694 l = [_expandaliases(aliases, a, [], cache) for a in l]
1695 result = _expandargs(result, dict(zip(alias.args, l)))
1695 result = _expandargs(result, dict(zip(alias.args, l)))
1696 else:
1696 else:
1697 result = tuple(_expandaliases(aliases, t, expanding, cache)
1697 result = tuple(_expandaliases(aliases, t, expanding, cache)
1698 for t in tree)
1698 for t in tree)
1699 return result
1699 return result
1700
1700
1701 def findaliases(ui, tree):
1701 def findaliases(ui, tree):
1702 _checkaliasarg(tree)
1702 _checkaliasarg(tree)
1703 aliases = {}
1703 aliases = {}
1704 for k, v in ui.configitems('revsetalias'):
1704 for k, v in ui.configitems('revsetalias'):
1705 alias = revsetalias(k, v)
1705 alias = revsetalias(k, v)
1706 aliases[alias.name] = alias
1706 aliases[alias.name] = alias
1707 return _expandaliases(aliases, tree, [], {})
1707 return _expandaliases(aliases, tree, [], {})
1708
1708
1709 parse = parser.parser(tokenize, elements).parse
1709 parse = parser.parser(tokenize, elements).parse
1710
1710
1711 def match(ui, spec):
1711 def match(ui, spec):
1712 if not spec:
1712 if not spec:
1713 raise error.ParseError(_("empty query"))
1713 raise error.ParseError(_("empty query"))
1714 tree, pos = parse(spec)
1714 tree, pos = parse(spec)
1715 if (pos != len(spec)):
1715 if (pos != len(spec)):
1716 raise error.ParseError(_("invalid token"), pos)
1716 raise error.ParseError(_("invalid token"), pos)
1717 if ui:
1717 if ui:
1718 tree = findaliases(ui, tree)
1718 tree = findaliases(ui, tree)
1719 weight, tree = optimize(tree, True)
1719 weight, tree = optimize(tree, True)
1720 def mfunc(repo, subset):
1720 def mfunc(repo, subset):
1721 return getset(repo, subset, tree)
1721 return getset(repo, subset, tree)
1722 return mfunc
1722 return mfunc
1723
1723
1724 def formatspec(expr, *args):
1724 def formatspec(expr, *args):
1725 '''
1725 '''
1726 This is a convenience function for using revsets internally, and
1726 This is a convenience function for using revsets internally, and
1727 escapes arguments appropriately. Aliases are intentionally ignored
1727 escapes arguments appropriately. Aliases are intentionally ignored
1728 so that intended expression behavior isn't accidentally subverted.
1728 so that intended expression behavior isn't accidentally subverted.
1729
1729
1730 Supported arguments:
1730 Supported arguments:
1731
1731
1732 %r = revset expression, parenthesized
1732 %r = revset expression, parenthesized
1733 %d = int(arg), no quoting
1733 %d = int(arg), no quoting
1734 %s = string(arg), escaped and single-quoted
1734 %s = string(arg), escaped and single-quoted
1735 %b = arg.branch(), escaped and single-quoted
1735 %b = arg.branch(), escaped and single-quoted
1736 %n = hex(arg), single-quoted
1736 %n = hex(arg), single-quoted
1737 %% = a literal '%'
1737 %% = a literal '%'
1738
1738
1739 Prefixing the type with 'l' specifies a parenthesized list of that type.
1739 Prefixing the type with 'l' specifies a parenthesized list of that type.
1740
1740
1741 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1741 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1742 '(10 or 11):: and ((this()) or (that()))'
1742 '(10 or 11):: and ((this()) or (that()))'
1743 >>> formatspec('%d:: and not %d::', 10, 20)
1743 >>> formatspec('%d:: and not %d::', 10, 20)
1744 '10:: and not 20::'
1744 '10:: and not 20::'
1745 >>> formatspec('%ld or %ld', [], [1])
1745 >>> formatspec('%ld or %ld', [], [1])
1746 "_list('') or 1"
1746 "_list('') or 1"
1747 >>> formatspec('keyword(%s)', 'foo\\xe9')
1747 >>> formatspec('keyword(%s)', 'foo\\xe9')
1748 "keyword('foo\\\\xe9')"
1748 "keyword('foo\\\\xe9')"
1749 >>> b = lambda: 'default'
1749 >>> b = lambda: 'default'
1750 >>> b.branch = b
1750 >>> b.branch = b
1751 >>> formatspec('branch(%b)', b)
1751 >>> formatspec('branch(%b)', b)
1752 "branch('default')"
1752 "branch('default')"
1753 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1753 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1754 "root(_list('a\\x00b\\x00c\\x00d'))"
1754 "root(_list('a\\x00b\\x00c\\x00d'))"
1755 '''
1755 '''
1756
1756
1757 def quote(s):
1757 def quote(s):
1758 return repr(str(s))
1758 return repr(str(s))
1759
1759
1760 def argtype(c, arg):
1760 def argtype(c, arg):
1761 if c == 'd':
1761 if c == 'd':
1762 return str(int(arg))
1762 return str(int(arg))
1763 elif c == 's':
1763 elif c == 's':
1764 return quote(arg)
1764 return quote(arg)
1765 elif c == 'r':
1765 elif c == 'r':
1766 parse(arg) # make sure syntax errors are confined
1766 parse(arg) # make sure syntax errors are confined
1767 return '(%s)' % arg
1767 return '(%s)' % arg
1768 elif c == 'n':
1768 elif c == 'n':
1769 return quote(node.hex(arg))
1769 return quote(node.hex(arg))
1770 elif c == 'b':
1770 elif c == 'b':
1771 return quote(arg.branch())
1771 return quote(arg.branch())
1772
1772
1773 def listexp(s, t):
1773 def listexp(s, t):
1774 l = len(s)
1774 l = len(s)
1775 if l == 0:
1775 if l == 0:
1776 return "_list('')"
1776 return "_list('')"
1777 elif l == 1:
1777 elif l == 1:
1778 return argtype(t, s[0])
1778 return argtype(t, s[0])
1779 elif t == 'd':
1779 elif t == 'd':
1780 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1780 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1781 elif t == 's':
1781 elif t == 's':
1782 return "_list('%s')" % "\0".join(s)
1782 return "_list('%s')" % "\0".join(s)
1783 elif t == 'n':
1783 elif t == 'n':
1784 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1784 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1785 elif t == 'b':
1785 elif t == 'b':
1786 return "_list('%s')" % "\0".join(a.branch() for a in s)
1786 return "_list('%s')" % "\0".join(a.branch() for a in s)
1787
1787
1788 m = l // 2
1788 m = l // 2
1789 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1789 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1790
1790
1791 ret = ''
1791 ret = ''
1792 pos = 0
1792 pos = 0
1793 arg = 0
1793 arg = 0
1794 while pos < len(expr):
1794 while pos < len(expr):
1795 c = expr[pos]
1795 c = expr[pos]
1796 if c == '%':
1796 if c == '%':
1797 pos += 1
1797 pos += 1
1798 d = expr[pos]
1798 d = expr[pos]
1799 if d == '%':
1799 if d == '%':
1800 ret += d
1800 ret += d
1801 elif d in 'dsnbr':
1801 elif d in 'dsnbr':
1802 ret += argtype(d, args[arg])
1802 ret += argtype(d, args[arg])
1803 arg += 1
1803 arg += 1
1804 elif d == 'l':
1804 elif d == 'l':
1805 # a list of some type
1805 # a list of some type
1806 pos += 1
1806 pos += 1
1807 d = expr[pos]
1807 d = expr[pos]
1808 ret += listexp(list(args[arg]), d)
1808 ret += listexp(list(args[arg]), d)
1809 arg += 1
1809 arg += 1
1810 else:
1810 else:
1811 raise util.Abort('unexpected revspec format character %s' % d)
1811 raise util.Abort('unexpected revspec format character %s' % d)
1812 else:
1812 else:
1813 ret += c
1813 ret += c
1814 pos += 1
1814 pos += 1
1815
1815
1816 return ret
1816 return ret
1817
1817
1818 def prettyformat(tree):
1818 def prettyformat(tree):
1819 def _prettyformat(tree, level, lines):
1819 def _prettyformat(tree, level, lines):
1820 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1820 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1821 lines.append((level, str(tree)))
1821 lines.append((level, str(tree)))
1822 else:
1822 else:
1823 lines.append((level, '(%s' % tree[0]))
1823 lines.append((level, '(%s' % tree[0]))
1824 for s in tree[1:]:
1824 for s in tree[1:]:
1825 _prettyformat(s, level + 1, lines)
1825 _prettyformat(s, level + 1, lines)
1826 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1826 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1827
1827
1828 lines = []
1828 lines = []
1829 _prettyformat(tree, 0, lines)
1829 _prettyformat(tree, 0, lines)
1830 output = '\n'.join((' '*l + s) for l, s in lines)
1830 output = '\n'.join((' '*l + s) for l, s in lines)
1831 return output
1831 return output
1832
1832
1833 # tell hggettext to extract docstrings from these functions:
1833 # tell hggettext to extract docstrings from these functions:
1834 i18nfunctions = symbols.values()
1834 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now