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