##// END OF EJS Templates
revset: avoid validating all tag nodes for tag(x)...
Matt Mackall -
r16825:b6ef1395 default
parent child Browse files
Show More
@@ -1,1644 +1,1646 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, collections
9 9 import parser, util, error, discovery, hbisect, phases
10 10 import node
11 11 import bookmarks as bookmarksmod
12 12 import match as matchmod
13 13 from i18n import _
14 14 import encoding
15 15
16 16 def _revancestors(repo, revs, followfirst):
17 17 """Like revlog.ancestors(), but supports followfirst."""
18 18 cut = followfirst and 1 or None
19 19 cl = repo.changelog
20 20 visit = collections.deque(revs)
21 21 seen = set([node.nullrev])
22 22 while visit:
23 23 for parent in cl.parentrevs(visit.popleft())[:cut]:
24 24 if parent not in seen:
25 25 visit.append(parent)
26 26 seen.add(parent)
27 27 yield parent
28 28
29 29 def _revdescendants(repo, revs, followfirst):
30 30 """Like revlog.descendants() but supports followfirst."""
31 31 cut = followfirst and 1 or None
32 32 cl = repo.changelog
33 33 first = min(revs)
34 34 nullrev = node.nullrev
35 35 if first == nullrev:
36 36 # Are there nodes with a null first parent and a non-null
37 37 # second one? Maybe. Do we care? Probably not.
38 38 for i in cl:
39 39 yield i
40 40 return
41 41
42 42 seen = set(revs)
43 43 for i in xrange(first + 1, len(cl)):
44 44 for x in cl.parentrevs(i)[:cut]:
45 45 if x != nullrev and x in seen:
46 46 seen.add(i)
47 47 yield i
48 48 break
49 49
50 50 elements = {
51 51 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
52 52 "~": (18, None, ("ancestor", 18)),
53 53 "^": (18, None, ("parent", 18), ("parentpost", 18)),
54 54 "-": (5, ("negate", 19), ("minus", 5)),
55 55 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
56 56 ("dagrangepost", 17)),
57 57 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
58 58 ("dagrangepost", 17)),
59 59 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
60 60 "not": (10, ("not", 10)),
61 61 "!": (10, ("not", 10)),
62 62 "and": (5, None, ("and", 5)),
63 63 "&": (5, None, ("and", 5)),
64 64 "or": (4, None, ("or", 4)),
65 65 "|": (4, None, ("or", 4)),
66 66 "+": (4, None, ("or", 4)),
67 67 ",": (2, None, ("list", 2)),
68 68 ")": (0, None, None),
69 69 "symbol": (0, ("symbol",), None),
70 70 "string": (0, ("string",), None),
71 71 "end": (0, None, None),
72 72 }
73 73
74 74 keywords = set(['and', 'or', 'not'])
75 75
76 76 def tokenize(program):
77 77 pos, l = 0, len(program)
78 78 while pos < l:
79 79 c = program[pos]
80 80 if c.isspace(): # skip inter-token whitespace
81 81 pass
82 82 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
83 83 yield ('::', None, pos)
84 84 pos += 1 # skip ahead
85 85 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
86 86 yield ('..', None, pos)
87 87 pos += 1 # skip ahead
88 88 elif c in "():,-|&+!~^": # handle simple operators
89 89 yield (c, None, pos)
90 90 elif (c in '"\'' or c == 'r' and
91 91 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
92 92 if c == 'r':
93 93 pos += 1
94 94 c = program[pos]
95 95 decode = lambda x: x
96 96 else:
97 97 decode = lambda x: x.decode('string-escape')
98 98 pos += 1
99 99 s = pos
100 100 while pos < l: # find closing quote
101 101 d = program[pos]
102 102 if d == '\\': # skip over escaped characters
103 103 pos += 2
104 104 continue
105 105 if d == c:
106 106 yield ('string', decode(program[s:pos]), s)
107 107 break
108 108 pos += 1
109 109 else:
110 110 raise error.ParseError(_("unterminated string"), s)
111 111 # gather up a symbol/keyword
112 112 elif c.isalnum() or c in '._' or ord(c) > 127:
113 113 s = pos
114 114 pos += 1
115 115 while pos < l: # find end of symbol
116 116 d = program[pos]
117 117 if not (d.isalnum() or d in "._/" or ord(d) > 127):
118 118 break
119 119 if d == '.' and program[pos - 1] == '.': # special case for ..
120 120 pos -= 1
121 121 break
122 122 pos += 1
123 123 sym = program[s:pos]
124 124 if sym in keywords: # operator keywords
125 125 yield (sym, None, s)
126 126 else:
127 127 yield ('symbol', sym, s)
128 128 pos -= 1
129 129 else:
130 130 raise error.ParseError(_("syntax error"), pos)
131 131 pos += 1
132 132 yield ('end', None, pos)
133 133
134 134 # helpers
135 135
136 136 def getstring(x, err):
137 137 if x and (x[0] == 'string' or x[0] == 'symbol'):
138 138 return x[1]
139 139 raise error.ParseError(err)
140 140
141 141 def getlist(x):
142 142 if not x:
143 143 return []
144 144 if x[0] == 'list':
145 145 return getlist(x[1]) + [x[2]]
146 146 return [x]
147 147
148 148 def getargs(x, min, max, err):
149 149 l = getlist(x)
150 150 if len(l) < min or (max >= 0 and len(l) > max):
151 151 raise error.ParseError(err)
152 152 return l
153 153
154 154 def getset(repo, subset, x):
155 155 if not x:
156 156 raise error.ParseError(_("missing argument"))
157 157 return methods[x[0]](repo, subset, *x[1:])
158 158
159 159 # operator methods
160 160
161 161 def stringset(repo, subset, x):
162 162 x = repo[x].rev()
163 163 if x == -1 and len(subset) == len(repo):
164 164 return [-1]
165 165 if len(subset) == len(repo) or x in subset:
166 166 return [x]
167 167 return []
168 168
169 169 def symbolset(repo, subset, x):
170 170 if x in symbols:
171 171 raise error.ParseError(_("can't use %s here") % x)
172 172 return stringset(repo, subset, x)
173 173
174 174 def rangeset(repo, subset, x, y):
175 175 m = getset(repo, subset, x)
176 176 if not m:
177 177 m = getset(repo, range(len(repo)), x)
178 178
179 179 n = getset(repo, subset, y)
180 180 if not n:
181 181 n = getset(repo, range(len(repo)), y)
182 182
183 183 if not m or not n:
184 184 return []
185 185 m, n = m[0], n[-1]
186 186
187 187 if m < n:
188 188 r = range(m, n + 1)
189 189 else:
190 190 r = range(m, n - 1, -1)
191 191 s = set(subset)
192 192 return [x for x in r if x in s]
193 193
194 194 def andset(repo, subset, x, y):
195 195 return getset(repo, getset(repo, subset, x), y)
196 196
197 197 def orset(repo, subset, x, y):
198 198 xl = getset(repo, subset, x)
199 199 s = set(xl)
200 200 yl = getset(repo, [r for r in subset if r not in s], y)
201 201 return xl + yl
202 202
203 203 def notset(repo, subset, x):
204 204 s = set(getset(repo, subset, x))
205 205 return [r for r in subset if r not in s]
206 206
207 207 def listset(repo, subset, a, b):
208 208 raise error.ParseError(_("can't use a list in this context"))
209 209
210 210 def func(repo, subset, a, b):
211 211 if a[0] == 'symbol' and a[1] in symbols:
212 212 return symbols[a[1]](repo, subset, b)
213 213 raise error.ParseError(_("not a function: %s") % a[1])
214 214
215 215 # functions
216 216
217 217 def adds(repo, subset, x):
218 218 """``adds(pattern)``
219 219 Changesets that add a file matching pattern.
220 220 """
221 221 # i18n: "adds" is a keyword
222 222 pat = getstring(x, _("adds requires a pattern"))
223 223 return checkstatus(repo, subset, pat, 1)
224 224
225 225 def ancestor(repo, subset, x):
226 226 """``ancestor(single, single)``
227 227 Greatest common ancestor of the two changesets.
228 228 """
229 229 # i18n: "ancestor" is a keyword
230 230 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
231 231 r = range(len(repo))
232 232 a = getset(repo, r, l[0])
233 233 b = getset(repo, r, l[1])
234 234 if len(a) != 1 or len(b) != 1:
235 235 # i18n: "ancestor" is a keyword
236 236 raise error.ParseError(_("ancestor arguments must be single revisions"))
237 237 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
238 238
239 239 return [r for r in an if r in subset]
240 240
241 241 def _ancestors(repo, subset, x, followfirst=False):
242 242 args = getset(repo, range(len(repo)), x)
243 243 if not args:
244 244 return []
245 245 s = set(_revancestors(repo, args, followfirst)) | set(args)
246 246 return [r for r in subset if r in s]
247 247
248 248 def ancestors(repo, subset, x):
249 249 """``ancestors(set)``
250 250 Changesets that are ancestors of a changeset in set.
251 251 """
252 252 return _ancestors(repo, subset, x)
253 253
254 254 def _firstancestors(repo, subset, x):
255 255 # ``_firstancestors(set)``
256 256 # Like ``ancestors(set)`` but follows only the first parents.
257 257 return _ancestors(repo, subset, x, followfirst=True)
258 258
259 259 def ancestorspec(repo, subset, x, n):
260 260 """``set~n``
261 261 Changesets that are the Nth ancestor (first parents only) of a changeset
262 262 in set.
263 263 """
264 264 try:
265 265 n = int(n[1])
266 266 except (TypeError, ValueError):
267 267 raise error.ParseError(_("~ expects a number"))
268 268 ps = set()
269 269 cl = repo.changelog
270 270 for r in getset(repo, subset, x):
271 271 for i in range(n):
272 272 r = cl.parentrevs(r)[0]
273 273 ps.add(r)
274 274 return [r for r in subset if r in ps]
275 275
276 276 def author(repo, subset, x):
277 277 """``author(string)``
278 278 Alias for ``user(string)``.
279 279 """
280 280 # i18n: "author" is a keyword
281 281 n = encoding.lower(getstring(x, _("author requires a string")))
282 282 kind, pattern, matcher = _substringmatcher(n)
283 283 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
284 284
285 285 def bisect(repo, subset, x):
286 286 """``bisect(string)``
287 287 Changesets marked in the specified bisect status:
288 288
289 289 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
290 290 - ``goods``, ``bads`` : csets topologicaly good/bad
291 291 - ``range`` : csets taking part in the bisection
292 292 - ``pruned`` : csets that are goods, bads or skipped
293 293 - ``untested`` : csets whose fate is yet unknown
294 294 - ``ignored`` : csets ignored due to DAG topology
295 295 - ``current`` : the cset currently being bisected
296 296 """
297 297 status = getstring(x, _("bisect requires a string")).lower()
298 298 state = set(hbisect.get(repo, status))
299 299 return [r for r in subset if r in state]
300 300
301 301 # Backward-compatibility
302 302 # - no help entry so that we do not advertise it any more
303 303 def bisected(repo, subset, x):
304 304 return bisect(repo, subset, x)
305 305
306 306 def bookmark(repo, subset, x):
307 307 """``bookmark([name])``
308 308 The named bookmark or all bookmarks.
309 309
310 310 If `name` starts with `re:`, the remainder of the name is treated as
311 311 a regular expression. To match a bookmark that actually starts with `re:`,
312 312 use the prefix `literal:`.
313 313 """
314 314 # i18n: "bookmark" is a keyword
315 315 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
316 316 if args:
317 317 bm = getstring(args[0],
318 318 # i18n: "bookmark" is a keyword
319 319 _('the argument to bookmark must be a string'))
320 320 kind, pattern, matcher = _stringmatcher(bm)
321 321 if kind == 'literal':
322 322 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
323 323 if not bmrev:
324 324 raise util.Abort(_("bookmark '%s' does not exist") % bm)
325 325 bmrev = repo[bmrev].rev()
326 326 return [r for r in subset if r == bmrev]
327 327 else:
328 328 matchrevs = set()
329 329 for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
330 330 if matcher(name):
331 331 matchrevs.add(bmrev)
332 332 if not matchrevs:
333 333 raise util.Abort(_("no bookmarks exist that match '%s'")
334 334 % pattern)
335 335 bmrevs = set()
336 336 for bmrev in matchrevs:
337 337 bmrevs.add(repo[bmrev].rev())
338 338 return [r for r in subset if r in bmrevs]
339 339
340 340 bms = set([repo[r].rev()
341 341 for r in bookmarksmod.listbookmarks(repo).values()])
342 342 return [r for r in subset if r in bms]
343 343
344 344 def branch(repo, subset, x):
345 345 """``branch(string or set)``
346 346 All changesets belonging to the given branch or the branches of the given
347 347 changesets.
348 348
349 349 If `string` starts with `re:`, the remainder of the name is treated as
350 350 a regular expression. To match a branch that actually starts with `re:`,
351 351 use the prefix `literal:`.
352 352 """
353 353 try:
354 354 b = getstring(x, '')
355 355 except error.ParseError:
356 356 # not a string, but another revspec, e.g. tip()
357 357 pass
358 358 else:
359 359 kind, pattern, matcher = _stringmatcher(b)
360 360 if kind == 'literal':
361 361 # note: falls through to the revspec case if no branch with
362 362 # this name exists
363 363 if pattern in repo.branchmap():
364 364 return [r for r in subset if matcher(repo[r].branch())]
365 365 else:
366 366 return [r for r in subset if matcher(repo[r].branch())]
367 367
368 368 s = getset(repo, range(len(repo)), x)
369 369 b = set()
370 370 for r in s:
371 371 b.add(repo[r].branch())
372 372 s = set(s)
373 373 return [r for r in subset if r in s or repo[r].branch() in b]
374 374
375 375 def checkstatus(repo, subset, pat, field):
376 376 m = None
377 377 s = []
378 378 hasset = matchmod.patkind(pat) == 'set'
379 379 fname = None
380 380 for r in subset:
381 381 c = repo[r]
382 382 if not m or hasset:
383 383 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
384 384 if not m.anypats() and len(m.files()) == 1:
385 385 fname = m.files()[0]
386 386 if fname is not None:
387 387 if fname not in c.files():
388 388 continue
389 389 else:
390 390 for f in c.files():
391 391 if m(f):
392 392 break
393 393 else:
394 394 continue
395 395 files = repo.status(c.p1().node(), c.node())[field]
396 396 if fname is not None:
397 397 if fname in files:
398 398 s.append(r)
399 399 else:
400 400 for f in files:
401 401 if m(f):
402 402 s.append(r)
403 403 break
404 404 return s
405 405
406 406 def _children(repo, narrow, parentset):
407 407 cs = set()
408 408 pr = repo.changelog.parentrevs
409 409 for r in narrow:
410 410 for p in pr(r):
411 411 if p in parentset:
412 412 cs.add(r)
413 413 return cs
414 414
415 415 def children(repo, subset, x):
416 416 """``children(set)``
417 417 Child changesets of changesets in set.
418 418 """
419 419 s = set(getset(repo, range(len(repo)), x))
420 420 cs = _children(repo, subset, s)
421 421 return [r for r in subset if r in cs]
422 422
423 423 def closed(repo, subset, x):
424 424 """``closed()``
425 425 Changeset is closed.
426 426 """
427 427 # i18n: "closed" is a keyword
428 428 getargs(x, 0, 0, _("closed takes no arguments"))
429 429 return [r for r in subset if repo[r].closesbranch()]
430 430
431 431 def contains(repo, subset, x):
432 432 """``contains(pattern)``
433 433 Revision contains a file matching pattern. See :hg:`help patterns`
434 434 for information about file patterns.
435 435 """
436 436 # i18n: "contains" is a keyword
437 437 pat = getstring(x, _("contains requires a pattern"))
438 438 m = None
439 439 s = []
440 440 if not matchmod.patkind(pat):
441 441 for r in subset:
442 442 if pat in repo[r]:
443 443 s.append(r)
444 444 else:
445 445 for r in subset:
446 446 c = repo[r]
447 447 if not m or matchmod.patkind(pat) == 'set':
448 448 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
449 449 for f in c.manifest():
450 450 if m(f):
451 451 s.append(r)
452 452 break
453 453 return s
454 454
455 455 def date(repo, subset, x):
456 456 """``date(interval)``
457 457 Changesets within the interval, see :hg:`help dates`.
458 458 """
459 459 # i18n: "date" is a keyword
460 460 ds = getstring(x, _("date requires a string"))
461 461 dm = util.matchdate(ds)
462 462 return [r for r in subset if dm(repo[r].date()[0])]
463 463
464 464 def desc(repo, subset, x):
465 465 """``desc(string)``
466 466 Search commit message for string. The match is case-insensitive.
467 467 """
468 468 # i18n: "desc" is a keyword
469 469 ds = encoding.lower(getstring(x, _("desc requires a string")))
470 470 l = []
471 471 for r in subset:
472 472 c = repo[r]
473 473 if ds in encoding.lower(c.description()):
474 474 l.append(r)
475 475 return l
476 476
477 477 def _descendants(repo, subset, x, followfirst=False):
478 478 args = getset(repo, range(len(repo)), x)
479 479 if not args:
480 480 return []
481 481 s = set(_revdescendants(repo, args, followfirst)) | set(args)
482 482 return [r for r in subset if r in s]
483 483
484 484 def descendants(repo, subset, x):
485 485 """``descendants(set)``
486 486 Changesets which are descendants of changesets in set.
487 487 """
488 488 return _descendants(repo, subset, x)
489 489
490 490 def _firstdescendants(repo, subset, x):
491 491 # ``_firstdescendants(set)``
492 492 # Like ``descendants(set)`` but follows only the first parents.
493 493 return _descendants(repo, subset, x, followfirst=True)
494 494
495 495 def draft(repo, subset, x):
496 496 """``draft()``
497 497 Changeset in draft phase."""
498 498 getargs(x, 0, 0, _("draft takes no arguments"))
499 499 pc = repo._phasecache
500 500 return [r for r in subset if pc.phase(repo, r) == phases.draft]
501 501
502 502 def extra(repo, subset, x):
503 503 """``extra(label, [value])``
504 504 Changesets with the given label in the extra metadata, with the given
505 505 optional value.
506 506
507 507 If `value` starts with `re:`, the remainder of the value is treated as
508 508 a regular expression. To match a value that actually starts with `re:`,
509 509 use the prefix `literal:`.
510 510 """
511 511
512 512 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
513 513 label = getstring(l[0], _('first argument to extra must be a string'))
514 514 value = None
515 515
516 516 if len(l) > 1:
517 517 value = getstring(l[1], _('second argument to extra must be a string'))
518 518 kind, value, matcher = _stringmatcher(value)
519 519
520 520 def _matchvalue(r):
521 521 extra = repo[r].extra()
522 522 return label in extra and (value is None or matcher(extra[label]))
523 523
524 524 return [r for r in subset if _matchvalue(r)]
525 525
526 526 def filelog(repo, subset, x):
527 527 """``filelog(pattern)``
528 528 Changesets connected to the specified filelog.
529 529 """
530 530
531 531 pat = getstring(x, _("filelog requires a pattern"))
532 532 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
533 533 ctx=repo[None])
534 534 s = set()
535 535
536 536 if not matchmod.patkind(pat):
537 537 for f in m.files():
538 538 fl = repo.file(f)
539 539 for fr in fl:
540 540 s.add(fl.linkrev(fr))
541 541 else:
542 542 for f in repo[None]:
543 543 if m(f):
544 544 fl = repo.file(f)
545 545 for fr in fl:
546 546 s.add(fl.linkrev(fr))
547 547
548 548 return [r for r in subset if r in s]
549 549
550 550 def first(repo, subset, x):
551 551 """``first(set, [n])``
552 552 An alias for limit().
553 553 """
554 554 return limit(repo, subset, x)
555 555
556 556 def _follow(repo, subset, x, name, followfirst=False):
557 557 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
558 558 c = repo['.']
559 559 if l:
560 560 x = getstring(l[0], _("%s expected a filename") % name)
561 561 if x in c:
562 562 cx = c[x]
563 563 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
564 564 # include the revision responsible for the most recent version
565 565 s.add(cx.linkrev())
566 566 else:
567 567 return []
568 568 else:
569 569 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
570 570
571 571 return [r for r in subset if r in s]
572 572
573 573 def follow(repo, subset, x):
574 574 """``follow([file])``
575 575 An alias for ``::.`` (ancestors of the working copy's first parent).
576 576 If a filename is specified, the history of the given file is followed,
577 577 including copies.
578 578 """
579 579 return _follow(repo, subset, x, 'follow')
580 580
581 581 def _followfirst(repo, subset, x):
582 582 # ``followfirst([file])``
583 583 # Like ``follow([file])`` but follows only the first parent of
584 584 # every revision or file revision.
585 585 return _follow(repo, subset, x, '_followfirst', followfirst=True)
586 586
587 587 def getall(repo, subset, x):
588 588 """``all()``
589 589 All changesets, the same as ``0:tip``.
590 590 """
591 591 # i18n: "all" is a keyword
592 592 getargs(x, 0, 0, _("all takes no arguments"))
593 593 return subset
594 594
595 595 def grep(repo, subset, x):
596 596 """``grep(regex)``
597 597 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
598 598 to ensure special escape characters are handled correctly. Unlike
599 599 ``keyword(string)``, the match is case-sensitive.
600 600 """
601 601 try:
602 602 # i18n: "grep" is a keyword
603 603 gr = re.compile(getstring(x, _("grep requires a string")))
604 604 except re.error, e:
605 605 raise error.ParseError(_('invalid match pattern: %s') % e)
606 606 l = []
607 607 for r in subset:
608 608 c = repo[r]
609 609 for e in c.files() + [c.user(), c.description()]:
610 610 if gr.search(e):
611 611 l.append(r)
612 612 break
613 613 return l
614 614
615 615 def _matchfiles(repo, subset, x):
616 616 # _matchfiles takes a revset list of prefixed arguments:
617 617 #
618 618 # [p:foo, i:bar, x:baz]
619 619 #
620 620 # builds a match object from them and filters subset. Allowed
621 621 # prefixes are 'p:' for regular patterns, 'i:' for include
622 622 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
623 623 # a revision identifier, or the empty string to reference the
624 624 # working directory, from which the match object is
625 625 # initialized. Use 'd:' to set the default matching mode, default
626 626 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
627 627
628 628 # i18n: "_matchfiles" is a keyword
629 629 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
630 630 pats, inc, exc = [], [], []
631 631 hasset = False
632 632 rev, default = None, None
633 633 for arg in l:
634 634 s = getstring(arg, _("_matchfiles requires string arguments"))
635 635 prefix, value = s[:2], s[2:]
636 636 if prefix == 'p:':
637 637 pats.append(value)
638 638 elif prefix == 'i:':
639 639 inc.append(value)
640 640 elif prefix == 'x:':
641 641 exc.append(value)
642 642 elif prefix == 'r:':
643 643 if rev is not None:
644 644 raise error.ParseError(_('_matchfiles expected at most one '
645 645 'revision'))
646 646 rev = value
647 647 elif prefix == 'd:':
648 648 if default is not None:
649 649 raise error.ParseError(_('_matchfiles expected at most one '
650 650 'default mode'))
651 651 default = value
652 652 else:
653 653 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
654 654 if not hasset and matchmod.patkind(value) == 'set':
655 655 hasset = True
656 656 if not default:
657 657 default = 'glob'
658 658 m = None
659 659 s = []
660 660 for r in subset:
661 661 c = repo[r]
662 662 if not m or (hasset and rev is None):
663 663 ctx = c
664 664 if rev is not None:
665 665 ctx = repo[rev or None]
666 666 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
667 667 exclude=exc, ctx=ctx, default=default)
668 668 for f in c.files():
669 669 if m(f):
670 670 s.append(r)
671 671 break
672 672 return s
673 673
674 674 def hasfile(repo, subset, x):
675 675 """``file(pattern)``
676 676 Changesets affecting files matched by pattern.
677 677 """
678 678 # i18n: "file" is a keyword
679 679 pat = getstring(x, _("file requires a pattern"))
680 680 return _matchfiles(repo, subset, ('string', 'p:' + pat))
681 681
682 682 def head(repo, subset, x):
683 683 """``head()``
684 684 Changeset is a named branch head.
685 685 """
686 686 # i18n: "head" is a keyword
687 687 getargs(x, 0, 0, _("head takes no arguments"))
688 688 hs = set()
689 689 for b, ls in repo.branchmap().iteritems():
690 690 hs.update(repo[h].rev() for h in ls)
691 691 return [r for r in subset if r in hs]
692 692
693 693 def heads(repo, subset, x):
694 694 """``heads(set)``
695 695 Members of set with no children in set.
696 696 """
697 697 s = getset(repo, subset, x)
698 698 ps = set(parents(repo, subset, x))
699 699 return [r for r in s if r not in ps]
700 700
701 701 def keyword(repo, subset, x):
702 702 """``keyword(string)``
703 703 Search commit message, user name, and names of changed files for
704 704 string. The match is case-insensitive.
705 705 """
706 706 # i18n: "keyword" is a keyword
707 707 kw = encoding.lower(getstring(x, _("keyword requires a string")))
708 708 l = []
709 709 for r in subset:
710 710 c = repo[r]
711 711 t = " ".join(c.files() + [c.user(), c.description()])
712 712 if kw in encoding.lower(t):
713 713 l.append(r)
714 714 return l
715 715
716 716 def limit(repo, subset, x):
717 717 """``limit(set, [n])``
718 718 First n members of set, defaulting to 1.
719 719 """
720 720 # i18n: "limit" is a keyword
721 721 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
722 722 try:
723 723 lim = 1
724 724 if len(l) == 2:
725 725 # i18n: "limit" is a keyword
726 726 lim = int(getstring(l[1], _("limit requires a number")))
727 727 except (TypeError, ValueError):
728 728 # i18n: "limit" is a keyword
729 729 raise error.ParseError(_("limit expects a number"))
730 730 ss = set(subset)
731 731 os = getset(repo, range(len(repo)), l[0])[:lim]
732 732 return [r for r in os if r in ss]
733 733
734 734 def last(repo, subset, x):
735 735 """``last(set, [n])``
736 736 Last n members of set, defaulting to 1.
737 737 """
738 738 # i18n: "last" is a keyword
739 739 l = getargs(x, 1, 2, _("last requires one or two arguments"))
740 740 try:
741 741 lim = 1
742 742 if len(l) == 2:
743 743 # i18n: "last" is a keyword
744 744 lim = int(getstring(l[1], _("last requires a number")))
745 745 except (TypeError, ValueError):
746 746 # i18n: "last" is a keyword
747 747 raise error.ParseError(_("last expects a number"))
748 748 ss = set(subset)
749 749 os = getset(repo, range(len(repo)), l[0])[-lim:]
750 750 return [r for r in os if r in ss]
751 751
752 752 def maxrev(repo, subset, x):
753 753 """``max(set)``
754 754 Changeset with highest revision number in set.
755 755 """
756 756 os = getset(repo, range(len(repo)), x)
757 757 if os:
758 758 m = max(os)
759 759 if m in subset:
760 760 return [m]
761 761 return []
762 762
763 763 def merge(repo, subset, x):
764 764 """``merge()``
765 765 Changeset is a merge changeset.
766 766 """
767 767 # i18n: "merge" is a keyword
768 768 getargs(x, 0, 0, _("merge takes no arguments"))
769 769 cl = repo.changelog
770 770 return [r for r in subset if cl.parentrevs(r)[1] != -1]
771 771
772 772 def minrev(repo, subset, x):
773 773 """``min(set)``
774 774 Changeset with lowest revision number in set.
775 775 """
776 776 os = getset(repo, range(len(repo)), x)
777 777 if os:
778 778 m = min(os)
779 779 if m in subset:
780 780 return [m]
781 781 return []
782 782
783 783 def modifies(repo, subset, x):
784 784 """``modifies(pattern)``
785 785 Changesets modifying files matched by pattern.
786 786 """
787 787 # i18n: "modifies" is a keyword
788 788 pat = getstring(x, _("modifies requires a pattern"))
789 789 return checkstatus(repo, subset, pat, 0)
790 790
791 791 def node_(repo, subset, x):
792 792 """``id(string)``
793 793 Revision non-ambiguously specified by the given hex string prefix.
794 794 """
795 795 # i18n: "id" is a keyword
796 796 l = getargs(x, 1, 1, _("id requires one argument"))
797 797 # i18n: "id" is a keyword
798 798 n = getstring(l[0], _("id requires a string"))
799 799 if len(n) == 40:
800 800 rn = repo[n].rev()
801 801 else:
802 802 rn = None
803 803 pm = repo.changelog._partialmatch(n)
804 804 if pm is not None:
805 805 rn = repo.changelog.rev(pm)
806 806
807 807 return [r for r in subset if r == rn]
808 808
809 809 def outgoing(repo, subset, x):
810 810 """``outgoing([path])``
811 811 Changesets not found in the specified destination repository, or the
812 812 default push location.
813 813 """
814 814 import hg # avoid start-up nasties
815 815 # i18n: "outgoing" is a keyword
816 816 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
817 817 # i18n: "outgoing" is a keyword
818 818 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
819 819 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
820 820 dest, branches = hg.parseurl(dest)
821 821 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
822 822 if revs:
823 823 revs = [repo.lookup(rev) for rev in revs]
824 824 other = hg.peer(repo, {}, dest)
825 825 repo.ui.pushbuffer()
826 826 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
827 827 repo.ui.popbuffer()
828 828 cl = repo.changelog
829 829 o = set([cl.rev(r) for r in outgoing.missing])
830 830 return [r for r in subset if r in o]
831 831
832 832 def p1(repo, subset, x):
833 833 """``p1([set])``
834 834 First parent of changesets in set, or the working directory.
835 835 """
836 836 if x is None:
837 837 p = repo[x].p1().rev()
838 838 return [r for r in subset if r == p]
839 839
840 840 ps = set()
841 841 cl = repo.changelog
842 842 for r in getset(repo, range(len(repo)), x):
843 843 ps.add(cl.parentrevs(r)[0])
844 844 return [r for r in subset if r in ps]
845 845
846 846 def p2(repo, subset, x):
847 847 """``p2([set])``
848 848 Second parent of changesets in set, or the working directory.
849 849 """
850 850 if x is None:
851 851 ps = repo[x].parents()
852 852 try:
853 853 p = ps[1].rev()
854 854 return [r for r in subset if r == p]
855 855 except IndexError:
856 856 return []
857 857
858 858 ps = set()
859 859 cl = repo.changelog
860 860 for r in getset(repo, range(len(repo)), x):
861 861 ps.add(cl.parentrevs(r)[1])
862 862 return [r for r in subset if r in ps]
863 863
864 864 def parents(repo, subset, x):
865 865 """``parents([set])``
866 866 The set of all parents for all changesets in set, or the working directory.
867 867 """
868 868 if x is None:
869 869 ps = tuple(p.rev() for p in repo[x].parents())
870 870 return [r for r in subset if r in ps]
871 871
872 872 ps = set()
873 873 cl = repo.changelog
874 874 for r in getset(repo, range(len(repo)), x):
875 875 ps.update(cl.parentrevs(r))
876 876 return [r for r in subset if r in ps]
877 877
878 878 def parentspec(repo, subset, x, n):
879 879 """``set^0``
880 880 The set.
881 881 ``set^1`` (or ``set^``), ``set^2``
882 882 First or second parent, respectively, of all changesets in set.
883 883 """
884 884 try:
885 885 n = int(n[1])
886 886 if n not in (0, 1, 2):
887 887 raise ValueError
888 888 except (TypeError, ValueError):
889 889 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
890 890 ps = set()
891 891 cl = repo.changelog
892 892 for r in getset(repo, subset, x):
893 893 if n == 0:
894 894 ps.add(r)
895 895 elif n == 1:
896 896 ps.add(cl.parentrevs(r)[0])
897 897 elif n == 2:
898 898 parents = cl.parentrevs(r)
899 899 if len(parents) > 1:
900 900 ps.add(parents[1])
901 901 return [r for r in subset if r in ps]
902 902
903 903 def present(repo, subset, x):
904 904 """``present(set)``
905 905 An empty set, if any revision in set isn't found; otherwise,
906 906 all revisions in set.
907 907
908 908 If any of specified revisions is not present in the local repository,
909 909 the query is normally aborted. But this predicate allows the query
910 910 to continue even in such cases.
911 911 """
912 912 try:
913 913 return getset(repo, subset, x)
914 914 except error.RepoLookupError:
915 915 return []
916 916
917 917 def public(repo, subset, x):
918 918 """``public()``
919 919 Changeset in public phase."""
920 920 getargs(x, 0, 0, _("public takes no arguments"))
921 921 pc = repo._phasecache
922 922 return [r for r in subset if pc.phase(repo, r) == phases.public]
923 923
924 924 def remote(repo, subset, x):
925 925 """``remote([id [,path]])``
926 926 Local revision that corresponds to the given identifier in a
927 927 remote repository, if present. Here, the '.' identifier is a
928 928 synonym for the current local branch.
929 929 """
930 930
931 931 import hg # avoid start-up nasties
932 932 # i18n: "remote" is a keyword
933 933 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
934 934
935 935 q = '.'
936 936 if len(l) > 0:
937 937 # i18n: "remote" is a keyword
938 938 q = getstring(l[0], _("remote requires a string id"))
939 939 if q == '.':
940 940 q = repo['.'].branch()
941 941
942 942 dest = ''
943 943 if len(l) > 1:
944 944 # i18n: "remote" is a keyword
945 945 dest = getstring(l[1], _("remote requires a repository path"))
946 946 dest = repo.ui.expandpath(dest or 'default')
947 947 dest, branches = hg.parseurl(dest)
948 948 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
949 949 if revs:
950 950 revs = [repo.lookup(rev) for rev in revs]
951 951 other = hg.peer(repo, {}, dest)
952 952 n = other.lookup(q)
953 953 if n in repo:
954 954 r = repo[n].rev()
955 955 if r in subset:
956 956 return [r]
957 957 return []
958 958
959 959 def removes(repo, subset, x):
960 960 """``removes(pattern)``
961 961 Changesets which remove files matching pattern.
962 962 """
963 963 # i18n: "removes" is a keyword
964 964 pat = getstring(x, _("removes requires a pattern"))
965 965 return checkstatus(repo, subset, pat, 2)
966 966
967 967 def rev(repo, subset, x):
968 968 """``rev(number)``
969 969 Revision with the given numeric identifier.
970 970 """
971 971 # i18n: "rev" is a keyword
972 972 l = getargs(x, 1, 1, _("rev requires one argument"))
973 973 try:
974 974 # i18n: "rev" is a keyword
975 975 l = int(getstring(l[0], _("rev requires a number")))
976 976 except (TypeError, ValueError):
977 977 # i18n: "rev" is a keyword
978 978 raise error.ParseError(_("rev expects a number"))
979 979 return [r for r in subset if r == l]
980 980
981 981 def matching(repo, subset, x):
982 982 """``matching(revision [, field])``
983 983 Changesets in which a given set of fields match the set of fields in the
984 984 selected revision or set.
985 985
986 986 To match more than one field pass the list of fields to match separated
987 987 by spaces (e.g. ``author description``).
988 988
989 989 Valid fields are most regular revision fields and some special fields.
990 990
991 991 Regular revision fields are ``description``, ``author``, ``branch``,
992 992 ``date``, ``files``, ``phase``, ``parents``, ``substate`` and ``user``.
993 993 Note that ``author`` and ``user`` are synonyms.
994 994
995 995 Special fields are ``summary`` and ``metadata``:
996 996 ``summary`` matches the first line of the description.
997 997 ``metadata`` is equivalent to matching ``description user date``
998 998 (i.e. it matches the main metadata fields).
999 999
1000 1000 ``metadata`` is the default field which is used when no fields are
1001 1001 specified. You can match more than one field at a time.
1002 1002 """
1003 1003 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1004 1004
1005 1005 revs = getset(repo, xrange(len(repo)), l[0])
1006 1006
1007 1007 fieldlist = ['metadata']
1008 1008 if len(l) > 1:
1009 1009 fieldlist = getstring(l[1],
1010 1010 _("matching requires a string "
1011 1011 "as its second argument")).split()
1012 1012
1013 1013 # Make sure that there are no repeated fields, and expand the
1014 1014 # 'special' 'metadata' field type
1015 1015 fields = []
1016 1016 for field in fieldlist:
1017 1017 if field == 'metadata':
1018 1018 fields += ['user', 'description', 'date']
1019 1019 else:
1020 1020 if field == 'author':
1021 1021 field = 'user'
1022 1022 fields.append(field)
1023 1023 fields = set(fields)
1024 1024 if 'summary' in fields and 'description' in fields:
1025 1025 # If a revision matches its description it also matches its summary
1026 1026 fields.discard('summary')
1027 1027
1028 1028 # We may want to match more than one field
1029 1029 # Not all fields take the same amount of time to be matched
1030 1030 # Sort the selected fields in order of increasing matching cost
1031 1031 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1032 1032 'files', 'description', 'substate']
1033 1033 def fieldkeyfunc(f):
1034 1034 try:
1035 1035 return fieldorder.index(f)
1036 1036 except ValueError:
1037 1037 # assume an unknown field is very costly
1038 1038 return len(fieldorder)
1039 1039 fields = list(fields)
1040 1040 fields.sort(key=fieldkeyfunc)
1041 1041
1042 1042 # Each field will be matched with its own "getfield" function
1043 1043 # which will be added to the getfieldfuncs array of functions
1044 1044 getfieldfuncs = []
1045 1045 _funcs = {
1046 1046 'user': lambda r: repo[r].user(),
1047 1047 'branch': lambda r: repo[r].branch(),
1048 1048 'date': lambda r: repo[r].date(),
1049 1049 'description': lambda r: repo[r].description(),
1050 1050 'files': lambda r: repo[r].files(),
1051 1051 'parents': lambda r: repo[r].parents(),
1052 1052 'phase': lambda r: repo[r].phase(),
1053 1053 'substate': lambda r: repo[r].substate,
1054 1054 'summary': lambda r: repo[r].description().splitlines()[0],
1055 1055 }
1056 1056 for info in fields:
1057 1057 getfield = _funcs.get(info, None)
1058 1058 if getfield is None:
1059 1059 raise error.ParseError(
1060 1060 _("unexpected field name passed to matching: %s") % info)
1061 1061 getfieldfuncs.append(getfield)
1062 1062 # convert the getfield array of functions into a "getinfo" function
1063 1063 # which returns an array of field values (or a single value if there
1064 1064 # is only one field to match)
1065 1065 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1066 1066
1067 1067 matches = set()
1068 1068 for rev in revs:
1069 1069 target = getinfo(rev)
1070 1070 for r in subset:
1071 1071 match = True
1072 1072 for n, f in enumerate(getfieldfuncs):
1073 1073 if target[n] != f(r):
1074 1074 match = False
1075 1075 break
1076 1076 if match:
1077 1077 matches.add(r)
1078 1078 return [r for r in subset if r in matches]
1079 1079
1080 1080 def reverse(repo, subset, x):
1081 1081 """``reverse(set)``
1082 1082 Reverse order of set.
1083 1083 """
1084 1084 l = getset(repo, subset, x)
1085 1085 l.reverse()
1086 1086 return l
1087 1087
1088 1088 def roots(repo, subset, x):
1089 1089 """``roots(set)``
1090 1090 Changesets in set with no parent changeset in set.
1091 1091 """
1092 1092 s = set(getset(repo, xrange(len(repo)), x))
1093 1093 subset = [r for r in subset if r in s]
1094 1094 cs = _children(repo, subset, s)
1095 1095 return [r for r in subset if r not in cs]
1096 1096
1097 1097 def secret(repo, subset, x):
1098 1098 """``secret()``
1099 1099 Changeset in secret phase."""
1100 1100 getargs(x, 0, 0, _("secret takes no arguments"))
1101 1101 pc = repo._phasecache
1102 1102 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1103 1103
1104 1104 def sort(repo, subset, x):
1105 1105 """``sort(set[, [-]key...])``
1106 1106 Sort set by keys. The default sort order is ascending, specify a key
1107 1107 as ``-key`` to sort in descending order.
1108 1108
1109 1109 The keys can be:
1110 1110
1111 1111 - ``rev`` for the revision number,
1112 1112 - ``branch`` for the branch name,
1113 1113 - ``desc`` for the commit message (description),
1114 1114 - ``user`` for user name (``author`` can be used as an alias),
1115 1115 - ``date`` for the commit date
1116 1116 """
1117 1117 # i18n: "sort" is a keyword
1118 1118 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1119 1119 keys = "rev"
1120 1120 if len(l) == 2:
1121 1121 keys = getstring(l[1], _("sort spec must be a string"))
1122 1122
1123 1123 s = l[0]
1124 1124 keys = keys.split()
1125 1125 l = []
1126 1126 def invert(s):
1127 1127 return "".join(chr(255 - ord(c)) for c in s)
1128 1128 for r in getset(repo, subset, s):
1129 1129 c = repo[r]
1130 1130 e = []
1131 1131 for k in keys:
1132 1132 if k == 'rev':
1133 1133 e.append(r)
1134 1134 elif k == '-rev':
1135 1135 e.append(-r)
1136 1136 elif k == 'branch':
1137 1137 e.append(c.branch())
1138 1138 elif k == '-branch':
1139 1139 e.append(invert(c.branch()))
1140 1140 elif k == 'desc':
1141 1141 e.append(c.description())
1142 1142 elif k == '-desc':
1143 1143 e.append(invert(c.description()))
1144 1144 elif k in 'user author':
1145 1145 e.append(c.user())
1146 1146 elif k in '-user -author':
1147 1147 e.append(invert(c.user()))
1148 1148 elif k == 'date':
1149 1149 e.append(c.date()[0])
1150 1150 elif k == '-date':
1151 1151 e.append(-c.date()[0])
1152 1152 else:
1153 1153 raise error.ParseError(_("unknown sort key %r") % k)
1154 1154 e.append(r)
1155 1155 l.append(e)
1156 1156 l.sort()
1157 1157 return [e[-1] for e in l]
1158 1158
1159 1159 def _stringmatcher(pattern):
1160 1160 """
1161 1161 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1162 1162 returns the matcher name, pattern, and matcher function.
1163 1163 missing or unknown prefixes are treated as literal matches.
1164 1164
1165 1165 helper for tests:
1166 1166 >>> def test(pattern, *tests):
1167 1167 ... kind, pattern, matcher = _stringmatcher(pattern)
1168 1168 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1169 1169
1170 1170 exact matching (no prefix):
1171 1171 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1172 1172 ('literal', 'abcdefg', [False, False, True])
1173 1173
1174 1174 regex matching ('re:' prefix)
1175 1175 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1176 1176 ('re', 'a.+b', [False, False, True])
1177 1177
1178 1178 force exact matches ('literal:' prefix)
1179 1179 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1180 1180 ('literal', 're:foobar', [False, True])
1181 1181
1182 1182 unknown prefixes are ignored and treated as literals
1183 1183 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1184 1184 ('literal', 'foo:bar', [False, False, True])
1185 1185 """
1186 1186 if pattern.startswith('re:'):
1187 1187 pattern = pattern[3:]
1188 1188 try:
1189 1189 regex = re.compile(pattern)
1190 1190 except re.error, e:
1191 1191 raise error.ParseError(_('invalid regular expression: %s')
1192 1192 % e)
1193 1193 return 're', pattern, regex.search
1194 1194 elif pattern.startswith('literal:'):
1195 1195 pattern = pattern[8:]
1196 1196 return 'literal', pattern, pattern.__eq__
1197 1197
1198 1198 def _substringmatcher(pattern):
1199 1199 kind, pattern, matcher = _stringmatcher(pattern)
1200 1200 if kind == 'literal':
1201 1201 matcher = lambda s: pattern in s
1202 1202 return kind, pattern, matcher
1203 1203
1204 1204 def tag(repo, subset, x):
1205 1205 """``tag([name])``
1206 1206 The specified tag by name, or all tagged revisions if no name is given.
1207 1207 """
1208 1208 # i18n: "tag" is a keyword
1209 1209 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1210 1210 cl = repo.changelog
1211 1211 if args:
1212 1212 pattern = getstring(args[0],
1213 1213 # i18n: "tag" is a keyword
1214 1214 _('the argument to tag must be a string'))
1215 1215 kind, pattern, matcher = _stringmatcher(pattern)
1216 1216 if kind == 'literal':
1217 if not repo.tags().get(pattern, None):
1217 # avoid resolving all tags
1218 tn = repo._tagscache.tags.get(pattern, None)
1219 if tn is None:
1218 1220 raise util.Abort(_("tag '%s' does not exist") % pattern)
1219 s = set([cl.rev(n) for t, n in repo.tagslist() if t == pattern])
1221 s = set([repo[tn].rev()])
1220 1222 else:
1221 1223 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1222 1224 if not s:
1223 1225 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1224 1226 else:
1225 1227 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1226 1228 return [r for r in subset if r in s]
1227 1229
1228 1230 def tagged(repo, subset, x):
1229 1231 return tag(repo, subset, x)
1230 1232
1231 1233 def user(repo, subset, x):
1232 1234 """``user(string)``
1233 1235 User name contains string. The match is case-insensitive.
1234 1236
1235 1237 If `string` starts with `re:`, the remainder of the string is treated as
1236 1238 a regular expression. To match a user that actually contains `re:`, use
1237 1239 the prefix `literal:`.
1238 1240 """
1239 1241 return author(repo, subset, x)
1240 1242
1241 1243 # for internal use
1242 1244 def _list(repo, subset, x):
1243 1245 s = getstring(x, "internal error")
1244 1246 if not s:
1245 1247 return []
1246 1248 if not isinstance(subset, set):
1247 1249 subset = set(subset)
1248 1250 ls = [repo[r].rev() for r in s.split('\0')]
1249 1251 return [r for r in ls if r in subset]
1250 1252
1251 1253 symbols = {
1252 1254 "adds": adds,
1253 1255 "all": getall,
1254 1256 "ancestor": ancestor,
1255 1257 "ancestors": ancestors,
1256 1258 "_firstancestors": _firstancestors,
1257 1259 "author": author,
1258 1260 "bisect": bisect,
1259 1261 "bisected": bisected,
1260 1262 "bookmark": bookmark,
1261 1263 "branch": branch,
1262 1264 "children": children,
1263 1265 "closed": closed,
1264 1266 "contains": contains,
1265 1267 "date": date,
1266 1268 "desc": desc,
1267 1269 "descendants": descendants,
1268 1270 "_firstdescendants": _firstdescendants,
1269 1271 "draft": draft,
1270 1272 "extra": extra,
1271 1273 "file": hasfile,
1272 1274 "filelog": filelog,
1273 1275 "first": first,
1274 1276 "follow": follow,
1275 1277 "_followfirst": _followfirst,
1276 1278 "grep": grep,
1277 1279 "head": head,
1278 1280 "heads": heads,
1279 1281 "id": node_,
1280 1282 "keyword": keyword,
1281 1283 "last": last,
1282 1284 "limit": limit,
1283 1285 "_matchfiles": _matchfiles,
1284 1286 "max": maxrev,
1285 1287 "merge": merge,
1286 1288 "min": minrev,
1287 1289 "modifies": modifies,
1288 1290 "outgoing": outgoing,
1289 1291 "p1": p1,
1290 1292 "p2": p2,
1291 1293 "parents": parents,
1292 1294 "present": present,
1293 1295 "public": public,
1294 1296 "remote": remote,
1295 1297 "removes": removes,
1296 1298 "rev": rev,
1297 1299 "reverse": reverse,
1298 1300 "roots": roots,
1299 1301 "sort": sort,
1300 1302 "secret": secret,
1301 1303 "matching": matching,
1302 1304 "tag": tag,
1303 1305 "tagged": tagged,
1304 1306 "user": user,
1305 1307 "_list": _list,
1306 1308 }
1307 1309
1308 1310 methods = {
1309 1311 "range": rangeset,
1310 1312 "string": stringset,
1311 1313 "symbol": symbolset,
1312 1314 "and": andset,
1313 1315 "or": orset,
1314 1316 "not": notset,
1315 1317 "list": listset,
1316 1318 "func": func,
1317 1319 "ancestor": ancestorspec,
1318 1320 "parent": parentspec,
1319 1321 "parentpost": p1,
1320 1322 }
1321 1323
1322 1324 def optimize(x, small):
1323 1325 if x is None:
1324 1326 return 0, x
1325 1327
1326 1328 smallbonus = 1
1327 1329 if small:
1328 1330 smallbonus = .5
1329 1331
1330 1332 op = x[0]
1331 1333 if op == 'minus':
1332 1334 return optimize(('and', x[1], ('not', x[2])), small)
1333 1335 elif op == 'dagrange':
1334 1336 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
1335 1337 ('func', ('symbol', 'ancestors'), x[2])), small)
1336 1338 elif op == 'dagrangepre':
1337 1339 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1338 1340 elif op == 'dagrangepost':
1339 1341 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1340 1342 elif op == 'rangepre':
1341 1343 return optimize(('range', ('string', '0'), x[1]), small)
1342 1344 elif op == 'rangepost':
1343 1345 return optimize(('range', x[1], ('string', 'tip')), small)
1344 1346 elif op == 'negate':
1345 1347 return optimize(('string',
1346 1348 '-' + getstring(x[1], _("can't negate that"))), small)
1347 1349 elif op in 'string symbol negate':
1348 1350 return smallbonus, x # single revisions are small
1349 1351 elif op == 'and' or op == 'dagrange':
1350 1352 wa, ta = optimize(x[1], True)
1351 1353 wb, tb = optimize(x[2], True)
1352 1354 w = min(wa, wb)
1353 1355 if wa > wb:
1354 1356 return w, (op, tb, ta)
1355 1357 return w, (op, ta, tb)
1356 1358 elif op == 'or':
1357 1359 wa, ta = optimize(x[1], False)
1358 1360 wb, tb = optimize(x[2], False)
1359 1361 if wb < wa:
1360 1362 wb, wa = wa, wb
1361 1363 return max(wa, wb), (op, ta, tb)
1362 1364 elif op == 'not':
1363 1365 o = optimize(x[1], not small)
1364 1366 return o[0], (op, o[1])
1365 1367 elif op == 'parentpost':
1366 1368 o = optimize(x[1], small)
1367 1369 return o[0], (op, o[1])
1368 1370 elif op == 'group':
1369 1371 return optimize(x[1], small)
1370 1372 elif op in 'range list parent ancestorspec':
1371 1373 if op == 'parent':
1372 1374 # x^:y means (x^) : y, not x ^ (:y)
1373 1375 post = ('parentpost', x[1])
1374 1376 if x[2][0] == 'dagrangepre':
1375 1377 return optimize(('dagrange', post, x[2][1]), small)
1376 1378 elif x[2][0] == 'rangepre':
1377 1379 return optimize(('range', post, x[2][1]), small)
1378 1380
1379 1381 wa, ta = optimize(x[1], small)
1380 1382 wb, tb = optimize(x[2], small)
1381 1383 return wa + wb, (op, ta, tb)
1382 1384 elif op == 'func':
1383 1385 f = getstring(x[1], _("not a symbol"))
1384 1386 wa, ta = optimize(x[2], small)
1385 1387 if f in ("author branch closed date desc file grep keyword "
1386 1388 "outgoing user"):
1387 1389 w = 10 # slow
1388 1390 elif f in "modifies adds removes":
1389 1391 w = 30 # slower
1390 1392 elif f == "contains":
1391 1393 w = 100 # very slow
1392 1394 elif f == "ancestor":
1393 1395 w = 1 * smallbonus
1394 1396 elif f in "reverse limit first":
1395 1397 w = 0
1396 1398 elif f in "sort":
1397 1399 w = 10 # assume most sorts look at changelog
1398 1400 else:
1399 1401 w = 1
1400 1402 return w + wa, (op, x[1], ta)
1401 1403 return 1, x
1402 1404
1403 1405 _aliasarg = ('func', ('symbol', '_aliasarg'))
1404 1406 def _getaliasarg(tree):
1405 1407 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1406 1408 return X, None otherwise.
1407 1409 """
1408 1410 if (len(tree) == 3 and tree[:2] == _aliasarg
1409 1411 and tree[2][0] == 'string'):
1410 1412 return tree[2][1]
1411 1413 return None
1412 1414
1413 1415 def _checkaliasarg(tree, known=None):
1414 1416 """Check tree contains no _aliasarg construct or only ones which
1415 1417 value is in known. Used to avoid alias placeholders injection.
1416 1418 """
1417 1419 if isinstance(tree, tuple):
1418 1420 arg = _getaliasarg(tree)
1419 1421 if arg is not None and (not known or arg not in known):
1420 1422 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1421 1423 for t in tree:
1422 1424 _checkaliasarg(t, known)
1423 1425
1424 1426 class revsetalias(object):
1425 1427 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1426 1428 args = None
1427 1429
1428 1430 def __init__(self, name, value):
1429 1431 '''Aliases like:
1430 1432
1431 1433 h = heads(default)
1432 1434 b($1) = ancestors($1) - ancestors(default)
1433 1435 '''
1434 1436 m = self.funcre.search(name)
1435 1437 if m:
1436 1438 self.name = m.group(1)
1437 1439 self.tree = ('func', ('symbol', m.group(1)))
1438 1440 self.args = [x.strip() for x in m.group(2).split(',')]
1439 1441 for arg in self.args:
1440 1442 # _aliasarg() is an unknown symbol only used separate
1441 1443 # alias argument placeholders from regular strings.
1442 1444 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1443 1445 else:
1444 1446 self.name = name
1445 1447 self.tree = ('symbol', name)
1446 1448
1447 1449 self.replacement, pos = parse(value)
1448 1450 if pos != len(value):
1449 1451 raise error.ParseError(_('invalid token'), pos)
1450 1452 # Check for placeholder injection
1451 1453 _checkaliasarg(self.replacement, self.args)
1452 1454
1453 1455 def _getalias(aliases, tree):
1454 1456 """If tree looks like an unexpanded alias, return it. Return None
1455 1457 otherwise.
1456 1458 """
1457 1459 if isinstance(tree, tuple) and tree:
1458 1460 if tree[0] == 'symbol' and len(tree) == 2:
1459 1461 name = tree[1]
1460 1462 alias = aliases.get(name)
1461 1463 if alias and alias.args is None and alias.tree == tree:
1462 1464 return alias
1463 1465 if tree[0] == 'func' and len(tree) > 1:
1464 1466 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1465 1467 name = tree[1][1]
1466 1468 alias = aliases.get(name)
1467 1469 if alias and alias.args is not None and alias.tree == tree[:2]:
1468 1470 return alias
1469 1471 return None
1470 1472
1471 1473 def _expandargs(tree, args):
1472 1474 """Replace _aliasarg instances with the substitution value of the
1473 1475 same name in args, recursively.
1474 1476 """
1475 1477 if not tree or not isinstance(tree, tuple):
1476 1478 return tree
1477 1479 arg = _getaliasarg(tree)
1478 1480 if arg is not None:
1479 1481 return args[arg]
1480 1482 return tuple(_expandargs(t, args) for t in tree)
1481 1483
1482 1484 def _expandaliases(aliases, tree, expanding):
1483 1485 """Expand aliases in tree, recursively.
1484 1486
1485 1487 'aliases' is a dictionary mapping user defined aliases to
1486 1488 revsetalias objects.
1487 1489 """
1488 1490 if not isinstance(tree, tuple):
1489 1491 # Do not expand raw strings
1490 1492 return tree
1491 1493 alias = _getalias(aliases, tree)
1492 1494 if alias is not None:
1493 1495 if alias in expanding:
1494 1496 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1495 1497 'detected') % alias.name)
1496 1498 expanding.append(alias)
1497 1499 result = _expandaliases(aliases, alias.replacement, expanding)
1498 1500 expanding.pop()
1499 1501 if alias.args is not None:
1500 1502 l = getlist(tree[2])
1501 1503 if len(l) != len(alias.args):
1502 1504 raise error.ParseError(
1503 1505 _('invalid number of arguments: %s') % len(l))
1504 1506 l = [_expandaliases(aliases, a, []) for a in l]
1505 1507 result = _expandargs(result, dict(zip(alias.args, l)))
1506 1508 else:
1507 1509 result = tuple(_expandaliases(aliases, t, expanding)
1508 1510 for t in tree)
1509 1511 return result
1510 1512
1511 1513 def findaliases(ui, tree):
1512 1514 _checkaliasarg(tree)
1513 1515 aliases = {}
1514 1516 for k, v in ui.configitems('revsetalias'):
1515 1517 alias = revsetalias(k, v)
1516 1518 aliases[alias.name] = alias
1517 1519 return _expandaliases(aliases, tree, [])
1518 1520
1519 1521 parse = parser.parser(tokenize, elements).parse
1520 1522
1521 1523 def match(ui, spec):
1522 1524 if not spec:
1523 1525 raise error.ParseError(_("empty query"))
1524 1526 tree, pos = parse(spec)
1525 1527 if (pos != len(spec)):
1526 1528 raise error.ParseError(_("invalid token"), pos)
1527 1529 if ui:
1528 1530 tree = findaliases(ui, tree)
1529 1531 weight, tree = optimize(tree, True)
1530 1532 def mfunc(repo, subset):
1531 1533 return getset(repo, subset, tree)
1532 1534 return mfunc
1533 1535
1534 1536 def formatspec(expr, *args):
1535 1537 '''
1536 1538 This is a convenience function for using revsets internally, and
1537 1539 escapes arguments appropriately. Aliases are intentionally ignored
1538 1540 so that intended expression behavior isn't accidentally subverted.
1539 1541
1540 1542 Supported arguments:
1541 1543
1542 1544 %r = revset expression, parenthesized
1543 1545 %d = int(arg), no quoting
1544 1546 %s = string(arg), escaped and single-quoted
1545 1547 %b = arg.branch(), escaped and single-quoted
1546 1548 %n = hex(arg), single-quoted
1547 1549 %% = a literal '%'
1548 1550
1549 1551 Prefixing the type with 'l' specifies a parenthesized list of that type.
1550 1552
1551 1553 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1552 1554 '(10 or 11):: and ((this()) or (that()))'
1553 1555 >>> formatspec('%d:: and not %d::', 10, 20)
1554 1556 '10:: and not 20::'
1555 1557 >>> formatspec('%ld or %ld', [], [1])
1556 1558 "_list('') or 1"
1557 1559 >>> formatspec('keyword(%s)', 'foo\\xe9')
1558 1560 "keyword('foo\\\\xe9')"
1559 1561 >>> b = lambda: 'default'
1560 1562 >>> b.branch = b
1561 1563 >>> formatspec('branch(%b)', b)
1562 1564 "branch('default')"
1563 1565 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1564 1566 "root(_list('a\\x00b\\x00c\\x00d'))"
1565 1567 '''
1566 1568
1567 1569 def quote(s):
1568 1570 return repr(str(s))
1569 1571
1570 1572 def argtype(c, arg):
1571 1573 if c == 'd':
1572 1574 return str(int(arg))
1573 1575 elif c == 's':
1574 1576 return quote(arg)
1575 1577 elif c == 'r':
1576 1578 parse(arg) # make sure syntax errors are confined
1577 1579 return '(%s)' % arg
1578 1580 elif c == 'n':
1579 1581 return quote(node.hex(arg))
1580 1582 elif c == 'b':
1581 1583 return quote(arg.branch())
1582 1584
1583 1585 def listexp(s, t):
1584 1586 l = len(s)
1585 1587 if l == 0:
1586 1588 return "_list('')"
1587 1589 elif l == 1:
1588 1590 return argtype(t, s[0])
1589 1591 elif t == 'd':
1590 1592 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1591 1593 elif t == 's':
1592 1594 return "_list('%s')" % "\0".join(s)
1593 1595 elif t == 'n':
1594 1596 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1595 1597 elif t == 'b':
1596 1598 return "_list('%s')" % "\0".join(a.branch() for a in s)
1597 1599
1598 1600 m = l // 2
1599 1601 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1600 1602
1601 1603 ret = ''
1602 1604 pos = 0
1603 1605 arg = 0
1604 1606 while pos < len(expr):
1605 1607 c = expr[pos]
1606 1608 if c == '%':
1607 1609 pos += 1
1608 1610 d = expr[pos]
1609 1611 if d == '%':
1610 1612 ret += d
1611 1613 elif d in 'dsnbr':
1612 1614 ret += argtype(d, args[arg])
1613 1615 arg += 1
1614 1616 elif d == 'l':
1615 1617 # a list of some type
1616 1618 pos += 1
1617 1619 d = expr[pos]
1618 1620 ret += listexp(list(args[arg]), d)
1619 1621 arg += 1
1620 1622 else:
1621 1623 raise util.Abort('unexpected revspec format character %s' % d)
1622 1624 else:
1623 1625 ret += c
1624 1626 pos += 1
1625 1627
1626 1628 return ret
1627 1629
1628 1630 def prettyformat(tree):
1629 1631 def _prettyformat(tree, level, lines):
1630 1632 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1631 1633 lines.append((level, str(tree)))
1632 1634 else:
1633 1635 lines.append((level, '(%s' % tree[0]))
1634 1636 for s in tree[1:]:
1635 1637 _prettyformat(s, level + 1, lines)
1636 1638 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1637 1639
1638 1640 lines = []
1639 1641 _prettyformat(tree, 0, lines)
1640 1642 output = '\n'.join((' '*l + s) for l, s in lines)
1641 1643 return output
1642 1644
1643 1645 # tell hggettext to extract docstrings from these functions:
1644 1646 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now