##// END OF EJS Templates
revset and fileset: fix typos in parser error messages
Mads Kiilerich -
r14717:c8ee2729 stable
parent child Browse files
Show More
@@ -1,424 +1,424 b''
1 # fileset.py - file set queries for mercurial
1 # fileset.py - file 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 parser, error, util, merge, re
8 import parser, error, util, merge, re
9 from i18n import _
9 from i18n import _
10
10
11 elements = {
11 elements = {
12 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
12 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
13 "-": (5, ("negate", 19), ("minus", 5)),
13 "-": (5, ("negate", 19), ("minus", 5)),
14 "not": (10, ("not", 10)),
14 "not": (10, ("not", 10)),
15 "!": (10, ("not", 10)),
15 "!": (10, ("not", 10)),
16 "and": (5, None, ("and", 5)),
16 "and": (5, None, ("and", 5)),
17 "&": (5, None, ("and", 5)),
17 "&": (5, None, ("and", 5)),
18 "or": (4, None, ("or", 4)),
18 "or": (4, None, ("or", 4)),
19 "|": (4, None, ("or", 4)),
19 "|": (4, None, ("or", 4)),
20 "+": (4, None, ("or", 4)),
20 "+": (4, None, ("or", 4)),
21 ",": (2, None, ("list", 2)),
21 ",": (2, None, ("list", 2)),
22 ")": (0, None, None),
22 ")": (0, None, None),
23 "symbol": (0, ("symbol",), None),
23 "symbol": (0, ("symbol",), None),
24 "string": (0, ("string",), None),
24 "string": (0, ("string",), None),
25 "end": (0, None, None),
25 "end": (0, None, None),
26 }
26 }
27
27
28 keywords = set(['and', 'or', 'not'])
28 keywords = set(['and', 'or', 'not'])
29
29
30 globchars = ".*{}[]?/\\"
30 globchars = ".*{}[]?/\\"
31
31
32 def tokenize(program):
32 def tokenize(program):
33 pos, l = 0, len(program)
33 pos, l = 0, len(program)
34 while pos < l:
34 while pos < l:
35 c = program[pos]
35 c = program[pos]
36 if c.isspace(): # skip inter-token whitespace
36 if c.isspace(): # skip inter-token whitespace
37 pass
37 pass
38 elif c in "(),-|&+!": # handle simple operators
38 elif c in "(),-|&+!": # handle simple operators
39 yield (c, None, pos)
39 yield (c, None, pos)
40 elif (c in '"\'' or c == 'r' and
40 elif (c in '"\'' or c == 'r' and
41 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
41 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
42 if c == 'r':
42 if c == 'r':
43 pos += 1
43 pos += 1
44 c = program[pos]
44 c = program[pos]
45 decode = lambda x: x
45 decode = lambda x: x
46 else:
46 else:
47 decode = lambda x: x.decode('string-escape')
47 decode = lambda x: x.decode('string-escape')
48 pos += 1
48 pos += 1
49 s = pos
49 s = pos
50 while pos < l: # find closing quote
50 while pos < l: # find closing quote
51 d = program[pos]
51 d = program[pos]
52 if d == '\\': # skip over escaped characters
52 if d == '\\': # skip over escaped characters
53 pos += 2
53 pos += 2
54 continue
54 continue
55 if d == c:
55 if d == c:
56 yield ('string', decode(program[s:pos]), s)
56 yield ('string', decode(program[s:pos]), s)
57 break
57 break
58 pos += 1
58 pos += 1
59 else:
59 else:
60 raise error.ParseError(_("unterminated string"), s)
60 raise error.ParseError(_("unterminated string"), s)
61 elif c.isalnum() or c in globchars or ord(c) > 127:
61 elif c.isalnum() or c in globchars or ord(c) > 127:
62 # gather up a symbol/keyword
62 # gather up a symbol/keyword
63 s = pos
63 s = pos
64 pos += 1
64 pos += 1
65 while pos < l: # find end of symbol
65 while pos < l: # find end of symbol
66 d = program[pos]
66 d = program[pos]
67 if not (d.isalnum() or d in globchars or ord(d) > 127):
67 if not (d.isalnum() or d in globchars or ord(d) > 127):
68 break
68 break
69 pos += 1
69 pos += 1
70 sym = program[s:pos]
70 sym = program[s:pos]
71 if sym in keywords: # operator keywords
71 if sym in keywords: # operator keywords
72 yield (sym, None, s)
72 yield (sym, None, s)
73 else:
73 else:
74 yield ('symbol', sym, s)
74 yield ('symbol', sym, s)
75 pos -= 1
75 pos -= 1
76 else:
76 else:
77 raise error.ParseError(_("syntax error"), pos)
77 raise error.ParseError(_("syntax error"), pos)
78 pos += 1
78 pos += 1
79 yield ('end', None, pos)
79 yield ('end', None, pos)
80
80
81 parse = parser.parser(tokenize, elements).parse
81 parse = parser.parser(tokenize, elements).parse
82
82
83 def getstring(x, err):
83 def getstring(x, err):
84 if x and (x[0] == 'string' or x[0] == 'symbol'):
84 if x and (x[0] == 'string' or x[0] == 'symbol'):
85 return x[1]
85 return x[1]
86 raise error.ParseError(err)
86 raise error.ParseError(err)
87
87
88 def getset(mctx, x):
88 def getset(mctx, x):
89 if not x:
89 if not x:
90 raise error.ParseError(_("missing argument"))
90 raise error.ParseError(_("missing argument"))
91 return methods[x[0]](mctx, *x[1:])
91 return methods[x[0]](mctx, *x[1:])
92
92
93 def stringset(mctx, x):
93 def stringset(mctx, x):
94 m = mctx.matcher([x])
94 m = mctx.matcher([x])
95 return [f for f in mctx.subset if m(f)]
95 return [f for f in mctx.subset if m(f)]
96
96
97 def andset(mctx, x, y):
97 def andset(mctx, x, y):
98 return getset(mctx.narrow(getset(mctx, x)), y)
98 return getset(mctx.narrow(getset(mctx, x)), y)
99
99
100 def orset(mctx, x, y):
100 def orset(mctx, x, y):
101 # needs optimizing
101 # needs optimizing
102 xl = getset(mctx, x)
102 xl = getset(mctx, x)
103 yl = getset(mctx, y)
103 yl = getset(mctx, y)
104 return xl + [f for f in yl if f not in xl]
104 return xl + [f for f in yl if f not in xl]
105
105
106 def notset(mctx, x):
106 def notset(mctx, x):
107 s = set(getset(mctx, x))
107 s = set(getset(mctx, x))
108 return [r for r in mctx.subset if r not in s]
108 return [r for r in mctx.subset if r not in s]
109
109
110 def listset(mctx, a, b):
110 def listset(mctx, a, b):
111 raise error.ParseError(_("can't use a list in this context"))
111 raise error.ParseError(_("can't use a list in this context"))
112
112
113 def modified(mctx, x):
113 def modified(mctx, x):
114 """``modified()``
114 """``modified()``
115 File that is modified according to status.
115 File that is modified according to status.
116 """
116 """
117 getargs(x, 0, 0, _("modified takes no arguments"))
117 getargs(x, 0, 0, _("modified takes no arguments"))
118 s = mctx.status()[0]
118 s = mctx.status()[0]
119 return [f for f in mctx.subset if f in s]
119 return [f for f in mctx.subset if f in s]
120
120
121 def added(mctx, x):
121 def added(mctx, x):
122 """``added()``
122 """``added()``
123 File that is added according to status.
123 File that is added according to status.
124 """
124 """
125 getargs(x, 0, 0, _("added takes no arguments"))
125 getargs(x, 0, 0, _("added takes no arguments"))
126 s = mctx.status()[1]
126 s = mctx.status()[1]
127 return [f for f in mctx.subset if f in s]
127 return [f for f in mctx.subset if f in s]
128
128
129 def removed(mctx, x):
129 def removed(mctx, x):
130 """``removed()``
130 """``removed()``
131 File that is removed according to status.
131 File that is removed according to status.
132 """
132 """
133 getargs(x, 0, 0, _("removed takes no arguments"))
133 getargs(x, 0, 0, _("removed takes no arguments"))
134 s = mctx.status()[2]
134 s = mctx.status()[2]
135 return [f for f in mctx.subset if f in s]
135 return [f for f in mctx.subset if f in s]
136
136
137 def deleted(mctx, x):
137 def deleted(mctx, x):
138 """``deleted()``
138 """``deleted()``
139 File that is deleted according to status.
139 File that is deleted according to status.
140 """
140 """
141 getargs(x, 0, 0, _("deleted takes no arguments"))
141 getargs(x, 0, 0, _("deleted takes no arguments"))
142 s = mctx.status()[3]
142 s = mctx.status()[3]
143 return [f for f in mctx.subset if f in s]
143 return [f for f in mctx.subset if f in s]
144
144
145 def unknown(mctx, x):
145 def unknown(mctx, x):
146 """``unknown()``
146 """``unknown()``
147 File that is unknown according to status. These files will only be
147 File that is unknown according to status. These files will only be
148 considered if this predicate is used.
148 considered if this predicate is used.
149 """
149 """
150 getargs(x, 0, 0, _("unknown takes no arguments"))
150 getargs(x, 0, 0, _("unknown takes no arguments"))
151 s = mctx.status()[4]
151 s = mctx.status()[4]
152 return [f for f in mctx.subset if f in s]
152 return [f for f in mctx.subset if f in s]
153
153
154 def ignored(mctx, x):
154 def ignored(mctx, x):
155 """``ignored()``
155 """``ignored()``
156 File that is ignored according to status. These files will only be
156 File that is ignored according to status. These files will only be
157 considered if this predicate is used.
157 considered if this predicate is used.
158 """
158 """
159 getargs(x, 0, 0, _("ignored takes no arguments"))
159 getargs(x, 0, 0, _("ignored takes no arguments"))
160 s = mctx.status()[5]
160 s = mctx.status()[5]
161 return [f for f in mctx.subset if f in s]
161 return [f for f in mctx.subset if f in s]
162
162
163 def clean(mctx, x):
163 def clean(mctx, x):
164 """``clean()``
164 """``clean()``
165 File that is clean according to status.
165 File that is clean according to status.
166 """
166 """
167 getargs(x, 0, 0, _("clean takes no arguments"))
167 getargs(x, 0, 0, _("clean takes no arguments"))
168 s = mctx.status()[6]
168 s = mctx.status()[6]
169 return [f for f in mctx.subset if f in s]
169 return [f for f in mctx.subset if f in s]
170
170
171 def func(mctx, a, b):
171 def func(mctx, a, b):
172 if a[0] == 'symbol' and a[1] in symbols:
172 if a[0] == 'symbol' and a[1] in symbols:
173 return symbols[a[1]](mctx, b)
173 return symbols[a[1]](mctx, b)
174 raise error.ParseError(_("not a function: %s") % a[1])
174 raise error.ParseError(_("not a function: %s") % a[1])
175
175
176 def getlist(x):
176 def getlist(x):
177 if not x:
177 if not x:
178 return []
178 return []
179 if x[0] == 'list':
179 if x[0] == 'list':
180 return getlist(x[1]) + [x[2]]
180 return getlist(x[1]) + [x[2]]
181 return [x]
181 return [x]
182
182
183 def getargs(x, min, max, err):
183 def getargs(x, min, max, err):
184 l = getlist(x)
184 l = getlist(x)
185 if len(l) < min or len(l) > max:
185 if len(l) < min or len(l) > max:
186 raise error.ParseError(err)
186 raise error.ParseError(err)
187 return l
187 return l
188
188
189 def binary(mctx, x):
189 def binary(mctx, x):
190 """``binary()``
190 """``binary()``
191 File that appears to be binary (contails NUL bytes).
191 File that appears to be binary (contails NUL bytes).
192 """
192 """
193 getargs(x, 0, 0, _("binary takes no arguments"))
193 getargs(x, 0, 0, _("binary takes no arguments"))
194 return [f for f in mctx.subset if util.binary(mctx.ctx[f].data())]
194 return [f for f in mctx.subset if util.binary(mctx.ctx[f].data())]
195
195
196 def exec_(mctx, x):
196 def exec_(mctx, x):
197 """``exec()``
197 """``exec()``
198 File that is marked as executable.
198 File that is marked as executable.
199 """
199 """
200 getargs(x, 0, 0, _("exec takes no arguments"))
200 getargs(x, 0, 0, _("exec takes no arguments"))
201 return [f for f in mctx.subset if mctx.ctx.flags(f) == 'x']
201 return [f for f in mctx.subset if mctx.ctx.flags(f) == 'x']
202
202
203 def symlink(mctx, x):
203 def symlink(mctx, x):
204 """``symlink()``
204 """``symlink()``
205 File that is marked as a symlink.
205 File that is marked as a symlink.
206 """
206 """
207 getargs(x, 0, 0, _("symlink takes no arguments"))
207 getargs(x, 0, 0, _("symlink takes no arguments"))
208 return [f for f in mctx.subset if mctx.ctx.flags(f) == 'l']
208 return [f for f in mctx.subset if mctx.ctx.flags(f) == 'l']
209
209
210 def resolved(mctx, x):
210 def resolved(mctx, x):
211 """``resolved()``
211 """``resolved()``
212 File that is marked resolved according to the resolve state.
212 File that is marked resolved according to the resolve state.
213 """
213 """
214 getargs(x, 0, 0, _("resolved takes no arguments"))
214 getargs(x, 0, 0, _("resolved takes no arguments"))
215 if mctx.ctx.rev() is not None:
215 if mctx.ctx.rev() is not None:
216 return []
216 return []
217 ms = merge.mergestate(mctx.ctx._repo)
217 ms = merge.mergestate(mctx.ctx._repo)
218 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
218 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
219
219
220 def unresolved(mctx, x):
220 def unresolved(mctx, x):
221 """``unresolved()``
221 """``unresolved()``
222 File that is marked unresolved according to the resolve state.
222 File that is marked unresolved according to the resolve state.
223 """
223 """
224 getargs(x, 0, 0, _("unresolved takes no arguments"))
224 getargs(x, 0, 0, _("unresolved takes no arguments"))
225 if mctx.ctx.rev() is not None:
225 if mctx.ctx.rev() is not None:
226 return []
226 return []
227 ms = merge.mergestate(mctx.ctx._repo)
227 ms = merge.mergestate(mctx.ctx._repo)
228 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
228 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
229
229
230 def hgignore(mctx, x):
230 def hgignore(mctx, x):
231 """``hgignore()``
231 """``hgignore()``
232 File that matches the active .hgignore pattern.
232 File that matches the active .hgignore pattern.
233 """
233 """
234 getargs(x, 0, 0, _("hgignore takes no arguments"))
234 getargs(x, 0, 0, _("hgignore takes no arguments"))
235 ignore = mctx.ctx._repo.dirstate._ignore
235 ignore = mctx.ctx._repo.dirstate._ignore
236 return [f for f in mctx.subset if ignore(f)]
236 return [f for f in mctx.subset if ignore(f)]
237
237
238 def grep(mctx, x):
238 def grep(mctx, x):
239 """``grep(regex)``
239 """``grep(regex)``
240 File contains the given regular expression.
240 File contains the given regular expression.
241 """
241 """
242 pat = getstring(x, _("grep requires a pattern"))
242 pat = getstring(x, _("grep requires a pattern"))
243 r = re.compile(pat)
243 r = re.compile(pat)
244 return [f for f in mctx.subset if r.search(mctx.ctx[f].data())]
244 return [f for f in mctx.subset if r.search(mctx.ctx[f].data())]
245
245
246 _units = dict(k=2**10, K=2**10, kB=2**10, KB=2**10,
246 _units = dict(k=2**10, K=2**10, kB=2**10, KB=2**10,
247 M=2**20, MB=2**20, G=2**30, GB=2**30)
247 M=2**20, MB=2**20, G=2**30, GB=2**30)
248
248
249 def _sizetoint(s):
249 def _sizetoint(s):
250 try:
250 try:
251 s = s.strip()
251 s = s.strip()
252 for k, v in _units.items():
252 for k, v in _units.items():
253 if s.endswith(k):
253 if s.endswith(k):
254 return int(float(s[:-len(k)]) * v)
254 return int(float(s[:-len(k)]) * v)
255 return int(s)
255 return int(s)
256 except ValueError:
256 except ValueError:
257 raise error.ParseError(_("couldn't parse size: %s") % s)
257 raise error.ParseError(_("couldn't parse size: %s") % s)
258
258
259 def _sizetomax(s):
259 def _sizetomax(s):
260 try:
260 try:
261 s = s.strip()
261 s = s.strip()
262 for k, v in _units.items():
262 for k, v in _units.items():
263 if s.endswith(k):
263 if s.endswith(k):
264 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
264 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
265 n = s[:-len(k)]
265 n = s[:-len(k)]
266 inc = 1.0
266 inc = 1.0
267 if "." in n:
267 if "." in n:
268 inc /= 10 ** len(n.split(".")[1])
268 inc /= 10 ** len(n.split(".")[1])
269 return int((float(n) + inc) * v) - 1
269 return int((float(n) + inc) * v) - 1
270 # no extension, this is a precise value
270 # no extension, this is a precise value
271 return int(s)
271 return int(s)
272 except ValueError:
272 except ValueError:
273 raise error.ParseError(_("couldn't parse size: %s") % s)
273 raise error.ParseError(_("couldn't parse size: %s") % s)
274
274
275 def size(mctx, x):
275 def size(mctx, x):
276 """``size(expression)``
276 """``size(expression)``
277 File size matches the given expression. Examples:
277 File size matches the given expression. Examples:
278
278
279 - 1k (files from 1024 to 2047 bytes)
279 - 1k (files from 1024 to 2047 bytes)
280 - < 20k (files less than 20480 bytes)
280 - < 20k (files less than 20480 bytes)
281 - >= .5MB (files at least 524288 bytes)
281 - >= .5MB (files at least 524288 bytes)
282 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
282 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
283 """
283 """
284
284
285 expr = getstring(x, _("grep requires a pattern")).strip()
285 expr = getstring(x, _("size requires an expression")).strip()
286 if '-' in expr: # do we have a range?
286 if '-' in expr: # do we have a range?
287 a, b = expr.split('-', 1)
287 a, b = expr.split('-', 1)
288 a = _sizetoint(a)
288 a = _sizetoint(a)
289 b = _sizetoint(b)
289 b = _sizetoint(b)
290 m = lambda x: x >= a and x <= b
290 m = lambda x: x >= a and x <= b
291 elif expr.startswith("<="):
291 elif expr.startswith("<="):
292 a = _sizetoint(expr[2:])
292 a = _sizetoint(expr[2:])
293 m = lambda x: x <= a
293 m = lambda x: x <= a
294 elif expr.startswith("<"):
294 elif expr.startswith("<"):
295 a = _sizetoint(expr[1:])
295 a = _sizetoint(expr[1:])
296 m = lambda x: x < a
296 m = lambda x: x < a
297 elif expr.startswith(">="):
297 elif expr.startswith(">="):
298 a = _sizetoint(expr[2:])
298 a = _sizetoint(expr[2:])
299 m = lambda x: x >= a
299 m = lambda x: x >= a
300 elif expr.startswith(">"):
300 elif expr.startswith(">"):
301 a = _sizetoint(expr[1:])
301 a = _sizetoint(expr[1:])
302 m = lambda x: x > a
302 m = lambda x: x > a
303 elif expr[0].isdigit or expr[0] == '.':
303 elif expr[0].isdigit or expr[0] == '.':
304 a = _sizetoint(expr)
304 a = _sizetoint(expr)
305 b = _sizetomax(expr)
305 b = _sizetomax(expr)
306 m = lambda x: x >= a and x <= b
306 m = lambda x: x >= a and x <= b
307 else:
307 else:
308 raise error.ParseError(_("couldn't parse size: %s") % expr)
308 raise error.ParseError(_("couldn't parse size: %s") % expr)
309
309
310 return [f for f in mctx.subset if m(mctx.ctx[f].size())]
310 return [f for f in mctx.subset if m(mctx.ctx[f].size())]
311
311
312 def encoding(mctx, x):
312 def encoding(mctx, x):
313 """``encoding(name)``
313 """``encoding(name)``
314 File can be successfully decoded with the given character
314 File can be successfully decoded with the given character
315 encoding. May not be useful for encodings other than ASCII and
315 encoding. May not be useful for encodings other than ASCII and
316 UTF-8.
316 UTF-8.
317 """
317 """
318
318
319 enc = getstring(x, _("encoding requires an encoding name"))
319 enc = getstring(x, _("encoding requires an encoding name"))
320
320
321 s = []
321 s = []
322 for f in mctx.subset:
322 for f in mctx.subset:
323 d = mctx.ctx[f].data()
323 d = mctx.ctx[f].data()
324 try:
324 try:
325 d.decode(enc)
325 d.decode(enc)
326 except LookupError:
326 except LookupError:
327 raise util.Abort(_("unknown encoding '%s'") % enc)
327 raise util.Abort(_("unknown encoding '%s'") % enc)
328 except UnicodeDecodeError:
328 except UnicodeDecodeError:
329 continue
329 continue
330 s.append(f)
330 s.append(f)
331
331
332 return s
332 return s
333
333
334 def copied(mctx, x):
334 def copied(mctx, x):
335 """``copied()``
335 """``copied()``
336 File that is recorded as being copied.
336 File that is recorded as being copied.
337 """
337 """
338 s = []
338 s = []
339 for f in mctx.subset:
339 for f in mctx.subset:
340 p = mctx.ctx[f].parents()
340 p = mctx.ctx[f].parents()
341 if p and p[0].path() != f:
341 if p and p[0].path() != f:
342 s.append(f)
342 s.append(f)
343 return s
343 return s
344
344
345 symbols = {
345 symbols = {
346 'added': added,
346 'added': added,
347 'binary': binary,
347 'binary': binary,
348 'clean': clean,
348 'clean': clean,
349 'copied': copied,
349 'copied': copied,
350 'deleted': deleted,
350 'deleted': deleted,
351 'encoding': encoding,
351 'encoding': encoding,
352 'exec': exec_,
352 'exec': exec_,
353 'grep': grep,
353 'grep': grep,
354 'ignored': ignored,
354 'ignored': ignored,
355 'hgignore': hgignore,
355 'hgignore': hgignore,
356 'modified': modified,
356 'modified': modified,
357 'removed': removed,
357 'removed': removed,
358 'resolved': resolved,
358 'resolved': resolved,
359 'size': size,
359 'size': size,
360 'symlink': symlink,
360 'symlink': symlink,
361 'unknown': unknown,
361 'unknown': unknown,
362 'unresolved': unresolved,
362 'unresolved': unresolved,
363 }
363 }
364
364
365 methods = {
365 methods = {
366 'string': stringset,
366 'string': stringset,
367 'symbol': stringset,
367 'symbol': stringset,
368 'and': andset,
368 'and': andset,
369 'or': orset,
369 'or': orset,
370 'list': listset,
370 'list': listset,
371 'group': getset,
371 'group': getset,
372 'not': notset,
372 'not': notset,
373 'func': func,
373 'func': func,
374 }
374 }
375
375
376 class matchctx(object):
376 class matchctx(object):
377 def __init__(self, ctx, subset=None, status=None):
377 def __init__(self, ctx, subset=None, status=None):
378 self.ctx = ctx
378 self.ctx = ctx
379 self.subset = subset
379 self.subset = subset
380 self._status = status
380 self._status = status
381 def status(self):
381 def status(self):
382 return self._status
382 return self._status
383 def matcher(self, patterns):
383 def matcher(self, patterns):
384 return self.ctx.match(patterns)
384 return self.ctx.match(patterns)
385 def filter(self, files):
385 def filter(self, files):
386 return [f for f in files if f in self.subset]
386 return [f for f in files if f in self.subset]
387 def narrow(self, files):
387 def narrow(self, files):
388 return matchctx(self.ctx, self.filter(files), self._status)
388 return matchctx(self.ctx, self.filter(files), self._status)
389
389
390 def _intree(funcs, tree):
390 def _intree(funcs, tree):
391 if isinstance(tree, tuple):
391 if isinstance(tree, tuple):
392 if tree[0] == 'func' and tree[1][0] == 'symbol':
392 if tree[0] == 'func' and tree[1][0] == 'symbol':
393 if tree[1][1] in funcs:
393 if tree[1][1] in funcs:
394 return True
394 return True
395 for s in tree[1:]:
395 for s in tree[1:]:
396 if _intree(funcs, s):
396 if _intree(funcs, s):
397 return True
397 return True
398 return False
398 return False
399
399
400 def getfileset(ctx, expr):
400 def getfileset(ctx, expr):
401 tree, pos = parse(expr)
401 tree, pos = parse(expr)
402 if (pos != len(expr)):
402 if (pos != len(expr)):
403 raise error.ParseError(_("invalid token"), pos)
403 raise error.ParseError(_("invalid token"), pos)
404
404
405 # do we need status info?
405 # do we need status info?
406 if _intree(['modified', 'added', 'removed', 'deleted',
406 if _intree(['modified', 'added', 'removed', 'deleted',
407 'unknown', 'ignored', 'clean'], tree):
407 'unknown', 'ignored', 'clean'], tree):
408 unknown = _intree(['unknown'], tree)
408 unknown = _intree(['unknown'], tree)
409 ignored = _intree(['ignored'], tree)
409 ignored = _intree(['ignored'], tree)
410
410
411 r = ctx._repo
411 r = ctx._repo
412 status = r.status(ctx.p1(), ctx,
412 status = r.status(ctx.p1(), ctx,
413 unknown=unknown, ignored=ignored, clean=True)
413 unknown=unknown, ignored=ignored, clean=True)
414 subset = []
414 subset = []
415 for c in status:
415 for c in status:
416 subset.extend(c)
416 subset.extend(c)
417 else:
417 else:
418 status = None
418 status = None
419 subset = ctx.walk(ctx.match([]))
419 subset = ctx.walk(ctx.match([]))
420
420
421 return getset(matchctx(ctx, subset, status), tree)
421 return getset(matchctx(ctx, subset, status), tree)
422
422
423 # tell hggettext to extract docstrings from these functions:
423 # tell hggettext to extract docstrings from these functions:
424 i18nfunctions = symbols.values()
424 i18nfunctions = symbols.values()
@@ -1,1045 +1,1045 b''
1 # revset.py - revision set queries for mercurial
1 # revset.py - revision set queries for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re
8 import re
9 import parser, util, error, discovery, hbisect
9 import parser, util, error, discovery, hbisect
10 import bookmarks as bookmarksmod
10 import bookmarks as bookmarksmod
11 import match as matchmod
11 import match as matchmod
12 from i18n import _
12 from i18n import _
13
13
14 elements = {
14 elements = {
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
16 "~": (18, None, ("ancestor", 18)),
16 "~": (18, None, ("ancestor", 18)),
17 "^": (18, None, ("parent", 18), ("parentpost", 18)),
17 "^": (18, None, ("parent", 18), ("parentpost", 18)),
18 "-": (5, ("negate", 19), ("minus", 5)),
18 "-": (5, ("negate", 19), ("minus", 5)),
19 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
19 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
20 ("dagrangepost", 17)),
20 ("dagrangepost", 17)),
21 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
21 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
22 ("dagrangepost", 17)),
22 ("dagrangepost", 17)),
23 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
23 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
24 "not": (10, ("not", 10)),
24 "not": (10, ("not", 10)),
25 "!": (10, ("not", 10)),
25 "!": (10, ("not", 10)),
26 "and": (5, None, ("and", 5)),
26 "and": (5, None, ("and", 5)),
27 "&": (5, None, ("and", 5)),
27 "&": (5, None, ("and", 5)),
28 "or": (4, None, ("or", 4)),
28 "or": (4, None, ("or", 4)),
29 "|": (4, None, ("or", 4)),
29 "|": (4, None, ("or", 4)),
30 "+": (4, None, ("or", 4)),
30 "+": (4, None, ("or", 4)),
31 ",": (2, None, ("list", 2)),
31 ",": (2, None, ("list", 2)),
32 ")": (0, None, None),
32 ")": (0, None, None),
33 "symbol": (0, ("symbol",), None),
33 "symbol": (0, ("symbol",), None),
34 "string": (0, ("string",), None),
34 "string": (0, ("string",), None),
35 "end": (0, None, None),
35 "end": (0, None, None),
36 }
36 }
37
37
38 keywords = set(['and', 'or', 'not'])
38 keywords = set(['and', 'or', 'not'])
39
39
40 def tokenize(program):
40 def tokenize(program):
41 pos, l = 0, len(program)
41 pos, l = 0, len(program)
42 while pos < l:
42 while pos < l:
43 c = program[pos]
43 c = program[pos]
44 if c.isspace(): # skip inter-token whitespace
44 if c.isspace(): # skip inter-token whitespace
45 pass
45 pass
46 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
46 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
47 yield ('::', None, pos)
47 yield ('::', None, pos)
48 pos += 1 # skip ahead
48 pos += 1 # skip ahead
49 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
49 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
50 yield ('..', None, pos)
50 yield ('..', None, pos)
51 pos += 1 # skip ahead
51 pos += 1 # skip ahead
52 elif c in "():,-|&+!~^": # handle simple operators
52 elif c in "():,-|&+!~^": # handle simple operators
53 yield (c, None, pos)
53 yield (c, None, pos)
54 elif (c in '"\'' or c == 'r' and
54 elif (c in '"\'' or c == 'r' and
55 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
55 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
56 if c == 'r':
56 if c == 'r':
57 pos += 1
57 pos += 1
58 c = program[pos]
58 c = program[pos]
59 decode = lambda x: x
59 decode = lambda x: x
60 else:
60 else:
61 decode = lambda x: x.decode('string-escape')
61 decode = lambda x: x.decode('string-escape')
62 pos += 1
62 pos += 1
63 s = pos
63 s = pos
64 while pos < l: # find closing quote
64 while pos < l: # find closing quote
65 d = program[pos]
65 d = program[pos]
66 if d == '\\': # skip over escaped characters
66 if d == '\\': # skip over escaped characters
67 pos += 2
67 pos += 2
68 continue
68 continue
69 if d == c:
69 if d == c:
70 yield ('string', decode(program[s:pos]), s)
70 yield ('string', decode(program[s:pos]), s)
71 break
71 break
72 pos += 1
72 pos += 1
73 else:
73 else:
74 raise error.ParseError(_("unterminated string"), s)
74 raise error.ParseError(_("unterminated string"), s)
75 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
75 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
76 s = pos
76 s = pos
77 pos += 1
77 pos += 1
78 while pos < l: # find end of symbol
78 while pos < l: # find end of symbol
79 d = program[pos]
79 d = program[pos]
80 if not (d.isalnum() or d in "._" or ord(d) > 127):
80 if not (d.isalnum() or d in "._" or ord(d) > 127):
81 break
81 break
82 if d == '.' and program[pos - 1] == '.': # special case for ..
82 if d == '.' and program[pos - 1] == '.': # special case for ..
83 pos -= 1
83 pos -= 1
84 break
84 break
85 pos += 1
85 pos += 1
86 sym = program[s:pos]
86 sym = program[s:pos]
87 if sym in keywords: # operator keywords
87 if sym in keywords: # operator keywords
88 yield (sym, None, s)
88 yield (sym, None, s)
89 else:
89 else:
90 yield ('symbol', sym, s)
90 yield ('symbol', sym, s)
91 pos -= 1
91 pos -= 1
92 else:
92 else:
93 raise error.ParseError(_("syntax error"), pos)
93 raise error.ParseError(_("syntax error"), pos)
94 pos += 1
94 pos += 1
95 yield ('end', None, pos)
95 yield ('end', None, pos)
96
96
97 # helpers
97 # helpers
98
98
99 def getstring(x, err):
99 def getstring(x, err):
100 if x and (x[0] == 'string' or x[0] == 'symbol'):
100 if x and (x[0] == 'string' or x[0] == 'symbol'):
101 return x[1]
101 return x[1]
102 raise error.ParseError(err)
102 raise error.ParseError(err)
103
103
104 def getlist(x):
104 def getlist(x):
105 if not x:
105 if not x:
106 return []
106 return []
107 if x[0] == 'list':
107 if x[0] == 'list':
108 return getlist(x[1]) + [x[2]]
108 return getlist(x[1]) + [x[2]]
109 return [x]
109 return [x]
110
110
111 def getargs(x, min, max, err):
111 def getargs(x, min, max, err):
112 l = getlist(x)
112 l = getlist(x)
113 if len(l) < min or len(l) > max:
113 if len(l) < min or len(l) > max:
114 raise error.ParseError(err)
114 raise error.ParseError(err)
115 return l
115 return l
116
116
117 def getset(repo, subset, x):
117 def getset(repo, subset, x):
118 if not x:
118 if not x:
119 raise error.ParseError(_("missing argument"))
119 raise error.ParseError(_("missing argument"))
120 return methods[x[0]](repo, subset, *x[1:])
120 return methods[x[0]](repo, subset, *x[1:])
121
121
122 # operator methods
122 # operator methods
123
123
124 def stringset(repo, subset, x):
124 def stringset(repo, subset, x):
125 x = repo[x].rev()
125 x = repo[x].rev()
126 if x == -1 and len(subset) == len(repo):
126 if x == -1 and len(subset) == len(repo):
127 return [-1]
127 return [-1]
128 if len(subset) == len(repo) or x in subset:
128 if len(subset) == len(repo) or x in subset:
129 return [x]
129 return [x]
130 return []
130 return []
131
131
132 def symbolset(repo, subset, x):
132 def symbolset(repo, subset, x):
133 if x in symbols:
133 if x in symbols:
134 raise error.ParseError(_("can't use %s here") % x)
134 raise error.ParseError(_("can't use %s here") % x)
135 return stringset(repo, subset, x)
135 return stringset(repo, subset, x)
136
136
137 def rangeset(repo, subset, x, y):
137 def rangeset(repo, subset, x, y):
138 m = getset(repo, subset, x)
138 m = getset(repo, subset, x)
139 if not m:
139 if not m:
140 m = getset(repo, range(len(repo)), x)
140 m = getset(repo, range(len(repo)), x)
141
141
142 n = getset(repo, subset, y)
142 n = getset(repo, subset, y)
143 if not n:
143 if not n:
144 n = getset(repo, range(len(repo)), y)
144 n = getset(repo, range(len(repo)), y)
145
145
146 if not m or not n:
146 if not m or not n:
147 return []
147 return []
148 m, n = m[0], n[-1]
148 m, n = m[0], n[-1]
149
149
150 if m < n:
150 if m < n:
151 r = range(m, n + 1)
151 r = range(m, n + 1)
152 else:
152 else:
153 r = range(m, n - 1, -1)
153 r = range(m, n - 1, -1)
154 s = set(subset)
154 s = set(subset)
155 return [x for x in r if x in s]
155 return [x for x in r if x in s]
156
156
157 def andset(repo, subset, x, y):
157 def andset(repo, subset, x, y):
158 return getset(repo, getset(repo, subset, x), y)
158 return getset(repo, getset(repo, subset, x), y)
159
159
160 def orset(repo, subset, x, y):
160 def orset(repo, subset, x, y):
161 xl = getset(repo, subset, x)
161 xl = getset(repo, subset, x)
162 s = set(xl)
162 s = set(xl)
163 yl = getset(repo, [r for r in subset if r not in s], y)
163 yl = getset(repo, [r for r in subset if r not in s], y)
164 return xl + yl
164 return xl + yl
165
165
166 def notset(repo, subset, x):
166 def notset(repo, subset, x):
167 s = set(getset(repo, subset, x))
167 s = set(getset(repo, subset, x))
168 return [r for r in subset if r not in s]
168 return [r for r in subset if r not in s]
169
169
170 def listset(repo, subset, a, b):
170 def listset(repo, subset, a, b):
171 raise error.ParseError(_("can't use a list in this context"))
171 raise error.ParseError(_("can't use a list in this context"))
172
172
173 def func(repo, subset, a, b):
173 def func(repo, subset, a, b):
174 if a[0] == 'symbol' and a[1] in symbols:
174 if a[0] == 'symbol' and a[1] in symbols:
175 return symbols[a[1]](repo, subset, b)
175 return symbols[a[1]](repo, subset, b)
176 raise error.ParseError(_("not a function: %s") % a[1])
176 raise error.ParseError(_("not a function: %s") % a[1])
177
177
178 # functions
178 # functions
179
179
180 def adds(repo, subset, x):
180 def adds(repo, subset, x):
181 """``adds(pattern)``
181 """``adds(pattern)``
182 Changesets that add a file matching pattern.
182 Changesets that add a file matching pattern.
183 """
183 """
184 # i18n: "adds" is a keyword
184 # i18n: "adds" is a keyword
185 pat = getstring(x, _("adds requires a pattern"))
185 pat = getstring(x, _("adds requires a pattern"))
186 return checkstatus(repo, subset, pat, 1)
186 return checkstatus(repo, subset, pat, 1)
187
187
188 def ancestor(repo, subset, x):
188 def ancestor(repo, subset, x):
189 """``ancestor(single, single)``
189 """``ancestor(single, single)``
190 Greatest common ancestor of the two changesets.
190 Greatest common ancestor of the two changesets.
191 """
191 """
192 # i18n: "ancestor" is a keyword
192 # i18n: "ancestor" is a keyword
193 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
193 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
194 r = range(len(repo))
194 r = range(len(repo))
195 a = getset(repo, r, l[0])
195 a = getset(repo, r, l[0])
196 b = getset(repo, r, l[1])
196 b = getset(repo, r, l[1])
197 if len(a) != 1 or len(b) != 1:
197 if len(a) != 1 or len(b) != 1:
198 # i18n: "ancestor" is a keyword
198 # i18n: "ancestor" is a keyword
199 raise error.ParseError(_("ancestor arguments must be single revisions"))
199 raise error.ParseError(_("ancestor arguments must be single revisions"))
200 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
200 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
201
201
202 return [r for r in an if r in subset]
202 return [r for r in an if r in subset]
203
203
204 def ancestors(repo, subset, x):
204 def ancestors(repo, subset, x):
205 """``ancestors(set)``
205 """``ancestors(set)``
206 Changesets that are ancestors of a changeset in set.
206 Changesets that are ancestors of a changeset in set.
207 """
207 """
208 args = getset(repo, range(len(repo)), x)
208 args = getset(repo, range(len(repo)), x)
209 if not args:
209 if not args:
210 return []
210 return []
211 s = set(repo.changelog.ancestors(*args)) | set(args)
211 s = set(repo.changelog.ancestors(*args)) | set(args)
212 return [r for r in subset if r in s]
212 return [r for r in subset if r in s]
213
213
214 def ancestorspec(repo, subset, x, n):
214 def ancestorspec(repo, subset, x, n):
215 """``set~n``
215 """``set~n``
216 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
216 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
217 """
217 """
218 try:
218 try:
219 n = int(n[1])
219 n = int(n[1])
220 except ValueError:
220 except ValueError:
221 raise error.ParseError(_("~ expects a number"))
221 raise error.ParseError(_("~ expects a number"))
222 ps = set()
222 ps = set()
223 cl = repo.changelog
223 cl = repo.changelog
224 for r in getset(repo, subset, x):
224 for r in getset(repo, subset, x):
225 for i in range(n):
225 for i in range(n):
226 r = cl.parentrevs(r)[0]
226 r = cl.parentrevs(r)[0]
227 ps.add(r)
227 ps.add(r)
228 return [r for r in subset if r in ps]
228 return [r for r in subset if r in ps]
229
229
230 def author(repo, subset, x):
230 def author(repo, subset, x):
231 """``author(string)``
231 """``author(string)``
232 Alias for ``user(string)``.
232 Alias for ``user(string)``.
233 """
233 """
234 # i18n: "author" is a keyword
234 # i18n: "author" is a keyword
235 n = getstring(x, _("author requires a string")).lower()
235 n = getstring(x, _("author requires a string")).lower()
236 return [r for r in subset if n in repo[r].user().lower()]
236 return [r for r in subset if n in repo[r].user().lower()]
237
237
238 def bisected(repo, subset, x):
238 def bisected(repo, subset, x):
239 """``bisected(string)``
239 """``bisected(string)``
240 Changesets marked in the specified bisect state (good, bad, skip).
240 Changesets marked in the specified bisect state (good, bad, skip).
241 """
241 """
242 state = getstring(x, _("bisect requires a string")).lower()
242 state = getstring(x, _("bisect requires a string")).lower()
243 if state not in ('good', 'bad', 'skip', 'unknown'):
243 if state not in ('good', 'bad', 'skip', 'unknown'):
244 raise error.ParseError(_('invalid bisect state'))
244 raise error.ParseError(_('invalid bisect state'))
245 marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state])
245 marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state])
246 return [r for r in subset if r in marked]
246 return [r for r in subset if r in marked]
247
247
248 def bookmark(repo, subset, x):
248 def bookmark(repo, subset, x):
249 """``bookmark([name])``
249 """``bookmark([name])``
250 The named bookmark or all bookmarks.
250 The named bookmark or all bookmarks.
251 """
251 """
252 # i18n: "bookmark" is a keyword
252 # i18n: "bookmark" is a keyword
253 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
253 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
254 if args:
254 if args:
255 bm = getstring(args[0],
255 bm = getstring(args[0],
256 # i18n: "bookmark" is a keyword
256 # i18n: "bookmark" is a keyword
257 _('the argument to bookmark must be a string'))
257 _('the argument to bookmark must be a string'))
258 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
258 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
259 if not bmrev:
259 if not bmrev:
260 raise util.Abort(_("bookmark '%s' does not exist") % bm)
260 raise util.Abort(_("bookmark '%s' does not exist") % bm)
261 bmrev = repo[bmrev].rev()
261 bmrev = repo[bmrev].rev()
262 return [r for r in subset if r == bmrev]
262 return [r for r in subset if r == bmrev]
263 bms = set([repo[r].rev()
263 bms = set([repo[r].rev()
264 for r in bookmarksmod.listbookmarks(repo).values()])
264 for r in bookmarksmod.listbookmarks(repo).values()])
265 return [r for r in subset if r in bms]
265 return [r for r in subset if r in bms]
266
266
267 def branch(repo, subset, x):
267 def branch(repo, subset, x):
268 """``branch(string or set)``
268 """``branch(string or set)``
269 All changesets belonging to the given branch or the branches of the given
269 All changesets belonging to the given branch or the branches of the given
270 changesets.
270 changesets.
271 """
271 """
272 try:
272 try:
273 b = getstring(x, '')
273 b = getstring(x, '')
274 if b in repo.branchmap():
274 if b in repo.branchmap():
275 return [r for r in subset if repo[r].branch() == b]
275 return [r for r in subset if repo[r].branch() == b]
276 except error.ParseError:
276 except error.ParseError:
277 # not a string, but another revspec, e.g. tip()
277 # not a string, but another revspec, e.g. tip()
278 pass
278 pass
279
279
280 s = getset(repo, range(len(repo)), x)
280 s = getset(repo, range(len(repo)), x)
281 b = set()
281 b = set()
282 for r in s:
282 for r in s:
283 b.add(repo[r].branch())
283 b.add(repo[r].branch())
284 s = set(s)
284 s = set(s)
285 return [r for r in subset if r in s or repo[r].branch() in b]
285 return [r for r in subset if r in s or repo[r].branch() in b]
286
286
287 def checkstatus(repo, subset, pat, field):
287 def checkstatus(repo, subset, pat, field):
288 m = matchmod.match(repo.root, repo.getcwd(), [pat])
288 m = matchmod.match(repo.root, repo.getcwd(), [pat])
289 s = []
289 s = []
290 fast = (m.files() == [pat])
290 fast = (m.files() == [pat])
291 for r in subset:
291 for r in subset:
292 c = repo[r]
292 c = repo[r]
293 if fast:
293 if fast:
294 if pat not in c.files():
294 if pat not in c.files():
295 continue
295 continue
296 else:
296 else:
297 for f in c.files():
297 for f in c.files():
298 if m(f):
298 if m(f):
299 break
299 break
300 else:
300 else:
301 continue
301 continue
302 files = repo.status(c.p1().node(), c.node())[field]
302 files = repo.status(c.p1().node(), c.node())[field]
303 if fast:
303 if fast:
304 if pat in files:
304 if pat in files:
305 s.append(r)
305 s.append(r)
306 else:
306 else:
307 for f in files:
307 for f in files:
308 if m(f):
308 if m(f):
309 s.append(r)
309 s.append(r)
310 break
310 break
311 return s
311 return s
312
312
313 def children(repo, subset, x):
313 def children(repo, subset, x):
314 """``children(set)``
314 """``children(set)``
315 Child changesets of changesets in set.
315 Child changesets of changesets in set.
316 """
316 """
317 cs = set()
317 cs = set()
318 cl = repo.changelog
318 cl = repo.changelog
319 s = set(getset(repo, range(len(repo)), x))
319 s = set(getset(repo, range(len(repo)), x))
320 for r in xrange(0, len(repo)):
320 for r in xrange(0, len(repo)):
321 for p in cl.parentrevs(r):
321 for p in cl.parentrevs(r):
322 if p in s:
322 if p in s:
323 cs.add(r)
323 cs.add(r)
324 return [r for r in subset if r in cs]
324 return [r for r in subset if r in cs]
325
325
326 def closed(repo, subset, x):
326 def closed(repo, subset, x):
327 """``closed()``
327 """``closed()``
328 Changeset is closed.
328 Changeset is closed.
329 """
329 """
330 # i18n: "closed" is a keyword
330 # i18n: "closed" is a keyword
331 getargs(x, 0, 0, _("closed takes no arguments"))
331 getargs(x, 0, 0, _("closed takes no arguments"))
332 return [r for r in subset if repo[r].extra().get('close')]
332 return [r for r in subset if repo[r].extra().get('close')]
333
333
334 def contains(repo, subset, x):
334 def contains(repo, subset, x):
335 """``contains(pattern)``
335 """``contains(pattern)``
336 Revision contains a file matching pattern. See :hg:`help patterns`
336 Revision contains a file matching pattern. See :hg:`help patterns`
337 for information about file patterns.
337 for information about file patterns.
338 """
338 """
339 # i18n: "contains" is a keyword
339 # i18n: "contains" is a keyword
340 pat = getstring(x, _("contains requires a pattern"))
340 pat = getstring(x, _("contains requires a pattern"))
341 m = matchmod.match(repo.root, repo.getcwd(), [pat])
341 m = matchmod.match(repo.root, repo.getcwd(), [pat])
342 s = []
342 s = []
343 if m.files() == [pat]:
343 if m.files() == [pat]:
344 for r in subset:
344 for r in subset:
345 if pat in repo[r]:
345 if pat in repo[r]:
346 s.append(r)
346 s.append(r)
347 else:
347 else:
348 for r in subset:
348 for r in subset:
349 for f in repo[r].manifest():
349 for f in repo[r].manifest():
350 if m(f):
350 if m(f):
351 s.append(r)
351 s.append(r)
352 break
352 break
353 return s
353 return s
354
354
355 def date(repo, subset, x):
355 def date(repo, subset, x):
356 """``date(interval)``
356 """``date(interval)``
357 Changesets within the interval, see :hg:`help dates`.
357 Changesets within the interval, see :hg:`help dates`.
358 """
358 """
359 # i18n: "date" is a keyword
359 # i18n: "date" is a keyword
360 ds = getstring(x, _("date requires a string"))
360 ds = getstring(x, _("date requires a string"))
361 dm = util.matchdate(ds)
361 dm = util.matchdate(ds)
362 return [r for r in subset if dm(repo[r].date()[0])]
362 return [r for r in subset if dm(repo[r].date()[0])]
363
363
364 def 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 ValueError:
524 except 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 ValueError:
540 except 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 requires a repository path"))
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 ValueError:
679 except 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 ValueError:
721 except 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 wa, ta = optimize(x[1], small)
931 wa, ta = optimize(x[1], small)
932 wb, tb = optimize(x[2], small)
932 wb, tb = optimize(x[2], small)
933 return wa + wb, (op, ta, tb)
933 return wa + wb, (op, ta, tb)
934 elif op == 'func':
934 elif op == 'func':
935 f = getstring(x[1], _("not a symbol"))
935 f = getstring(x[1], _("not a symbol"))
936 wa, ta = optimize(x[2], small)
936 wa, ta = optimize(x[2], small)
937 if f in ("author branch closed date desc file grep keyword "
937 if f in ("author branch closed date desc file grep keyword "
938 "outgoing user"):
938 "outgoing user"):
939 w = 10 # slow
939 w = 10 # slow
940 elif f in "modifies adds removes":
940 elif f in "modifies adds removes":
941 w = 30 # slower
941 w = 30 # slower
942 elif f == "contains":
942 elif f == "contains":
943 w = 100 # very slow
943 w = 100 # very slow
944 elif f == "ancestor":
944 elif f == "ancestor":
945 w = 1 * smallbonus
945 w = 1 * smallbonus
946 elif f in "reverse limit":
946 elif f in "reverse limit":
947 w = 0
947 w = 0
948 elif f in "sort":
948 elif f in "sort":
949 w = 10 # assume most sorts look at changelog
949 w = 10 # assume most sorts look at changelog
950 else:
950 else:
951 w = 1
951 w = 1
952 return w + wa, (op, x[1], ta)
952 return w + wa, (op, x[1], ta)
953 return 1, x
953 return 1, x
954
954
955 class revsetalias(object):
955 class revsetalias(object):
956 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
956 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
957 args = ()
957 args = ()
958
958
959 def __init__(self, token, value):
959 def __init__(self, token, value):
960 '''Aliases like:
960 '''Aliases like:
961
961
962 h = heads(default)
962 h = heads(default)
963 b($1) = ancestors($1) - ancestors(default)
963 b($1) = ancestors($1) - ancestors(default)
964 '''
964 '''
965 if isinstance(token, tuple):
965 if isinstance(token, tuple):
966 self.type, self.name = token
966 self.type, self.name = token
967 else:
967 else:
968 m = self.funcre.search(token)
968 m = self.funcre.search(token)
969 if m:
969 if m:
970 self.type = 'func'
970 self.type = 'func'
971 self.name = m.group(1)
971 self.name = m.group(1)
972 self.args = [x.strip() for x in m.group(2).split(',')]
972 self.args = [x.strip() for x in m.group(2).split(',')]
973 else:
973 else:
974 self.type = 'symbol'
974 self.type = 'symbol'
975 self.name = token
975 self.name = token
976
976
977 if isinstance(value, str):
977 if isinstance(value, str):
978 for arg in self.args:
978 for arg in self.args:
979 value = value.replace(arg, repr(arg))
979 value = value.replace(arg, repr(arg))
980 self.replacement, pos = parse(value)
980 self.replacement, pos = parse(value)
981 if pos != len(value):
981 if pos != len(value):
982 raise error.ParseError(_('invalid token'), pos)
982 raise error.ParseError(_('invalid token'), pos)
983 else:
983 else:
984 self.replacement = value
984 self.replacement = value
985
985
986 def match(self, tree):
986 def match(self, tree):
987 if not tree:
987 if not tree:
988 return False
988 return False
989 if tree == (self.type, self.name):
989 if tree == (self.type, self.name):
990 return True
990 return True
991 if tree[0] != self.type:
991 if tree[0] != self.type:
992 return False
992 return False
993 if len(tree) > 1 and tree[1] != ('symbol', self.name):
993 if len(tree) > 1 and tree[1] != ('symbol', self.name):
994 return False
994 return False
995 # 'func' + funcname + args
995 # 'func' + funcname + args
996 if ((self.args and len(tree) != 3) or
996 if ((self.args and len(tree) != 3) or
997 (len(self.args) == 1 and tree[2][0] == 'list') or
997 (len(self.args) == 1 and tree[2][0] == 'list') or
998 (len(self.args) > 1 and (tree[2][0] != 'list' or
998 (len(self.args) > 1 and (tree[2][0] != 'list' or
999 len(tree[2]) - 1 != len(self.args)))):
999 len(tree[2]) - 1 != len(self.args)))):
1000 raise error.ParseError(_('invalid amount of arguments'),
1000 raise error.ParseError(_('invalid amount of arguments'),
1001 len(tree) - 2)
1001 len(tree) - 2)
1002 return True
1002 return True
1003
1003
1004 def replace(self, tree):
1004 def replace(self, tree):
1005 if tree == (self.type, self.name):
1005 if tree == (self.type, self.name):
1006 return self.replacement
1006 return self.replacement
1007 result = self.replacement
1007 result = self.replacement
1008 def getsubtree(i):
1008 def getsubtree(i):
1009 if tree[2][0] == 'list':
1009 if tree[2][0] == 'list':
1010 return tree[2][i + 1]
1010 return tree[2][i + 1]
1011 return tree[i + 2]
1011 return tree[i + 2]
1012 for i, v in enumerate(self.args):
1012 for i, v in enumerate(self.args):
1013 valalias = revsetalias(('string', v), getsubtree(i))
1013 valalias = revsetalias(('string', v), getsubtree(i))
1014 result = valalias.process(result)
1014 result = valalias.process(result)
1015 return result
1015 return result
1016
1016
1017 def process(self, tree):
1017 def process(self, tree):
1018 if self.match(tree):
1018 if self.match(tree):
1019 return self.replace(tree)
1019 return self.replace(tree)
1020 if isinstance(tree, tuple):
1020 if isinstance(tree, tuple):
1021 return tuple(map(self.process, tree))
1021 return tuple(map(self.process, tree))
1022 return tree
1022 return tree
1023
1023
1024 def findaliases(ui, tree):
1024 def findaliases(ui, tree):
1025 for k, v in ui.configitems('revsetalias'):
1025 for k, v in ui.configitems('revsetalias'):
1026 alias = revsetalias(k, v)
1026 alias = revsetalias(k, v)
1027 tree = alias.process(tree)
1027 tree = alias.process(tree)
1028 return tree
1028 return tree
1029
1029
1030 parse = parser.parser(tokenize, elements).parse
1030 parse = parser.parser(tokenize, elements).parse
1031
1031
1032 def match(ui, spec):
1032 def match(ui, spec):
1033 if not spec:
1033 if not spec:
1034 raise error.ParseError(_("empty query"))
1034 raise error.ParseError(_("empty query"))
1035 tree, pos = parse(spec)
1035 tree, pos = parse(spec)
1036 if (pos != len(spec)):
1036 if (pos != len(spec)):
1037 raise error.ParseError(_("invalid token"), pos)
1037 raise error.ParseError(_("invalid token"), pos)
1038 tree = findaliases(ui, tree)
1038 tree = findaliases(ui, tree)
1039 weight, tree = optimize(tree, True)
1039 weight, tree = optimize(tree, True)
1040 def mfunc(repo, subset):
1040 def mfunc(repo, subset):
1041 return getset(repo, subset, tree)
1041 return getset(repo, subset, tree)
1042 return mfunc
1042 return mfunc
1043
1043
1044 # tell hggettext to extract docstrings from these functions:
1044 # tell hggettext to extract docstrings from these functions:
1045 i18nfunctions = symbols.values()
1045 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now