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