##// END OF EJS Templates
revset: introduce filelog() to emulate log's fast path...
Matt Mackall -
r14342:c0b6a734 default
parent child Browse files
Show More
@@ -1,985 +1,1009 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
9 import parser, util, error, discovery, hbisect
10 import bookmarks as bookmarksmod
10 import bookmarks as bookmarksmod
11 import match as matchmod
11 import match as matchmod
12 from i18n import _
12 from i18n import _
13
13
14 elements = {
14 elements = {
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
16 "~": (18, None, ("ancestor", 18)),
16 "~": (18, None, ("ancestor", 18)),
17 "^": (18, None, ("parent", 18), ("parentpost", 18)),
17 "^": (18, None, ("parent", 18), ("parentpost", 18)),
18 "-": (5, ("negate", 19), ("minus", 5)),
18 "-": (5, ("negate", 19), ("minus", 5)),
19 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
19 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
20 ("dagrangepost", 17)),
20 ("dagrangepost", 17)),
21 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
21 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
22 ("dagrangepost", 17)),
22 ("dagrangepost", 17)),
23 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
23 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
24 "not": (10, ("not", 10)),
24 "not": (10, ("not", 10)),
25 "!": (10, ("not", 10)),
25 "!": (10, ("not", 10)),
26 "and": (5, None, ("and", 5)),
26 "and": (5, None, ("and", 5)),
27 "&": (5, None, ("and", 5)),
27 "&": (5, None, ("and", 5)),
28 "or": (4, None, ("or", 4)),
28 "or": (4, None, ("or", 4)),
29 "|": (4, None, ("or", 4)),
29 "|": (4, None, ("or", 4)),
30 "+": (4, None, ("or", 4)),
30 "+": (4, None, ("or", 4)),
31 ",": (2, None, ("list", 2)),
31 ",": (2, None, ("list", 2)),
32 ")": (0, None, None),
32 ")": (0, None, None),
33 "symbol": (0, ("symbol",), None),
33 "symbol": (0, ("symbol",), None),
34 "string": (0, ("string",), None),
34 "string": (0, ("string",), None),
35 "end": (0, None, None),
35 "end": (0, None, None),
36 }
36 }
37
37
38 keywords = set(['and', 'or', 'not'])
38 keywords = set(['and', 'or', 'not'])
39
39
40 def tokenize(program):
40 def tokenize(program):
41 pos, l = 0, len(program)
41 pos, l = 0, len(program)
42 while pos < l:
42 while pos < l:
43 c = program[pos]
43 c = program[pos]
44 if c.isspace(): # skip inter-token whitespace
44 if c.isspace(): # skip inter-token whitespace
45 pass
45 pass
46 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
46 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
47 yield ('::', None, pos)
47 yield ('::', None, pos)
48 pos += 1 # skip ahead
48 pos += 1 # skip ahead
49 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
49 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
50 yield ('..', None, pos)
50 yield ('..', None, pos)
51 pos += 1 # skip ahead
51 pos += 1 # skip ahead
52 elif c in "():,-|&+!~^": # handle simple operators
52 elif c in "():,-|&+!~^": # handle simple operators
53 yield (c, None, pos)
53 yield (c, None, pos)
54 elif (c in '"\'' or c == 'r' and
54 elif (c in '"\'' or c == 'r' and
55 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
55 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
56 if c == 'r':
56 if c == 'r':
57 pos += 1
57 pos += 1
58 c = program[pos]
58 c = program[pos]
59 decode = lambda x: x
59 decode = lambda x: x
60 else:
60 else:
61 decode = lambda x: x.decode('string-escape')
61 decode = lambda x: x.decode('string-escape')
62 pos += 1
62 pos += 1
63 s = pos
63 s = pos
64 while pos < l: # find closing quote
64 while pos < l: # find closing quote
65 d = program[pos]
65 d = program[pos]
66 if d == '\\': # skip over escaped characters
66 if d == '\\': # skip over escaped characters
67 pos += 2
67 pos += 2
68 continue
68 continue
69 if d == c:
69 if d == c:
70 yield ('string', decode(program[s:pos]), s)
70 yield ('string', decode(program[s:pos]), s)
71 break
71 break
72 pos += 1
72 pos += 1
73 else:
73 else:
74 raise error.ParseError(_("unterminated string"), s)
74 raise error.ParseError(_("unterminated string"), s)
75 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
75 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
76 s = pos
76 s = pos
77 pos += 1
77 pos += 1
78 while pos < l: # find end of symbol
78 while pos < l: # find end of symbol
79 d = program[pos]
79 d = program[pos]
80 if not (d.isalnum() or d in "._" or ord(d) > 127):
80 if not (d.isalnum() or d in "._" or ord(d) > 127):
81 break
81 break
82 if d == '.' and program[pos - 1] == '.': # special case for ..
82 if d == '.' and program[pos - 1] == '.': # special case for ..
83 pos -= 1
83 pos -= 1
84 break
84 break
85 pos += 1
85 pos += 1
86 sym = program[s:pos]
86 sym = program[s:pos]
87 if sym in keywords: # operator keywords
87 if sym in keywords: # operator keywords
88 yield (sym, None, s)
88 yield (sym, None, s)
89 else:
89 else:
90 yield ('symbol', sym, s)
90 yield ('symbol', sym, s)
91 pos -= 1
91 pos -= 1
92 else:
92 else:
93 raise error.ParseError(_("syntax error"), pos)
93 raise error.ParseError(_("syntax error"), pos)
94 pos += 1
94 pos += 1
95 yield ('end', None, pos)
95 yield ('end', None, pos)
96
96
97 # helpers
97 # helpers
98
98
99 def getstring(x, err):
99 def getstring(x, err):
100 if x and (x[0] == 'string' or x[0] == 'symbol'):
100 if x and (x[0] == 'string' or x[0] == 'symbol'):
101 return x[1]
101 return x[1]
102 raise error.ParseError(err)
102 raise error.ParseError(err)
103
103
104 def getlist(x):
104 def getlist(x):
105 if not x:
105 if not x:
106 return []
106 return []
107 if x[0] == 'list':
107 if x[0] == 'list':
108 return getlist(x[1]) + [x[2]]
108 return getlist(x[1]) + [x[2]]
109 return [x]
109 return [x]
110
110
111 def getargs(x, min, max, err):
111 def getargs(x, min, max, err):
112 l = getlist(x)
112 l = getlist(x)
113 if len(l) < min or len(l) > max:
113 if len(l) < min or len(l) > max:
114 raise error.ParseError(err)
114 raise error.ParseError(err)
115 return l
115 return l
116
116
117 def getset(repo, subset, x):
117 def getset(repo, subset, x):
118 if not x:
118 if not x:
119 raise error.ParseError(_("missing argument"))
119 raise error.ParseError(_("missing argument"))
120 return methods[x[0]](repo, subset, *x[1:])
120 return methods[x[0]](repo, subset, *x[1:])
121
121
122 # operator methods
122 # operator methods
123
123
124 def stringset(repo, subset, x):
124 def stringset(repo, subset, x):
125 x = repo[x].rev()
125 x = repo[x].rev()
126 if x == -1 and len(subset) == len(repo):
126 if x == -1 and len(subset) == len(repo):
127 return [-1]
127 return [-1]
128 if len(subset) == len(repo) or x in subset:
128 if len(subset) == len(repo) or x in subset:
129 return [x]
129 return [x]
130 return []
130 return []
131
131
132 def symbolset(repo, subset, x):
132 def symbolset(repo, subset, x):
133 if x in symbols:
133 if x in symbols:
134 raise error.ParseError(_("can't use %s here") % x)
134 raise error.ParseError(_("can't use %s here") % x)
135 return stringset(repo, subset, x)
135 return stringset(repo, subset, x)
136
136
137 def rangeset(repo, subset, x, y):
137 def rangeset(repo, subset, x, y):
138 m = getset(repo, subset, x)
138 m = getset(repo, subset, x)
139 if not m:
139 if not m:
140 m = getset(repo, range(len(repo)), x)
140 m = getset(repo, range(len(repo)), x)
141
141
142 n = getset(repo, subset, y)
142 n = getset(repo, subset, y)
143 if not n:
143 if not n:
144 n = getset(repo, range(len(repo)), y)
144 n = getset(repo, range(len(repo)), y)
145
145
146 if not m or not n:
146 if not m or not n:
147 return []
147 return []
148 m, n = m[0], n[-1]
148 m, n = m[0], n[-1]
149
149
150 if m < n:
150 if m < n:
151 r = range(m, n + 1)
151 r = range(m, n + 1)
152 else:
152 else:
153 r = range(m, n - 1, -1)
153 r = range(m, n - 1, -1)
154 s = set(subset)
154 s = set(subset)
155 return [x for x in r if x in s]
155 return [x for x in r if x in s]
156
156
157 def andset(repo, subset, x, y):
157 def andset(repo, subset, x, y):
158 return getset(repo, getset(repo, subset, x), y)
158 return getset(repo, getset(repo, subset, x), y)
159
159
160 def orset(repo, subset, x, y):
160 def orset(repo, subset, x, y):
161 xl = getset(repo, subset, x)
161 xl = getset(repo, subset, x)
162 s = set(xl)
162 s = set(xl)
163 yl = getset(repo, [r for r in subset if r not in s], y)
163 yl = getset(repo, [r for r in subset if r not in s], y)
164 return xl + yl
164 return xl + yl
165
165
166 def notset(repo, subset, x):
166 def notset(repo, subset, x):
167 s = set(getset(repo, subset, x))
167 s = set(getset(repo, subset, x))
168 return [r for r in subset if r not in s]
168 return [r for r in subset if r not in s]
169
169
170 def listset(repo, subset, a, b):
170 def listset(repo, subset, a, b):
171 raise error.ParseError(_("can't use a list in this context"))
171 raise error.ParseError(_("can't use a list in this context"))
172
172
173 def func(repo, subset, a, b):
173 def func(repo, subset, a, b):
174 if a[0] == 'symbol' and a[1] in symbols:
174 if a[0] == 'symbol' and a[1] in symbols:
175 return symbols[a[1]](repo, subset, b)
175 return symbols[a[1]](repo, subset, b)
176 raise error.ParseError(_("not a function: %s") % a[1])
176 raise error.ParseError(_("not a function: %s") % a[1])
177
177
178 # functions
178 # functions
179
179
180 def adds(repo, subset, x):
180 def adds(repo, subset, x):
181 """``adds(pattern)``
181 """``adds(pattern)``
182 Changesets that add a file matching pattern.
182 Changesets that add a file matching pattern.
183 """
183 """
184 # i18n: "adds" is a keyword
184 # i18n: "adds" is a keyword
185 pat = getstring(x, _("adds requires a pattern"))
185 pat = getstring(x, _("adds requires a pattern"))
186 return checkstatus(repo, subset, pat, 1)
186 return checkstatus(repo, subset, pat, 1)
187
187
188 def ancestor(repo, subset, x):
188 def ancestor(repo, subset, x):
189 """``ancestor(single, single)``
189 """``ancestor(single, single)``
190 Greatest common ancestor of the two changesets.
190 Greatest common ancestor of the two changesets.
191 """
191 """
192 # i18n: "ancestor" is a keyword
192 # i18n: "ancestor" is a keyword
193 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
193 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
194 r = range(len(repo))
194 r = range(len(repo))
195 a = getset(repo, r, l[0])
195 a = getset(repo, r, l[0])
196 b = getset(repo, r, l[1])
196 b = getset(repo, r, l[1])
197 if len(a) != 1 or len(b) != 1:
197 if len(a) != 1 or len(b) != 1:
198 # i18n: "ancestor" is a keyword
198 # i18n: "ancestor" is a keyword
199 raise error.ParseError(_("ancestor arguments must be single revisions"))
199 raise error.ParseError(_("ancestor arguments must be single revisions"))
200 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
200 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
201
201
202 return [r for r in an if r in subset]
202 return [r for r in an if r in subset]
203
203
204 def ancestors(repo, subset, x):
204 def ancestors(repo, subset, x):
205 """``ancestors(set)``
205 """``ancestors(set)``
206 Changesets that are ancestors of a changeset in set.
206 Changesets that are ancestors of a changeset in set.
207 """
207 """
208 args = getset(repo, range(len(repo)), x)
208 args = getset(repo, range(len(repo)), x)
209 if not args:
209 if not args:
210 return []
210 return []
211 s = set(repo.changelog.ancestors(*args)) | set(args)
211 s = set(repo.changelog.ancestors(*args)) | set(args)
212 return [r for r in subset if r in s]
212 return [r for r in subset if r in s]
213
213
214 def ancestorspec(repo, subset, x, n):
214 def ancestorspec(repo, subset, x, n):
215 """``set~n``
215 """``set~n``
216 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
216 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
217 """
217 """
218 try:
218 try:
219 n = int(n[1])
219 n = int(n[1])
220 except ValueError:
220 except ValueError:
221 raise error.ParseError(_("~ expects a number"))
221 raise error.ParseError(_("~ expects a number"))
222 ps = set()
222 ps = set()
223 cl = repo.changelog
223 cl = repo.changelog
224 for r in getset(repo, subset, x):
224 for r in getset(repo, subset, x):
225 for i in range(n):
225 for i in range(n):
226 r = cl.parentrevs(r)[0]
226 r = cl.parentrevs(r)[0]
227 ps.add(r)
227 ps.add(r)
228 return [r for r in subset if r in ps]
228 return [r for r in subset if r in ps]
229
229
230 def author(repo, subset, x):
230 def author(repo, subset, x):
231 """``author(string)``
231 """``author(string)``
232 Alias for ``user(string)``.
232 Alias for ``user(string)``.
233 """
233 """
234 # i18n: "author" is a keyword
234 # i18n: "author" is a keyword
235 n = getstring(x, _("author requires a string")).lower()
235 n = getstring(x, _("author requires a string")).lower()
236 return [r for r in subset if n in repo[r].user().lower()]
236 return [r for r in subset if n in repo[r].user().lower()]
237
237
238 def bisected(repo, subset, x):
238 def bisected(repo, subset, x):
239 """``bisected(string)``
239 """``bisected(string)``
240 Changesets marked in the specified bisect state (good, bad, skip).
240 Changesets marked in the specified bisect state (good, bad, skip).
241 """
241 """
242 state = getstring(x, _("bisect requires a string")).lower()
242 state = getstring(x, _("bisect requires a string")).lower()
243 if state not in ('good', 'bad', 'skip', 'unknown'):
243 if state not in ('good', 'bad', 'skip', 'unknown'):
244 raise error.ParseError(_('invalid bisect state'))
244 raise error.ParseError(_('invalid bisect state'))
245 marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state])
245 marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state])
246 return [r for r in subset if r in marked]
246 return [r for r in subset if r in marked]
247
247
248 def bookmark(repo, subset, x):
248 def bookmark(repo, subset, x):
249 """``bookmark([name])``
249 """``bookmark([name])``
250 The named bookmark or all bookmarks.
250 The named bookmark or all bookmarks.
251 """
251 """
252 # i18n: "bookmark" is a keyword
252 # i18n: "bookmark" is a keyword
253 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
253 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
254 if args:
254 if args:
255 bm = getstring(args[0],
255 bm = getstring(args[0],
256 # i18n: "bookmark" is a keyword
256 # i18n: "bookmark" is a keyword
257 _('the argument to bookmark must be a string'))
257 _('the argument to bookmark must be a string'))
258 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
258 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
259 if not bmrev:
259 if not bmrev:
260 raise util.Abort(_("bookmark '%s' does not exist") % bm)
260 raise util.Abort(_("bookmark '%s' does not exist") % bm)
261 bmrev = repo[bmrev].rev()
261 bmrev = repo[bmrev].rev()
262 return [r for r in subset if r == bmrev]
262 return [r for r in subset if r == bmrev]
263 bms = set([repo[r].rev()
263 bms = set([repo[r].rev()
264 for r in bookmarksmod.listbookmarks(repo).values()])
264 for r in bookmarksmod.listbookmarks(repo).values()])
265 return [r for r in subset if r in bms]
265 return [r for r in subset if r in bms]
266
266
267 def branch(repo, subset, x):
267 def branch(repo, subset, x):
268 """``branch(string or set)``
268 """``branch(string or set)``
269 All changesets belonging to the given branch or the branches of the given
269 All changesets belonging to the given branch or the branches of the given
270 changesets.
270 changesets.
271 """
271 """
272 try:
272 try:
273 b = getstring(x, '')
273 b = getstring(x, '')
274 if b in repo.branchmap():
274 if b in repo.branchmap():
275 return [r for r in subset if repo[r].branch() == b]
275 return [r for r in subset if repo[r].branch() == b]
276 except error.ParseError:
276 except error.ParseError:
277 # not a string, but another revspec, e.g. tip()
277 # not a string, but another revspec, e.g. tip()
278 pass
278 pass
279
279
280 s = getset(repo, range(len(repo)), x)
280 s = getset(repo, range(len(repo)), x)
281 b = set()
281 b = set()
282 for r in s:
282 for r in s:
283 b.add(repo[r].branch())
283 b.add(repo[r].branch())
284 s = set(s)
284 s = set(s)
285 return [r for r in subset if r in s or repo[r].branch() in b]
285 return [r for r in subset if r in s or repo[r].branch() in b]
286
286
287 def checkstatus(repo, subset, pat, field):
287 def checkstatus(repo, subset, pat, field):
288 m = matchmod.match(repo.root, repo.getcwd(), [pat])
288 m = matchmod.match(repo.root, repo.getcwd(), [pat])
289 s = []
289 s = []
290 fast = (m.files() == [pat])
290 fast = (m.files() == [pat])
291 for r in subset:
291 for r in subset:
292 c = repo[r]
292 c = repo[r]
293 if fast:
293 if fast:
294 if pat not in c.files():
294 if pat not in c.files():
295 continue
295 continue
296 else:
296 else:
297 for f in c.files():
297 for f in c.files():
298 if m(f):
298 if m(f):
299 break
299 break
300 else:
300 else:
301 continue
301 continue
302 files = repo.status(c.p1().node(), c.node())[field]
302 files = repo.status(c.p1().node(), c.node())[field]
303 if fast:
303 if fast:
304 if pat in files:
304 if pat in files:
305 s.append(r)
305 s.append(r)
306 else:
306 else:
307 for f in files:
307 for f in files:
308 if m(f):
308 if m(f):
309 s.append(r)
309 s.append(r)
310 break
310 break
311 return s
311 return s
312
312
313 def children(repo, subset, x):
313 def children(repo, subset, x):
314 """``children(set)``
314 """``children(set)``
315 Child changesets of changesets in set.
315 Child changesets of changesets in set.
316 """
316 """
317 cs = set()
317 cs = set()
318 cl = repo.changelog
318 cl = repo.changelog
319 s = set(getset(repo, range(len(repo)), x))
319 s = set(getset(repo, range(len(repo)), x))
320 for r in xrange(0, len(repo)):
320 for r in xrange(0, len(repo)):
321 for p in cl.parentrevs(r):
321 for p in cl.parentrevs(r):
322 if p in s:
322 if p in s:
323 cs.add(r)
323 cs.add(r)
324 return [r for r in subset if r in cs]
324 return [r for r in subset if r in cs]
325
325
326 def closed(repo, subset, x):
326 def closed(repo, subset, x):
327 """``closed()``
327 """``closed()``
328 Changeset is closed.
328 Changeset is closed.
329 """
329 """
330 # i18n: "closed" is a keyword
330 # i18n: "closed" is a keyword
331 getargs(x, 0, 0, _("closed takes no arguments"))
331 getargs(x, 0, 0, _("closed takes no arguments"))
332 return [r for r in subset if repo[r].extra().get('close')]
332 return [r for r in subset if repo[r].extra().get('close')]
333
333
334 def contains(repo, subset, x):
334 def contains(repo, subset, x):
335 """``contains(pattern)``
335 """``contains(pattern)``
336 Revision contains pattern.
336 Revision contains pattern.
337 """
337 """
338 # i18n: "contains" is a keyword
338 # i18n: "contains" is a keyword
339 pat = getstring(x, _("contains requires a pattern"))
339 pat = getstring(x, _("contains requires a pattern"))
340 m = matchmod.match(repo.root, repo.getcwd(), [pat])
340 m = matchmod.match(repo.root, repo.getcwd(), [pat])
341 s = []
341 s = []
342 if m.files() == [pat]:
342 if m.files() == [pat]:
343 for r in subset:
343 for r in subset:
344 if pat in repo[r]:
344 if pat in repo[r]:
345 s.append(r)
345 s.append(r)
346 else:
346 else:
347 for r in subset:
347 for r in subset:
348 for f in repo[r].manifest():
348 for f in repo[r].manifest():
349 if m(f):
349 if m(f):
350 s.append(r)
350 s.append(r)
351 break
351 break
352 return s
352 return s
353
353
354 def date(repo, subset, x):
354 def date(repo, subset, x):
355 """``date(interval)``
355 """``date(interval)``
356 Changesets within the interval, see :hg:`help dates`.
356 Changesets within the interval, see :hg:`help dates`.
357 """
357 """
358 # i18n: "date" is a keyword
358 # i18n: "date" is a keyword
359 ds = getstring(x, _("date requires a string"))
359 ds = getstring(x, _("date requires a string"))
360 dm = util.matchdate(ds)
360 dm = util.matchdate(ds)
361 return [r for r in subset if dm(repo[r].date()[0])]
361 return [r for r in subset if dm(repo[r].date()[0])]
362
362
363 def descendants(repo, subset, x):
363 def descendants(repo, subset, x):
364 """``descendants(set)``
364 """``descendants(set)``
365 Changesets which are descendants of changesets in set.
365 Changesets which are descendants of changesets in set.
366 """
366 """
367 args = getset(repo, range(len(repo)), x)
367 args = getset(repo, range(len(repo)), x)
368 if not args:
368 if not args:
369 return []
369 return []
370 s = set(repo.changelog.descendants(*args)) | set(args)
370 s = set(repo.changelog.descendants(*args)) | set(args)
371 return [r for r in subset if r in s]
371 return [r for r in subset if r in s]
372
372
373 def filelog(repo, subset, x):
374 """``filelog(pattern)``
375 Changesets connected to the specified filelog.
376 """
377
378 pat = getstring(x, _("filelog requires a pattern"))
379 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
380 s = set()
381
382 if not m.anypats():
383 for f in m.files():
384 fl = repo.file(f)
385 for fr in fl:
386 s.add(fl.linkrev(fr))
387 else:
388 for f in repo[None]:
389 if m(f):
390 fl = repo.file(f)
391 for fr in fl:
392 s.add(fl.linkrev(fr))
393
394 return [r for r in subset if r in s]
395
373 def follow(repo, subset, x):
396 def follow(repo, subset, x):
374 """``follow()``
397 """``follow()``
375 An alias for ``::.`` (ancestors of the working copy's first parent).
398 An alias for ``::.`` (ancestors of the working copy's first parent).
376 """
399 """
377 # i18n: "follow" is a keyword
400 # i18n: "follow" is a keyword
378 getargs(x, 0, 0, _("follow takes no arguments"))
401 getargs(x, 0, 0, _("follow takes no arguments"))
379 p = repo['.'].rev()
402 p = repo['.'].rev()
380 s = set(repo.changelog.ancestors(p)) | set([p])
403 s = set(repo.changelog.ancestors(p)) | set([p])
381 return [r for r in subset if r in s]
404 return [r for r in subset if r in s]
382
405
383 def getall(repo, subset, x):
406 def getall(repo, subset, x):
384 """``all()``
407 """``all()``
385 All changesets, the same as ``0:tip``.
408 All changesets, the same as ``0:tip``.
386 """
409 """
387 # i18n: "all" is a keyword
410 # i18n: "all" is a keyword
388 getargs(x, 0, 0, _("all takes no arguments"))
411 getargs(x, 0, 0, _("all takes no arguments"))
389 return subset
412 return subset
390
413
391 def grep(repo, subset, x):
414 def grep(repo, subset, x):
392 """``grep(regex)``
415 """``grep(regex)``
393 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
416 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
394 to ensure special escape characters are handled correctly.
417 to ensure special escape characters are handled correctly.
395 """
418 """
396 try:
419 try:
397 # i18n: "grep" is a keyword
420 # i18n: "grep" is a keyword
398 gr = re.compile(getstring(x, _("grep requires a string")))
421 gr = re.compile(getstring(x, _("grep requires a string")))
399 except re.error, e:
422 except re.error, e:
400 raise error.ParseError(_('invalid match pattern: %s') % e)
423 raise error.ParseError(_('invalid match pattern: %s') % e)
401 l = []
424 l = []
402 for r in subset:
425 for r in subset:
403 c = repo[r]
426 c = repo[r]
404 for e in c.files() + [c.user(), c.description()]:
427 for e in c.files() + [c.user(), c.description()]:
405 if gr.search(e):
428 if gr.search(e):
406 l.append(r)
429 l.append(r)
407 break
430 break
408 return l
431 return l
409
432
410 def hasfile(repo, subset, x):
433 def hasfile(repo, subset, x):
411 """``file(pattern)``
434 """``file(pattern)``
412 Changesets affecting files matched by pattern.
435 Changesets affecting files matched by pattern.
413 """
436 """
414 # i18n: "file" is a keyword
437 # i18n: "file" is a keyword
415 pat = getstring(x, _("file requires a pattern"))
438 pat = getstring(x, _("file requires a pattern"))
416 m = matchmod.match(repo.root, repo.getcwd(), [pat])
439 m = matchmod.match(repo.root, repo.getcwd(), [pat])
417 s = []
440 s = []
418 for r in subset:
441 for r in subset:
419 for f in repo[r].files():
442 for f in repo[r].files():
420 if m(f):
443 if m(f):
421 s.append(r)
444 s.append(r)
422 break
445 break
423 return s
446 return s
424
447
425 def head(repo, subset, x):
448 def head(repo, subset, x):
426 """``head()``
449 """``head()``
427 Changeset is a named branch head.
450 Changeset is a named branch head.
428 """
451 """
429 # i18n: "head" is a keyword
452 # i18n: "head" is a keyword
430 getargs(x, 0, 0, _("head takes no arguments"))
453 getargs(x, 0, 0, _("head takes no arguments"))
431 hs = set()
454 hs = set()
432 for b, ls in repo.branchmap().iteritems():
455 for b, ls in repo.branchmap().iteritems():
433 hs.update(repo[h].rev() for h in ls)
456 hs.update(repo[h].rev() for h in ls)
434 return [r for r in subset if r in hs]
457 return [r for r in subset if r in hs]
435
458
436 def heads(repo, subset, x):
459 def heads(repo, subset, x):
437 """``heads(set)``
460 """``heads(set)``
438 Members of set with no children in set.
461 Members of set with no children in set.
439 """
462 """
440 s = getset(repo, subset, x)
463 s = getset(repo, subset, x)
441 ps = set(parents(repo, subset, x))
464 ps = set(parents(repo, subset, x))
442 return [r for r in s if r not in ps]
465 return [r for r in s if r not in ps]
443
466
444 def keyword(repo, subset, x):
467 def keyword(repo, subset, x):
445 """``keyword(string)``
468 """``keyword(string)``
446 Search commit message, user name, and names of changed files for
469 Search commit message, user name, and names of changed files for
447 string.
470 string.
448 """
471 """
449 # i18n: "keyword" is a keyword
472 # i18n: "keyword" is a keyword
450 kw = getstring(x, _("keyword requires a string")).lower()
473 kw = getstring(x, _("keyword requires a string")).lower()
451 l = []
474 l = []
452 for r in subset:
475 for r in subset:
453 c = repo[r]
476 c = repo[r]
454 t = " ".join(c.files() + [c.user(), c.description()])
477 t = " ".join(c.files() + [c.user(), c.description()])
455 if kw in t.lower():
478 if kw in t.lower():
456 l.append(r)
479 l.append(r)
457 return l
480 return l
458
481
459 def limit(repo, subset, x):
482 def limit(repo, subset, x):
460 """``limit(set, n)``
483 """``limit(set, n)``
461 First n members of set.
484 First n members of set.
462 """
485 """
463 # i18n: "limit" is a keyword
486 # i18n: "limit" is a keyword
464 l = getargs(x, 2, 2, _("limit requires two arguments"))
487 l = getargs(x, 2, 2, _("limit requires two arguments"))
465 try:
488 try:
466 # i18n: "limit" is a keyword
489 # i18n: "limit" is a keyword
467 lim = int(getstring(l[1], _("limit requires a number")))
490 lim = int(getstring(l[1], _("limit requires a number")))
468 except ValueError:
491 except ValueError:
469 # i18n: "limit" is a keyword
492 # i18n: "limit" is a keyword
470 raise error.ParseError(_("limit expects a number"))
493 raise error.ParseError(_("limit expects a number"))
471 ss = set(subset)
494 ss = set(subset)
472 os = getset(repo, range(len(repo)), l[0])[:lim]
495 os = getset(repo, range(len(repo)), l[0])[:lim]
473 return [r for r in os if r in ss]
496 return [r for r in os if r in ss]
474
497
475 def last(repo, subset, x):
498 def last(repo, subset, x):
476 """``last(set, n)``
499 """``last(set, n)``
477 Last n members of set.
500 Last n members of set.
478 """
501 """
479 # i18n: "last" is a keyword
502 # i18n: "last" is a keyword
480 l = getargs(x, 2, 2, _("last requires two arguments"))
503 l = getargs(x, 2, 2, _("last requires two arguments"))
481 try:
504 try:
482 # i18n: "last" is a keyword
505 # i18n: "last" is a keyword
483 lim = int(getstring(l[1], _("last requires a number")))
506 lim = int(getstring(l[1], _("last requires a number")))
484 except ValueError:
507 except ValueError:
485 # i18n: "last" is a keyword
508 # i18n: "last" is a keyword
486 raise error.ParseError(_("last expects a number"))
509 raise error.ParseError(_("last expects a number"))
487 ss = set(subset)
510 ss = set(subset)
488 os = getset(repo, range(len(repo)), l[0])[-lim:]
511 os = getset(repo, range(len(repo)), l[0])[-lim:]
489 return [r for r in os if r in ss]
512 return [r for r in os if r in ss]
490
513
491 def maxrev(repo, subset, x):
514 def maxrev(repo, subset, x):
492 """``max(set)``
515 """``max(set)``
493 Changeset with highest revision number in set.
516 Changeset with highest revision number in set.
494 """
517 """
495 os = getset(repo, range(len(repo)), x)
518 os = getset(repo, range(len(repo)), x)
496 if os:
519 if os:
497 m = max(os)
520 m = max(os)
498 if m in subset:
521 if m in subset:
499 return [m]
522 return [m]
500 return []
523 return []
501
524
502 def merge(repo, subset, x):
525 def merge(repo, subset, x):
503 """``merge()``
526 """``merge()``
504 Changeset is a merge changeset.
527 Changeset is a merge changeset.
505 """
528 """
506 # i18n: "merge" is a keyword
529 # i18n: "merge" is a keyword
507 getargs(x, 0, 0, _("merge takes no arguments"))
530 getargs(x, 0, 0, _("merge takes no arguments"))
508 cl = repo.changelog
531 cl = repo.changelog
509 return [r for r in subset if cl.parentrevs(r)[1] != -1]
532 return [r for r in subset if cl.parentrevs(r)[1] != -1]
510
533
511 def minrev(repo, subset, x):
534 def minrev(repo, subset, x):
512 """``min(set)``
535 """``min(set)``
513 Changeset with lowest revision number in set.
536 Changeset with lowest revision number in set.
514 """
537 """
515 os = getset(repo, range(len(repo)), x)
538 os = getset(repo, range(len(repo)), x)
516 if os:
539 if os:
517 m = min(os)
540 m = min(os)
518 if m in subset:
541 if m in subset:
519 return [m]
542 return [m]
520 return []
543 return []
521
544
522 def modifies(repo, subset, x):
545 def modifies(repo, subset, x):
523 """``modifies(pattern)``
546 """``modifies(pattern)``
524 Changesets modifying files matched by pattern.
547 Changesets modifying files matched by pattern.
525 """
548 """
526 # i18n: "modifies" is a keyword
549 # i18n: "modifies" is a keyword
527 pat = getstring(x, _("modifies requires a pattern"))
550 pat = getstring(x, _("modifies requires a pattern"))
528 return checkstatus(repo, subset, pat, 0)
551 return checkstatus(repo, subset, pat, 0)
529
552
530 def node(repo, subset, x):
553 def node(repo, subset, x):
531 """``id(string)``
554 """``id(string)``
532 Revision non-ambiguously specified by the given hex string prefix.
555 Revision non-ambiguously specified by the given hex string prefix.
533 """
556 """
534 # i18n: "id" is a keyword
557 # i18n: "id" is a keyword
535 l = getargs(x, 1, 1, _("id requires one argument"))
558 l = getargs(x, 1, 1, _("id requires one argument"))
536 # i18n: "id" is a keyword
559 # i18n: "id" is a keyword
537 n = getstring(l[0], _("id requires a string"))
560 n = getstring(l[0], _("id requires a string"))
538 if len(n) == 40:
561 if len(n) == 40:
539 rn = repo[n].rev()
562 rn = repo[n].rev()
540 else:
563 else:
541 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
564 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
542 return [r for r in subset if r == rn]
565 return [r for r in subset if r == rn]
543
566
544 def outgoing(repo, subset, x):
567 def outgoing(repo, subset, x):
545 """``outgoing([path])``
568 """``outgoing([path])``
546 Changesets not found in the specified destination repository, or the
569 Changesets not found in the specified destination repository, or the
547 default push location.
570 default push location.
548 """
571 """
549 import hg # avoid start-up nasties
572 import hg # avoid start-up nasties
550 # i18n: "outgoing" is a keyword
573 # i18n: "outgoing" is a keyword
551 l = getargs(x, 0, 1, _("outgoing requires a repository path"))
574 l = getargs(x, 0, 1, _("outgoing requires a repository path"))
552 # i18n: "outgoing" is a keyword
575 # i18n: "outgoing" is a keyword
553 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
576 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
554 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
577 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
555 dest, branches = hg.parseurl(dest)
578 dest, branches = hg.parseurl(dest)
556 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
579 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
557 if revs:
580 if revs:
558 revs = [repo.lookup(rev) for rev in revs]
581 revs = [repo.lookup(rev) for rev in revs]
559 other = hg.repository(hg.remoteui(repo, {}), dest)
582 other = hg.repository(hg.remoteui(repo, {}), dest)
560 repo.ui.pushbuffer()
583 repo.ui.pushbuffer()
561 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
584 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
562 repo.ui.popbuffer()
585 repo.ui.popbuffer()
563 cl = repo.changelog
586 cl = repo.changelog
564 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
587 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
565 return [r for r in subset if r in o]
588 return [r for r in subset if r in o]
566
589
567 def p1(repo, subset, x):
590 def p1(repo, subset, x):
568 """``p1([set])``
591 """``p1([set])``
569 First parent of changesets in set, or the working directory.
592 First parent of changesets in set, or the working directory.
570 """
593 """
571 if x is None:
594 if x is None:
572 p = repo[x].p1().rev()
595 p = repo[x].p1().rev()
573 return [r for r in subset if r == p]
596 return [r for r in subset if r == p]
574
597
575 ps = set()
598 ps = set()
576 cl = repo.changelog
599 cl = repo.changelog
577 for r in getset(repo, range(len(repo)), x):
600 for r in getset(repo, range(len(repo)), x):
578 ps.add(cl.parentrevs(r)[0])
601 ps.add(cl.parentrevs(r)[0])
579 return [r for r in subset if r in ps]
602 return [r for r in subset if r in ps]
580
603
581 def p2(repo, subset, x):
604 def p2(repo, subset, x):
582 """``p2([set])``
605 """``p2([set])``
583 Second parent of changesets in set, or the working directory.
606 Second parent of changesets in set, or the working directory.
584 """
607 """
585 if x is None:
608 if x is None:
586 ps = repo[x].parents()
609 ps = repo[x].parents()
587 try:
610 try:
588 p = ps[1].rev()
611 p = ps[1].rev()
589 return [r for r in subset if r == p]
612 return [r for r in subset if r == p]
590 except IndexError:
613 except IndexError:
591 return []
614 return []
592
615
593 ps = set()
616 ps = set()
594 cl = repo.changelog
617 cl = repo.changelog
595 for r in getset(repo, range(len(repo)), x):
618 for r in getset(repo, range(len(repo)), x):
596 ps.add(cl.parentrevs(r)[1])
619 ps.add(cl.parentrevs(r)[1])
597 return [r for r in subset if r in ps]
620 return [r for r in subset if r in ps]
598
621
599 def parents(repo, subset, x):
622 def parents(repo, subset, x):
600 """``parents([set])``
623 """``parents([set])``
601 The set of all parents for all changesets in set, or the working directory.
624 The set of all parents for all changesets in set, or the working directory.
602 """
625 """
603 if x is None:
626 if x is None:
604 ps = tuple(p.rev() for p in repo[x].parents())
627 ps = tuple(p.rev() for p in repo[x].parents())
605 return [r for r in subset if r in ps]
628 return [r for r in subset if r in ps]
606
629
607 ps = set()
630 ps = set()
608 cl = repo.changelog
631 cl = repo.changelog
609 for r in getset(repo, range(len(repo)), x):
632 for r in getset(repo, range(len(repo)), x):
610 ps.update(cl.parentrevs(r))
633 ps.update(cl.parentrevs(r))
611 return [r for r in subset if r in ps]
634 return [r for r in subset if r in ps]
612
635
613 def parentspec(repo, subset, x, n):
636 def parentspec(repo, subset, x, n):
614 """``set^0``
637 """``set^0``
615 The set.
638 The set.
616 ``set^1`` (or ``set^``), ``set^2``
639 ``set^1`` (or ``set^``), ``set^2``
617 First or second parent, respectively, of all changesets in set.
640 First or second parent, respectively, of all changesets in set.
618 """
641 """
619 try:
642 try:
620 n = int(n[1])
643 n = int(n[1])
621 if n not in (0, 1, 2):
644 if n not in (0, 1, 2):
622 raise ValueError
645 raise ValueError
623 except ValueError:
646 except ValueError:
624 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
647 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
625 ps = set()
648 ps = set()
626 cl = repo.changelog
649 cl = repo.changelog
627 for r in getset(repo, subset, x):
650 for r in getset(repo, subset, x):
628 if n == 0:
651 if n == 0:
629 ps.add(r)
652 ps.add(r)
630 elif n == 1:
653 elif n == 1:
631 ps.add(cl.parentrevs(r)[0])
654 ps.add(cl.parentrevs(r)[0])
632 elif n == 2:
655 elif n == 2:
633 parents = cl.parentrevs(r)
656 parents = cl.parentrevs(r)
634 if len(parents) > 1:
657 if len(parents) > 1:
635 ps.add(parents[1])
658 ps.add(parents[1])
636 return [r for r in subset if r in ps]
659 return [r for r in subset if r in ps]
637
660
638 def present(repo, subset, x):
661 def present(repo, subset, x):
639 """``present(set)``
662 """``present(set)``
640 An empty set, if any revision in set isn't found; otherwise,
663 An empty set, if any revision in set isn't found; otherwise,
641 all revisions in set.
664 all revisions in set.
642 """
665 """
643 try:
666 try:
644 return getset(repo, subset, x)
667 return getset(repo, subset, x)
645 except error.RepoLookupError:
668 except error.RepoLookupError:
646 return []
669 return []
647
670
648 def removes(repo, subset, x):
671 def removes(repo, subset, x):
649 """``removes(pattern)``
672 """``removes(pattern)``
650 Changesets which remove files matching pattern.
673 Changesets which remove files matching pattern.
651 """
674 """
652 # i18n: "removes" is a keyword
675 # i18n: "removes" is a keyword
653 pat = getstring(x, _("removes requires a pattern"))
676 pat = getstring(x, _("removes requires a pattern"))
654 return checkstatus(repo, subset, pat, 2)
677 return checkstatus(repo, subset, pat, 2)
655
678
656 def rev(repo, subset, x):
679 def rev(repo, subset, x):
657 """``rev(number)``
680 """``rev(number)``
658 Revision with the given numeric identifier.
681 Revision with the given numeric identifier.
659 """
682 """
660 # i18n: "rev" is a keyword
683 # i18n: "rev" is a keyword
661 l = getargs(x, 1, 1, _("rev requires one argument"))
684 l = getargs(x, 1, 1, _("rev requires one argument"))
662 try:
685 try:
663 # i18n: "rev" is a keyword
686 # i18n: "rev" is a keyword
664 l = int(getstring(l[0], _("rev requires a number")))
687 l = int(getstring(l[0], _("rev requires a number")))
665 except ValueError:
688 except ValueError:
666 # i18n: "rev" is a keyword
689 # i18n: "rev" is a keyword
667 raise error.ParseError(_("rev expects a number"))
690 raise error.ParseError(_("rev expects a number"))
668 return [r for r in subset if r == l]
691 return [r for r in subset if r == l]
669
692
670 def reverse(repo, subset, x):
693 def reverse(repo, subset, x):
671 """``reverse(set)``
694 """``reverse(set)``
672 Reverse order of set.
695 Reverse order of set.
673 """
696 """
674 l = getset(repo, subset, x)
697 l = getset(repo, subset, x)
675 l.reverse()
698 l.reverse()
676 return l
699 return l
677
700
678 def roots(repo, subset, x):
701 def roots(repo, subset, x):
679 """``roots(set)``
702 """``roots(set)``
680 Changesets with no parent changeset in set.
703 Changesets with no parent changeset in set.
681 """
704 """
682 s = getset(repo, subset, x)
705 s = getset(repo, subset, x)
683 cs = set(children(repo, subset, x))
706 cs = set(children(repo, subset, x))
684 return [r for r in s if r not in cs]
707 return [r for r in s if r not in cs]
685
708
686 def sort(repo, subset, x):
709 def sort(repo, subset, x):
687 """``sort(set[, [-]key...])``
710 """``sort(set[, [-]key...])``
688 Sort set by keys. The default sort order is ascending, specify a key
711 Sort set by keys. The default sort order is ascending, specify a key
689 as ``-key`` to sort in descending order.
712 as ``-key`` to sort in descending order.
690
713
691 The keys can be:
714 The keys can be:
692
715
693 - ``rev`` for the revision number,
716 - ``rev`` for the revision number,
694 - ``branch`` for the branch name,
717 - ``branch`` for the branch name,
695 - ``desc`` for the commit message (description),
718 - ``desc`` for the commit message (description),
696 - ``user`` for user name (``author`` can be used as an alias),
719 - ``user`` for user name (``author`` can be used as an alias),
697 - ``date`` for the commit date
720 - ``date`` for the commit date
698 """
721 """
699 # i18n: "sort" is a keyword
722 # i18n: "sort" is a keyword
700 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
723 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
701 keys = "rev"
724 keys = "rev"
702 if len(l) == 2:
725 if len(l) == 2:
703 keys = getstring(l[1], _("sort spec must be a string"))
726 keys = getstring(l[1], _("sort spec must be a string"))
704
727
705 s = l[0]
728 s = l[0]
706 keys = keys.split()
729 keys = keys.split()
707 l = []
730 l = []
708 def invert(s):
731 def invert(s):
709 return "".join(chr(255 - ord(c)) for c in s)
732 return "".join(chr(255 - ord(c)) for c in s)
710 for r in getset(repo, subset, s):
733 for r in getset(repo, subset, s):
711 c = repo[r]
734 c = repo[r]
712 e = []
735 e = []
713 for k in keys:
736 for k in keys:
714 if k == 'rev':
737 if k == 'rev':
715 e.append(r)
738 e.append(r)
716 elif k == '-rev':
739 elif k == '-rev':
717 e.append(-r)
740 e.append(-r)
718 elif k == 'branch':
741 elif k == 'branch':
719 e.append(c.branch())
742 e.append(c.branch())
720 elif k == '-branch':
743 elif k == '-branch':
721 e.append(invert(c.branch()))
744 e.append(invert(c.branch()))
722 elif k == 'desc':
745 elif k == 'desc':
723 e.append(c.description())
746 e.append(c.description())
724 elif k == '-desc':
747 elif k == '-desc':
725 e.append(invert(c.description()))
748 e.append(invert(c.description()))
726 elif k in 'user author':
749 elif k in 'user author':
727 e.append(c.user())
750 e.append(c.user())
728 elif k in '-user -author':
751 elif k in '-user -author':
729 e.append(invert(c.user()))
752 e.append(invert(c.user()))
730 elif k == 'date':
753 elif k == 'date':
731 e.append(c.date()[0])
754 e.append(c.date()[0])
732 elif k == '-date':
755 elif k == '-date':
733 e.append(-c.date()[0])
756 e.append(-c.date()[0])
734 else:
757 else:
735 raise error.ParseError(_("unknown sort key %r") % k)
758 raise error.ParseError(_("unknown sort key %r") % k)
736 e.append(r)
759 e.append(r)
737 l.append(e)
760 l.append(e)
738 l.sort()
761 l.sort()
739 return [e[-1] for e in l]
762 return [e[-1] for e in l]
740
763
741 def tag(repo, subset, x):
764 def tag(repo, subset, x):
742 """``tag(name)``
765 """``tag(name)``
743 The specified tag by name, or all tagged revisions if no name is given.
766 The specified tag by name, or all tagged revisions if no name is given.
744 """
767 """
745 # i18n: "tag" is a keyword
768 # i18n: "tag" is a keyword
746 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
769 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
747 cl = repo.changelog
770 cl = repo.changelog
748 if args:
771 if args:
749 tn = getstring(args[0],
772 tn = getstring(args[0],
750 # i18n: "tag" is a keyword
773 # i18n: "tag" is a keyword
751 _('the argument to tag must be a string'))
774 _('the argument to tag must be a string'))
752 if not repo.tags().get(tn, None):
775 if not repo.tags().get(tn, None):
753 raise util.Abort(_("tag '%s' does not exist") % tn)
776 raise util.Abort(_("tag '%s' does not exist") % tn)
754 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
777 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
755 else:
778 else:
756 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
779 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
757 return [r for r in subset if r in s]
780 return [r for r in subset if r in s]
758
781
759 def tagged(repo, subset, x):
782 def tagged(repo, subset, x):
760 return tag(repo, subset, x)
783 return tag(repo, subset, x)
761
784
762 def user(repo, subset, x):
785 def user(repo, subset, x):
763 """``user(string)``
786 """``user(string)``
764 User name is string.
787 User name is string.
765 """
788 """
766 return author(repo, subset, x)
789 return author(repo, subset, x)
767
790
768 symbols = {
791 symbols = {
769 "adds": adds,
792 "adds": adds,
770 "all": getall,
793 "all": getall,
771 "ancestor": ancestor,
794 "ancestor": ancestor,
772 "ancestors": ancestors,
795 "ancestors": ancestors,
773 "author": author,
796 "author": author,
774 "bisected": bisected,
797 "bisected": bisected,
775 "bookmark": bookmark,
798 "bookmark": bookmark,
776 "branch": branch,
799 "branch": branch,
777 "children": children,
800 "children": children,
778 "closed": closed,
801 "closed": closed,
779 "contains": contains,
802 "contains": contains,
780 "date": date,
803 "date": date,
781 "descendants": descendants,
804 "descendants": descendants,
782 "file": hasfile,
805 "file": hasfile,
806 "filelog": filelog,
783 "follow": follow,
807 "follow": follow,
784 "grep": grep,
808 "grep": grep,
785 "head": head,
809 "head": head,
786 "heads": heads,
810 "heads": heads,
787 "keyword": keyword,
811 "keyword": keyword,
788 "last": last,
812 "last": last,
789 "limit": limit,
813 "limit": limit,
790 "max": maxrev,
814 "max": maxrev,
791 "min": minrev,
815 "min": minrev,
792 "merge": merge,
816 "merge": merge,
793 "modifies": modifies,
817 "modifies": modifies,
794 "id": node,
818 "id": node,
795 "outgoing": outgoing,
819 "outgoing": outgoing,
796 "p1": p1,
820 "p1": p1,
797 "p2": p2,
821 "p2": p2,
798 "parents": parents,
822 "parents": parents,
799 "present": present,
823 "present": present,
800 "removes": removes,
824 "removes": removes,
801 "reverse": reverse,
825 "reverse": reverse,
802 "rev": rev,
826 "rev": rev,
803 "roots": roots,
827 "roots": roots,
804 "sort": sort,
828 "sort": sort,
805 "tag": tag,
829 "tag": tag,
806 "tagged": tagged,
830 "tagged": tagged,
807 "user": user,
831 "user": user,
808 }
832 }
809
833
810 methods = {
834 methods = {
811 "range": rangeset,
835 "range": rangeset,
812 "string": stringset,
836 "string": stringset,
813 "symbol": symbolset,
837 "symbol": symbolset,
814 "and": andset,
838 "and": andset,
815 "or": orset,
839 "or": orset,
816 "not": notset,
840 "not": notset,
817 "list": listset,
841 "list": listset,
818 "func": func,
842 "func": func,
819 "ancestor": ancestorspec,
843 "ancestor": ancestorspec,
820 "parent": parentspec,
844 "parent": parentspec,
821 "parentpost": p1,
845 "parentpost": p1,
822 }
846 }
823
847
824 def optimize(x, small):
848 def optimize(x, small):
825 if x is None:
849 if x is None:
826 return 0, x
850 return 0, x
827
851
828 smallbonus = 1
852 smallbonus = 1
829 if small:
853 if small:
830 smallbonus = .5
854 smallbonus = .5
831
855
832 op = x[0]
856 op = x[0]
833 if op == 'minus':
857 if op == 'minus':
834 return optimize(('and', x[1], ('not', x[2])), small)
858 return optimize(('and', x[1], ('not', x[2])), small)
835 elif op == 'dagrange':
859 elif op == 'dagrange':
836 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
860 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
837 ('func', ('symbol', 'ancestors'), x[2])), small)
861 ('func', ('symbol', 'ancestors'), x[2])), small)
838 elif op == 'dagrangepre':
862 elif op == 'dagrangepre':
839 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
863 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
840 elif op == 'dagrangepost':
864 elif op == 'dagrangepost':
841 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
865 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
842 elif op == 'rangepre':
866 elif op == 'rangepre':
843 return optimize(('range', ('string', '0'), x[1]), small)
867 return optimize(('range', ('string', '0'), x[1]), small)
844 elif op == 'rangepost':
868 elif op == 'rangepost':
845 return optimize(('range', x[1], ('string', 'tip')), small)
869 return optimize(('range', x[1], ('string', 'tip')), small)
846 elif op == 'negate':
870 elif op == 'negate':
847 return optimize(('string',
871 return optimize(('string',
848 '-' + getstring(x[1], _("can't negate that"))), small)
872 '-' + getstring(x[1], _("can't negate that"))), small)
849 elif op in 'string symbol negate':
873 elif op in 'string symbol negate':
850 return smallbonus, x # single revisions are small
874 return smallbonus, x # single revisions are small
851 elif op == 'and' or op == 'dagrange':
875 elif op == 'and' or op == 'dagrange':
852 wa, ta = optimize(x[1], True)
876 wa, ta = optimize(x[1], True)
853 wb, tb = optimize(x[2], True)
877 wb, tb = optimize(x[2], True)
854 w = min(wa, wb)
878 w = min(wa, wb)
855 if wa > wb:
879 if wa > wb:
856 return w, (op, tb, ta)
880 return w, (op, tb, ta)
857 return w, (op, ta, tb)
881 return w, (op, ta, tb)
858 elif op == 'or':
882 elif op == 'or':
859 wa, ta = optimize(x[1], False)
883 wa, ta = optimize(x[1], False)
860 wb, tb = optimize(x[2], False)
884 wb, tb = optimize(x[2], False)
861 if wb < wa:
885 if wb < wa:
862 wb, wa = wa, wb
886 wb, wa = wa, wb
863 return max(wa, wb), (op, ta, tb)
887 return max(wa, wb), (op, ta, tb)
864 elif op == 'not':
888 elif op == 'not':
865 o = optimize(x[1], not small)
889 o = optimize(x[1], not small)
866 return o[0], (op, o[1])
890 return o[0], (op, o[1])
867 elif op == 'parentpost':
891 elif op == 'parentpost':
868 o = optimize(x[1], small)
892 o = optimize(x[1], small)
869 return o[0], (op, o[1])
893 return o[0], (op, o[1])
870 elif op == 'group':
894 elif op == 'group':
871 return optimize(x[1], small)
895 return optimize(x[1], small)
872 elif op in 'range list parent ancestorspec':
896 elif op in 'range list parent ancestorspec':
873 wa, ta = optimize(x[1], small)
897 wa, ta = optimize(x[1], small)
874 wb, tb = optimize(x[2], small)
898 wb, tb = optimize(x[2], small)
875 return wa + wb, (op, ta, tb)
899 return wa + wb, (op, ta, tb)
876 elif op == 'func':
900 elif op == 'func':
877 f = getstring(x[1], _("not a symbol"))
901 f = getstring(x[1], _("not a symbol"))
878 wa, ta = optimize(x[2], small)
902 wa, ta = optimize(x[2], small)
879 if f in "grep date user author keyword branch file outgoing closed":
903 if f in "grep date user author keyword branch file outgoing closed":
880 w = 10 # slow
904 w = 10 # slow
881 elif f in "modifies adds removes":
905 elif f in "modifies adds removes":
882 w = 30 # slower
906 w = 30 # slower
883 elif f == "contains":
907 elif f == "contains":
884 w = 100 # very slow
908 w = 100 # very slow
885 elif f == "ancestor":
909 elif f == "ancestor":
886 w = 1 * smallbonus
910 w = 1 * smallbonus
887 elif f in "reverse limit":
911 elif f in "reverse limit":
888 w = 0
912 w = 0
889 elif f in "sort":
913 elif f in "sort":
890 w = 10 # assume most sorts look at changelog
914 w = 10 # assume most sorts look at changelog
891 else:
915 else:
892 w = 1
916 w = 1
893 return w + wa, (op, x[1], ta)
917 return w + wa, (op, x[1], ta)
894 return 1, x
918 return 1, x
895
919
896 class revsetalias(object):
920 class revsetalias(object):
897 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
921 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
898 args = ()
922 args = ()
899
923
900 def __init__(self, token, value):
924 def __init__(self, token, value):
901 '''Aliases like:
925 '''Aliases like:
902
926
903 h = heads(default)
927 h = heads(default)
904 b($1) = ancestors($1) - ancestors(default)
928 b($1) = ancestors($1) - ancestors(default)
905 '''
929 '''
906 if isinstance(token, tuple):
930 if isinstance(token, tuple):
907 self.type, self.name = token
931 self.type, self.name = token
908 else:
932 else:
909 m = self.funcre.search(token)
933 m = self.funcre.search(token)
910 if m:
934 if m:
911 self.type = 'func'
935 self.type = 'func'
912 self.name = m.group(1)
936 self.name = m.group(1)
913 self.args = [x.strip() for x in m.group(2).split(',')]
937 self.args = [x.strip() for x in m.group(2).split(',')]
914 else:
938 else:
915 self.type = 'symbol'
939 self.type = 'symbol'
916 self.name = token
940 self.name = token
917
941
918 if isinstance(value, str):
942 if isinstance(value, str):
919 for arg in self.args:
943 for arg in self.args:
920 value = value.replace(arg, repr(arg))
944 value = value.replace(arg, repr(arg))
921 self.replacement, pos = parse(value)
945 self.replacement, pos = parse(value)
922 if pos != len(value):
946 if pos != len(value):
923 raise error.ParseError('invalid token', pos)
947 raise error.ParseError('invalid token', pos)
924 else:
948 else:
925 self.replacement = value
949 self.replacement = value
926
950
927 def match(self, tree):
951 def match(self, tree):
928 if not tree:
952 if not tree:
929 return False
953 return False
930 if tree == (self.type, self.name):
954 if tree == (self.type, self.name):
931 return True
955 return True
932 if tree[0] != self.type:
956 if tree[0] != self.type:
933 return False
957 return False
934 if len(tree) > 1 and tree[1] != ('symbol', self.name):
958 if len(tree) > 1 and tree[1] != ('symbol', self.name):
935 return False
959 return False
936 # 'func' + funcname + args
960 # 'func' + funcname + args
937 if ((self.args and len(tree) != 3) or
961 if ((self.args and len(tree) != 3) or
938 (len(self.args) == 1 and tree[2][0] == 'list') or
962 (len(self.args) == 1 and tree[2][0] == 'list') or
939 (len(self.args) > 1 and (tree[2][0] != 'list' or
963 (len(self.args) > 1 and (tree[2][0] != 'list' or
940 len(tree[2]) - 1 != len(self.args)))):
964 len(tree[2]) - 1 != len(self.args)))):
941 raise error.ParseError('invalid amount of arguments', len(tree) - 2)
965 raise error.ParseError('invalid amount of arguments', len(tree) - 2)
942 return True
966 return True
943
967
944 def replace(self, tree):
968 def replace(self, tree):
945 if tree == (self.type, self.name):
969 if tree == (self.type, self.name):
946 return self.replacement
970 return self.replacement
947 result = self.replacement
971 result = self.replacement
948 def getsubtree(i):
972 def getsubtree(i):
949 if tree[2][0] == 'list':
973 if tree[2][0] == 'list':
950 return tree[2][i + 1]
974 return tree[2][i + 1]
951 return tree[i + 2]
975 return tree[i + 2]
952 for i, v in enumerate(self.args):
976 for i, v in enumerate(self.args):
953 valalias = revsetalias(('string', v), getsubtree(i))
977 valalias = revsetalias(('string', v), getsubtree(i))
954 result = valalias.process(result)
978 result = valalias.process(result)
955 return result
979 return result
956
980
957 def process(self, tree):
981 def process(self, tree):
958 if self.match(tree):
982 if self.match(tree):
959 return self.replace(tree)
983 return self.replace(tree)
960 if isinstance(tree, tuple):
984 if isinstance(tree, tuple):
961 return tuple(map(self.process, tree))
985 return tuple(map(self.process, tree))
962 return tree
986 return tree
963
987
964 def findaliases(ui, tree):
988 def findaliases(ui, tree):
965 for k, v in ui.configitems('revsetalias'):
989 for k, v in ui.configitems('revsetalias'):
966 alias = revsetalias(k, v)
990 alias = revsetalias(k, v)
967 tree = alias.process(tree)
991 tree = alias.process(tree)
968 return tree
992 return tree
969
993
970 parse = parser.parser(tokenize, elements).parse
994 parse = parser.parser(tokenize, elements).parse
971
995
972 def match(ui, spec):
996 def match(ui, spec):
973 if not spec:
997 if not spec:
974 raise error.ParseError(_("empty query"))
998 raise error.ParseError(_("empty query"))
975 tree, pos = parse(spec)
999 tree, pos = parse(spec)
976 if (pos != len(spec)):
1000 if (pos != len(spec)):
977 raise error.ParseError("invalid token", pos)
1001 raise error.ParseError("invalid token", pos)
978 tree = findaliases(ui, tree)
1002 tree = findaliases(ui, tree)
979 weight, tree = optimize(tree, True)
1003 weight, tree = optimize(tree, True)
980 def mfunc(repo, subset):
1004 def mfunc(repo, subset):
981 return getset(repo, subset, tree)
1005 return getset(repo, subset, tree)
982 return mfunc
1006 return mfunc
983
1007
984 # tell hggettext to extract docstrings from these functions:
1008 # tell hggettext to extract docstrings from these functions:
985 i18nfunctions = symbols.values()
1009 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now