##// 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 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 error.ParseError(_("couldn't parse size: %s") % s)
258 258
259 259 def _sizetomax(s):
260 260 try:
261 261 s = s.strip()
262 262 for k, v in _units.items():
263 263 if s.endswith(k):
264 264 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
265 265 n = s[:-len(k)]
266 266 inc = 1.0
267 267 if "." in n:
268 268 inc /= 10 ** len(n.split(".")[1])
269 269 return int((float(n) + inc) * v) - 1
270 270 # no extension, this is a precise value
271 271 return int(s)
272 272 except ValueError:
273 273 raise error.ParseError(_("couldn't parse size: %s") % s)
274 274
275 275 def size(mctx, x):
276 276 """``size(expression)``
277 277 File size matches the given expression. Examples:
278 278
279 279 - 1k (files from 1024 to 2047 bytes)
280 280 - < 20k (files less than 20480 bytes)
281 281 - >= .5MB (files at least 524288 bytes)
282 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 286 if '-' in expr: # do we have a range?
287 287 a, b = expr.split('-', 1)
288 288 a = _sizetoint(a)
289 289 b = _sizetoint(b)
290 290 m = lambda x: x >= a and x <= b
291 291 elif expr.startswith("<="):
292 292 a = _sizetoint(expr[2:])
293 293 m = lambda x: x <= a
294 294 elif expr.startswith("<"):
295 295 a = _sizetoint(expr[1:])
296 296 m = lambda x: x < a
297 297 elif expr.startswith(">="):
298 298 a = _sizetoint(expr[2:])
299 299 m = lambda x: x >= a
300 300 elif expr.startswith(">"):
301 301 a = _sizetoint(expr[1:])
302 302 m = lambda x: x > a
303 303 elif expr[0].isdigit or expr[0] == '.':
304 304 a = _sizetoint(expr)
305 305 b = _sizetomax(expr)
306 306 m = lambda x: x >= a and x <= b
307 307 else:
308 308 raise error.ParseError(_("couldn't parse size: %s") % expr)
309 309
310 310 return [f for f in mctx.subset if m(mctx.ctx[f].size())]
311 311
312 312 def encoding(mctx, x):
313 313 """``encoding(name)``
314 314 File can be successfully decoded with the given character
315 315 encoding. May not be useful for encodings other than ASCII and
316 316 UTF-8.
317 317 """
318 318
319 319 enc = getstring(x, _("encoding requires an encoding name"))
320 320
321 321 s = []
322 322 for f in mctx.subset:
323 323 d = mctx.ctx[f].data()
324 324 try:
325 325 d.decode(enc)
326 326 except LookupError:
327 327 raise util.Abort(_("unknown encoding '%s'") % enc)
328 328 except UnicodeDecodeError:
329 329 continue
330 330 s.append(f)
331 331
332 332 return s
333 333
334 334 def copied(mctx, x):
335 335 """``copied()``
336 336 File that is recorded as being copied.
337 337 """
338 338 s = []
339 339 for f in mctx.subset:
340 340 p = mctx.ctx[f].parents()
341 341 if p and p[0].path() != f:
342 342 s.append(f)
343 343 return s
344 344
345 345 symbols = {
346 346 'added': added,
347 347 'binary': binary,
348 348 'clean': clean,
349 349 'copied': copied,
350 350 'deleted': deleted,
351 351 'encoding': encoding,
352 352 'exec': exec_,
353 353 'grep': grep,
354 354 'ignored': ignored,
355 355 'hgignore': hgignore,
356 356 'modified': modified,
357 357 'removed': removed,
358 358 'resolved': resolved,
359 359 'size': size,
360 360 'symlink': symlink,
361 361 'unknown': unknown,
362 362 'unresolved': unresolved,
363 363 }
364 364
365 365 methods = {
366 366 'string': stringset,
367 367 'symbol': stringset,
368 368 'and': andset,
369 369 'or': orset,
370 370 'list': listset,
371 371 'group': getset,
372 372 'not': notset,
373 373 'func': func,
374 374 }
375 375
376 376 class matchctx(object):
377 377 def __init__(self, ctx, subset=None, status=None):
378 378 self.ctx = ctx
379 379 self.subset = subset
380 380 self._status = status
381 381 def status(self):
382 382 return self._status
383 383 def matcher(self, patterns):
384 384 return self.ctx.match(patterns)
385 385 def filter(self, files):
386 386 return [f for f in files if f in self.subset]
387 387 def narrow(self, files):
388 388 return matchctx(self.ctx, self.filter(files), self._status)
389 389
390 390 def _intree(funcs, tree):
391 391 if isinstance(tree, tuple):
392 392 if tree[0] == 'func' and tree[1][0] == 'symbol':
393 393 if tree[1][1] in funcs:
394 394 return True
395 395 for s in tree[1:]:
396 396 if _intree(funcs, s):
397 397 return True
398 398 return False
399 399
400 400 def getfileset(ctx, expr):
401 401 tree, pos = parse(expr)
402 402 if (pos != len(expr)):
403 403 raise error.ParseError(_("invalid token"), pos)
404 404
405 405 # do we need status info?
406 406 if _intree(['modified', 'added', 'removed', 'deleted',
407 407 'unknown', 'ignored', 'clean'], tree):
408 408 unknown = _intree(['unknown'], tree)
409 409 ignored = _intree(['ignored'], tree)
410 410
411 411 r = ctx._repo
412 412 status = r.status(ctx.p1(), ctx,
413 413 unknown=unknown, ignored=ignored, clean=True)
414 414 subset = []
415 415 for c in status:
416 416 subset.extend(c)
417 417 else:
418 418 status = None
419 419 subset = ctx.walk(ctx.match([]))
420 420
421 421 return getset(matchctx(ctx, subset, status), tree)
422 422
423 423 # tell hggettext to extract docstrings from these functions:
424 424 i18nfunctions = symbols.values()
@@ -1,1045 +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 x = getstring(l[0], "follow expected a filename")
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, x):
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 l = getargs(x, 0, 1, _("outgoing requires a repository path"))
607 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
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 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 1000 raise error.ParseError(_('invalid amount of arguments'),
1001 1001 len(tree) - 2)
1002 1002 return True
1003 1003
1004 1004 def replace(self, tree):
1005 1005 if tree == (self.type, self.name):
1006 1006 return self.replacement
1007 1007 result = self.replacement
1008 1008 def getsubtree(i):
1009 1009 if tree[2][0] == 'list':
1010 1010 return tree[2][i + 1]
1011 1011 return tree[i + 2]
1012 1012 for i, v in enumerate(self.args):
1013 1013 valalias = revsetalias(('string', v), getsubtree(i))
1014 1014 result = valalias.process(result)
1015 1015 return result
1016 1016
1017 1017 def process(self, tree):
1018 1018 if self.match(tree):
1019 1019 return self.replace(tree)
1020 1020 if isinstance(tree, tuple):
1021 1021 return tuple(map(self.process, tree))
1022 1022 return tree
1023 1023
1024 1024 def findaliases(ui, tree):
1025 1025 for k, v in ui.configitems('revsetalias'):
1026 1026 alias = revsetalias(k, v)
1027 1027 tree = alias.process(tree)
1028 1028 return tree
1029 1029
1030 1030 parse = parser.parser(tokenize, elements).parse
1031 1031
1032 1032 def match(ui, spec):
1033 1033 if not spec:
1034 1034 raise error.ParseError(_("empty query"))
1035 1035 tree, pos = parse(spec)
1036 1036 if (pos != len(spec)):
1037 1037 raise error.ParseError(_("invalid token"), pos)
1038 1038 tree = findaliases(ui, tree)
1039 1039 weight, tree = optimize(tree, True)
1040 1040 def mfunc(repo, subset):
1041 1041 return getset(repo, subset, tree)
1042 1042 return mfunc
1043 1043
1044 1044 # tell hggettext to extract docstrings from these functions:
1045 1045 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now