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