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