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