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