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