##// END OF EJS Templates
revset: add follow(filename) to follow a filename's history across copies
Matt Mackall -
r14343:9ed227f7 default
parent child Browse files
Show More
@@ -1,1009 +1,1027 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):
373 def filelog(repo, subset, x):
374 """``filelog(pattern)``
374 """``filelog(pattern)``
375 Changesets connected to the specified filelog.
375 Changesets connected to the specified filelog.
376 """
376 """
377
377
378 pat = getstring(x, _("filelog requires a pattern"))
378 pat = getstring(x, _("filelog requires a pattern"))
379 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
379 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
380 s = set()
380 s = set()
381
381
382 if not m.anypats():
382 if not m.anypats():
383 for f in m.files():
383 for f in m.files():
384 fl = repo.file(f)
384 fl = repo.file(f)
385 for fr in fl:
385 for fr in fl:
386 s.add(fl.linkrev(fr))
386 s.add(fl.linkrev(fr))
387 else:
387 else:
388 for f in repo[None]:
388 for f in repo[None]:
389 if m(f):
389 if m(f):
390 fl = repo.file(f)
390 fl = repo.file(f)
391 for fr in fl:
391 for fr in fl:
392 s.add(fl.linkrev(fr))
392 s.add(fl.linkrev(fr))
393
393
394 return [r for r in subset if r in s]
394 return [r for r in subset if r in s]
395
395
396 def follow(repo, subset, x):
396 def follow(repo, subset, x):
397 """``follow([file])``
398 An alias for ``::.`` (ancestors of the working copy's first parent).
399 If a filename is specified, the history of the given file is followed,
400 including copies.
401 """
402 # i18n: "follow" is a keyword
403 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
404 p = repo['.'].rev()
405 if l:
406 x = getstring(l[0], "follow expected a filename")
407 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
408 else:
409 s = set(repo.changelog.ancestors(p))
410
411 s |= set([p])
412 return [r for r in subset if r in s]
413
414 def followfile(repo, subset, f):
397 """``follow()``
415 """``follow()``
398 An alias for ``::.`` (ancestors of the working copy's first parent).
416 An alias for ``::.`` (ancestors of the working copy's first parent).
399 """
417 """
400 # i18n: "follow" is a keyword
418 # i18n: "follow" is a keyword
401 getargs(x, 0, 0, _("follow takes no arguments"))
419 getargs(x, 0, 0, _("follow takes no arguments"))
402 p = repo['.'].rev()
420 p = repo['.'].rev()
403 s = set(repo.changelog.ancestors(p)) | set([p])
421 s = set(repo.changelog.ancestors(p)) | set([p])
404 return [r for r in subset if r in s]
422 return [r for r in subset if r in s]
405
423
406 def getall(repo, subset, x):
424 def getall(repo, subset, x):
407 """``all()``
425 """``all()``
408 All changesets, the same as ``0:tip``.
426 All changesets, the same as ``0:tip``.
409 """
427 """
410 # i18n: "all" is a keyword
428 # i18n: "all" is a keyword
411 getargs(x, 0, 0, _("all takes no arguments"))
429 getargs(x, 0, 0, _("all takes no arguments"))
412 return subset
430 return subset
413
431
414 def grep(repo, subset, x):
432 def grep(repo, subset, x):
415 """``grep(regex)``
433 """``grep(regex)``
416 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
434 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
417 to ensure special escape characters are handled correctly.
435 to ensure special escape characters are handled correctly.
418 """
436 """
419 try:
437 try:
420 # i18n: "grep" is a keyword
438 # i18n: "grep" is a keyword
421 gr = re.compile(getstring(x, _("grep requires a string")))
439 gr = re.compile(getstring(x, _("grep requires a string")))
422 except re.error, e:
440 except re.error, e:
423 raise error.ParseError(_('invalid match pattern: %s') % e)
441 raise error.ParseError(_('invalid match pattern: %s') % e)
424 l = []
442 l = []
425 for r in subset:
443 for r in subset:
426 c = repo[r]
444 c = repo[r]
427 for e in c.files() + [c.user(), c.description()]:
445 for e in c.files() + [c.user(), c.description()]:
428 if gr.search(e):
446 if gr.search(e):
429 l.append(r)
447 l.append(r)
430 break
448 break
431 return l
449 return l
432
450
433 def hasfile(repo, subset, x):
451 def hasfile(repo, subset, x):
434 """``file(pattern)``
452 """``file(pattern)``
435 Changesets affecting files matched by pattern.
453 Changesets affecting files matched by pattern.
436 """
454 """
437 # i18n: "file" is a keyword
455 # i18n: "file" is a keyword
438 pat = getstring(x, _("file requires a pattern"))
456 pat = getstring(x, _("file requires a pattern"))
439 m = matchmod.match(repo.root, repo.getcwd(), [pat])
457 m = matchmod.match(repo.root, repo.getcwd(), [pat])
440 s = []
458 s = []
441 for r in subset:
459 for r in subset:
442 for f in repo[r].files():
460 for f in repo[r].files():
443 if m(f):
461 if m(f):
444 s.append(r)
462 s.append(r)
445 break
463 break
446 return s
464 return s
447
465
448 def head(repo, subset, x):
466 def head(repo, subset, x):
449 """``head()``
467 """``head()``
450 Changeset is a named branch head.
468 Changeset is a named branch head.
451 """
469 """
452 # i18n: "head" is a keyword
470 # i18n: "head" is a keyword
453 getargs(x, 0, 0, _("head takes no arguments"))
471 getargs(x, 0, 0, _("head takes no arguments"))
454 hs = set()
472 hs = set()
455 for b, ls in repo.branchmap().iteritems():
473 for b, ls in repo.branchmap().iteritems():
456 hs.update(repo[h].rev() for h in ls)
474 hs.update(repo[h].rev() for h in ls)
457 return [r for r in subset if r in hs]
475 return [r for r in subset if r in hs]
458
476
459 def heads(repo, subset, x):
477 def heads(repo, subset, x):
460 """``heads(set)``
478 """``heads(set)``
461 Members of set with no children in set.
479 Members of set with no children in set.
462 """
480 """
463 s = getset(repo, subset, x)
481 s = getset(repo, subset, x)
464 ps = set(parents(repo, subset, x))
482 ps = set(parents(repo, subset, x))
465 return [r for r in s if r not in ps]
483 return [r for r in s if r not in ps]
466
484
467 def keyword(repo, subset, x):
485 def keyword(repo, subset, x):
468 """``keyword(string)``
486 """``keyword(string)``
469 Search commit message, user name, and names of changed files for
487 Search commit message, user name, and names of changed files for
470 string.
488 string.
471 """
489 """
472 # i18n: "keyword" is a keyword
490 # i18n: "keyword" is a keyword
473 kw = getstring(x, _("keyword requires a string")).lower()
491 kw = getstring(x, _("keyword requires a string")).lower()
474 l = []
492 l = []
475 for r in subset:
493 for r in subset:
476 c = repo[r]
494 c = repo[r]
477 t = " ".join(c.files() + [c.user(), c.description()])
495 t = " ".join(c.files() + [c.user(), c.description()])
478 if kw in t.lower():
496 if kw in t.lower():
479 l.append(r)
497 l.append(r)
480 return l
498 return l
481
499
482 def limit(repo, subset, x):
500 def limit(repo, subset, x):
483 """``limit(set, n)``
501 """``limit(set, n)``
484 First n members of set.
502 First n members of set.
485 """
503 """
486 # i18n: "limit" is a keyword
504 # i18n: "limit" is a keyword
487 l = getargs(x, 2, 2, _("limit requires two arguments"))
505 l = getargs(x, 2, 2, _("limit requires two arguments"))
488 try:
506 try:
489 # i18n: "limit" is a keyword
507 # i18n: "limit" is a keyword
490 lim = int(getstring(l[1], _("limit requires a number")))
508 lim = int(getstring(l[1], _("limit requires a number")))
491 except ValueError:
509 except ValueError:
492 # i18n: "limit" is a keyword
510 # i18n: "limit" is a keyword
493 raise error.ParseError(_("limit expects a number"))
511 raise error.ParseError(_("limit expects a number"))
494 ss = set(subset)
512 ss = set(subset)
495 os = getset(repo, range(len(repo)), l[0])[:lim]
513 os = getset(repo, range(len(repo)), l[0])[:lim]
496 return [r for r in os if r in ss]
514 return [r for r in os if r in ss]
497
515
498 def last(repo, subset, x):
516 def last(repo, subset, x):
499 """``last(set, n)``
517 """``last(set, n)``
500 Last n members of set.
518 Last n members of set.
501 """
519 """
502 # i18n: "last" is a keyword
520 # i18n: "last" is a keyword
503 l = getargs(x, 2, 2, _("last requires two arguments"))
521 l = getargs(x, 2, 2, _("last requires two arguments"))
504 try:
522 try:
505 # i18n: "last" is a keyword
523 # i18n: "last" is a keyword
506 lim = int(getstring(l[1], _("last requires a number")))
524 lim = int(getstring(l[1], _("last requires a number")))
507 except ValueError:
525 except ValueError:
508 # i18n: "last" is a keyword
526 # i18n: "last" is a keyword
509 raise error.ParseError(_("last expects a number"))
527 raise error.ParseError(_("last expects a number"))
510 ss = set(subset)
528 ss = set(subset)
511 os = getset(repo, range(len(repo)), l[0])[-lim:]
529 os = getset(repo, range(len(repo)), l[0])[-lim:]
512 return [r for r in os if r in ss]
530 return [r for r in os if r in ss]
513
531
514 def maxrev(repo, subset, x):
532 def maxrev(repo, subset, x):
515 """``max(set)``
533 """``max(set)``
516 Changeset with highest revision number in set.
534 Changeset with highest revision number in set.
517 """
535 """
518 os = getset(repo, range(len(repo)), x)
536 os = getset(repo, range(len(repo)), x)
519 if os:
537 if os:
520 m = max(os)
538 m = max(os)
521 if m in subset:
539 if m in subset:
522 return [m]
540 return [m]
523 return []
541 return []
524
542
525 def merge(repo, subset, x):
543 def merge(repo, subset, x):
526 """``merge()``
544 """``merge()``
527 Changeset is a merge changeset.
545 Changeset is a merge changeset.
528 """
546 """
529 # i18n: "merge" is a keyword
547 # i18n: "merge" is a keyword
530 getargs(x, 0, 0, _("merge takes no arguments"))
548 getargs(x, 0, 0, _("merge takes no arguments"))
531 cl = repo.changelog
549 cl = repo.changelog
532 return [r for r in subset if cl.parentrevs(r)[1] != -1]
550 return [r for r in subset if cl.parentrevs(r)[1] != -1]
533
551
534 def minrev(repo, subset, x):
552 def minrev(repo, subset, x):
535 """``min(set)``
553 """``min(set)``
536 Changeset with lowest revision number in set.
554 Changeset with lowest revision number in set.
537 """
555 """
538 os = getset(repo, range(len(repo)), x)
556 os = getset(repo, range(len(repo)), x)
539 if os:
557 if os:
540 m = min(os)
558 m = min(os)
541 if m in subset:
559 if m in subset:
542 return [m]
560 return [m]
543 return []
561 return []
544
562
545 def modifies(repo, subset, x):
563 def modifies(repo, subset, x):
546 """``modifies(pattern)``
564 """``modifies(pattern)``
547 Changesets modifying files matched by pattern.
565 Changesets modifying files matched by pattern.
548 """
566 """
549 # i18n: "modifies" is a keyword
567 # i18n: "modifies" is a keyword
550 pat = getstring(x, _("modifies requires a pattern"))
568 pat = getstring(x, _("modifies requires a pattern"))
551 return checkstatus(repo, subset, pat, 0)
569 return checkstatus(repo, subset, pat, 0)
552
570
553 def node(repo, subset, x):
571 def node(repo, subset, x):
554 """``id(string)``
572 """``id(string)``
555 Revision non-ambiguously specified by the given hex string prefix.
573 Revision non-ambiguously specified by the given hex string prefix.
556 """
574 """
557 # i18n: "id" is a keyword
575 # i18n: "id" is a keyword
558 l = getargs(x, 1, 1, _("id requires one argument"))
576 l = getargs(x, 1, 1, _("id requires one argument"))
559 # i18n: "id" is a keyword
577 # i18n: "id" is a keyword
560 n = getstring(l[0], _("id requires a string"))
578 n = getstring(l[0], _("id requires a string"))
561 if len(n) == 40:
579 if len(n) == 40:
562 rn = repo[n].rev()
580 rn = repo[n].rev()
563 else:
581 else:
564 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
582 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
565 return [r for r in subset if r == rn]
583 return [r for r in subset if r == rn]
566
584
567 def outgoing(repo, subset, x):
585 def outgoing(repo, subset, x):
568 """``outgoing([path])``
586 """``outgoing([path])``
569 Changesets not found in the specified destination repository, or the
587 Changesets not found in the specified destination repository, or the
570 default push location.
588 default push location.
571 """
589 """
572 import hg # avoid start-up nasties
590 import hg # avoid start-up nasties
573 # i18n: "outgoing" is a keyword
591 # i18n: "outgoing" is a keyword
574 l = getargs(x, 0, 1, _("outgoing requires a repository path"))
592 l = getargs(x, 0, 1, _("outgoing requires a repository path"))
575 # i18n: "outgoing" is a keyword
593 # i18n: "outgoing" is a keyword
576 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
594 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
577 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
595 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
578 dest, branches = hg.parseurl(dest)
596 dest, branches = hg.parseurl(dest)
579 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
597 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
580 if revs:
598 if revs:
581 revs = [repo.lookup(rev) for rev in revs]
599 revs = [repo.lookup(rev) for rev in revs]
582 other = hg.repository(hg.remoteui(repo, {}), dest)
600 other = hg.repository(hg.remoteui(repo, {}), dest)
583 repo.ui.pushbuffer()
601 repo.ui.pushbuffer()
584 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
602 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
585 repo.ui.popbuffer()
603 repo.ui.popbuffer()
586 cl = repo.changelog
604 cl = repo.changelog
587 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
605 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
588 return [r for r in subset if r in o]
606 return [r for r in subset if r in o]
589
607
590 def p1(repo, subset, x):
608 def p1(repo, subset, x):
591 """``p1([set])``
609 """``p1([set])``
592 First parent of changesets in set, or the working directory.
610 First parent of changesets in set, or the working directory.
593 """
611 """
594 if x is None:
612 if x is None:
595 p = repo[x].p1().rev()
613 p = repo[x].p1().rev()
596 return [r for r in subset if r == p]
614 return [r for r in subset if r == p]
597
615
598 ps = set()
616 ps = set()
599 cl = repo.changelog
617 cl = repo.changelog
600 for r in getset(repo, range(len(repo)), x):
618 for r in getset(repo, range(len(repo)), x):
601 ps.add(cl.parentrevs(r)[0])
619 ps.add(cl.parentrevs(r)[0])
602 return [r for r in subset if r in ps]
620 return [r for r in subset if r in ps]
603
621
604 def p2(repo, subset, x):
622 def p2(repo, subset, x):
605 """``p2([set])``
623 """``p2([set])``
606 Second parent of changesets in set, or the working directory.
624 Second parent of changesets in set, or the working directory.
607 """
625 """
608 if x is None:
626 if x is None:
609 ps = repo[x].parents()
627 ps = repo[x].parents()
610 try:
628 try:
611 p = ps[1].rev()
629 p = ps[1].rev()
612 return [r for r in subset if r == p]
630 return [r for r in subset if r == p]
613 except IndexError:
631 except IndexError:
614 return []
632 return []
615
633
616 ps = set()
634 ps = set()
617 cl = repo.changelog
635 cl = repo.changelog
618 for r in getset(repo, range(len(repo)), x):
636 for r in getset(repo, range(len(repo)), x):
619 ps.add(cl.parentrevs(r)[1])
637 ps.add(cl.parentrevs(r)[1])
620 return [r for r in subset if r in ps]
638 return [r for r in subset if r in ps]
621
639
622 def parents(repo, subset, x):
640 def parents(repo, subset, x):
623 """``parents([set])``
641 """``parents([set])``
624 The set of all parents for all changesets in set, or the working directory.
642 The set of all parents for all changesets in set, or the working directory.
625 """
643 """
626 if x is None:
644 if x is None:
627 ps = tuple(p.rev() for p in repo[x].parents())
645 ps = tuple(p.rev() for p in repo[x].parents())
628 return [r for r in subset if r in ps]
646 return [r for r in subset if r in ps]
629
647
630 ps = set()
648 ps = set()
631 cl = repo.changelog
649 cl = repo.changelog
632 for r in getset(repo, range(len(repo)), x):
650 for r in getset(repo, range(len(repo)), x):
633 ps.update(cl.parentrevs(r))
651 ps.update(cl.parentrevs(r))
634 return [r for r in subset if r in ps]
652 return [r for r in subset if r in ps]
635
653
636 def parentspec(repo, subset, x, n):
654 def parentspec(repo, subset, x, n):
637 """``set^0``
655 """``set^0``
638 The set.
656 The set.
639 ``set^1`` (or ``set^``), ``set^2``
657 ``set^1`` (or ``set^``), ``set^2``
640 First or second parent, respectively, of all changesets in set.
658 First or second parent, respectively, of all changesets in set.
641 """
659 """
642 try:
660 try:
643 n = int(n[1])
661 n = int(n[1])
644 if n not in (0, 1, 2):
662 if n not in (0, 1, 2):
645 raise ValueError
663 raise ValueError
646 except ValueError:
664 except ValueError:
647 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
665 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
648 ps = set()
666 ps = set()
649 cl = repo.changelog
667 cl = repo.changelog
650 for r in getset(repo, subset, x):
668 for r in getset(repo, subset, x):
651 if n == 0:
669 if n == 0:
652 ps.add(r)
670 ps.add(r)
653 elif n == 1:
671 elif n == 1:
654 ps.add(cl.parentrevs(r)[0])
672 ps.add(cl.parentrevs(r)[0])
655 elif n == 2:
673 elif n == 2:
656 parents = cl.parentrevs(r)
674 parents = cl.parentrevs(r)
657 if len(parents) > 1:
675 if len(parents) > 1:
658 ps.add(parents[1])
676 ps.add(parents[1])
659 return [r for r in subset if r in ps]
677 return [r for r in subset if r in ps]
660
678
661 def present(repo, subset, x):
679 def present(repo, subset, x):
662 """``present(set)``
680 """``present(set)``
663 An empty set, if any revision in set isn't found; otherwise,
681 An empty set, if any revision in set isn't found; otherwise,
664 all revisions in set.
682 all revisions in set.
665 """
683 """
666 try:
684 try:
667 return getset(repo, subset, x)
685 return getset(repo, subset, x)
668 except error.RepoLookupError:
686 except error.RepoLookupError:
669 return []
687 return []
670
688
671 def removes(repo, subset, x):
689 def removes(repo, subset, x):
672 """``removes(pattern)``
690 """``removes(pattern)``
673 Changesets which remove files matching pattern.
691 Changesets which remove files matching pattern.
674 """
692 """
675 # i18n: "removes" is a keyword
693 # i18n: "removes" is a keyword
676 pat = getstring(x, _("removes requires a pattern"))
694 pat = getstring(x, _("removes requires a pattern"))
677 return checkstatus(repo, subset, pat, 2)
695 return checkstatus(repo, subset, pat, 2)
678
696
679 def rev(repo, subset, x):
697 def rev(repo, subset, x):
680 """``rev(number)``
698 """``rev(number)``
681 Revision with the given numeric identifier.
699 Revision with the given numeric identifier.
682 """
700 """
683 # i18n: "rev" is a keyword
701 # i18n: "rev" is a keyword
684 l = getargs(x, 1, 1, _("rev requires one argument"))
702 l = getargs(x, 1, 1, _("rev requires one argument"))
685 try:
703 try:
686 # i18n: "rev" is a keyword
704 # i18n: "rev" is a keyword
687 l = int(getstring(l[0], _("rev requires a number")))
705 l = int(getstring(l[0], _("rev requires a number")))
688 except ValueError:
706 except ValueError:
689 # i18n: "rev" is a keyword
707 # i18n: "rev" is a keyword
690 raise error.ParseError(_("rev expects a number"))
708 raise error.ParseError(_("rev expects a number"))
691 return [r for r in subset if r == l]
709 return [r for r in subset if r == l]
692
710
693 def reverse(repo, subset, x):
711 def reverse(repo, subset, x):
694 """``reverse(set)``
712 """``reverse(set)``
695 Reverse order of set.
713 Reverse order of set.
696 """
714 """
697 l = getset(repo, subset, x)
715 l = getset(repo, subset, x)
698 l.reverse()
716 l.reverse()
699 return l
717 return l
700
718
701 def roots(repo, subset, x):
719 def roots(repo, subset, x):
702 """``roots(set)``
720 """``roots(set)``
703 Changesets with no parent changeset in set.
721 Changesets with no parent changeset in set.
704 """
722 """
705 s = getset(repo, subset, x)
723 s = getset(repo, subset, x)
706 cs = set(children(repo, subset, x))
724 cs = set(children(repo, subset, x))
707 return [r for r in s if r not in cs]
725 return [r for r in s if r not in cs]
708
726
709 def sort(repo, subset, x):
727 def sort(repo, subset, x):
710 """``sort(set[, [-]key...])``
728 """``sort(set[, [-]key...])``
711 Sort set by keys. The default sort order is ascending, specify a key
729 Sort set by keys. The default sort order is ascending, specify a key
712 as ``-key`` to sort in descending order.
730 as ``-key`` to sort in descending order.
713
731
714 The keys can be:
732 The keys can be:
715
733
716 - ``rev`` for the revision number,
734 - ``rev`` for the revision number,
717 - ``branch`` for the branch name,
735 - ``branch`` for the branch name,
718 - ``desc`` for the commit message (description),
736 - ``desc`` for the commit message (description),
719 - ``user`` for user name (``author`` can be used as an alias),
737 - ``user`` for user name (``author`` can be used as an alias),
720 - ``date`` for the commit date
738 - ``date`` for the commit date
721 """
739 """
722 # i18n: "sort" is a keyword
740 # i18n: "sort" is a keyword
723 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
741 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
724 keys = "rev"
742 keys = "rev"
725 if len(l) == 2:
743 if len(l) == 2:
726 keys = getstring(l[1], _("sort spec must be a string"))
744 keys = getstring(l[1], _("sort spec must be a string"))
727
745
728 s = l[0]
746 s = l[0]
729 keys = keys.split()
747 keys = keys.split()
730 l = []
748 l = []
731 def invert(s):
749 def invert(s):
732 return "".join(chr(255 - ord(c)) for c in s)
750 return "".join(chr(255 - ord(c)) for c in s)
733 for r in getset(repo, subset, s):
751 for r in getset(repo, subset, s):
734 c = repo[r]
752 c = repo[r]
735 e = []
753 e = []
736 for k in keys:
754 for k in keys:
737 if k == 'rev':
755 if k == 'rev':
738 e.append(r)
756 e.append(r)
739 elif k == '-rev':
757 elif k == '-rev':
740 e.append(-r)
758 e.append(-r)
741 elif k == 'branch':
759 elif k == 'branch':
742 e.append(c.branch())
760 e.append(c.branch())
743 elif k == '-branch':
761 elif k == '-branch':
744 e.append(invert(c.branch()))
762 e.append(invert(c.branch()))
745 elif k == 'desc':
763 elif k == 'desc':
746 e.append(c.description())
764 e.append(c.description())
747 elif k == '-desc':
765 elif k == '-desc':
748 e.append(invert(c.description()))
766 e.append(invert(c.description()))
749 elif k in 'user author':
767 elif k in 'user author':
750 e.append(c.user())
768 e.append(c.user())
751 elif k in '-user -author':
769 elif k in '-user -author':
752 e.append(invert(c.user()))
770 e.append(invert(c.user()))
753 elif k == 'date':
771 elif k == 'date':
754 e.append(c.date()[0])
772 e.append(c.date()[0])
755 elif k == '-date':
773 elif k == '-date':
756 e.append(-c.date()[0])
774 e.append(-c.date()[0])
757 else:
775 else:
758 raise error.ParseError(_("unknown sort key %r") % k)
776 raise error.ParseError(_("unknown sort key %r") % k)
759 e.append(r)
777 e.append(r)
760 l.append(e)
778 l.append(e)
761 l.sort()
779 l.sort()
762 return [e[-1] for e in l]
780 return [e[-1] for e in l]
763
781
764 def tag(repo, subset, x):
782 def tag(repo, subset, x):
765 """``tag(name)``
783 """``tag(name)``
766 The specified tag by name, or all tagged revisions if no name is given.
784 The specified tag by name, or all tagged revisions if no name is given.
767 """
785 """
768 # i18n: "tag" is a keyword
786 # i18n: "tag" is a keyword
769 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
787 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
770 cl = repo.changelog
788 cl = repo.changelog
771 if args:
789 if args:
772 tn = getstring(args[0],
790 tn = getstring(args[0],
773 # i18n: "tag" is a keyword
791 # i18n: "tag" is a keyword
774 _('the argument to tag must be a string'))
792 _('the argument to tag must be a string'))
775 if not repo.tags().get(tn, None):
793 if not repo.tags().get(tn, None):
776 raise util.Abort(_("tag '%s' does not exist") % tn)
794 raise util.Abort(_("tag '%s' does not exist") % tn)
777 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
795 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
778 else:
796 else:
779 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
797 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
780 return [r for r in subset if r in s]
798 return [r for r in subset if r in s]
781
799
782 def tagged(repo, subset, x):
800 def tagged(repo, subset, x):
783 return tag(repo, subset, x)
801 return tag(repo, subset, x)
784
802
785 def user(repo, subset, x):
803 def user(repo, subset, x):
786 """``user(string)``
804 """``user(string)``
787 User name is string.
805 User name is string.
788 """
806 """
789 return author(repo, subset, x)
807 return author(repo, subset, x)
790
808
791 symbols = {
809 symbols = {
792 "adds": adds,
810 "adds": adds,
793 "all": getall,
811 "all": getall,
794 "ancestor": ancestor,
812 "ancestor": ancestor,
795 "ancestors": ancestors,
813 "ancestors": ancestors,
796 "author": author,
814 "author": author,
797 "bisected": bisected,
815 "bisected": bisected,
798 "bookmark": bookmark,
816 "bookmark": bookmark,
799 "branch": branch,
817 "branch": branch,
800 "children": children,
818 "children": children,
801 "closed": closed,
819 "closed": closed,
802 "contains": contains,
820 "contains": contains,
803 "date": date,
821 "date": date,
804 "descendants": descendants,
822 "descendants": descendants,
805 "file": hasfile,
823 "file": hasfile,
806 "filelog": filelog,
824 "filelog": filelog,
807 "follow": follow,
825 "follow": follow,
808 "grep": grep,
826 "grep": grep,
809 "head": head,
827 "head": head,
810 "heads": heads,
828 "heads": heads,
811 "keyword": keyword,
829 "keyword": keyword,
812 "last": last,
830 "last": last,
813 "limit": limit,
831 "limit": limit,
814 "max": maxrev,
832 "max": maxrev,
815 "min": minrev,
833 "min": minrev,
816 "merge": merge,
834 "merge": merge,
817 "modifies": modifies,
835 "modifies": modifies,
818 "id": node,
836 "id": node,
819 "outgoing": outgoing,
837 "outgoing": outgoing,
820 "p1": p1,
838 "p1": p1,
821 "p2": p2,
839 "p2": p2,
822 "parents": parents,
840 "parents": parents,
823 "present": present,
841 "present": present,
824 "removes": removes,
842 "removes": removes,
825 "reverse": reverse,
843 "reverse": reverse,
826 "rev": rev,
844 "rev": rev,
827 "roots": roots,
845 "roots": roots,
828 "sort": sort,
846 "sort": sort,
829 "tag": tag,
847 "tag": tag,
830 "tagged": tagged,
848 "tagged": tagged,
831 "user": user,
849 "user": user,
832 }
850 }
833
851
834 methods = {
852 methods = {
835 "range": rangeset,
853 "range": rangeset,
836 "string": stringset,
854 "string": stringset,
837 "symbol": symbolset,
855 "symbol": symbolset,
838 "and": andset,
856 "and": andset,
839 "or": orset,
857 "or": orset,
840 "not": notset,
858 "not": notset,
841 "list": listset,
859 "list": listset,
842 "func": func,
860 "func": func,
843 "ancestor": ancestorspec,
861 "ancestor": ancestorspec,
844 "parent": parentspec,
862 "parent": parentspec,
845 "parentpost": p1,
863 "parentpost": p1,
846 }
864 }
847
865
848 def optimize(x, small):
866 def optimize(x, small):
849 if x is None:
867 if x is None:
850 return 0, x
868 return 0, x
851
869
852 smallbonus = 1
870 smallbonus = 1
853 if small:
871 if small:
854 smallbonus = .5
872 smallbonus = .5
855
873
856 op = x[0]
874 op = x[0]
857 if op == 'minus':
875 if op == 'minus':
858 return optimize(('and', x[1], ('not', x[2])), small)
876 return optimize(('and', x[1], ('not', x[2])), small)
859 elif op == 'dagrange':
877 elif op == 'dagrange':
860 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
878 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
861 ('func', ('symbol', 'ancestors'), x[2])), small)
879 ('func', ('symbol', 'ancestors'), x[2])), small)
862 elif op == 'dagrangepre':
880 elif op == 'dagrangepre':
863 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
881 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
864 elif op == 'dagrangepost':
882 elif op == 'dagrangepost':
865 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
883 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
866 elif op == 'rangepre':
884 elif op == 'rangepre':
867 return optimize(('range', ('string', '0'), x[1]), small)
885 return optimize(('range', ('string', '0'), x[1]), small)
868 elif op == 'rangepost':
886 elif op == 'rangepost':
869 return optimize(('range', x[1], ('string', 'tip')), small)
887 return optimize(('range', x[1], ('string', 'tip')), small)
870 elif op == 'negate':
888 elif op == 'negate':
871 return optimize(('string',
889 return optimize(('string',
872 '-' + getstring(x[1], _("can't negate that"))), small)
890 '-' + getstring(x[1], _("can't negate that"))), small)
873 elif op in 'string symbol negate':
891 elif op in 'string symbol negate':
874 return smallbonus, x # single revisions are small
892 return smallbonus, x # single revisions are small
875 elif op == 'and' or op == 'dagrange':
893 elif op == 'and' or op == 'dagrange':
876 wa, ta = optimize(x[1], True)
894 wa, ta = optimize(x[1], True)
877 wb, tb = optimize(x[2], True)
895 wb, tb = optimize(x[2], True)
878 w = min(wa, wb)
896 w = min(wa, wb)
879 if wa > wb:
897 if wa > wb:
880 return w, (op, tb, ta)
898 return w, (op, tb, ta)
881 return w, (op, ta, tb)
899 return w, (op, ta, tb)
882 elif op == 'or':
900 elif op == 'or':
883 wa, ta = optimize(x[1], False)
901 wa, ta = optimize(x[1], False)
884 wb, tb = optimize(x[2], False)
902 wb, tb = optimize(x[2], False)
885 if wb < wa:
903 if wb < wa:
886 wb, wa = wa, wb
904 wb, wa = wa, wb
887 return max(wa, wb), (op, ta, tb)
905 return max(wa, wb), (op, ta, tb)
888 elif op == 'not':
906 elif op == 'not':
889 o = optimize(x[1], not small)
907 o = optimize(x[1], not small)
890 return o[0], (op, o[1])
908 return o[0], (op, o[1])
891 elif op == 'parentpost':
909 elif op == 'parentpost':
892 o = optimize(x[1], small)
910 o = optimize(x[1], small)
893 return o[0], (op, o[1])
911 return o[0], (op, o[1])
894 elif op == 'group':
912 elif op == 'group':
895 return optimize(x[1], small)
913 return optimize(x[1], small)
896 elif op in 'range list parent ancestorspec':
914 elif op in 'range list parent ancestorspec':
897 wa, ta = optimize(x[1], small)
915 wa, ta = optimize(x[1], small)
898 wb, tb = optimize(x[2], small)
916 wb, tb = optimize(x[2], small)
899 return wa + wb, (op, ta, tb)
917 return wa + wb, (op, ta, tb)
900 elif op == 'func':
918 elif op == 'func':
901 f = getstring(x[1], _("not a symbol"))
919 f = getstring(x[1], _("not a symbol"))
902 wa, ta = optimize(x[2], small)
920 wa, ta = optimize(x[2], small)
903 if f in "grep date user author keyword branch file outgoing closed":
921 if f in "grep date user author keyword branch file outgoing closed":
904 w = 10 # slow
922 w = 10 # slow
905 elif f in "modifies adds removes":
923 elif f in "modifies adds removes":
906 w = 30 # slower
924 w = 30 # slower
907 elif f == "contains":
925 elif f == "contains":
908 w = 100 # very slow
926 w = 100 # very slow
909 elif f == "ancestor":
927 elif f == "ancestor":
910 w = 1 * smallbonus
928 w = 1 * smallbonus
911 elif f in "reverse limit":
929 elif f in "reverse limit":
912 w = 0
930 w = 0
913 elif f in "sort":
931 elif f in "sort":
914 w = 10 # assume most sorts look at changelog
932 w = 10 # assume most sorts look at changelog
915 else:
933 else:
916 w = 1
934 w = 1
917 return w + wa, (op, x[1], ta)
935 return w + wa, (op, x[1], ta)
918 return 1, x
936 return 1, x
919
937
920 class revsetalias(object):
938 class revsetalias(object):
921 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
939 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
922 args = ()
940 args = ()
923
941
924 def __init__(self, token, value):
942 def __init__(self, token, value):
925 '''Aliases like:
943 '''Aliases like:
926
944
927 h = heads(default)
945 h = heads(default)
928 b($1) = ancestors($1) - ancestors(default)
946 b($1) = ancestors($1) - ancestors(default)
929 '''
947 '''
930 if isinstance(token, tuple):
948 if isinstance(token, tuple):
931 self.type, self.name = token
949 self.type, self.name = token
932 else:
950 else:
933 m = self.funcre.search(token)
951 m = self.funcre.search(token)
934 if m:
952 if m:
935 self.type = 'func'
953 self.type = 'func'
936 self.name = m.group(1)
954 self.name = m.group(1)
937 self.args = [x.strip() for x in m.group(2).split(',')]
955 self.args = [x.strip() for x in m.group(2).split(',')]
938 else:
956 else:
939 self.type = 'symbol'
957 self.type = 'symbol'
940 self.name = token
958 self.name = token
941
959
942 if isinstance(value, str):
960 if isinstance(value, str):
943 for arg in self.args:
961 for arg in self.args:
944 value = value.replace(arg, repr(arg))
962 value = value.replace(arg, repr(arg))
945 self.replacement, pos = parse(value)
963 self.replacement, pos = parse(value)
946 if pos != len(value):
964 if pos != len(value):
947 raise error.ParseError('invalid token', pos)
965 raise error.ParseError('invalid token', pos)
948 else:
966 else:
949 self.replacement = value
967 self.replacement = value
950
968
951 def match(self, tree):
969 def match(self, tree):
952 if not tree:
970 if not tree:
953 return False
971 return False
954 if tree == (self.type, self.name):
972 if tree == (self.type, self.name):
955 return True
973 return True
956 if tree[0] != self.type:
974 if tree[0] != self.type:
957 return False
975 return False
958 if len(tree) > 1 and tree[1] != ('symbol', self.name):
976 if len(tree) > 1 and tree[1] != ('symbol', self.name):
959 return False
977 return False
960 # 'func' + funcname + args
978 # 'func' + funcname + args
961 if ((self.args and len(tree) != 3) or
979 if ((self.args and len(tree) != 3) or
962 (len(self.args) == 1 and tree[2][0] == 'list') or
980 (len(self.args) == 1 and tree[2][0] == 'list') or
963 (len(self.args) > 1 and (tree[2][0] != 'list' or
981 (len(self.args) > 1 and (tree[2][0] != 'list' or
964 len(tree[2]) - 1 != len(self.args)))):
982 len(tree[2]) - 1 != len(self.args)))):
965 raise error.ParseError('invalid amount of arguments', len(tree) - 2)
983 raise error.ParseError('invalid amount of arguments', len(tree) - 2)
966 return True
984 return True
967
985
968 def replace(self, tree):
986 def replace(self, tree):
969 if tree == (self.type, self.name):
987 if tree == (self.type, self.name):
970 return self.replacement
988 return self.replacement
971 result = self.replacement
989 result = self.replacement
972 def getsubtree(i):
990 def getsubtree(i):
973 if tree[2][0] == 'list':
991 if tree[2][0] == 'list':
974 return tree[2][i + 1]
992 return tree[2][i + 1]
975 return tree[i + 2]
993 return tree[i + 2]
976 for i, v in enumerate(self.args):
994 for i, v in enumerate(self.args):
977 valalias = revsetalias(('string', v), getsubtree(i))
995 valalias = revsetalias(('string', v), getsubtree(i))
978 result = valalias.process(result)
996 result = valalias.process(result)
979 return result
997 return result
980
998
981 def process(self, tree):
999 def process(self, tree):
982 if self.match(tree):
1000 if self.match(tree):
983 return self.replace(tree)
1001 return self.replace(tree)
984 if isinstance(tree, tuple):
1002 if isinstance(tree, tuple):
985 return tuple(map(self.process, tree))
1003 return tuple(map(self.process, tree))
986 return tree
1004 return tree
987
1005
988 def findaliases(ui, tree):
1006 def findaliases(ui, tree):
989 for k, v in ui.configitems('revsetalias'):
1007 for k, v in ui.configitems('revsetalias'):
990 alias = revsetalias(k, v)
1008 alias = revsetalias(k, v)
991 tree = alias.process(tree)
1009 tree = alias.process(tree)
992 return tree
1010 return tree
993
1011
994 parse = parser.parser(tokenize, elements).parse
1012 parse = parser.parser(tokenize, elements).parse
995
1013
996 def match(ui, spec):
1014 def match(ui, spec):
997 if not spec:
1015 if not spec:
998 raise error.ParseError(_("empty query"))
1016 raise error.ParseError(_("empty query"))
999 tree, pos = parse(spec)
1017 tree, pos = parse(spec)
1000 if (pos != len(spec)):
1018 if (pos != len(spec)):
1001 raise error.ParseError("invalid token", pos)
1019 raise error.ParseError("invalid token", pos)
1002 tree = findaliases(ui, tree)
1020 tree = findaliases(ui, tree)
1003 weight, tree = optimize(tree, True)
1021 weight, tree = optimize(tree, True)
1004 def mfunc(repo, subset):
1022 def mfunc(repo, subset):
1005 return getset(repo, subset, tree)
1023 return getset(repo, subset, tree)
1006 return mfunc
1024 return mfunc
1007
1025
1008 # tell hggettext to extract docstrings from these functions:
1026 # tell hggettext to extract docstrings from these functions:
1009 i18nfunctions = symbols.values()
1027 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now