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