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