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