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