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