##// END OF EJS Templates
revset: drop unreachable code
Bryan O'Sullivan -
r16859:eeb464ed default
parent child Browse files
Show More
@@ -1,1649 +1,1649
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, 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 = util.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 1217 # avoid resolving all tags
1218 1218 tn = repo._tagscache.tags.get(pattern, None)
1219 1219 if tn is None:
1220 1220 raise util.Abort(_("tag '%s' does not exist") % pattern)
1221 1221 s = set([repo[tn].rev()])
1222 1222 else:
1223 1223 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1224 1224 if not s:
1225 1225 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1226 1226 else:
1227 1227 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1228 1228 return [r for r in subset if r in s]
1229 1229
1230 1230 def tagged(repo, subset, x):
1231 1231 return tag(repo, subset, x)
1232 1232
1233 1233 def user(repo, subset, x):
1234 1234 """``user(string)``
1235 1235 User name contains string. The match is case-insensitive.
1236 1236
1237 1237 If `string` starts with `re:`, the remainder of the string is treated as
1238 1238 a regular expression. To match a user that actually contains `re:`, use
1239 1239 the prefix `literal:`.
1240 1240 """
1241 1241 return author(repo, subset, x)
1242 1242
1243 1243 # for internal use
1244 1244 def _list(repo, subset, x):
1245 1245 s = getstring(x, "internal error")
1246 1246 if not s:
1247 1247 return []
1248 1248 if not isinstance(subset, set):
1249 1249 subset = set(subset)
1250 1250 ls = [repo[r].rev() for r in s.split('\0')]
1251 1251 return [r for r in ls if r in subset]
1252 1252
1253 1253 symbols = {
1254 1254 "adds": adds,
1255 1255 "all": getall,
1256 1256 "ancestor": ancestor,
1257 1257 "ancestors": ancestors,
1258 1258 "_firstancestors": _firstancestors,
1259 1259 "author": author,
1260 1260 "bisect": bisect,
1261 1261 "bisected": bisected,
1262 1262 "bookmark": bookmark,
1263 1263 "branch": branch,
1264 1264 "children": children,
1265 1265 "closed": closed,
1266 1266 "contains": contains,
1267 1267 "date": date,
1268 1268 "desc": desc,
1269 1269 "descendants": descendants,
1270 1270 "_firstdescendants": _firstdescendants,
1271 1271 "draft": draft,
1272 1272 "extra": extra,
1273 1273 "file": hasfile,
1274 1274 "filelog": filelog,
1275 1275 "first": first,
1276 1276 "follow": follow,
1277 1277 "_followfirst": _followfirst,
1278 1278 "grep": grep,
1279 1279 "head": head,
1280 1280 "heads": heads,
1281 1281 "id": node_,
1282 1282 "keyword": keyword,
1283 1283 "last": last,
1284 1284 "limit": limit,
1285 1285 "_matchfiles": _matchfiles,
1286 1286 "max": maxrev,
1287 1287 "merge": merge,
1288 1288 "min": minrev,
1289 1289 "modifies": modifies,
1290 1290 "outgoing": outgoing,
1291 1291 "p1": p1,
1292 1292 "p2": p2,
1293 1293 "parents": parents,
1294 1294 "present": present,
1295 1295 "public": public,
1296 1296 "remote": remote,
1297 1297 "removes": removes,
1298 1298 "rev": rev,
1299 1299 "reverse": reverse,
1300 1300 "roots": roots,
1301 1301 "sort": sort,
1302 1302 "secret": secret,
1303 1303 "matching": matching,
1304 1304 "tag": tag,
1305 1305 "tagged": tagged,
1306 1306 "user": user,
1307 1307 "_list": _list,
1308 1308 }
1309 1309
1310 1310 methods = {
1311 1311 "range": rangeset,
1312 1312 "string": stringset,
1313 1313 "symbol": symbolset,
1314 1314 "and": andset,
1315 1315 "or": orset,
1316 1316 "not": notset,
1317 1317 "list": listset,
1318 1318 "func": func,
1319 1319 "ancestor": ancestorspec,
1320 1320 "parent": parentspec,
1321 1321 "parentpost": p1,
1322 1322 }
1323 1323
1324 1324 def optimize(x, small):
1325 1325 if x is None:
1326 1326 return 0, x
1327 1327
1328 1328 smallbonus = 1
1329 1329 if small:
1330 1330 smallbonus = .5
1331 1331
1332 1332 op = x[0]
1333 1333 if op == 'minus':
1334 1334 return optimize(('and', x[1], ('not', x[2])), small)
1335 1335 elif op == 'dagrange':
1336 1336 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
1337 1337 ('func', ('symbol', 'ancestors'), x[2])), small)
1338 1338 elif op == 'dagrangepre':
1339 1339 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1340 1340 elif op == 'dagrangepost':
1341 1341 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1342 1342 elif op == 'rangepre':
1343 1343 return optimize(('range', ('string', '0'), x[1]), small)
1344 1344 elif op == 'rangepost':
1345 1345 return optimize(('range', x[1], ('string', 'tip')), small)
1346 1346 elif op == 'negate':
1347 1347 return optimize(('string',
1348 1348 '-' + getstring(x[1], _("can't negate that"))), small)
1349 1349 elif op in 'string symbol negate':
1350 1350 return smallbonus, x # single revisions are small
1351 elif op == 'and' or op == 'dagrange':
1351 elif op == 'and':
1352 1352 wa, ta = optimize(x[1], True)
1353 1353 wb, tb = optimize(x[2], True)
1354 1354 w = min(wa, wb)
1355 1355 if wa > wb:
1356 1356 return w, (op, tb, ta)
1357 1357 return w, (op, ta, tb)
1358 1358 elif op == 'or':
1359 1359 wa, ta = optimize(x[1], False)
1360 1360 wb, tb = optimize(x[2], False)
1361 1361 if wb < wa:
1362 1362 wb, wa = wa, wb
1363 1363 return max(wa, wb), (op, ta, tb)
1364 1364 elif op == 'not':
1365 1365 o = optimize(x[1], not small)
1366 1366 return o[0], (op, o[1])
1367 1367 elif op == 'parentpost':
1368 1368 o = optimize(x[1], small)
1369 1369 return o[0], (op, o[1])
1370 1370 elif op == 'group':
1371 1371 return optimize(x[1], small)
1372 1372 elif op in 'range list parent ancestorspec':
1373 1373 if op == 'parent':
1374 1374 # x^:y means (x^) : y, not x ^ (:y)
1375 1375 post = ('parentpost', x[1])
1376 1376 if x[2][0] == 'dagrangepre':
1377 1377 return optimize(('dagrange', post, x[2][1]), small)
1378 1378 elif x[2][0] == 'rangepre':
1379 1379 return optimize(('range', post, x[2][1]), small)
1380 1380
1381 1381 wa, ta = optimize(x[1], small)
1382 1382 wb, tb = optimize(x[2], small)
1383 1383 return wa + wb, (op, ta, tb)
1384 1384 elif op == 'func':
1385 1385 f = getstring(x[1], _("not a symbol"))
1386 1386 wa, ta = optimize(x[2], small)
1387 1387 if f in ("author branch closed date desc file grep keyword "
1388 1388 "outgoing user"):
1389 1389 w = 10 # slow
1390 1390 elif f in "modifies adds removes":
1391 1391 w = 30 # slower
1392 1392 elif f == "contains":
1393 1393 w = 100 # very slow
1394 1394 elif f == "ancestor":
1395 1395 w = 1 * smallbonus
1396 1396 elif f in "reverse limit first":
1397 1397 w = 0
1398 1398 elif f in "sort":
1399 1399 w = 10 # assume most sorts look at changelog
1400 1400 else:
1401 1401 w = 1
1402 1402 return w + wa, (op, x[1], ta)
1403 1403 return 1, x
1404 1404
1405 1405 _aliasarg = ('func', ('symbol', '_aliasarg'))
1406 1406 def _getaliasarg(tree):
1407 1407 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1408 1408 return X, None otherwise.
1409 1409 """
1410 1410 if (len(tree) == 3 and tree[:2] == _aliasarg
1411 1411 and tree[2][0] == 'string'):
1412 1412 return tree[2][1]
1413 1413 return None
1414 1414
1415 1415 def _checkaliasarg(tree, known=None):
1416 1416 """Check tree contains no _aliasarg construct or only ones which
1417 1417 value is in known. Used to avoid alias placeholders injection.
1418 1418 """
1419 1419 if isinstance(tree, tuple):
1420 1420 arg = _getaliasarg(tree)
1421 1421 if arg is not None and (not known or arg not in known):
1422 1422 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1423 1423 for t in tree:
1424 1424 _checkaliasarg(t, known)
1425 1425
1426 1426 class revsetalias(object):
1427 1427 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1428 1428 args = None
1429 1429
1430 1430 def __init__(self, name, value):
1431 1431 '''Aliases like:
1432 1432
1433 1433 h = heads(default)
1434 1434 b($1) = ancestors($1) - ancestors(default)
1435 1435 '''
1436 1436 m = self.funcre.search(name)
1437 1437 if m:
1438 1438 self.name = m.group(1)
1439 1439 self.tree = ('func', ('symbol', m.group(1)))
1440 1440 self.args = [x.strip() for x in m.group(2).split(',')]
1441 1441 for arg in self.args:
1442 1442 # _aliasarg() is an unknown symbol only used separate
1443 1443 # alias argument placeholders from regular strings.
1444 1444 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1445 1445 else:
1446 1446 self.name = name
1447 1447 self.tree = ('symbol', name)
1448 1448
1449 1449 self.replacement, pos = parse(value)
1450 1450 if pos != len(value):
1451 1451 raise error.ParseError(_('invalid token'), pos)
1452 1452 # Check for placeholder injection
1453 1453 _checkaliasarg(self.replacement, self.args)
1454 1454
1455 1455 def _getalias(aliases, tree):
1456 1456 """If tree looks like an unexpanded alias, return it. Return None
1457 1457 otherwise.
1458 1458 """
1459 1459 if isinstance(tree, tuple) and tree:
1460 1460 if tree[0] == 'symbol' and len(tree) == 2:
1461 1461 name = tree[1]
1462 1462 alias = aliases.get(name)
1463 1463 if alias and alias.args is None and alias.tree == tree:
1464 1464 return alias
1465 1465 if tree[0] == 'func' and len(tree) > 1:
1466 1466 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1467 1467 name = tree[1][1]
1468 1468 alias = aliases.get(name)
1469 1469 if alias and alias.args is not None and alias.tree == tree[:2]:
1470 1470 return alias
1471 1471 return None
1472 1472
1473 1473 def _expandargs(tree, args):
1474 1474 """Replace _aliasarg instances with the substitution value of the
1475 1475 same name in args, recursively.
1476 1476 """
1477 1477 if not tree or not isinstance(tree, tuple):
1478 1478 return tree
1479 1479 arg = _getaliasarg(tree)
1480 1480 if arg is not None:
1481 1481 return args[arg]
1482 1482 return tuple(_expandargs(t, args) for t in tree)
1483 1483
1484 1484 def _expandaliases(aliases, tree, expanding, cache):
1485 1485 """Expand aliases in tree, recursively.
1486 1486
1487 1487 'aliases' is a dictionary mapping user defined aliases to
1488 1488 revsetalias objects.
1489 1489 """
1490 1490 if not isinstance(tree, tuple):
1491 1491 # Do not expand raw strings
1492 1492 return tree
1493 1493 alias = _getalias(aliases, tree)
1494 1494 if alias is not None:
1495 1495 if alias in expanding:
1496 1496 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1497 1497 'detected') % alias.name)
1498 1498 expanding.append(alias)
1499 1499 if alias.name not in cache:
1500 1500 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1501 1501 expanding, cache)
1502 1502 result = cache[alias.name]
1503 1503 expanding.pop()
1504 1504 if alias.args is not None:
1505 1505 l = getlist(tree[2])
1506 1506 if len(l) != len(alias.args):
1507 1507 raise error.ParseError(
1508 1508 _('invalid number of arguments: %s') % len(l))
1509 1509 l = [_expandaliases(aliases, a, [], cache) for a in l]
1510 1510 result = _expandargs(result, dict(zip(alias.args, l)))
1511 1511 else:
1512 1512 result = tuple(_expandaliases(aliases, t, expanding, cache)
1513 1513 for t in tree)
1514 1514 return result
1515 1515
1516 1516 def findaliases(ui, tree):
1517 1517 _checkaliasarg(tree)
1518 1518 aliases = {}
1519 1519 for k, v in ui.configitems('revsetalias'):
1520 1520 alias = revsetalias(k, v)
1521 1521 aliases[alias.name] = alias
1522 1522 return _expandaliases(aliases, tree, [], {})
1523 1523
1524 1524 parse = parser.parser(tokenize, elements).parse
1525 1525
1526 1526 def match(ui, spec):
1527 1527 if not spec:
1528 1528 raise error.ParseError(_("empty query"))
1529 1529 tree, pos = parse(spec)
1530 1530 if (pos != len(spec)):
1531 1531 raise error.ParseError(_("invalid token"), pos)
1532 1532 if ui:
1533 1533 tree = findaliases(ui, tree)
1534 1534 weight, tree = optimize(tree, True)
1535 1535 def mfunc(repo, subset):
1536 1536 return getset(repo, subset, tree)
1537 1537 return mfunc
1538 1538
1539 1539 def formatspec(expr, *args):
1540 1540 '''
1541 1541 This is a convenience function for using revsets internally, and
1542 1542 escapes arguments appropriately. Aliases are intentionally ignored
1543 1543 so that intended expression behavior isn't accidentally subverted.
1544 1544
1545 1545 Supported arguments:
1546 1546
1547 1547 %r = revset expression, parenthesized
1548 1548 %d = int(arg), no quoting
1549 1549 %s = string(arg), escaped and single-quoted
1550 1550 %b = arg.branch(), escaped and single-quoted
1551 1551 %n = hex(arg), single-quoted
1552 1552 %% = a literal '%'
1553 1553
1554 1554 Prefixing the type with 'l' specifies a parenthesized list of that type.
1555 1555
1556 1556 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1557 1557 '(10 or 11):: and ((this()) or (that()))'
1558 1558 >>> formatspec('%d:: and not %d::', 10, 20)
1559 1559 '10:: and not 20::'
1560 1560 >>> formatspec('%ld or %ld', [], [1])
1561 1561 "_list('') or 1"
1562 1562 >>> formatspec('keyword(%s)', 'foo\\xe9')
1563 1563 "keyword('foo\\\\xe9')"
1564 1564 >>> b = lambda: 'default'
1565 1565 >>> b.branch = b
1566 1566 >>> formatspec('branch(%b)', b)
1567 1567 "branch('default')"
1568 1568 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1569 1569 "root(_list('a\\x00b\\x00c\\x00d'))"
1570 1570 '''
1571 1571
1572 1572 def quote(s):
1573 1573 return repr(str(s))
1574 1574
1575 1575 def argtype(c, arg):
1576 1576 if c == 'd':
1577 1577 return str(int(arg))
1578 1578 elif c == 's':
1579 1579 return quote(arg)
1580 1580 elif c == 'r':
1581 1581 parse(arg) # make sure syntax errors are confined
1582 1582 return '(%s)' % arg
1583 1583 elif c == 'n':
1584 1584 return quote(node.hex(arg))
1585 1585 elif c == 'b':
1586 1586 return quote(arg.branch())
1587 1587
1588 1588 def listexp(s, t):
1589 1589 l = len(s)
1590 1590 if l == 0:
1591 1591 return "_list('')"
1592 1592 elif l == 1:
1593 1593 return argtype(t, s[0])
1594 1594 elif t == 'd':
1595 1595 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1596 1596 elif t == 's':
1597 1597 return "_list('%s')" % "\0".join(s)
1598 1598 elif t == 'n':
1599 1599 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1600 1600 elif t == 'b':
1601 1601 return "_list('%s')" % "\0".join(a.branch() for a in s)
1602 1602
1603 1603 m = l // 2
1604 1604 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1605 1605
1606 1606 ret = ''
1607 1607 pos = 0
1608 1608 arg = 0
1609 1609 while pos < len(expr):
1610 1610 c = expr[pos]
1611 1611 if c == '%':
1612 1612 pos += 1
1613 1613 d = expr[pos]
1614 1614 if d == '%':
1615 1615 ret += d
1616 1616 elif d in 'dsnbr':
1617 1617 ret += argtype(d, args[arg])
1618 1618 arg += 1
1619 1619 elif d == 'l':
1620 1620 # a list of some type
1621 1621 pos += 1
1622 1622 d = expr[pos]
1623 1623 ret += listexp(list(args[arg]), d)
1624 1624 arg += 1
1625 1625 else:
1626 1626 raise util.Abort('unexpected revspec format character %s' % d)
1627 1627 else:
1628 1628 ret += c
1629 1629 pos += 1
1630 1630
1631 1631 return ret
1632 1632
1633 1633 def prettyformat(tree):
1634 1634 def _prettyformat(tree, level, lines):
1635 1635 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1636 1636 lines.append((level, str(tree)))
1637 1637 else:
1638 1638 lines.append((level, '(%s' % tree[0]))
1639 1639 for s in tree[1:]:
1640 1640 _prettyformat(s, level + 1, lines)
1641 1641 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1642 1642
1643 1643 lines = []
1644 1644 _prettyformat(tree, 0, lines)
1645 1645 output = '\n'.join((' '*l + s) for l, s in lines)
1646 1646 return output
1647 1647
1648 1648 # tell hggettext to extract docstrings from these functions:
1649 1649 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now