##// END OF EJS Templates
revset: try to handle hyphenated symbols if lookup callback is available...
Matt Mackall -
r20780:403f1f73 default
parent child Browse files
Show More
@@ -1,2826 +1,2842 b''
1 1 # revset.py - revision set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import re
9 9 import parser, util, error, discovery, hbisect, phases
10 10 import node
11 11 import heapq
12 12 import match as matchmod
13 13 import ancestor as ancestormod
14 14 from i18n import _
15 15 import encoding
16 16 import obsolete as obsmod
17 17 import pathutil
18 18 import repoview
19 19
20 20 def _revancestors(repo, revs, followfirst):
21 21 """Like revlog.ancestors(), but supports followfirst."""
22 22 cut = followfirst and 1 or None
23 23 cl = repo.changelog
24 24
25 25 def iterate():
26 26 revqueue, revsnode = None, None
27 27 h = []
28 28
29 29 revs.descending()
30 30 revqueue = util.deque(revs)
31 31 if revqueue:
32 32 revsnode = revqueue.popleft()
33 33 heapq.heappush(h, -revsnode)
34 34
35 35 seen = set([node.nullrev])
36 36 while h:
37 37 current = -heapq.heappop(h)
38 38 if current not in seen:
39 39 if revsnode and current == revsnode:
40 40 if revqueue:
41 41 revsnode = revqueue.popleft()
42 42 heapq.heappush(h, -revsnode)
43 43 seen.add(current)
44 44 yield current
45 45 for parent in cl.parentrevs(current)[:cut]:
46 46 if parent != node.nullrev:
47 47 heapq.heappush(h, -parent)
48 48
49 49 return _descgeneratorset(iterate())
50 50
51 51 def _revdescendants(repo, revs, followfirst):
52 52 """Like revlog.descendants() but supports followfirst."""
53 53 cut = followfirst and 1 or None
54 54
55 55 def iterate():
56 56 cl = repo.changelog
57 57 first = min(revs)
58 58 nullrev = node.nullrev
59 59 if first == nullrev:
60 60 # Are there nodes with a null first parent and a non-null
61 61 # second one? Maybe. Do we care? Probably not.
62 62 for i in cl:
63 63 yield i
64 64 else:
65 65 seen = set(revs)
66 66 for i in cl.revs(first + 1):
67 67 for x in cl.parentrevs(i)[:cut]:
68 68 if x != nullrev and x in seen:
69 69 seen.add(i)
70 70 yield i
71 71 break
72 72
73 73 return _ascgeneratorset(iterate())
74 74
75 75 def _revsbetween(repo, roots, heads):
76 76 """Return all paths between roots and heads, inclusive of both endpoint
77 77 sets."""
78 78 if not roots:
79 79 return baseset([])
80 80 parentrevs = repo.changelog.parentrevs
81 81 visit = baseset(heads)
82 82 reachable = set()
83 83 seen = {}
84 84 minroot = min(roots)
85 85 roots = set(roots)
86 86 # open-code the post-order traversal due to the tiny size of
87 87 # sys.getrecursionlimit()
88 88 while visit:
89 89 rev = visit.pop()
90 90 if rev in roots:
91 91 reachable.add(rev)
92 92 parents = parentrevs(rev)
93 93 seen[rev] = parents
94 94 for parent in parents:
95 95 if parent >= minroot and parent not in seen:
96 96 visit.append(parent)
97 97 if not reachable:
98 98 return baseset([])
99 99 for rev in sorted(seen):
100 100 for parent in seen[rev]:
101 101 if parent in reachable:
102 102 reachable.add(rev)
103 103 return baseset(sorted(reachable))
104 104
105 105 elements = {
106 106 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
107 107 "~": (18, None, ("ancestor", 18)),
108 108 "^": (18, None, ("parent", 18), ("parentpost", 18)),
109 109 "-": (5, ("negate", 19), ("minus", 5)),
110 110 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
111 111 ("dagrangepost", 17)),
112 112 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
113 113 ("dagrangepost", 17)),
114 114 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
115 115 "not": (10, ("not", 10)),
116 116 "!": (10, ("not", 10)),
117 117 "and": (5, None, ("and", 5)),
118 118 "&": (5, None, ("and", 5)),
119 119 "or": (4, None, ("or", 4)),
120 120 "|": (4, None, ("or", 4)),
121 121 "+": (4, None, ("or", 4)),
122 122 ",": (2, None, ("list", 2)),
123 123 ")": (0, None, None),
124 124 "symbol": (0, ("symbol",), None),
125 125 "string": (0, ("string",), None),
126 126 "end": (0, None, None),
127 127 }
128 128
129 129 keywords = set(['and', 'or', 'not'])
130 130
131 131 def tokenize(program, lookup=None):
132 132 '''
133 133 Parse a revset statement into a stream of tokens
134 134
135 135 Check that @ is a valid unquoted token character (issue3686):
136 136 >>> list(tokenize("@::"))
137 137 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
138 138
139 139 '''
140 140
141 141 pos, l = 0, len(program)
142 142 while pos < l:
143 143 c = program[pos]
144 144 if c.isspace(): # skip inter-token whitespace
145 145 pass
146 146 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
147 147 yield ('::', None, pos)
148 148 pos += 1 # skip ahead
149 149 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
150 150 yield ('..', None, pos)
151 151 pos += 1 # skip ahead
152 152 elif c in "():,-|&+!~^": # handle simple operators
153 153 yield (c, None, pos)
154 154 elif (c in '"\'' or c == 'r' and
155 155 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
156 156 if c == 'r':
157 157 pos += 1
158 158 c = program[pos]
159 159 decode = lambda x: x
160 160 else:
161 161 decode = lambda x: x.decode('string-escape')
162 162 pos += 1
163 163 s = pos
164 164 while pos < l: # find closing quote
165 165 d = program[pos]
166 166 if d == '\\': # skip over escaped characters
167 167 pos += 2
168 168 continue
169 169 if d == c:
170 170 yield ('string', decode(program[s:pos]), s)
171 171 break
172 172 pos += 1
173 173 else:
174 174 raise error.ParseError(_("unterminated string"), s)
175 175 # gather up a symbol/keyword
176 176 elif c.isalnum() or c in '._@' or ord(c) > 127:
177 177 s = pos
178 178 pos += 1
179 179 while pos < l: # find end of symbol
180 180 d = program[pos]
181 if not (d.isalnum() or d in "._/@" or ord(d) > 127):
181 if not (d.isalnum() or d in "-._/@" or ord(d) > 127):
182 182 break
183 183 if d == '.' and program[pos - 1] == '.': # special case for ..
184 184 pos -= 1
185 185 break
186 186 pos += 1
187 187 sym = program[s:pos]
188 188 if sym in keywords: # operator keywords
189 189 yield (sym, None, s)
190 elif '-' in sym:
191 # some jerk gave us foo-bar-baz, try to check if it's a symbol
192 if lookup and lookup(sym):
193 # looks like a real symbol
194 yield ('symbol', sym, s)
195 else:
196 # looks like an expression
197 parts = sym.split('-')
198 for p in parts[:-1]:
199 if p: # possible consecutive -
200 yield ('symbol', p, s)
201 s += len(p)
202 yield ('-', None, pos)
203 s += 1
204 if parts[-1]: # possible trailing -
205 yield ('symbol', parts[-1], s)
190 206 else:
191 207 yield ('symbol', sym, s)
192 208 pos -= 1
193 209 else:
194 210 raise error.ParseError(_("syntax error"), pos)
195 211 pos += 1
196 212 yield ('end', None, pos)
197 213
198 214 # helpers
199 215
200 216 def getstring(x, err):
201 217 if x and (x[0] == 'string' or x[0] == 'symbol'):
202 218 return x[1]
203 219 raise error.ParseError(err)
204 220
205 221 def getlist(x):
206 222 if not x:
207 223 return []
208 224 if x[0] == 'list':
209 225 return getlist(x[1]) + [x[2]]
210 226 return [x]
211 227
212 228 def getargs(x, min, max, err):
213 229 l = getlist(x)
214 230 if len(l) < min or (max >= 0 and len(l) > max):
215 231 raise error.ParseError(err)
216 232 return l
217 233
218 234 def getset(repo, subset, x):
219 235 if not x:
220 236 raise error.ParseError(_("missing argument"))
221 237 s = methods[x[0]](repo, subset, *x[1:])
222 238 if util.safehasattr(s, 'set'):
223 239 return s
224 240 return baseset(s)
225 241
226 242 def _getrevsource(repo, r):
227 243 extra = repo[r].extra()
228 244 for label in ('source', 'transplant_source', 'rebase_source'):
229 245 if label in extra:
230 246 try:
231 247 return repo[extra[label]].rev()
232 248 except error.RepoLookupError:
233 249 pass
234 250 return None
235 251
236 252 # operator methods
237 253
238 254 def stringset(repo, subset, x):
239 255 x = repo[x].rev()
240 256 if x == -1 and len(subset) == len(repo):
241 257 return baseset([-1])
242 258 if len(subset) == len(repo) or x in subset:
243 259 return baseset([x])
244 260 return baseset([])
245 261
246 262 def symbolset(repo, subset, x):
247 263 if x in symbols:
248 264 raise error.ParseError(_("can't use %s here") % x)
249 265 return stringset(repo, subset, x)
250 266
251 267 def rangeset(repo, subset, x, y):
252 268 cl = baseset(repo.changelog)
253 269 m = getset(repo, cl, x)
254 270 n = getset(repo, cl, y)
255 271
256 272 if not m or not n:
257 273 return baseset([])
258 274 m, n = m[0], n[-1]
259 275
260 276 if m < n:
261 277 r = spanset(repo, m, n + 1)
262 278 else:
263 279 r = spanset(repo, m, n - 1)
264 280 return r & subset
265 281
266 282 def dagrange(repo, subset, x, y):
267 283 r = spanset(repo)
268 284 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
269 285 s = subset.set()
270 286 return xs.filter(lambda r: r in s)
271 287
272 288 def andset(repo, subset, x, y):
273 289 return getset(repo, getset(repo, subset, x), y)
274 290
275 291 def orset(repo, subset, x, y):
276 292 xl = getset(repo, subset, x)
277 293 yl = getset(repo, subset - xl, y)
278 294 return xl + yl
279 295
280 296 def notset(repo, subset, x):
281 297 return subset - getset(repo, subset, x)
282 298
283 299 def listset(repo, subset, a, b):
284 300 raise error.ParseError(_("can't use a list in this context"))
285 301
286 302 def func(repo, subset, a, b):
287 303 if a[0] == 'symbol' and a[1] in symbols:
288 304 return symbols[a[1]](repo, subset, b)
289 305 raise error.ParseError(_("not a function: %s") % a[1])
290 306
291 307 # functions
292 308
293 309 def adds(repo, subset, x):
294 310 """``adds(pattern)``
295 311 Changesets that add a file matching pattern.
296 312
297 313 The pattern without explicit kind like ``glob:`` is expected to be
298 314 relative to the current directory and match against a file or a
299 315 directory.
300 316 """
301 317 # i18n: "adds" is a keyword
302 318 pat = getstring(x, _("adds requires a pattern"))
303 319 return checkstatus(repo, subset, pat, 1)
304 320
305 321 def ancestor(repo, subset, x):
306 322 """``ancestor(*changeset)``
307 323 Greatest common ancestor of the changesets.
308 324
309 325 Accepts 0 or more changesets.
310 326 Will return empty list when passed no args.
311 327 Greatest common ancestor of a single changeset is that changeset.
312 328 """
313 329 # i18n: "ancestor" is a keyword
314 330 l = getlist(x)
315 331 rl = spanset(repo)
316 332 anc = None
317 333
318 334 # (getset(repo, rl, i) for i in l) generates a list of lists
319 335 rev = repo.changelog.rev
320 336 ancestor = repo.changelog.ancestor
321 337 node = repo.changelog.node
322 338 for revs in (getset(repo, rl, i) for i in l):
323 339 for r in revs:
324 340 if anc is None:
325 341 anc = r
326 342 else:
327 343 anc = rev(ancestor(node(anc), node(r)))
328 344
329 345 if anc is not None and anc in subset:
330 346 return baseset([anc])
331 347 return baseset([])
332 348
333 349 def _ancestors(repo, subset, x, followfirst=False):
334 350 args = getset(repo, spanset(repo), x)
335 351 if not args:
336 352 return baseset([])
337 353 s = _revancestors(repo, args, followfirst)
338 354 return subset.filter(lambda r: r in s)
339 355
340 356 def ancestors(repo, subset, x):
341 357 """``ancestors(set)``
342 358 Changesets that are ancestors of a changeset in set.
343 359 """
344 360 return _ancestors(repo, subset, x)
345 361
346 362 def _firstancestors(repo, subset, x):
347 363 # ``_firstancestors(set)``
348 364 # Like ``ancestors(set)`` but follows only the first parents.
349 365 return _ancestors(repo, subset, x, followfirst=True)
350 366
351 367 def ancestorspec(repo, subset, x, n):
352 368 """``set~n``
353 369 Changesets that are the Nth ancestor (first parents only) of a changeset
354 370 in set.
355 371 """
356 372 try:
357 373 n = int(n[1])
358 374 except (TypeError, ValueError):
359 375 raise error.ParseError(_("~ expects a number"))
360 376 ps = set()
361 377 cl = repo.changelog
362 378 for r in getset(repo, baseset(cl), x):
363 379 for i in range(n):
364 380 r = cl.parentrevs(r)[0]
365 381 ps.add(r)
366 382 return subset.filter(lambda r: r in ps)
367 383
368 384 def author(repo, subset, x):
369 385 """``author(string)``
370 386 Alias for ``user(string)``.
371 387 """
372 388 # i18n: "author" is a keyword
373 389 n = encoding.lower(getstring(x, _("author requires a string")))
374 390 kind, pattern, matcher = _substringmatcher(n)
375 391 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
376 392
377 393 def only(repo, subset, x):
378 394 """``only(set, [set])``
379 395 Changesets that are ancestors of the first set that are not ancestors
380 396 of any other head in the repo. If a second set is specified, the result
381 397 is ancestors of the first set that are not ancestors of the second set
382 398 (i.e. ::<set1> - ::<set2>).
383 399 """
384 400 cl = repo.changelog
385 401 args = getargs(x, 1, 2, _('only takes one or two arguments'))
386 402 include = getset(repo, spanset(repo), args[0]).set()
387 403 if len(args) == 1:
388 404 descendants = set(_revdescendants(repo, include, False))
389 405 exclude = [rev for rev in cl.headrevs()
390 406 if not rev in descendants and not rev in include]
391 407 else:
392 408 exclude = getset(repo, spanset(repo), args[1])
393 409
394 410 results = set(ancestormod.missingancestors(include, exclude, cl.parentrevs))
395 411 return lazyset(subset, lambda x: x in results)
396 412
397 413 def bisect(repo, subset, x):
398 414 """``bisect(string)``
399 415 Changesets marked in the specified bisect status:
400 416
401 417 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
402 418 - ``goods``, ``bads`` : csets topologically good/bad
403 419 - ``range`` : csets taking part in the bisection
404 420 - ``pruned`` : csets that are goods, bads or skipped
405 421 - ``untested`` : csets whose fate is yet unknown
406 422 - ``ignored`` : csets ignored due to DAG topology
407 423 - ``current`` : the cset currently being bisected
408 424 """
409 425 # i18n: "bisect" is a keyword
410 426 status = getstring(x, _("bisect requires a string")).lower()
411 427 state = set(hbisect.get(repo, status))
412 428 return subset.filter(lambda r: r in state)
413 429
414 430 # Backward-compatibility
415 431 # - no help entry so that we do not advertise it any more
416 432 def bisected(repo, subset, x):
417 433 return bisect(repo, subset, x)
418 434
419 435 def bookmark(repo, subset, x):
420 436 """``bookmark([name])``
421 437 The named bookmark or all bookmarks.
422 438
423 439 If `name` starts with `re:`, the remainder of the name is treated as
424 440 a regular expression. To match a bookmark that actually starts with `re:`,
425 441 use the prefix `literal:`.
426 442 """
427 443 # i18n: "bookmark" is a keyword
428 444 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
429 445 if args:
430 446 bm = getstring(args[0],
431 447 # i18n: "bookmark" is a keyword
432 448 _('the argument to bookmark must be a string'))
433 449 kind, pattern, matcher = _stringmatcher(bm)
434 450 if kind == 'literal':
435 451 bmrev = repo._bookmarks.get(bm, None)
436 452 if not bmrev:
437 453 raise util.Abort(_("bookmark '%s' does not exist") % bm)
438 454 bmrev = repo[bmrev].rev()
439 455 return subset.filter(lambda r: r == bmrev)
440 456 else:
441 457 matchrevs = set()
442 458 for name, bmrev in repo._bookmarks.iteritems():
443 459 if matcher(name):
444 460 matchrevs.add(bmrev)
445 461 if not matchrevs:
446 462 raise util.Abort(_("no bookmarks exist that match '%s'")
447 463 % pattern)
448 464 bmrevs = set()
449 465 for bmrev in matchrevs:
450 466 bmrevs.add(repo[bmrev].rev())
451 467 return subset & bmrevs
452 468
453 469 bms = set([repo[r].rev()
454 470 for r in repo._bookmarks.values()])
455 471 return subset.filter(lambda r: r in bms)
456 472
457 473 def branch(repo, subset, x):
458 474 """``branch(string or set)``
459 475 All changesets belonging to the given branch or the branches of the given
460 476 changesets.
461 477
462 478 If `string` starts with `re:`, the remainder of the name is treated as
463 479 a regular expression. To match a branch that actually starts with `re:`,
464 480 use the prefix `literal:`.
465 481 """
466 482 try:
467 483 b = getstring(x, '')
468 484 except error.ParseError:
469 485 # not a string, but another revspec, e.g. tip()
470 486 pass
471 487 else:
472 488 kind, pattern, matcher = _stringmatcher(b)
473 489 if kind == 'literal':
474 490 # note: falls through to the revspec case if no branch with
475 491 # this name exists
476 492 if pattern in repo.branchmap():
477 493 return subset.filter(lambda r: matcher(repo[r].branch()))
478 494 else:
479 495 return subset.filter(lambda r: matcher(repo[r].branch()))
480 496
481 497 s = getset(repo, spanset(repo), x)
482 498 b = set()
483 499 for r in s:
484 500 b.add(repo[r].branch())
485 501 s = s.set()
486 502 return subset.filter(lambda r: r in s or repo[r].branch() in b)
487 503
488 504 def bumped(repo, subset, x):
489 505 """``bumped()``
490 506 Mutable changesets marked as successors of public changesets.
491 507
492 508 Only non-public and non-obsolete changesets can be `bumped`.
493 509 """
494 510 # i18n: "bumped" is a keyword
495 511 getargs(x, 0, 0, _("bumped takes no arguments"))
496 512 bumped = obsmod.getrevs(repo, 'bumped')
497 513 return subset & bumped
498 514
499 515 def bundle(repo, subset, x):
500 516 """``bundle()``
501 517 Changesets in the bundle.
502 518
503 519 Bundle must be specified by the -R option."""
504 520
505 521 try:
506 522 bundlerevs = repo.changelog.bundlerevs
507 523 except AttributeError:
508 524 raise util.Abort(_("no bundle provided - specify with -R"))
509 525 return subset & bundlerevs
510 526
511 527 def checkstatus(repo, subset, pat, field):
512 528 hasset = matchmod.patkind(pat) == 'set'
513 529
514 530 def matches(x):
515 531 m = None
516 532 fname = None
517 533 c = repo[x]
518 534 if not m or hasset:
519 535 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
520 536 if not m.anypats() and len(m.files()) == 1:
521 537 fname = m.files()[0]
522 538 if fname is not None:
523 539 if fname not in c.files():
524 540 return False
525 541 else:
526 542 for f in c.files():
527 543 if m(f):
528 544 break
529 545 else:
530 546 return False
531 547 files = repo.status(c.p1().node(), c.node())[field]
532 548 if fname is not None:
533 549 if fname in files:
534 550 return True
535 551 else:
536 552 for f in files:
537 553 if m(f):
538 554 return True
539 555
540 556 return subset.filter(matches)
541 557
542 558 def _children(repo, narrow, parentset):
543 559 cs = set()
544 560 if not parentset:
545 561 return baseset(cs)
546 562 pr = repo.changelog.parentrevs
547 563 minrev = min(parentset)
548 564 for r in narrow:
549 565 if r <= minrev:
550 566 continue
551 567 for p in pr(r):
552 568 if p in parentset:
553 569 cs.add(r)
554 570 return baseset(cs)
555 571
556 572 def children(repo, subset, x):
557 573 """``children(set)``
558 574 Child changesets of changesets in set.
559 575 """
560 576 s = getset(repo, baseset(repo), x).set()
561 577 cs = _children(repo, subset, s)
562 578 return subset & cs
563 579
564 580 def closed(repo, subset, x):
565 581 """``closed()``
566 582 Changeset is closed.
567 583 """
568 584 # i18n: "closed" is a keyword
569 585 getargs(x, 0, 0, _("closed takes no arguments"))
570 586 return subset.filter(lambda r: repo[r].closesbranch())
571 587
572 588 def contains(repo, subset, x):
573 589 """``contains(pattern)``
574 590 Revision contains a file matching pattern. See :hg:`help patterns`
575 591 for information about file patterns.
576 592
577 593 The pattern without explicit kind like ``glob:`` is expected to be
578 594 relative to the current directory and match against a file exactly
579 595 for efficiency.
580 596 """
581 597 # i18n: "contains" is a keyword
582 598 pat = getstring(x, _("contains requires a pattern"))
583 599
584 600 def matches(x):
585 601 if not matchmod.patkind(pat):
586 602 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
587 603 if pats in repo[x]:
588 604 return True
589 605 else:
590 606 c = repo[x]
591 607 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
592 608 for f in c.manifest():
593 609 if m(f):
594 610 return True
595 611 return False
596 612
597 613 return subset.filter(matches)
598 614
599 615 def converted(repo, subset, x):
600 616 """``converted([id])``
601 617 Changesets converted from the given identifier in the old repository if
602 618 present, or all converted changesets if no identifier is specified.
603 619 """
604 620
605 621 # There is exactly no chance of resolving the revision, so do a simple
606 622 # string compare and hope for the best
607 623
608 624 rev = None
609 625 # i18n: "converted" is a keyword
610 626 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
611 627 if l:
612 628 # i18n: "converted" is a keyword
613 629 rev = getstring(l[0], _('converted requires a revision'))
614 630
615 631 def _matchvalue(r):
616 632 source = repo[r].extra().get('convert_revision', None)
617 633 return source is not None and (rev is None or source.startswith(rev))
618 634
619 635 return subset.filter(lambda r: _matchvalue(r))
620 636
621 637 def date(repo, subset, x):
622 638 """``date(interval)``
623 639 Changesets within the interval, see :hg:`help dates`.
624 640 """
625 641 # i18n: "date" is a keyword
626 642 ds = getstring(x, _("date requires a string"))
627 643 dm = util.matchdate(ds)
628 644 return subset.filter(lambda x: dm(repo[x].date()[0]))
629 645
630 646 def desc(repo, subset, x):
631 647 """``desc(string)``
632 648 Search commit message for string. The match is case-insensitive.
633 649 """
634 650 # i18n: "desc" is a keyword
635 651 ds = encoding.lower(getstring(x, _("desc requires a string")))
636 652
637 653 def matches(x):
638 654 c = repo[x]
639 655 return ds in encoding.lower(c.description())
640 656
641 657 return subset.filter(matches)
642 658
643 659 def _descendants(repo, subset, x, followfirst=False):
644 660 args = getset(repo, spanset(repo), x)
645 661 if not args:
646 662 return baseset([])
647 663 s = _revdescendants(repo, args, followfirst)
648 664 a = set(args)
649 665 return subset.filter(lambda r: r in s or r in a)
650 666
651 667 def descendants(repo, subset, x):
652 668 """``descendants(set)``
653 669 Changesets which are descendants of changesets in set.
654 670 """
655 671 return _descendants(repo, subset, x)
656 672
657 673 def _firstdescendants(repo, subset, x):
658 674 # ``_firstdescendants(set)``
659 675 # Like ``descendants(set)`` but follows only the first parents.
660 676 return _descendants(repo, subset, x, followfirst=True)
661 677
662 678 def destination(repo, subset, x):
663 679 """``destination([set])``
664 680 Changesets that were created by a graft, transplant or rebase operation,
665 681 with the given revisions specified as the source. Omitting the optional set
666 682 is the same as passing all().
667 683 """
668 684 if x is not None:
669 685 args = getset(repo, spanset(repo), x).set()
670 686 else:
671 687 args = getall(repo, spanset(repo), x).set()
672 688
673 689 dests = set()
674 690
675 691 # subset contains all of the possible destinations that can be returned, so
676 692 # iterate over them and see if their source(s) were provided in the args.
677 693 # Even if the immediate src of r is not in the args, src's source (or
678 694 # further back) may be. Scanning back further than the immediate src allows
679 695 # transitive transplants and rebases to yield the same results as transitive
680 696 # grafts.
681 697 for r in subset:
682 698 src = _getrevsource(repo, r)
683 699 lineage = None
684 700
685 701 while src is not None:
686 702 if lineage is None:
687 703 lineage = list()
688 704
689 705 lineage.append(r)
690 706
691 707 # The visited lineage is a match if the current source is in the arg
692 708 # set. Since every candidate dest is visited by way of iterating
693 709 # subset, any dests further back in the lineage will be tested by a
694 710 # different iteration over subset. Likewise, if the src was already
695 711 # selected, the current lineage can be selected without going back
696 712 # further.
697 713 if src in args or src in dests:
698 714 dests.update(lineage)
699 715 break
700 716
701 717 r = src
702 718 src = _getrevsource(repo, r)
703 719
704 720 return subset.filter(lambda r: r in dests)
705 721
706 722 def divergent(repo, subset, x):
707 723 """``divergent()``
708 724 Final successors of changesets with an alternative set of final successors.
709 725 """
710 726 # i18n: "divergent" is a keyword
711 727 getargs(x, 0, 0, _("divergent takes no arguments"))
712 728 divergent = obsmod.getrevs(repo, 'divergent')
713 729 return subset.filter(lambda r: r in divergent)
714 730
715 731 def draft(repo, subset, x):
716 732 """``draft()``
717 733 Changeset in draft phase."""
718 734 # i18n: "draft" is a keyword
719 735 getargs(x, 0, 0, _("draft takes no arguments"))
720 736 pc = repo._phasecache
721 737 return subset.filter(lambda r: pc.phase(repo, r) == phases.draft)
722 738
723 739 def extinct(repo, subset, x):
724 740 """``extinct()``
725 741 Obsolete changesets with obsolete descendants only.
726 742 """
727 743 # i18n: "extinct" is a keyword
728 744 getargs(x, 0, 0, _("extinct takes no arguments"))
729 745 extincts = obsmod.getrevs(repo, 'extinct')
730 746 return subset & extincts
731 747
732 748 def extra(repo, subset, x):
733 749 """``extra(label, [value])``
734 750 Changesets with the given label in the extra metadata, with the given
735 751 optional value.
736 752
737 753 If `value` starts with `re:`, the remainder of the value is treated as
738 754 a regular expression. To match a value that actually starts with `re:`,
739 755 use the prefix `literal:`.
740 756 """
741 757
742 758 # i18n: "extra" is a keyword
743 759 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
744 760 # i18n: "extra" is a keyword
745 761 label = getstring(l[0], _('first argument to extra must be a string'))
746 762 value = None
747 763
748 764 if len(l) > 1:
749 765 # i18n: "extra" is a keyword
750 766 value = getstring(l[1], _('second argument to extra must be a string'))
751 767 kind, value, matcher = _stringmatcher(value)
752 768
753 769 def _matchvalue(r):
754 770 extra = repo[r].extra()
755 771 return label in extra and (value is None or matcher(extra[label]))
756 772
757 773 return subset.filter(lambda r: _matchvalue(r))
758 774
759 775 def filelog(repo, subset, x):
760 776 """``filelog(pattern)``
761 777 Changesets connected to the specified filelog.
762 778
763 779 For performance reasons, ``filelog()`` does not show every changeset
764 780 that affects the requested file(s). See :hg:`help log` for details. For
765 781 a slower, more accurate result, use ``file()``.
766 782
767 783 The pattern without explicit kind like ``glob:`` is expected to be
768 784 relative to the current directory and match against a file exactly
769 785 for efficiency.
770 786 """
771 787
772 788 # i18n: "filelog" is a keyword
773 789 pat = getstring(x, _("filelog requires a pattern"))
774 790 s = set()
775 791
776 792 if not matchmod.patkind(pat):
777 793 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
778 794 fl = repo.file(f)
779 795 for fr in fl:
780 796 s.add(fl.linkrev(fr))
781 797 else:
782 798 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
783 799 for f in repo[None]:
784 800 if m(f):
785 801 fl = repo.file(f)
786 802 for fr in fl:
787 803 s.add(fl.linkrev(fr))
788 804
789 805 return subset.filter(lambda r: r in s)
790 806
791 807 def first(repo, subset, x):
792 808 """``first(set, [n])``
793 809 An alias for limit().
794 810 """
795 811 return limit(repo, subset, x)
796 812
797 813 def _follow(repo, subset, x, name, followfirst=False):
798 814 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
799 815 c = repo['.']
800 816 if l:
801 817 x = getstring(l[0], _("%s expected a filename") % name)
802 818 if x in c:
803 819 cx = c[x]
804 820 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
805 821 # include the revision responsible for the most recent version
806 822 s.add(cx.linkrev())
807 823 else:
808 824 return baseset([])
809 825 else:
810 826 s = _revancestors(repo, baseset([c.rev()]), followfirst)
811 827
812 828 return subset.filter(lambda r: r in s)
813 829
814 830 def follow(repo, subset, x):
815 831 """``follow([file])``
816 832 An alias for ``::.`` (ancestors of the working copy's first parent).
817 833 If a filename is specified, the history of the given file is followed,
818 834 including copies.
819 835 """
820 836 return _follow(repo, subset, x, 'follow')
821 837
822 838 def _followfirst(repo, subset, x):
823 839 # ``followfirst([file])``
824 840 # Like ``follow([file])`` but follows only the first parent of
825 841 # every revision or file revision.
826 842 return _follow(repo, subset, x, '_followfirst', followfirst=True)
827 843
828 844 def getall(repo, subset, x):
829 845 """``all()``
830 846 All changesets, the same as ``0:tip``.
831 847 """
832 848 # i18n: "all" is a keyword
833 849 getargs(x, 0, 0, _("all takes no arguments"))
834 850 return subset
835 851
836 852 def grep(repo, subset, x):
837 853 """``grep(regex)``
838 854 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
839 855 to ensure special escape characters are handled correctly. Unlike
840 856 ``keyword(string)``, the match is case-sensitive.
841 857 """
842 858 try:
843 859 # i18n: "grep" is a keyword
844 860 gr = re.compile(getstring(x, _("grep requires a string")))
845 861 except re.error, e:
846 862 raise error.ParseError(_('invalid match pattern: %s') % e)
847 863
848 864 def matches(x):
849 865 c = repo[x]
850 866 for e in c.files() + [c.user(), c.description()]:
851 867 if gr.search(e):
852 868 return True
853 869 return False
854 870
855 871 return subset.filter(matches)
856 872
857 873 def _matchfiles(repo, subset, x):
858 874 # _matchfiles takes a revset list of prefixed arguments:
859 875 #
860 876 # [p:foo, i:bar, x:baz]
861 877 #
862 878 # builds a match object from them and filters subset. Allowed
863 879 # prefixes are 'p:' for regular patterns, 'i:' for include
864 880 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
865 881 # a revision identifier, or the empty string to reference the
866 882 # working directory, from which the match object is
867 883 # initialized. Use 'd:' to set the default matching mode, default
868 884 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
869 885
870 886 # i18n: "_matchfiles" is a keyword
871 887 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
872 888 pats, inc, exc = [], [], []
873 889 hasset = False
874 890 rev, default = None, None
875 891 for arg in l:
876 892 # i18n: "_matchfiles" is a keyword
877 893 s = getstring(arg, _("_matchfiles requires string arguments"))
878 894 prefix, value = s[:2], s[2:]
879 895 if prefix == 'p:':
880 896 pats.append(value)
881 897 elif prefix == 'i:':
882 898 inc.append(value)
883 899 elif prefix == 'x:':
884 900 exc.append(value)
885 901 elif prefix == 'r:':
886 902 if rev is not None:
887 903 # i18n: "_matchfiles" is a keyword
888 904 raise error.ParseError(_('_matchfiles expected at most one '
889 905 'revision'))
890 906 rev = value
891 907 elif prefix == 'd:':
892 908 if default is not None:
893 909 # i18n: "_matchfiles" is a keyword
894 910 raise error.ParseError(_('_matchfiles expected at most one '
895 911 'default mode'))
896 912 default = value
897 913 else:
898 914 # i18n: "_matchfiles" is a keyword
899 915 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
900 916 if not hasset and matchmod.patkind(value) == 'set':
901 917 hasset = True
902 918 if not default:
903 919 default = 'glob'
904 920
905 921 def matches(x):
906 922 m = None
907 923 c = repo[x]
908 924 if not m or (hasset and rev is None):
909 925 ctx = c
910 926 if rev is not None:
911 927 ctx = repo[rev or None]
912 928 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
913 929 exclude=exc, ctx=ctx, default=default)
914 930 for f in c.files():
915 931 if m(f):
916 932 return True
917 933 return False
918 934
919 935 return subset.filter(matches)
920 936
921 937 def hasfile(repo, subset, x):
922 938 """``file(pattern)``
923 939 Changesets affecting files matched by pattern.
924 940
925 941 For a faster but less accurate result, consider using ``filelog()``
926 942 instead.
927 943
928 944 This predicate uses ``glob:`` as the default kind of pattern.
929 945 """
930 946 # i18n: "file" is a keyword
931 947 pat = getstring(x, _("file requires a pattern"))
932 948 return _matchfiles(repo, subset, ('string', 'p:' + pat))
933 949
934 950 def head(repo, subset, x):
935 951 """``head()``
936 952 Changeset is a named branch head.
937 953 """
938 954 # i18n: "head" is a keyword
939 955 getargs(x, 0, 0, _("head takes no arguments"))
940 956 hs = set()
941 957 for b, ls in repo.branchmap().iteritems():
942 958 hs.update(repo[h].rev() for h in ls)
943 959 return baseset(hs).filter(subset.__contains__)
944 960
945 961 def heads(repo, subset, x):
946 962 """``heads(set)``
947 963 Members of set with no children in set.
948 964 """
949 965 s = getset(repo, subset, x)
950 966 ps = parents(repo, subset, x)
951 967 return s - ps
952 968
953 969 def hidden(repo, subset, x):
954 970 """``hidden()``
955 971 Hidden changesets.
956 972 """
957 973 # i18n: "hidden" is a keyword
958 974 getargs(x, 0, 0, _("hidden takes no arguments"))
959 975 hiddenrevs = repoview.filterrevs(repo, 'visible')
960 976 return subset & hiddenrevs
961 977
962 978 def keyword(repo, subset, x):
963 979 """``keyword(string)``
964 980 Search commit message, user name, and names of changed files for
965 981 string. The match is case-insensitive.
966 982 """
967 983 # i18n: "keyword" is a keyword
968 984 kw = encoding.lower(getstring(x, _("keyword requires a string")))
969 985
970 986 def matches(r):
971 987 c = repo[r]
972 988 return util.any(kw in encoding.lower(t) for t in c.files() + [c.user(),
973 989 c.description()])
974 990
975 991 return subset.filter(matches)
976 992
977 993 def limit(repo, subset, x):
978 994 """``limit(set, [n])``
979 995 First n members of set, defaulting to 1.
980 996 """
981 997 # i18n: "limit" is a keyword
982 998 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
983 999 try:
984 1000 lim = 1
985 1001 if len(l) == 2:
986 1002 # i18n: "limit" is a keyword
987 1003 lim = int(getstring(l[1], _("limit requires a number")))
988 1004 except (TypeError, ValueError):
989 1005 # i18n: "limit" is a keyword
990 1006 raise error.ParseError(_("limit expects a number"))
991 1007 ss = subset.set()
992 1008 os = getset(repo, spanset(repo), l[0])
993 1009 bs = baseset([])
994 1010 it = iter(os)
995 1011 for x in xrange(lim):
996 1012 try:
997 1013 y = it.next()
998 1014 if y in ss:
999 1015 bs.append(y)
1000 1016 except (StopIteration):
1001 1017 break
1002 1018 return bs
1003 1019
1004 1020 def last(repo, subset, x):
1005 1021 """``last(set, [n])``
1006 1022 Last n members of set, defaulting to 1.
1007 1023 """
1008 1024 # i18n: "last" is a keyword
1009 1025 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1010 1026 try:
1011 1027 lim = 1
1012 1028 if len(l) == 2:
1013 1029 # i18n: "last" is a keyword
1014 1030 lim = int(getstring(l[1], _("last requires a number")))
1015 1031 except (TypeError, ValueError):
1016 1032 # i18n: "last" is a keyword
1017 1033 raise error.ParseError(_("last expects a number"))
1018 1034 ss = subset.set()
1019 1035 os = getset(repo, spanset(repo), l[0])
1020 1036 os.reverse()
1021 1037 bs = baseset([])
1022 1038 it = iter(os)
1023 1039 for x in xrange(lim):
1024 1040 try:
1025 1041 y = it.next()
1026 1042 if y in ss:
1027 1043 bs.append(y)
1028 1044 except (StopIteration):
1029 1045 break
1030 1046 return bs
1031 1047
1032 1048 def maxrev(repo, subset, x):
1033 1049 """``max(set)``
1034 1050 Changeset with highest revision number in set.
1035 1051 """
1036 1052 os = getset(repo, spanset(repo), x)
1037 1053 if os:
1038 1054 m = os.max()
1039 1055 if m in subset:
1040 1056 return baseset([m])
1041 1057 return baseset([])
1042 1058
1043 1059 def merge(repo, subset, x):
1044 1060 """``merge()``
1045 1061 Changeset is a merge changeset.
1046 1062 """
1047 1063 # i18n: "merge" is a keyword
1048 1064 getargs(x, 0, 0, _("merge takes no arguments"))
1049 1065 cl = repo.changelog
1050 1066 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1051 1067
1052 1068 def branchpoint(repo, subset, x):
1053 1069 """``branchpoint()``
1054 1070 Changesets with more than one child.
1055 1071 """
1056 1072 # i18n: "branchpoint" is a keyword
1057 1073 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1058 1074 cl = repo.changelog
1059 1075 if not subset:
1060 1076 return baseset([])
1061 1077 baserev = min(subset)
1062 1078 parentscount = [0]*(len(repo) - baserev)
1063 1079 for r in cl.revs(start=baserev + 1):
1064 1080 for p in cl.parentrevs(r):
1065 1081 if p >= baserev:
1066 1082 parentscount[p - baserev] += 1
1067 1083 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1068 1084
1069 1085 def minrev(repo, subset, x):
1070 1086 """``min(set)``
1071 1087 Changeset with lowest revision number in set.
1072 1088 """
1073 1089 os = getset(repo, spanset(repo), x)
1074 1090 if os:
1075 1091 m = os.min()
1076 1092 if m in subset:
1077 1093 return baseset([m])
1078 1094 return baseset([])
1079 1095
1080 1096 def _missingancestors(repo, subset, x):
1081 1097 # i18n: "_missingancestors" is a keyword
1082 1098 revs, bases = getargs(x, 2, 2,
1083 1099 _("_missingancestors requires two arguments"))
1084 1100 rs = baseset(repo)
1085 1101 revs = getset(repo, rs, revs)
1086 1102 bases = getset(repo, rs, bases)
1087 1103 missing = set(repo.changelog.findmissingrevs(bases, revs))
1088 1104 return baseset([r for r in subset if r in missing])
1089 1105
1090 1106 def modifies(repo, subset, x):
1091 1107 """``modifies(pattern)``
1092 1108 Changesets modifying files matched by pattern.
1093 1109
1094 1110 The pattern without explicit kind like ``glob:`` is expected to be
1095 1111 relative to the current directory and match against a file or a
1096 1112 directory.
1097 1113 """
1098 1114 # i18n: "modifies" is a keyword
1099 1115 pat = getstring(x, _("modifies requires a pattern"))
1100 1116 return checkstatus(repo, subset, pat, 0)
1101 1117
1102 1118 def node_(repo, subset, x):
1103 1119 """``id(string)``
1104 1120 Revision non-ambiguously specified by the given hex string prefix.
1105 1121 """
1106 1122 # i18n: "id" is a keyword
1107 1123 l = getargs(x, 1, 1, _("id requires one argument"))
1108 1124 # i18n: "id" is a keyword
1109 1125 n = getstring(l[0], _("id requires a string"))
1110 1126 if len(n) == 40:
1111 1127 rn = repo[n].rev()
1112 1128 else:
1113 1129 rn = None
1114 1130 pm = repo.changelog._partialmatch(n)
1115 1131 if pm is not None:
1116 1132 rn = repo.changelog.rev(pm)
1117 1133
1118 1134 return subset.filter(lambda r: r == rn)
1119 1135
1120 1136 def obsolete(repo, subset, x):
1121 1137 """``obsolete()``
1122 1138 Mutable changeset with a newer version."""
1123 1139 # i18n: "obsolete" is a keyword
1124 1140 getargs(x, 0, 0, _("obsolete takes no arguments"))
1125 1141 obsoletes = obsmod.getrevs(repo, 'obsolete')
1126 1142 return subset & obsoletes
1127 1143
1128 1144 def origin(repo, subset, x):
1129 1145 """``origin([set])``
1130 1146 Changesets that were specified as a source for the grafts, transplants or
1131 1147 rebases that created the given revisions. Omitting the optional set is the
1132 1148 same as passing all(). If a changeset created by these operations is itself
1133 1149 specified as a source for one of these operations, only the source changeset
1134 1150 for the first operation is selected.
1135 1151 """
1136 1152 if x is not None:
1137 1153 args = getset(repo, spanset(repo), x).set()
1138 1154 else:
1139 1155 args = getall(repo, spanset(repo), x).set()
1140 1156
1141 1157 def _firstsrc(rev):
1142 1158 src = _getrevsource(repo, rev)
1143 1159 if src is None:
1144 1160 return None
1145 1161
1146 1162 while True:
1147 1163 prev = _getrevsource(repo, src)
1148 1164
1149 1165 if prev is None:
1150 1166 return src
1151 1167 src = prev
1152 1168
1153 1169 o = set([_firstsrc(r) for r in args])
1154 1170 return subset.filter(lambda r: r in o)
1155 1171
1156 1172 def outgoing(repo, subset, x):
1157 1173 """``outgoing([path])``
1158 1174 Changesets not found in the specified destination repository, or the
1159 1175 default push location.
1160 1176 """
1161 1177 import hg # avoid start-up nasties
1162 1178 # i18n: "outgoing" is a keyword
1163 1179 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1164 1180 # i18n: "outgoing" is a keyword
1165 1181 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1166 1182 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1167 1183 dest, branches = hg.parseurl(dest)
1168 1184 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1169 1185 if revs:
1170 1186 revs = [repo.lookup(rev) for rev in revs]
1171 1187 other = hg.peer(repo, {}, dest)
1172 1188 repo.ui.pushbuffer()
1173 1189 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1174 1190 repo.ui.popbuffer()
1175 1191 cl = repo.changelog
1176 1192 o = set([cl.rev(r) for r in outgoing.missing])
1177 1193 return subset.filter(lambda r: r in o)
1178 1194
1179 1195 def p1(repo, subset, x):
1180 1196 """``p1([set])``
1181 1197 First parent of changesets in set, or the working directory.
1182 1198 """
1183 1199 if x is None:
1184 1200 p = repo[x].p1().rev()
1185 1201 return subset.filter(lambda r: r == p)
1186 1202
1187 1203 ps = set()
1188 1204 cl = repo.changelog
1189 1205 for r in getset(repo, spanset(repo), x):
1190 1206 ps.add(cl.parentrevs(r)[0])
1191 1207 return subset & ps
1192 1208
1193 1209 def p2(repo, subset, x):
1194 1210 """``p2([set])``
1195 1211 Second parent of changesets in set, or the working directory.
1196 1212 """
1197 1213 if x is None:
1198 1214 ps = repo[x].parents()
1199 1215 try:
1200 1216 p = ps[1].rev()
1201 1217 return subset.filter(lambda r: r == p)
1202 1218 except IndexError:
1203 1219 return baseset([])
1204 1220
1205 1221 ps = set()
1206 1222 cl = repo.changelog
1207 1223 for r in getset(repo, spanset(repo), x):
1208 1224 ps.add(cl.parentrevs(r)[1])
1209 1225 return subset & ps
1210 1226
1211 1227 def parents(repo, subset, x):
1212 1228 """``parents([set])``
1213 1229 The set of all parents for all changesets in set, or the working directory.
1214 1230 """
1215 1231 if x is None:
1216 1232 ps = tuple(p.rev() for p in repo[x].parents())
1217 1233 return subset & ps
1218 1234
1219 1235 ps = set()
1220 1236 cl = repo.changelog
1221 1237 for r in getset(repo, spanset(repo), x):
1222 1238 ps.update(cl.parentrevs(r))
1223 1239 return subset & ps
1224 1240
1225 1241 def parentspec(repo, subset, x, n):
1226 1242 """``set^0``
1227 1243 The set.
1228 1244 ``set^1`` (or ``set^``), ``set^2``
1229 1245 First or second parent, respectively, of all changesets in set.
1230 1246 """
1231 1247 try:
1232 1248 n = int(n[1])
1233 1249 if n not in (0, 1, 2):
1234 1250 raise ValueError
1235 1251 except (TypeError, ValueError):
1236 1252 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1237 1253 ps = set()
1238 1254 cl = repo.changelog
1239 1255 for r in getset(repo, baseset(cl), x):
1240 1256 if n == 0:
1241 1257 ps.add(r)
1242 1258 elif n == 1:
1243 1259 ps.add(cl.parentrevs(r)[0])
1244 1260 elif n == 2:
1245 1261 parents = cl.parentrevs(r)
1246 1262 if len(parents) > 1:
1247 1263 ps.add(parents[1])
1248 1264 return subset & ps
1249 1265
1250 1266 def present(repo, subset, x):
1251 1267 """``present(set)``
1252 1268 An empty set, if any revision in set isn't found; otherwise,
1253 1269 all revisions in set.
1254 1270
1255 1271 If any of specified revisions is not present in the local repository,
1256 1272 the query is normally aborted. But this predicate allows the query
1257 1273 to continue even in such cases.
1258 1274 """
1259 1275 try:
1260 1276 return getset(repo, subset, x)
1261 1277 except error.RepoLookupError:
1262 1278 return baseset([])
1263 1279
1264 1280 def public(repo, subset, x):
1265 1281 """``public()``
1266 1282 Changeset in public phase."""
1267 1283 # i18n: "public" is a keyword
1268 1284 getargs(x, 0, 0, _("public takes no arguments"))
1269 1285 pc = repo._phasecache
1270 1286 return subset.filter(lambda r: pc.phase(repo, r) == phases.public)
1271 1287
1272 1288 def remote(repo, subset, x):
1273 1289 """``remote([id [,path]])``
1274 1290 Local revision that corresponds to the given identifier in a
1275 1291 remote repository, if present. Here, the '.' identifier is a
1276 1292 synonym for the current local branch.
1277 1293 """
1278 1294
1279 1295 import hg # avoid start-up nasties
1280 1296 # i18n: "remote" is a keyword
1281 1297 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1282 1298
1283 1299 q = '.'
1284 1300 if len(l) > 0:
1285 1301 # i18n: "remote" is a keyword
1286 1302 q = getstring(l[0], _("remote requires a string id"))
1287 1303 if q == '.':
1288 1304 q = repo['.'].branch()
1289 1305
1290 1306 dest = ''
1291 1307 if len(l) > 1:
1292 1308 # i18n: "remote" is a keyword
1293 1309 dest = getstring(l[1], _("remote requires a repository path"))
1294 1310 dest = repo.ui.expandpath(dest or 'default')
1295 1311 dest, branches = hg.parseurl(dest)
1296 1312 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1297 1313 if revs:
1298 1314 revs = [repo.lookup(rev) for rev in revs]
1299 1315 other = hg.peer(repo, {}, dest)
1300 1316 n = other.lookup(q)
1301 1317 if n in repo:
1302 1318 r = repo[n].rev()
1303 1319 if r in subset:
1304 1320 return baseset([r])
1305 1321 return baseset([])
1306 1322
1307 1323 def removes(repo, subset, x):
1308 1324 """``removes(pattern)``
1309 1325 Changesets which remove files matching pattern.
1310 1326
1311 1327 The pattern without explicit kind like ``glob:`` is expected to be
1312 1328 relative to the current directory and match against a file or a
1313 1329 directory.
1314 1330 """
1315 1331 # i18n: "removes" is a keyword
1316 1332 pat = getstring(x, _("removes requires a pattern"))
1317 1333 return checkstatus(repo, subset, pat, 2)
1318 1334
1319 1335 def rev(repo, subset, x):
1320 1336 """``rev(number)``
1321 1337 Revision with the given numeric identifier.
1322 1338 """
1323 1339 # i18n: "rev" is a keyword
1324 1340 l = getargs(x, 1, 1, _("rev requires one argument"))
1325 1341 try:
1326 1342 # i18n: "rev" is a keyword
1327 1343 l = int(getstring(l[0], _("rev requires a number")))
1328 1344 except (TypeError, ValueError):
1329 1345 # i18n: "rev" is a keyword
1330 1346 raise error.ParseError(_("rev expects a number"))
1331 1347 return subset.filter(lambda r: r == l)
1332 1348
1333 1349 def matching(repo, subset, x):
1334 1350 """``matching(revision [, field])``
1335 1351 Changesets in which a given set of fields match the set of fields in the
1336 1352 selected revision or set.
1337 1353
1338 1354 To match more than one field pass the list of fields to match separated
1339 1355 by spaces (e.g. ``author description``).
1340 1356
1341 1357 Valid fields are most regular revision fields and some special fields.
1342 1358
1343 1359 Regular revision fields are ``description``, ``author``, ``branch``,
1344 1360 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1345 1361 and ``diff``.
1346 1362 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1347 1363 contents of the revision. Two revisions matching their ``diff`` will
1348 1364 also match their ``files``.
1349 1365
1350 1366 Special fields are ``summary`` and ``metadata``:
1351 1367 ``summary`` matches the first line of the description.
1352 1368 ``metadata`` is equivalent to matching ``description user date``
1353 1369 (i.e. it matches the main metadata fields).
1354 1370
1355 1371 ``metadata`` is the default field which is used when no fields are
1356 1372 specified. You can match more than one field at a time.
1357 1373 """
1358 1374 # i18n: "matching" is a keyword
1359 1375 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1360 1376
1361 1377 revs = getset(repo, baseset(repo.changelog), l[0])
1362 1378
1363 1379 fieldlist = ['metadata']
1364 1380 if len(l) > 1:
1365 1381 fieldlist = getstring(l[1],
1366 1382 # i18n: "matching" is a keyword
1367 1383 _("matching requires a string "
1368 1384 "as its second argument")).split()
1369 1385
1370 1386 # Make sure that there are no repeated fields,
1371 1387 # expand the 'special' 'metadata' field type
1372 1388 # and check the 'files' whenever we check the 'diff'
1373 1389 fields = []
1374 1390 for field in fieldlist:
1375 1391 if field == 'metadata':
1376 1392 fields += ['user', 'description', 'date']
1377 1393 elif field == 'diff':
1378 1394 # a revision matching the diff must also match the files
1379 1395 # since matching the diff is very costly, make sure to
1380 1396 # also match the files first
1381 1397 fields += ['files', 'diff']
1382 1398 else:
1383 1399 if field == 'author':
1384 1400 field = 'user'
1385 1401 fields.append(field)
1386 1402 fields = set(fields)
1387 1403 if 'summary' in fields and 'description' in fields:
1388 1404 # If a revision matches its description it also matches its summary
1389 1405 fields.discard('summary')
1390 1406
1391 1407 # We may want to match more than one field
1392 1408 # Not all fields take the same amount of time to be matched
1393 1409 # Sort the selected fields in order of increasing matching cost
1394 1410 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1395 1411 'files', 'description', 'substate', 'diff']
1396 1412 def fieldkeyfunc(f):
1397 1413 try:
1398 1414 return fieldorder.index(f)
1399 1415 except ValueError:
1400 1416 # assume an unknown field is very costly
1401 1417 return len(fieldorder)
1402 1418 fields = list(fields)
1403 1419 fields.sort(key=fieldkeyfunc)
1404 1420
1405 1421 # Each field will be matched with its own "getfield" function
1406 1422 # which will be added to the getfieldfuncs array of functions
1407 1423 getfieldfuncs = []
1408 1424 _funcs = {
1409 1425 'user': lambda r: repo[r].user(),
1410 1426 'branch': lambda r: repo[r].branch(),
1411 1427 'date': lambda r: repo[r].date(),
1412 1428 'description': lambda r: repo[r].description(),
1413 1429 'files': lambda r: repo[r].files(),
1414 1430 'parents': lambda r: repo[r].parents(),
1415 1431 'phase': lambda r: repo[r].phase(),
1416 1432 'substate': lambda r: repo[r].substate,
1417 1433 'summary': lambda r: repo[r].description().splitlines()[0],
1418 1434 'diff': lambda r: list(repo[r].diff(git=True),)
1419 1435 }
1420 1436 for info in fields:
1421 1437 getfield = _funcs.get(info, None)
1422 1438 if getfield is None:
1423 1439 raise error.ParseError(
1424 1440 # i18n: "matching" is a keyword
1425 1441 _("unexpected field name passed to matching: %s") % info)
1426 1442 getfieldfuncs.append(getfield)
1427 1443 # convert the getfield array of functions into a "getinfo" function
1428 1444 # which returns an array of field values (or a single value if there
1429 1445 # is only one field to match)
1430 1446 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1431 1447
1432 1448 def matches(x):
1433 1449 for rev in revs:
1434 1450 target = getinfo(rev)
1435 1451 match = True
1436 1452 for n, f in enumerate(getfieldfuncs):
1437 1453 if target[n] != f(x):
1438 1454 match = False
1439 1455 if match:
1440 1456 return True
1441 1457 return False
1442 1458
1443 1459 return subset.filter(matches)
1444 1460
1445 1461 def reverse(repo, subset, x):
1446 1462 """``reverse(set)``
1447 1463 Reverse order of set.
1448 1464 """
1449 1465 l = getset(repo, subset, x)
1450 1466 l.reverse()
1451 1467 return l
1452 1468
1453 1469 def roots(repo, subset, x):
1454 1470 """``roots(set)``
1455 1471 Changesets in set with no parent changeset in set.
1456 1472 """
1457 1473 s = getset(repo, baseset(repo.changelog), x).set()
1458 1474 subset = baseset([r for r in subset if r in s])
1459 1475 cs = _children(repo, subset, s)
1460 1476 return subset - cs
1461 1477
1462 1478 def secret(repo, subset, x):
1463 1479 """``secret()``
1464 1480 Changeset in secret phase."""
1465 1481 # i18n: "secret" is a keyword
1466 1482 getargs(x, 0, 0, _("secret takes no arguments"))
1467 1483 pc = repo._phasecache
1468 1484 return subset.filter(lambda x: pc.phase(repo, x) == phases.secret)
1469 1485
1470 1486 def sort(repo, subset, x):
1471 1487 """``sort(set[, [-]key...])``
1472 1488 Sort set by keys. The default sort order is ascending, specify a key
1473 1489 as ``-key`` to sort in descending order.
1474 1490
1475 1491 The keys can be:
1476 1492
1477 1493 - ``rev`` for the revision number,
1478 1494 - ``branch`` for the branch name,
1479 1495 - ``desc`` for the commit message (description),
1480 1496 - ``user`` for user name (``author`` can be used as an alias),
1481 1497 - ``date`` for the commit date
1482 1498 """
1483 1499 # i18n: "sort" is a keyword
1484 1500 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1485 1501 keys = "rev"
1486 1502 if len(l) == 2:
1487 1503 # i18n: "sort" is a keyword
1488 1504 keys = getstring(l[1], _("sort spec must be a string"))
1489 1505
1490 1506 s = l[0]
1491 1507 keys = keys.split()
1492 1508 l = []
1493 1509 def invert(s):
1494 1510 return "".join(chr(255 - ord(c)) for c in s)
1495 1511 revs = getset(repo, subset, s)
1496 1512 if keys == ["rev"]:
1497 1513 revs.sort()
1498 1514 return revs
1499 1515 elif keys == ["-rev"]:
1500 1516 revs.sort(reverse=True)
1501 1517 return revs
1502 1518 for r in revs:
1503 1519 c = repo[r]
1504 1520 e = []
1505 1521 for k in keys:
1506 1522 if k == 'rev':
1507 1523 e.append(r)
1508 1524 elif k == '-rev':
1509 1525 e.append(-r)
1510 1526 elif k == 'branch':
1511 1527 e.append(c.branch())
1512 1528 elif k == '-branch':
1513 1529 e.append(invert(c.branch()))
1514 1530 elif k == 'desc':
1515 1531 e.append(c.description())
1516 1532 elif k == '-desc':
1517 1533 e.append(invert(c.description()))
1518 1534 elif k in 'user author':
1519 1535 e.append(c.user())
1520 1536 elif k in '-user -author':
1521 1537 e.append(invert(c.user()))
1522 1538 elif k == 'date':
1523 1539 e.append(c.date()[0])
1524 1540 elif k == '-date':
1525 1541 e.append(-c.date()[0])
1526 1542 else:
1527 1543 raise error.ParseError(_("unknown sort key %r") % k)
1528 1544 e.append(r)
1529 1545 l.append(e)
1530 1546 l.sort()
1531 1547 return baseset([e[-1] for e in l])
1532 1548
1533 1549 def _stringmatcher(pattern):
1534 1550 """
1535 1551 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1536 1552 returns the matcher name, pattern, and matcher function.
1537 1553 missing or unknown prefixes are treated as literal matches.
1538 1554
1539 1555 helper for tests:
1540 1556 >>> def test(pattern, *tests):
1541 1557 ... kind, pattern, matcher = _stringmatcher(pattern)
1542 1558 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1543 1559
1544 1560 exact matching (no prefix):
1545 1561 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1546 1562 ('literal', 'abcdefg', [False, False, True])
1547 1563
1548 1564 regex matching ('re:' prefix)
1549 1565 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1550 1566 ('re', 'a.+b', [False, False, True])
1551 1567
1552 1568 force exact matches ('literal:' prefix)
1553 1569 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1554 1570 ('literal', 're:foobar', [False, True])
1555 1571
1556 1572 unknown prefixes are ignored and treated as literals
1557 1573 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1558 1574 ('literal', 'foo:bar', [False, False, True])
1559 1575 """
1560 1576 if pattern.startswith('re:'):
1561 1577 pattern = pattern[3:]
1562 1578 try:
1563 1579 regex = re.compile(pattern)
1564 1580 except re.error, e:
1565 1581 raise error.ParseError(_('invalid regular expression: %s')
1566 1582 % e)
1567 1583 return 're', pattern, regex.search
1568 1584 elif pattern.startswith('literal:'):
1569 1585 pattern = pattern[8:]
1570 1586 return 'literal', pattern, pattern.__eq__
1571 1587
1572 1588 def _substringmatcher(pattern):
1573 1589 kind, pattern, matcher = _stringmatcher(pattern)
1574 1590 if kind == 'literal':
1575 1591 matcher = lambda s: pattern in s
1576 1592 return kind, pattern, matcher
1577 1593
1578 1594 def tag(repo, subset, x):
1579 1595 """``tag([name])``
1580 1596 The specified tag by name, or all tagged revisions if no name is given.
1581 1597 """
1582 1598 # i18n: "tag" is a keyword
1583 1599 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1584 1600 cl = repo.changelog
1585 1601 if args:
1586 1602 pattern = getstring(args[0],
1587 1603 # i18n: "tag" is a keyword
1588 1604 _('the argument to tag must be a string'))
1589 1605 kind, pattern, matcher = _stringmatcher(pattern)
1590 1606 if kind == 'literal':
1591 1607 # avoid resolving all tags
1592 1608 tn = repo._tagscache.tags.get(pattern, None)
1593 1609 if tn is None:
1594 1610 raise util.Abort(_("tag '%s' does not exist") % pattern)
1595 1611 s = set([repo[tn].rev()])
1596 1612 else:
1597 1613 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1598 1614 else:
1599 1615 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1600 1616 return subset & s
1601 1617
1602 1618 def tagged(repo, subset, x):
1603 1619 return tag(repo, subset, x)
1604 1620
1605 1621 def unstable(repo, subset, x):
1606 1622 """``unstable()``
1607 1623 Non-obsolete changesets with obsolete ancestors.
1608 1624 """
1609 1625 # i18n: "unstable" is a keyword
1610 1626 getargs(x, 0, 0, _("unstable takes no arguments"))
1611 1627 unstables = obsmod.getrevs(repo, 'unstable')
1612 1628 return subset & unstables
1613 1629
1614 1630
1615 1631 def user(repo, subset, x):
1616 1632 """``user(string)``
1617 1633 User name contains string. The match is case-insensitive.
1618 1634
1619 1635 If `string` starts with `re:`, the remainder of the string is treated as
1620 1636 a regular expression. To match a user that actually contains `re:`, use
1621 1637 the prefix `literal:`.
1622 1638 """
1623 1639 return author(repo, subset, x)
1624 1640
1625 1641 # for internal use
1626 1642 def _list(repo, subset, x):
1627 1643 s = getstring(x, "internal error")
1628 1644 if not s:
1629 1645 return baseset([])
1630 1646 ls = [repo[r].rev() for r in s.split('\0')]
1631 1647 s = subset.set()
1632 1648 return baseset([r for r in ls if r in s])
1633 1649
1634 1650 # for internal use
1635 1651 def _intlist(repo, subset, x):
1636 1652 s = getstring(x, "internal error")
1637 1653 if not s:
1638 1654 return baseset([])
1639 1655 ls = [int(r) for r in s.split('\0')]
1640 1656 s = subset.set()
1641 1657 return baseset([r for r in ls if r in s])
1642 1658
1643 1659 # for internal use
1644 1660 def _hexlist(repo, subset, x):
1645 1661 s = getstring(x, "internal error")
1646 1662 if not s:
1647 1663 return baseset([])
1648 1664 cl = repo.changelog
1649 1665 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
1650 1666 s = subset.set()
1651 1667 return baseset([r for r in ls if r in s])
1652 1668
1653 1669 symbols = {
1654 1670 "adds": adds,
1655 1671 "all": getall,
1656 1672 "ancestor": ancestor,
1657 1673 "ancestors": ancestors,
1658 1674 "_firstancestors": _firstancestors,
1659 1675 "author": author,
1660 1676 "only": only,
1661 1677 "bisect": bisect,
1662 1678 "bisected": bisected,
1663 1679 "bookmark": bookmark,
1664 1680 "branch": branch,
1665 1681 "branchpoint": branchpoint,
1666 1682 "bumped": bumped,
1667 1683 "bundle": bundle,
1668 1684 "children": children,
1669 1685 "closed": closed,
1670 1686 "contains": contains,
1671 1687 "converted": converted,
1672 1688 "date": date,
1673 1689 "desc": desc,
1674 1690 "descendants": descendants,
1675 1691 "_firstdescendants": _firstdescendants,
1676 1692 "destination": destination,
1677 1693 "divergent": divergent,
1678 1694 "draft": draft,
1679 1695 "extinct": extinct,
1680 1696 "extra": extra,
1681 1697 "file": hasfile,
1682 1698 "filelog": filelog,
1683 1699 "first": first,
1684 1700 "follow": follow,
1685 1701 "_followfirst": _followfirst,
1686 1702 "grep": grep,
1687 1703 "head": head,
1688 1704 "heads": heads,
1689 1705 "hidden": hidden,
1690 1706 "id": node_,
1691 1707 "keyword": keyword,
1692 1708 "last": last,
1693 1709 "limit": limit,
1694 1710 "_matchfiles": _matchfiles,
1695 1711 "max": maxrev,
1696 1712 "merge": merge,
1697 1713 "min": minrev,
1698 1714 "_missingancestors": _missingancestors,
1699 1715 "modifies": modifies,
1700 1716 "obsolete": obsolete,
1701 1717 "origin": origin,
1702 1718 "outgoing": outgoing,
1703 1719 "p1": p1,
1704 1720 "p2": p2,
1705 1721 "parents": parents,
1706 1722 "present": present,
1707 1723 "public": public,
1708 1724 "remote": remote,
1709 1725 "removes": removes,
1710 1726 "rev": rev,
1711 1727 "reverse": reverse,
1712 1728 "roots": roots,
1713 1729 "sort": sort,
1714 1730 "secret": secret,
1715 1731 "matching": matching,
1716 1732 "tag": tag,
1717 1733 "tagged": tagged,
1718 1734 "user": user,
1719 1735 "unstable": unstable,
1720 1736 "_list": _list,
1721 1737 "_intlist": _intlist,
1722 1738 "_hexlist": _hexlist,
1723 1739 }
1724 1740
1725 1741 # symbols which can't be used for a DoS attack for any given input
1726 1742 # (e.g. those which accept regexes as plain strings shouldn't be included)
1727 1743 # functions that just return a lot of changesets (like all) don't count here
1728 1744 safesymbols = set([
1729 1745 "adds",
1730 1746 "all",
1731 1747 "ancestor",
1732 1748 "ancestors",
1733 1749 "_firstancestors",
1734 1750 "author",
1735 1751 "bisect",
1736 1752 "bisected",
1737 1753 "bookmark",
1738 1754 "branch",
1739 1755 "branchpoint",
1740 1756 "bumped",
1741 1757 "bundle",
1742 1758 "children",
1743 1759 "closed",
1744 1760 "converted",
1745 1761 "date",
1746 1762 "desc",
1747 1763 "descendants",
1748 1764 "_firstdescendants",
1749 1765 "destination",
1750 1766 "divergent",
1751 1767 "draft",
1752 1768 "extinct",
1753 1769 "extra",
1754 1770 "file",
1755 1771 "filelog",
1756 1772 "first",
1757 1773 "follow",
1758 1774 "_followfirst",
1759 1775 "head",
1760 1776 "heads",
1761 1777 "hidden",
1762 1778 "id",
1763 1779 "keyword",
1764 1780 "last",
1765 1781 "limit",
1766 1782 "_matchfiles",
1767 1783 "max",
1768 1784 "merge",
1769 1785 "min",
1770 1786 "_missingancestors",
1771 1787 "modifies",
1772 1788 "obsolete",
1773 1789 "origin",
1774 1790 "outgoing",
1775 1791 "p1",
1776 1792 "p2",
1777 1793 "parents",
1778 1794 "present",
1779 1795 "public",
1780 1796 "remote",
1781 1797 "removes",
1782 1798 "rev",
1783 1799 "reverse",
1784 1800 "roots",
1785 1801 "sort",
1786 1802 "secret",
1787 1803 "matching",
1788 1804 "tag",
1789 1805 "tagged",
1790 1806 "user",
1791 1807 "unstable",
1792 1808 "_list",
1793 1809 "_intlist",
1794 1810 "_hexlist",
1795 1811 ])
1796 1812
1797 1813 methods = {
1798 1814 "range": rangeset,
1799 1815 "dagrange": dagrange,
1800 1816 "string": stringset,
1801 1817 "symbol": symbolset,
1802 1818 "and": andset,
1803 1819 "or": orset,
1804 1820 "not": notset,
1805 1821 "list": listset,
1806 1822 "func": func,
1807 1823 "ancestor": ancestorspec,
1808 1824 "parent": parentspec,
1809 1825 "parentpost": p1,
1810 1826 }
1811 1827
1812 1828 def optimize(x, small):
1813 1829 if x is None:
1814 1830 return 0, x
1815 1831
1816 1832 smallbonus = 1
1817 1833 if small:
1818 1834 smallbonus = .5
1819 1835
1820 1836 op = x[0]
1821 1837 if op == 'minus':
1822 1838 return optimize(('and', x[1], ('not', x[2])), small)
1823 1839 elif op == 'dagrangepre':
1824 1840 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1825 1841 elif op == 'dagrangepost':
1826 1842 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1827 1843 elif op == 'rangepre':
1828 1844 return optimize(('range', ('string', '0'), x[1]), small)
1829 1845 elif op == 'rangepost':
1830 1846 return optimize(('range', x[1], ('string', 'tip')), small)
1831 1847 elif op == 'negate':
1832 1848 return optimize(('string',
1833 1849 '-' + getstring(x[1], _("can't negate that"))), small)
1834 1850 elif op in 'string symbol negate':
1835 1851 return smallbonus, x # single revisions are small
1836 1852 elif op == 'and':
1837 1853 wa, ta = optimize(x[1], True)
1838 1854 wb, tb = optimize(x[2], True)
1839 1855
1840 1856 # (::x and not ::y)/(not ::y and ::x) have a fast path
1841 1857 def ismissingancestors(revs, bases):
1842 1858 return (
1843 1859 revs[0] == 'func'
1844 1860 and getstring(revs[1], _('not a symbol')) == 'ancestors'
1845 1861 and bases[0] == 'not'
1846 1862 and bases[1][0] == 'func'
1847 1863 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
1848 1864
1849 1865 w = min(wa, wb)
1850 1866 if ismissingancestors(ta, tb):
1851 1867 return w, ('func', ('symbol', '_missingancestors'),
1852 1868 ('list', ta[2], tb[1][2]))
1853 1869 if ismissingancestors(tb, ta):
1854 1870 return w, ('func', ('symbol', '_missingancestors'),
1855 1871 ('list', tb[2], ta[1][2]))
1856 1872
1857 1873 if wa > wb:
1858 1874 return w, (op, tb, ta)
1859 1875 return w, (op, ta, tb)
1860 1876 elif op == 'or':
1861 1877 wa, ta = optimize(x[1], False)
1862 1878 wb, tb = optimize(x[2], False)
1863 1879 if wb < wa:
1864 1880 wb, wa = wa, wb
1865 1881 return max(wa, wb), (op, ta, tb)
1866 1882 elif op == 'not':
1867 1883 o = optimize(x[1], not small)
1868 1884 return o[0], (op, o[1])
1869 1885 elif op == 'parentpost':
1870 1886 o = optimize(x[1], small)
1871 1887 return o[0], (op, o[1])
1872 1888 elif op == 'group':
1873 1889 return optimize(x[1], small)
1874 1890 elif op in 'dagrange range list parent ancestorspec':
1875 1891 if op == 'parent':
1876 1892 # x^:y means (x^) : y, not x ^ (:y)
1877 1893 post = ('parentpost', x[1])
1878 1894 if x[2][0] == 'dagrangepre':
1879 1895 return optimize(('dagrange', post, x[2][1]), small)
1880 1896 elif x[2][0] == 'rangepre':
1881 1897 return optimize(('range', post, x[2][1]), small)
1882 1898
1883 1899 wa, ta = optimize(x[1], small)
1884 1900 wb, tb = optimize(x[2], small)
1885 1901 return wa + wb, (op, ta, tb)
1886 1902 elif op == 'func':
1887 1903 f = getstring(x[1], _("not a symbol"))
1888 1904 wa, ta = optimize(x[2], small)
1889 1905 if f in ("author branch closed date desc file grep keyword "
1890 1906 "outgoing user"):
1891 1907 w = 10 # slow
1892 1908 elif f in "modifies adds removes":
1893 1909 w = 30 # slower
1894 1910 elif f == "contains":
1895 1911 w = 100 # very slow
1896 1912 elif f == "ancestor":
1897 1913 w = 1 * smallbonus
1898 1914 elif f in "reverse limit first":
1899 1915 w = 0
1900 1916 elif f in "sort":
1901 1917 w = 10 # assume most sorts look at changelog
1902 1918 else:
1903 1919 w = 1
1904 1920 return w + wa, (op, x[1], ta)
1905 1921 return 1, x
1906 1922
1907 1923 _aliasarg = ('func', ('symbol', '_aliasarg'))
1908 1924 def _getaliasarg(tree):
1909 1925 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1910 1926 return X, None otherwise.
1911 1927 """
1912 1928 if (len(tree) == 3 and tree[:2] == _aliasarg
1913 1929 and tree[2][0] == 'string'):
1914 1930 return tree[2][1]
1915 1931 return None
1916 1932
1917 1933 def _checkaliasarg(tree, known=None):
1918 1934 """Check tree contains no _aliasarg construct or only ones which
1919 1935 value is in known. Used to avoid alias placeholders injection.
1920 1936 """
1921 1937 if isinstance(tree, tuple):
1922 1938 arg = _getaliasarg(tree)
1923 1939 if arg is not None and (not known or arg not in known):
1924 1940 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1925 1941 for t in tree:
1926 1942 _checkaliasarg(t, known)
1927 1943
1928 1944 class revsetalias(object):
1929 1945 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1930 1946 args = None
1931 1947
1932 1948 def __init__(self, name, value):
1933 1949 '''Aliases like:
1934 1950
1935 1951 h = heads(default)
1936 1952 b($1) = ancestors($1) - ancestors(default)
1937 1953 '''
1938 1954 m = self.funcre.search(name)
1939 1955 if m:
1940 1956 self.name = m.group(1)
1941 1957 self.tree = ('func', ('symbol', m.group(1)))
1942 1958 self.args = [x.strip() for x in m.group(2).split(',')]
1943 1959 for arg in self.args:
1944 1960 # _aliasarg() is an unknown symbol only used separate
1945 1961 # alias argument placeholders from regular strings.
1946 1962 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1947 1963 else:
1948 1964 self.name = name
1949 1965 self.tree = ('symbol', name)
1950 1966
1951 1967 self.replacement, pos = parse(value)
1952 1968 if pos != len(value):
1953 1969 raise error.ParseError(_('invalid token'), pos)
1954 1970 # Check for placeholder injection
1955 1971 _checkaliasarg(self.replacement, self.args)
1956 1972
1957 1973 def _getalias(aliases, tree):
1958 1974 """If tree looks like an unexpanded alias, return it. Return None
1959 1975 otherwise.
1960 1976 """
1961 1977 if isinstance(tree, tuple) and tree:
1962 1978 if tree[0] == 'symbol' and len(tree) == 2:
1963 1979 name = tree[1]
1964 1980 alias = aliases.get(name)
1965 1981 if alias and alias.args is None and alias.tree == tree:
1966 1982 return alias
1967 1983 if tree[0] == 'func' and len(tree) > 1:
1968 1984 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1969 1985 name = tree[1][1]
1970 1986 alias = aliases.get(name)
1971 1987 if alias and alias.args is not None and alias.tree == tree[:2]:
1972 1988 return alias
1973 1989 return None
1974 1990
1975 1991 def _expandargs(tree, args):
1976 1992 """Replace _aliasarg instances with the substitution value of the
1977 1993 same name in args, recursively.
1978 1994 """
1979 1995 if not tree or not isinstance(tree, tuple):
1980 1996 return tree
1981 1997 arg = _getaliasarg(tree)
1982 1998 if arg is not None:
1983 1999 return args[arg]
1984 2000 return tuple(_expandargs(t, args) for t in tree)
1985 2001
1986 2002 def _expandaliases(aliases, tree, expanding, cache):
1987 2003 """Expand aliases in tree, recursively.
1988 2004
1989 2005 'aliases' is a dictionary mapping user defined aliases to
1990 2006 revsetalias objects.
1991 2007 """
1992 2008 if not isinstance(tree, tuple):
1993 2009 # Do not expand raw strings
1994 2010 return tree
1995 2011 alias = _getalias(aliases, tree)
1996 2012 if alias is not None:
1997 2013 if alias in expanding:
1998 2014 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1999 2015 'detected') % alias.name)
2000 2016 expanding.append(alias)
2001 2017 if alias.name not in cache:
2002 2018 cache[alias.name] = _expandaliases(aliases, alias.replacement,
2003 2019 expanding, cache)
2004 2020 result = cache[alias.name]
2005 2021 expanding.pop()
2006 2022 if alias.args is not None:
2007 2023 l = getlist(tree[2])
2008 2024 if len(l) != len(alias.args):
2009 2025 raise error.ParseError(
2010 2026 _('invalid number of arguments: %s') % len(l))
2011 2027 l = [_expandaliases(aliases, a, [], cache) for a in l]
2012 2028 result = _expandargs(result, dict(zip(alias.args, l)))
2013 2029 else:
2014 2030 result = tuple(_expandaliases(aliases, t, expanding, cache)
2015 2031 for t in tree)
2016 2032 return result
2017 2033
2018 2034 def findaliases(ui, tree):
2019 2035 _checkaliasarg(tree)
2020 2036 aliases = {}
2021 2037 for k, v in ui.configitems('revsetalias'):
2022 2038 alias = revsetalias(k, v)
2023 2039 aliases[alias.name] = alias
2024 2040 return _expandaliases(aliases, tree, [], {})
2025 2041
2026 2042 def parse(spec, lookup=None):
2027 2043 p = parser.parser(tokenize, elements)
2028 2044 return p.parse(spec, lookup=lookup)
2029 2045
2030 2046 def match(ui, spec, repo=None):
2031 2047 if not spec:
2032 2048 raise error.ParseError(_("empty query"))
2033 2049 lookup = None
2034 2050 if repo:
2035 2051 lookup = repo.__contains__
2036 2052 tree, pos = parse(spec, lookup)
2037 2053 if (pos != len(spec)):
2038 2054 raise error.ParseError(_("invalid token"), pos)
2039 2055 if ui:
2040 2056 tree = findaliases(ui, tree)
2041 2057 weight, tree = optimize(tree, True)
2042 2058 def mfunc(repo, subset):
2043 2059 if util.safehasattr(subset, 'set'):
2044 2060 return getset(repo, subset, tree)
2045 2061 return getset(repo, baseset(subset), tree)
2046 2062 return mfunc
2047 2063
2048 2064 def formatspec(expr, *args):
2049 2065 '''
2050 2066 This is a convenience function for using revsets internally, and
2051 2067 escapes arguments appropriately. Aliases are intentionally ignored
2052 2068 so that intended expression behavior isn't accidentally subverted.
2053 2069
2054 2070 Supported arguments:
2055 2071
2056 2072 %r = revset expression, parenthesized
2057 2073 %d = int(arg), no quoting
2058 2074 %s = string(arg), escaped and single-quoted
2059 2075 %b = arg.branch(), escaped and single-quoted
2060 2076 %n = hex(arg), single-quoted
2061 2077 %% = a literal '%'
2062 2078
2063 2079 Prefixing the type with 'l' specifies a parenthesized list of that type.
2064 2080
2065 2081 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2066 2082 '(10 or 11):: and ((this()) or (that()))'
2067 2083 >>> formatspec('%d:: and not %d::', 10, 20)
2068 2084 '10:: and not 20::'
2069 2085 >>> formatspec('%ld or %ld', [], [1])
2070 2086 "_list('') or 1"
2071 2087 >>> formatspec('keyword(%s)', 'foo\\xe9')
2072 2088 "keyword('foo\\\\xe9')"
2073 2089 >>> b = lambda: 'default'
2074 2090 >>> b.branch = b
2075 2091 >>> formatspec('branch(%b)', b)
2076 2092 "branch('default')"
2077 2093 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2078 2094 "root(_list('a\\x00b\\x00c\\x00d'))"
2079 2095 '''
2080 2096
2081 2097 def quote(s):
2082 2098 return repr(str(s))
2083 2099
2084 2100 def argtype(c, arg):
2085 2101 if c == 'd':
2086 2102 return str(int(arg))
2087 2103 elif c == 's':
2088 2104 return quote(arg)
2089 2105 elif c == 'r':
2090 2106 parse(arg) # make sure syntax errors are confined
2091 2107 return '(%s)' % arg
2092 2108 elif c == 'n':
2093 2109 return quote(node.hex(arg))
2094 2110 elif c == 'b':
2095 2111 return quote(arg.branch())
2096 2112
2097 2113 def listexp(s, t):
2098 2114 l = len(s)
2099 2115 if l == 0:
2100 2116 return "_list('')"
2101 2117 elif l == 1:
2102 2118 return argtype(t, s[0])
2103 2119 elif t == 'd':
2104 2120 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2105 2121 elif t == 's':
2106 2122 return "_list('%s')" % "\0".join(s)
2107 2123 elif t == 'n':
2108 2124 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2109 2125 elif t == 'b':
2110 2126 return "_list('%s')" % "\0".join(a.branch() for a in s)
2111 2127
2112 2128 m = l // 2
2113 2129 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2114 2130
2115 2131 ret = ''
2116 2132 pos = 0
2117 2133 arg = 0
2118 2134 while pos < len(expr):
2119 2135 c = expr[pos]
2120 2136 if c == '%':
2121 2137 pos += 1
2122 2138 d = expr[pos]
2123 2139 if d == '%':
2124 2140 ret += d
2125 2141 elif d in 'dsnbr':
2126 2142 ret += argtype(d, args[arg])
2127 2143 arg += 1
2128 2144 elif d == 'l':
2129 2145 # a list of some type
2130 2146 pos += 1
2131 2147 d = expr[pos]
2132 2148 ret += listexp(list(args[arg]), d)
2133 2149 arg += 1
2134 2150 else:
2135 2151 raise util.Abort('unexpected revspec format character %s' % d)
2136 2152 else:
2137 2153 ret += c
2138 2154 pos += 1
2139 2155
2140 2156 return ret
2141 2157
2142 2158 def prettyformat(tree):
2143 2159 def _prettyformat(tree, level, lines):
2144 2160 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2145 2161 lines.append((level, str(tree)))
2146 2162 else:
2147 2163 lines.append((level, '(%s' % tree[0]))
2148 2164 for s in tree[1:]:
2149 2165 _prettyformat(s, level + 1, lines)
2150 2166 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
2151 2167
2152 2168 lines = []
2153 2169 _prettyformat(tree, 0, lines)
2154 2170 output = '\n'.join((' '*l + s) for l, s in lines)
2155 2171 return output
2156 2172
2157 2173 def depth(tree):
2158 2174 if isinstance(tree, tuple):
2159 2175 return max(map(depth, tree)) + 1
2160 2176 else:
2161 2177 return 0
2162 2178
2163 2179 def funcsused(tree):
2164 2180 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2165 2181 return set()
2166 2182 else:
2167 2183 funcs = set()
2168 2184 for s in tree[1:]:
2169 2185 funcs |= funcsused(s)
2170 2186 if tree[0] == 'func':
2171 2187 funcs.add(tree[1][1])
2172 2188 return funcs
2173 2189
2174 2190 class baseset(list):
2175 2191 """Basic data structure that represents a revset and contains the basic
2176 2192 operation that it should be able to perform.
2177 2193
2178 2194 Every method in this class should be implemented by any smartset class.
2179 2195 """
2180 2196 def __init__(self, data=()):
2181 2197 super(baseset, self).__init__(data)
2182 2198 self._set = None
2183 2199
2184 2200 def ascending(self):
2185 2201 """Sorts the set in ascending order (in place).
2186 2202
2187 2203 This is part of the mandatory API for smartset."""
2188 2204 self.sort()
2189 2205
2190 2206 def descending(self):
2191 2207 """Sorts the set in descending order (in place).
2192 2208
2193 2209 This is part of the mandatory API for smartset."""
2194 2210 self.sort(reverse=True)
2195 2211
2196 2212 def min(self):
2197 2213 return min(self)
2198 2214
2199 2215 def max(self):
2200 2216 return max(self)
2201 2217
2202 2218 def set(self):
2203 2219 """Returns a set or a smartset containing all the elements.
2204 2220
2205 2221 The returned structure should be the fastest option for membership
2206 2222 testing.
2207 2223
2208 2224 This is part of the mandatory API for smartset."""
2209 2225 if not self._set:
2210 2226 self._set = set(self)
2211 2227 return self._set
2212 2228
2213 2229 def __sub__(self, other):
2214 2230 """Returns a new object with the substraction of the two collections.
2215 2231
2216 2232 This is part of the mandatory API for smartset."""
2217 2233 if isinstance(other, baseset):
2218 2234 s = other.set()
2219 2235 else:
2220 2236 s = set(other)
2221 2237 return baseset(self.set() - s)
2222 2238
2223 2239 def __and__(self, other):
2224 2240 """Returns a new object with the intersection of the two collections.
2225 2241
2226 2242 This is part of the mandatory API for smartset."""
2227 2243 if isinstance(other, baseset):
2228 2244 other = other.set()
2229 2245 return baseset([y for y in self if y in other])
2230 2246
2231 2247 def __add__(self, other):
2232 2248 """Returns a new object with the union of the two collections.
2233 2249
2234 2250 This is part of the mandatory API for smartset."""
2235 2251 s = self.set()
2236 2252 l = [r for r in other if r not in s]
2237 2253 return baseset(list(self) + l)
2238 2254
2239 2255 def isascending(self):
2240 2256 """Returns True if the collection is ascending order, False if not.
2241 2257
2242 2258 This is part of the mandatory API for smartset."""
2243 2259 return False
2244 2260
2245 2261 def isdescending(self):
2246 2262 """Returns True if the collection is descending order, False if not.
2247 2263
2248 2264 This is part of the mandatory API for smartset."""
2249 2265 return False
2250 2266
2251 2267 def filter(self, condition):
2252 2268 """Returns this smartset filtered by condition as a new smartset.
2253 2269
2254 2270 `condition` is a callable which takes a revision number and returns a
2255 2271 boolean.
2256 2272
2257 2273 This is part of the mandatory API for smartset."""
2258 2274 return lazyset(self, condition)
2259 2275
2260 2276 class _orderedsetmixin(object):
2261 2277 """Mixin class with utility methods for smartsets
2262 2278
2263 2279 This should be extended by smartsets which have the isascending(),
2264 2280 isdescending() and reverse() methods"""
2265 2281
2266 2282 def _first(self):
2267 2283 """return the first revision in the set"""
2268 2284 for r in self:
2269 2285 return r
2270 2286 return None
2271 2287
2272 2288 def _last(self):
2273 2289 """return the last revision in the set"""
2274 2290 self.reverse()
2275 2291 m = self._first()
2276 2292 self.reverse()
2277 2293 return m
2278 2294
2279 2295 def min(self):
2280 2296 """return the smallest element in the set"""
2281 2297 if self.isascending():
2282 2298 return self._first()
2283 2299 return self._last()
2284 2300
2285 2301 def max(self):
2286 2302 """return the largest element in the set"""
2287 2303 if self.isascending():
2288 2304 return self._last()
2289 2305 return self._first()
2290 2306
2291 2307 class lazyset(object):
2292 2308 """Duck type for baseset class which iterates lazily over the revisions in
2293 2309 the subset and contains a function which tests for membership in the
2294 2310 revset
2295 2311 """
2296 2312 def __init__(self, subset, condition=lambda x: True):
2297 2313 """
2298 2314 condition: a function that decide whether a revision in the subset
2299 2315 belongs to the revset or not.
2300 2316 """
2301 2317 self._subset = subset
2302 2318 self._condition = condition
2303 2319 self._cache = {}
2304 2320
2305 2321 def ascending(self):
2306 2322 self._subset.sort()
2307 2323
2308 2324 def descending(self):
2309 2325 self._subset.sort(reverse=True)
2310 2326
2311 2327 def min(self):
2312 2328 return min(self)
2313 2329
2314 2330 def max(self):
2315 2331 return max(self)
2316 2332
2317 2333 def __contains__(self, x):
2318 2334 c = self._cache
2319 2335 if x not in c:
2320 2336 c[x] = x in self._subset and self._condition(x)
2321 2337 return c[x]
2322 2338
2323 2339 def __iter__(self):
2324 2340 cond = self._condition
2325 2341 for x in self._subset:
2326 2342 if cond(x):
2327 2343 yield x
2328 2344
2329 2345 def __and__(self, x):
2330 2346 return lazyset(self, lambda r: r in x)
2331 2347
2332 2348 def __sub__(self, x):
2333 2349 return lazyset(self, lambda r: r not in x)
2334 2350
2335 2351 def __add__(self, x):
2336 2352 return _addset(self, x)
2337 2353
2338 2354 def __nonzero__(self):
2339 2355 for r in self:
2340 2356 return True
2341 2357 return False
2342 2358
2343 2359 def __len__(self):
2344 2360 # Basic implementation to be changed in future patches.
2345 2361 l = baseset([r for r in self])
2346 2362 return len(l)
2347 2363
2348 2364 def __getitem__(self, x):
2349 2365 # Basic implementation to be changed in future patches.
2350 2366 l = baseset([r for r in self])
2351 2367 return l[x]
2352 2368
2353 2369 def sort(self, reverse=False):
2354 2370 if not util.safehasattr(self._subset, 'sort'):
2355 2371 self._subset = baseset(self._subset)
2356 2372 self._subset.sort(reverse=reverse)
2357 2373
2358 2374 def reverse(self):
2359 2375 self._subset.reverse()
2360 2376
2361 2377 def set(self):
2362 2378 return set([r for r in self])
2363 2379
2364 2380 def isascending(self):
2365 2381 return False
2366 2382
2367 2383 def isdescending(self):
2368 2384 return False
2369 2385
2370 2386 def filter(self, l):
2371 2387 return lazyset(self, l)
2372 2388
2373 2389 class orderedlazyset(_orderedsetmixin, lazyset):
2374 2390 """Subclass of lazyset which subset can be ordered either ascending or
2375 2391 descendingly
2376 2392 """
2377 2393 def __init__(self, subset, condition, ascending=True):
2378 2394 super(orderedlazyset, self).__init__(subset, condition)
2379 2395 self._ascending = ascending
2380 2396
2381 2397 def filter(self, l):
2382 2398 return orderedlazyset(self, l, ascending=self._ascending)
2383 2399
2384 2400 def ascending(self):
2385 2401 if not self._ascending:
2386 2402 self.reverse()
2387 2403
2388 2404 def descending(self):
2389 2405 if self._ascending:
2390 2406 self.reverse()
2391 2407
2392 2408 def __and__(self, x):
2393 2409 return orderedlazyset(self, lambda r: r in x,
2394 2410 ascending=self._ascending)
2395 2411
2396 2412 def __sub__(self, x):
2397 2413 return orderedlazyset(self, lambda r: r not in x,
2398 2414 ascending=self._ascending)
2399 2415
2400 2416 def __add__(self, x):
2401 2417 kwargs = {}
2402 2418 if self.isascending() and x.isascending():
2403 2419 kwargs['ascending'] = True
2404 2420 if self.isdescending() and x.isdescending():
2405 2421 kwargs['ascending'] = False
2406 2422 return _addset(self, x, **kwargs)
2407 2423
2408 2424 def sort(self, reverse=False):
2409 2425 if reverse:
2410 2426 if self._ascending:
2411 2427 self._subset.sort(reverse=reverse)
2412 2428 else:
2413 2429 if not self._ascending:
2414 2430 self._subset.sort(reverse=reverse)
2415 2431 self._ascending = not reverse
2416 2432
2417 2433 def isascending(self):
2418 2434 return self._ascending
2419 2435
2420 2436 def isdescending(self):
2421 2437 return not self._ascending
2422 2438
2423 2439 def reverse(self):
2424 2440 self._subset.reverse()
2425 2441 self._ascending = not self._ascending
2426 2442
2427 2443 class _addset(_orderedsetmixin):
2428 2444 """Represent the addition of two sets
2429 2445
2430 2446 Wrapper structure for lazily adding two structures without losing much
2431 2447 performance on the __contains__ method
2432 2448
2433 2449 If the ascending attribute is set, that means the two structures are
2434 2450 ordered in either an ascending or descending way. Therefore, we can add
2435 2451 them mantaining the order by iterating over both at the same time
2436 2452
2437 2453 This class does not duck-type baseset and it's only supposed to be used
2438 2454 internally
2439 2455 """
2440 2456 def __init__(self, revs1, revs2, ascending=None):
2441 2457 self._r1 = revs1
2442 2458 self._r2 = revs2
2443 2459 self._iter = None
2444 2460 self._ascending = ascending
2445 2461 self._genlist = None
2446 2462
2447 2463 @util.propertycache
2448 2464 def _list(self):
2449 2465 if not self._genlist:
2450 2466 self._genlist = baseset(self._iterator())
2451 2467 return self._genlist
2452 2468
2453 2469 def filter(self, condition):
2454 2470 if self._ascending is not None:
2455 2471 return orderedlazyset(self, condition, ascending=self._ascending)
2456 2472 return lazyset(self, condition)
2457 2473
2458 2474 def ascending(self):
2459 2475 if self._ascending is None:
2460 2476 self.sort()
2461 2477 self._ascending = True
2462 2478 else:
2463 2479 if not self._ascending:
2464 2480 self.reverse()
2465 2481
2466 2482 def descending(self):
2467 2483 if self._ascending is None:
2468 2484 self.sort(reverse=True)
2469 2485 self._ascending = False
2470 2486 else:
2471 2487 if self._ascending:
2472 2488 self.reverse()
2473 2489
2474 2490 def __and__(self, other):
2475 2491 filterfunc = other.__contains__
2476 2492 if self._ascending is not None:
2477 2493 return orderedlazyset(self, filterfunc, ascending=self._ascending)
2478 2494 return lazyset(self, filterfunc)
2479 2495
2480 2496 def __sub__(self, other):
2481 2497 filterfunc = lambda r: r not in other
2482 2498 if self._ascending is not None:
2483 2499 return orderedlazyset(self, filterfunc, ascending=self._ascending)
2484 2500 return lazyset(self, filterfunc)
2485 2501
2486 2502 def __add__(self, other):
2487 2503 """When both collections are ascending or descending, preserve the order
2488 2504 """
2489 2505 kwargs = {}
2490 2506 if self._ascending is not None:
2491 2507 if self.isascending() and other.isascending():
2492 2508 kwargs['ascending'] = True
2493 2509 if self.isdescending() and other.isdescending():
2494 2510 kwargs['ascending'] = False
2495 2511 return _addset(self, other, **kwargs)
2496 2512
2497 2513 def _iterator(self):
2498 2514 """Iterate over both collections without repeating elements
2499 2515
2500 2516 If the ascending attribute is not set, iterate over the first one and
2501 2517 then over the second one checking for membership on the first one so we
2502 2518 dont yield any duplicates.
2503 2519
2504 2520 If the ascending attribute is set, iterate over both collections at the
2505 2521 same time, yielding only one value at a time in the given order.
2506 2522 """
2507 2523 if not self._iter:
2508 2524 def gen():
2509 2525 if self._ascending is None:
2510 2526 for r in self._r1:
2511 2527 yield r
2512 2528 s = self._r1.set()
2513 2529 for r in self._r2:
2514 2530 if r not in s:
2515 2531 yield r
2516 2532 else:
2517 2533 iter1 = iter(self._r1)
2518 2534 iter2 = iter(self._r2)
2519 2535
2520 2536 val1 = None
2521 2537 val2 = None
2522 2538
2523 2539 choice = max
2524 2540 if self._ascending:
2525 2541 choice = min
2526 2542 try:
2527 2543 # Consume both iterators in an ordered way until one is
2528 2544 # empty
2529 2545 while True:
2530 2546 if val1 is None:
2531 2547 val1 = iter1.next()
2532 2548 if val2 is None:
2533 2549 val2 = iter2.next()
2534 2550 next = choice(val1, val2)
2535 2551 yield next
2536 2552 if val1 == next:
2537 2553 val1 = None
2538 2554 if val2 == next:
2539 2555 val2 = None
2540 2556 except StopIteration:
2541 2557 # Flush any remaining values and consume the other one
2542 2558 it = iter2
2543 2559 if val1 is not None:
2544 2560 yield val1
2545 2561 it = iter1
2546 2562 elif val2 is not None:
2547 2563 # might have been equality and both are empty
2548 2564 yield val2
2549 2565 for val in it:
2550 2566 yield val
2551 2567
2552 2568 self._iter = _generatorset(gen())
2553 2569
2554 2570 return self._iter
2555 2571
2556 2572 def __iter__(self):
2557 2573 if self._genlist:
2558 2574 return iter(self._genlist)
2559 2575 return iter(self._iterator())
2560 2576
2561 2577 def __contains__(self, x):
2562 2578 return x in self._r1 or x in self._r2
2563 2579
2564 2580 def set(self):
2565 2581 return self
2566 2582
2567 2583 def sort(self, reverse=False):
2568 2584 """Sort the added set
2569 2585
2570 2586 For this we use the cached list with all the generated values and if we
2571 2587 know they are ascending or descending we can sort them in a smart way.
2572 2588 """
2573 2589 if self._ascending is None:
2574 2590 self._list.sort(reverse=reverse)
2575 2591 self._ascending = not reverse
2576 2592 else:
2577 2593 if bool(self._ascending) == bool(reverse):
2578 2594 self.reverse()
2579 2595
2580 2596 def isascending(self):
2581 2597 return self._ascending is not None and self._ascending
2582 2598
2583 2599 def isdescending(self):
2584 2600 return self._ascending is not None and not self._ascending
2585 2601
2586 2602 def reverse(self):
2587 2603 self._list.reverse()
2588 2604 if self._ascending is not None:
2589 2605 self._ascending = not self._ascending
2590 2606
2591 2607 class _generatorset(object):
2592 2608 """Wrap a generator for lazy iteration
2593 2609
2594 2610 Wrapper structure for generators that provides lazy membership and can
2595 2611 be iterated more than once.
2596 2612 When asked for membership it generates values until either it finds the
2597 2613 requested one or has gone through all the elements in the generator
2598 2614
2599 2615 This class does not duck-type baseset and it's only supposed to be used
2600 2616 internally
2601 2617 """
2602 2618 def __init__(self, gen):
2603 2619 """
2604 2620 gen: a generator producing the values for the generatorset.
2605 2621 """
2606 2622 self._gen = gen
2607 2623 self._iter = iter(gen)
2608 2624 self._cache = {}
2609 2625 self._genlist = baseset([])
2610 2626 self._iterated = False
2611 2627 self._finished = False
2612 2628
2613 2629 def __contains__(self, x):
2614 2630 if x in self._cache:
2615 2631 return self._cache[x]
2616 2632
2617 2633 # Use __iter__ which caches values and stores them into self._genlist
2618 2634 for l in self:
2619 2635 if l == x:
2620 2636 return True
2621 2637
2622 2638 self._finished = True
2623 2639 self._cache[x] = False
2624 2640 return False
2625 2641
2626 2642 def __iter__(self):
2627 2643 if self._iterated:
2628 2644 # At least a part of the list should be cached if iteration has
2629 2645 # started over the generatorset.
2630 2646 for l in self._genlist:
2631 2647 yield l
2632 2648 else:
2633 2649 # Starting iteration over the generatorset.
2634 2650 self._iterated = True
2635 2651
2636 2652 for item in self._gen:
2637 2653 self._cache[item] = True
2638 2654 self._genlist.append(item)
2639 2655 yield item
2640 2656
2641 2657 # Iteration over the generator has finished. Whole value list should be
2642 2658 # cached in self._genlist
2643 2659 self._finished = True
2644 2660
2645 2661 def set(self):
2646 2662 return self
2647 2663
2648 2664 def sort(self, reverse=False):
2649 2665 if not self._finished:
2650 2666 for i in self:
2651 2667 continue
2652 2668 self._genlist.sort(reverse=reverse)
2653 2669
2654 2670 class _ascgeneratorset(_generatorset):
2655 2671 """Wrap a generator of ascending elements for lazy iteration
2656 2672
2657 2673 Same structure as _generatorset but stops iterating after it goes past
2658 2674 the value when asked for membership and the element is not contained
2659 2675
2660 2676 This class does not duck-type baseset and it's only supposed to be used
2661 2677 internally
2662 2678 """
2663 2679 def __contains__(self, x):
2664 2680 if x in self._cache:
2665 2681 return self._cache[x]
2666 2682
2667 2683 for l in self:
2668 2684 if l == x:
2669 2685 return True
2670 2686 if l > x:
2671 2687 break
2672 2688
2673 2689 self._cache[x] = False
2674 2690 return False
2675 2691
2676 2692 class _descgeneratorset(_generatorset):
2677 2693 """Wrap a generator of descending elements for lazy iteration
2678 2694
2679 2695 Same structure as _generatorset but stops iterating after it goes past
2680 2696 the value when asked for membership and the element is not contained
2681 2697
2682 2698 This class does not duck-type baseset and it's only supposed to be used
2683 2699 internally
2684 2700 """
2685 2701 def __contains__(self, x):
2686 2702 if x in self._cache:
2687 2703 return self._cache[x]
2688 2704
2689 2705 for l in self:
2690 2706 if l == x:
2691 2707 return True
2692 2708 if l < x:
2693 2709 break
2694 2710
2695 2711 self._cache[x] = False
2696 2712 return False
2697 2713
2698 2714 class spanset(_orderedsetmixin):
2699 2715 """Duck type for baseset class which represents a range of revisions and
2700 2716 can work lazily and without having all the range in memory
2701 2717
2702 2718 Note that spanset(x, y) behave almost like xrange(x, y) except for two
2703 2719 notable points:
2704 2720 - when x < y it will be automatically descending,
2705 2721 - revision filtered with this repoview will be skipped.
2706 2722
2707 2723 """
2708 2724 def __init__(self, repo, start=0, end=None):
2709 2725 """
2710 2726 start: first revision included the set
2711 2727 (default to 0)
2712 2728 end: first revision excluded (last+1)
2713 2729 (default to len(repo)
2714 2730
2715 2731 Spanset will be descending if `end` < `start`.
2716 2732 """
2717 2733 self._start = start
2718 2734 if end is not None:
2719 2735 self._end = end
2720 2736 else:
2721 2737 self._end = len(repo)
2722 2738 self._hiddenrevs = repo.changelog.filteredrevs
2723 2739
2724 2740 def ascending(self):
2725 2741 if self._start > self._end:
2726 2742 self.reverse()
2727 2743
2728 2744 def descending(self):
2729 2745 if self._start < self._end:
2730 2746 self.reverse()
2731 2747
2732 2748 def _contained(self, rev):
2733 2749 return (rev <= self._start and rev > self._end) or (rev >= self._start
2734 2750 and rev < self._end)
2735 2751
2736 2752 def __iter__(self):
2737 2753 if self._start <= self._end:
2738 2754 iterrange = xrange(self._start, self._end)
2739 2755 else:
2740 2756 iterrange = xrange(self._start, self._end, -1)
2741 2757
2742 2758 if self._hiddenrevs:
2743 2759 s = self._hiddenrevs
2744 2760 for r in iterrange:
2745 2761 if r not in s:
2746 2762 yield r
2747 2763 else:
2748 2764 for r in iterrange:
2749 2765 yield r
2750 2766
2751 2767 def __contains__(self, x):
2752 2768 return self._contained(x) and not (self._hiddenrevs and rev in
2753 2769 self._hiddenrevs)
2754 2770
2755 2771 def __nonzero__(self):
2756 2772 for r in self:
2757 2773 return True
2758 2774 return False
2759 2775
2760 2776 def __and__(self, x):
2761 2777 if isinstance(x, baseset):
2762 2778 x = x.set()
2763 2779 if self._start <= self._end:
2764 2780 return orderedlazyset(self, lambda r: r in x)
2765 2781 else:
2766 2782 return orderedlazyset(self, lambda r: r in x, ascending=False)
2767 2783
2768 2784 def __sub__(self, x):
2769 2785 if isinstance(x, baseset):
2770 2786 x = x.set()
2771 2787 if self._start <= self._end:
2772 2788 return orderedlazyset(self, lambda r: r not in x)
2773 2789 else:
2774 2790 return orderedlazyset(self, lambda r: r not in x, ascending=False)
2775 2791
2776 2792 def __add__(self, x):
2777 2793 kwargs = {}
2778 2794 if self.isascending() and x.isascending():
2779 2795 kwargs['ascending'] = True
2780 2796 if self.isdescending() and x.isdescending():
2781 2797 kwargs['ascending'] = False
2782 2798 return _addset(self, x, **kwargs)
2783 2799
2784 2800 def __len__(self):
2785 2801 if not self._hiddenrevs:
2786 2802 return abs(self._end - self._start)
2787 2803 else:
2788 2804 count = 0
2789 2805 for rev in self._hiddenrevs:
2790 2806 if self._contained(rev):
2791 2807 count += 1
2792 2808 return abs(self._end - self._start) - count
2793 2809
2794 2810 def __getitem__(self, x):
2795 2811 # Basic implementation to be changed in future patches.
2796 2812 l = baseset([r for r in self])
2797 2813 return l[x]
2798 2814
2799 2815 def sort(self, reverse=False):
2800 2816 if bool(reverse) != (self._start > self._end):
2801 2817 self.reverse()
2802 2818
2803 2819 def reverse(self):
2804 2820 # Just switch the _start and _end parameters
2805 2821 if self._start <= self._end:
2806 2822 self._start, self._end = self._end - 1, self._start - 1
2807 2823 else:
2808 2824 self._start, self._end = self._end + 1, self._start + 1
2809 2825
2810 2826 def set(self):
2811 2827 return self
2812 2828
2813 2829 def isascending(self):
2814 2830 return self._start < self._end
2815 2831
2816 2832 def isdescending(self):
2817 2833 return self._start > self._end
2818 2834
2819 2835 def filter(self, l):
2820 2836 if self._start <= self._end:
2821 2837 return orderedlazyset(self, l)
2822 2838 else:
2823 2839 return orderedlazyset(self, l, ascending=False)
2824 2840
2825 2841 # tell hggettext to extract docstrings from these functions:
2826 2842 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now