##// END OF EJS Templates
revset: mark the place where we are combining sets in the wrong direction...
Pierre-Yves David -
r25548:9584bcf2 default
parent child Browse files
Show More
@@ -1,3573 +1,3582 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, hbisect, phases
10 10 import node
11 11 import heapq
12 12 import match as matchmod
13 13 from i18n import _
14 14 import encoding
15 15 import obsolete as obsmod
16 16 import pathutil
17 17 import repoview
18 18
19 19 def _revancestors(repo, revs, followfirst):
20 20 """Like revlog.ancestors(), but supports followfirst."""
21 21 if followfirst:
22 22 cut = 1
23 23 else:
24 24 cut = None
25 25 cl = repo.changelog
26 26
27 27 def iterate():
28 28 revs.sort(reverse=True)
29 29 irevs = iter(revs)
30 30 h = []
31 31
32 32 inputrev = next(irevs, None)
33 33 if inputrev is not None:
34 34 heapq.heappush(h, -inputrev)
35 35
36 36 seen = set()
37 37 while h:
38 38 current = -heapq.heappop(h)
39 39 if current == inputrev:
40 40 inputrev = next(irevs, None)
41 41 if inputrev is not None:
42 42 heapq.heappush(h, -inputrev)
43 43 if current not in seen:
44 44 seen.add(current)
45 45 yield current
46 46 for parent in cl.parentrevs(current)[:cut]:
47 47 if parent != node.nullrev:
48 48 heapq.heappush(h, -parent)
49 49
50 50 return generatorset(iterate(), iterasc=False)
51 51
52 52 def _revdescendants(repo, revs, followfirst):
53 53 """Like revlog.descendants() but supports followfirst."""
54 54 if followfirst:
55 55 cut = 1
56 56 else:
57 57 cut = None
58 58
59 59 def iterate():
60 60 cl = repo.changelog
61 61 first = min(revs)
62 62 nullrev = node.nullrev
63 63 if first == nullrev:
64 64 # Are there nodes with a null first parent and a non-null
65 65 # second one? Maybe. Do we care? Probably not.
66 66 for i in cl:
67 67 yield i
68 68 else:
69 69 seen = set(revs)
70 70 for i in cl.revs(first + 1):
71 71 for x in cl.parentrevs(i)[:cut]:
72 72 if x != nullrev and x in seen:
73 73 seen.add(i)
74 74 yield i
75 75 break
76 76
77 77 return generatorset(iterate(), iterasc=True)
78 78
79 79 def _revsbetween(repo, roots, heads):
80 80 """Return all paths between roots and heads, inclusive of both endpoint
81 81 sets."""
82 82 if not roots:
83 83 return baseset()
84 84 parentrevs = repo.changelog.parentrevs
85 85 visit = list(heads)
86 86 reachable = set()
87 87 seen = {}
88 88 minroot = min(roots)
89 89 roots = set(roots)
90 90 # open-code the post-order traversal due to the tiny size of
91 91 # sys.getrecursionlimit()
92 92 while visit:
93 93 rev = visit.pop()
94 94 if rev in roots:
95 95 reachable.add(rev)
96 96 parents = parentrevs(rev)
97 97 seen[rev] = parents
98 98 for parent in parents:
99 99 if parent >= minroot and parent not in seen:
100 100 visit.append(parent)
101 101 if not reachable:
102 102 return baseset()
103 103 for rev in sorted(seen):
104 104 for parent in seen[rev]:
105 105 if parent in reachable:
106 106 reachable.add(rev)
107 107 return baseset(sorted(reachable))
108 108
109 109 elements = {
110 110 "(": (21, ("group", 1, ")"), ("func", 1, ")")),
111 111 "##": (20, None, ("_concat", 20)),
112 112 "~": (18, None, ("ancestor", 18)),
113 113 "^": (18, None, ("parent", 18), ("parentpost", 18)),
114 114 "-": (5, ("negate", 19), ("minus", 5)),
115 115 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
116 116 ("dagrangepost", 17)),
117 117 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
118 118 ("dagrangepost", 17)),
119 119 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
120 120 "not": (10, ("not", 10)),
121 121 "!": (10, ("not", 10)),
122 122 "and": (5, None, ("and", 5)),
123 123 "&": (5, None, ("and", 5)),
124 124 "%": (5, None, ("only", 5), ("onlypost", 5)),
125 125 "or": (4, None, ("or", 4)),
126 126 "|": (4, None, ("or", 4)),
127 127 "+": (4, None, ("or", 4)),
128 128 ",": (2, None, ("list", 2)),
129 129 ")": (0, None, None),
130 130 "symbol": (0, ("symbol",), None),
131 131 "string": (0, ("string",), None),
132 132 "end": (0, None, None),
133 133 }
134 134
135 135 keywords = set(['and', 'or', 'not'])
136 136
137 137 # default set of valid characters for the initial letter of symbols
138 138 _syminitletters = set(c for c in [chr(i) for i in xrange(256)]
139 139 if c.isalnum() or c in '._@' or ord(c) > 127)
140 140
141 141 # default set of valid characters for non-initial letters of symbols
142 142 _symletters = set(c for c in [chr(i) for i in xrange(256)]
143 143 if c.isalnum() or c in '-._/@' or ord(c) > 127)
144 144
145 145 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
146 146 '''
147 147 Parse a revset statement into a stream of tokens
148 148
149 149 ``syminitletters`` is the set of valid characters for the initial
150 150 letter of symbols.
151 151
152 152 By default, character ``c`` is recognized as valid for initial
153 153 letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
154 154
155 155 ``symletters`` is the set of valid characters for non-initial
156 156 letters of symbols.
157 157
158 158 By default, character ``c`` is recognized as valid for non-initial
159 159 letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
160 160
161 161 Check that @ is a valid unquoted token character (issue3686):
162 162 >>> list(tokenize("@::"))
163 163 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
164 164
165 165 '''
166 166 if syminitletters is None:
167 167 syminitletters = _syminitletters
168 168 if symletters is None:
169 169 symletters = _symletters
170 170
171 171 pos, l = 0, len(program)
172 172 while pos < l:
173 173 c = program[pos]
174 174 if c.isspace(): # skip inter-token whitespace
175 175 pass
176 176 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
177 177 yield ('::', None, pos)
178 178 pos += 1 # skip ahead
179 179 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
180 180 yield ('..', None, pos)
181 181 pos += 1 # skip ahead
182 182 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
183 183 yield ('##', None, pos)
184 184 pos += 1 # skip ahead
185 185 elif c in "():,-|&+!~^%": # handle simple operators
186 186 yield (c, None, pos)
187 187 elif (c in '"\'' or c == 'r' and
188 188 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
189 189 if c == 'r':
190 190 pos += 1
191 191 c = program[pos]
192 192 decode = lambda x: x
193 193 else:
194 194 decode = lambda x: x.decode('string-escape')
195 195 pos += 1
196 196 s = pos
197 197 while pos < l: # find closing quote
198 198 d = program[pos]
199 199 if d == '\\': # skip over escaped characters
200 200 pos += 2
201 201 continue
202 202 if d == c:
203 203 yield ('string', decode(program[s:pos]), s)
204 204 break
205 205 pos += 1
206 206 else:
207 207 raise error.ParseError(_("unterminated string"), s)
208 208 # gather up a symbol/keyword
209 209 elif c in syminitletters:
210 210 s = pos
211 211 pos += 1
212 212 while pos < l: # find end of symbol
213 213 d = program[pos]
214 214 if d not in symletters:
215 215 break
216 216 if d == '.' and program[pos - 1] == '.': # special case for ..
217 217 pos -= 1
218 218 break
219 219 pos += 1
220 220 sym = program[s:pos]
221 221 if sym in keywords: # operator keywords
222 222 yield (sym, None, s)
223 223 elif '-' in sym:
224 224 # some jerk gave us foo-bar-baz, try to check if it's a symbol
225 225 if lookup and lookup(sym):
226 226 # looks like a real symbol
227 227 yield ('symbol', sym, s)
228 228 else:
229 229 # looks like an expression
230 230 parts = sym.split('-')
231 231 for p in parts[:-1]:
232 232 if p: # possible consecutive -
233 233 yield ('symbol', p, s)
234 234 s += len(p)
235 235 yield ('-', None, pos)
236 236 s += 1
237 237 if parts[-1]: # possible trailing -
238 238 yield ('symbol', parts[-1], s)
239 239 else:
240 240 yield ('symbol', sym, s)
241 241 pos -= 1
242 242 else:
243 243 raise error.ParseError(_("syntax error in revset '%s'") %
244 244 program, pos)
245 245 pos += 1
246 246 yield ('end', None, pos)
247 247
248 248 def parseerrordetail(inst):
249 249 """Compose error message from specified ParseError object
250 250 """
251 251 if len(inst.args) > 1:
252 252 return _('at %s: %s') % (inst.args[1], inst.args[0])
253 253 else:
254 254 return inst.args[0]
255 255
256 256 # helpers
257 257
258 258 def getstring(x, err):
259 259 if x and (x[0] == 'string' or x[0] == 'symbol'):
260 260 return x[1]
261 261 raise error.ParseError(err)
262 262
263 263 def getlist(x):
264 264 if not x:
265 265 return []
266 266 if x[0] == 'list':
267 267 return getlist(x[1]) + [x[2]]
268 268 return [x]
269 269
270 270 def getargs(x, min, max, err):
271 271 l = getlist(x)
272 272 if len(l) < min or (max >= 0 and len(l) > max):
273 273 raise error.ParseError(err)
274 274 return l
275 275
276 276 def isvalidsymbol(tree):
277 277 """Examine whether specified ``tree`` is valid ``symbol`` or not
278 278 """
279 279 return tree[0] == 'symbol' and len(tree) > 1
280 280
281 281 def getsymbol(tree):
282 282 """Get symbol name from valid ``symbol`` in ``tree``
283 283
284 284 This assumes that ``tree`` is already examined by ``isvalidsymbol``.
285 285 """
286 286 return tree[1]
287 287
288 288 def isvalidfunc(tree):
289 289 """Examine whether specified ``tree`` is valid ``func`` or not
290 290 """
291 291 return tree[0] == 'func' and len(tree) > 1 and isvalidsymbol(tree[1])
292 292
293 293 def getfuncname(tree):
294 294 """Get function name from valid ``func`` in ``tree``
295 295
296 296 This assumes that ``tree`` is already examined by ``isvalidfunc``.
297 297 """
298 298 return getsymbol(tree[1])
299 299
300 300 def getfuncargs(tree):
301 301 """Get list of function arguments from valid ``func`` in ``tree``
302 302
303 303 This assumes that ``tree`` is already examined by ``isvalidfunc``.
304 304 """
305 305 if len(tree) > 2:
306 306 return getlist(tree[2])
307 307 else:
308 308 return []
309 309
310 310 def getset(repo, subset, x):
311 311 if not x:
312 312 raise error.ParseError(_("missing argument"))
313 313 s = methods[x[0]](repo, subset, *x[1:])
314 314 if util.safehasattr(s, 'isascending'):
315 315 return s
316 316 return baseset(s)
317 317
318 318 def _getrevsource(repo, r):
319 319 extra = repo[r].extra()
320 320 for label in ('source', 'transplant_source', 'rebase_source'):
321 321 if label in extra:
322 322 try:
323 323 return repo[extra[label]].rev()
324 324 except error.RepoLookupError:
325 325 pass
326 326 return None
327 327
328 328 # operator methods
329 329
330 330 def stringset(repo, subset, x):
331 331 x = repo[x].rev()
332 332 if (x in subset
333 333 or x == node.nullrev and isinstance(subset, fullreposet)):
334 334 return baseset([x])
335 335 return baseset()
336 336
337 337 def rangeset(repo, subset, x, y):
338 338 m = getset(repo, fullreposet(repo), x)
339 339 n = getset(repo, fullreposet(repo), y)
340 340
341 341 if not m or not n:
342 342 return baseset()
343 343 m, n = m.first(), n.last()
344 344
345 345 if m < n:
346 346 r = spanset(repo, m, n + 1)
347 347 else:
348 348 r = spanset(repo, m, n - 1)
349 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
350 # necessary to ensure we preserve the order in subset.
351 #
352 # This has performance implication, carrying the sorting over when possible
353 # would be more efficient.
349 354 return r & subset
350 355
351 356 def dagrange(repo, subset, x, y):
352 357 r = fullreposet(repo)
353 358 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
359 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
360 # necessary to ensure we preserve the order in subset.
354 361 return xs & subset
355 362
356 363 def andset(repo, subset, x, y):
357 364 return getset(repo, getset(repo, subset, x), y)
358 365
359 366 def orset(repo, subset, *xs):
360 367 rs = [getset(repo, subset, x) for x in xs]
361 368 return _combinesets(rs)
362 369
363 370 def notset(repo, subset, x):
364 371 return subset - getset(repo, subset, x)
365 372
366 373 def listset(repo, subset, a, b):
367 374 raise error.ParseError(_("can't use a list in this context"))
368 375
369 376 def func(repo, subset, a, b):
370 377 if a[0] == 'symbol' and a[1] in symbols:
371 378 return symbols[a[1]](repo, subset, b)
372 379 raise error.UnknownIdentifier(a[1], symbols.keys())
373 380
374 381 # functions
375 382
376 383 def adds(repo, subset, x):
377 384 """``adds(pattern)``
378 385 Changesets that add a file matching pattern.
379 386
380 387 The pattern without explicit kind like ``glob:`` is expected to be
381 388 relative to the current directory and match against a file or a
382 389 directory.
383 390 """
384 391 # i18n: "adds" is a keyword
385 392 pat = getstring(x, _("adds requires a pattern"))
386 393 return checkstatus(repo, subset, pat, 1)
387 394
388 395 def ancestor(repo, subset, x):
389 396 """``ancestor(*changeset)``
390 397 A greatest common ancestor of the changesets.
391 398
392 399 Accepts 0 or more changesets.
393 400 Will return empty list when passed no args.
394 401 Greatest common ancestor of a single changeset is that changeset.
395 402 """
396 403 # i18n: "ancestor" is a keyword
397 404 l = getlist(x)
398 405 rl = fullreposet(repo)
399 406 anc = None
400 407
401 408 # (getset(repo, rl, i) for i in l) generates a list of lists
402 409 for revs in (getset(repo, rl, i) for i in l):
403 410 for r in revs:
404 411 if anc is None:
405 412 anc = repo[r]
406 413 else:
407 414 anc = anc.ancestor(repo[r])
408 415
409 416 if anc is not None and anc.rev() in subset:
410 417 return baseset([anc.rev()])
411 418 return baseset()
412 419
413 420 def _ancestors(repo, subset, x, followfirst=False):
414 421 heads = getset(repo, fullreposet(repo), x)
415 422 if not heads:
416 423 return baseset()
417 424 s = _revancestors(repo, heads, followfirst)
418 425 return subset & s
419 426
420 427 def ancestors(repo, subset, x):
421 428 """``ancestors(set)``
422 429 Changesets that are ancestors of a changeset in set.
423 430 """
424 431 return _ancestors(repo, subset, x)
425 432
426 433 def _firstancestors(repo, subset, x):
427 434 # ``_firstancestors(set)``
428 435 # Like ``ancestors(set)`` but follows only the first parents.
429 436 return _ancestors(repo, subset, x, followfirst=True)
430 437
431 438 def ancestorspec(repo, subset, x, n):
432 439 """``set~n``
433 440 Changesets that are the Nth ancestor (first parents only) of a changeset
434 441 in set.
435 442 """
436 443 try:
437 444 n = int(n[1])
438 445 except (TypeError, ValueError):
439 446 raise error.ParseError(_("~ expects a number"))
440 447 ps = set()
441 448 cl = repo.changelog
442 449 for r in getset(repo, fullreposet(repo), x):
443 450 for i in range(n):
444 451 r = cl.parentrevs(r)[0]
445 452 ps.add(r)
446 453 return subset & ps
447 454
448 455 def author(repo, subset, x):
449 456 """``author(string)``
450 457 Alias for ``user(string)``.
451 458 """
452 459 # i18n: "author" is a keyword
453 460 n = encoding.lower(getstring(x, _("author requires a string")))
454 461 kind, pattern, matcher = _substringmatcher(n)
455 462 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
456 463
457 464 def bisect(repo, subset, x):
458 465 """``bisect(string)``
459 466 Changesets marked in the specified bisect status:
460 467
461 468 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
462 469 - ``goods``, ``bads`` : csets topologically good/bad
463 470 - ``range`` : csets taking part in the bisection
464 471 - ``pruned`` : csets that are goods, bads or skipped
465 472 - ``untested`` : csets whose fate is yet unknown
466 473 - ``ignored`` : csets ignored due to DAG topology
467 474 - ``current`` : the cset currently being bisected
468 475 """
469 476 # i18n: "bisect" is a keyword
470 477 status = getstring(x, _("bisect requires a string")).lower()
471 478 state = set(hbisect.get(repo, status))
472 479 return subset & state
473 480
474 481 # Backward-compatibility
475 482 # - no help entry so that we do not advertise it any more
476 483 def bisected(repo, subset, x):
477 484 return bisect(repo, subset, x)
478 485
479 486 def bookmark(repo, subset, x):
480 487 """``bookmark([name])``
481 488 The named bookmark or all bookmarks.
482 489
483 490 If `name` starts with `re:`, the remainder of the name is treated as
484 491 a regular expression. To match a bookmark that actually starts with `re:`,
485 492 use the prefix `literal:`.
486 493 """
487 494 # i18n: "bookmark" is a keyword
488 495 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
489 496 if args:
490 497 bm = getstring(args[0],
491 498 # i18n: "bookmark" is a keyword
492 499 _('the argument to bookmark must be a string'))
493 500 kind, pattern, matcher = _stringmatcher(bm)
494 501 bms = set()
495 502 if kind == 'literal':
496 503 bmrev = repo._bookmarks.get(pattern, None)
497 504 if not bmrev:
498 505 raise error.RepoLookupError(_("bookmark '%s' does not exist")
499 506 % bm)
500 507 bms.add(repo[bmrev].rev())
501 508 else:
502 509 matchrevs = set()
503 510 for name, bmrev in repo._bookmarks.iteritems():
504 511 if matcher(name):
505 512 matchrevs.add(bmrev)
506 513 if not matchrevs:
507 514 raise error.RepoLookupError(_("no bookmarks exist"
508 515 " that match '%s'") % pattern)
509 516 for bmrev in matchrevs:
510 517 bms.add(repo[bmrev].rev())
511 518 else:
512 519 bms = set([repo[r].rev()
513 520 for r in repo._bookmarks.values()])
514 521 bms -= set([node.nullrev])
515 522 return subset & bms
516 523
517 524 def branch(repo, subset, x):
518 525 """``branch(string or set)``
519 526 All changesets belonging to the given branch or the branches of the given
520 527 changesets.
521 528
522 529 If `string` starts with `re:`, the remainder of the name is treated as
523 530 a regular expression. To match a branch that actually starts with `re:`,
524 531 use the prefix `literal:`.
525 532 """
526 533 getbi = repo.revbranchcache().branchinfo
527 534
528 535 try:
529 536 b = getstring(x, '')
530 537 except error.ParseError:
531 538 # not a string, but another revspec, e.g. tip()
532 539 pass
533 540 else:
534 541 kind, pattern, matcher = _stringmatcher(b)
535 542 if kind == 'literal':
536 543 # note: falls through to the revspec case if no branch with
537 544 # this name exists
538 545 if pattern in repo.branchmap():
539 546 return subset.filter(lambda r: matcher(getbi(r)[0]))
540 547 else:
541 548 return subset.filter(lambda r: matcher(getbi(r)[0]))
542 549
543 550 s = getset(repo, fullreposet(repo), x)
544 551 b = set()
545 552 for r in s:
546 553 b.add(getbi(r)[0])
547 554 c = s.__contains__
548 555 return subset.filter(lambda r: c(r) or getbi(r)[0] in b)
549 556
550 557 def bumped(repo, subset, x):
551 558 """``bumped()``
552 559 Mutable changesets marked as successors of public changesets.
553 560
554 561 Only non-public and non-obsolete changesets can be `bumped`.
555 562 """
556 563 # i18n: "bumped" is a keyword
557 564 getargs(x, 0, 0, _("bumped takes no arguments"))
558 565 bumped = obsmod.getrevs(repo, 'bumped')
559 566 return subset & bumped
560 567
561 568 def bundle(repo, subset, x):
562 569 """``bundle()``
563 570 Changesets in the bundle.
564 571
565 572 Bundle must be specified by the -R option."""
566 573
567 574 try:
568 575 bundlerevs = repo.changelog.bundlerevs
569 576 except AttributeError:
570 577 raise util.Abort(_("no bundle provided - specify with -R"))
571 578 return subset & bundlerevs
572 579
573 580 def checkstatus(repo, subset, pat, field):
574 581 hasset = matchmod.patkind(pat) == 'set'
575 582
576 583 mcache = [None]
577 584 def matches(x):
578 585 c = repo[x]
579 586 if not mcache[0] or hasset:
580 587 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
581 588 m = mcache[0]
582 589 fname = None
583 590 if not m.anypats() and len(m.files()) == 1:
584 591 fname = m.files()[0]
585 592 if fname is not None:
586 593 if fname not in c.files():
587 594 return False
588 595 else:
589 596 for f in c.files():
590 597 if m(f):
591 598 break
592 599 else:
593 600 return False
594 601 files = repo.status(c.p1().node(), c.node())[field]
595 602 if fname is not None:
596 603 if fname in files:
597 604 return True
598 605 else:
599 606 for f in files:
600 607 if m(f):
601 608 return True
602 609
603 610 return subset.filter(matches)
604 611
605 612 def _children(repo, narrow, parentset):
606 613 cs = set()
607 614 if not parentset:
608 615 return baseset(cs)
609 616 pr = repo.changelog.parentrevs
610 617 minrev = min(parentset)
611 618 for r in narrow:
612 619 if r <= minrev:
613 620 continue
614 621 for p in pr(r):
615 622 if p in parentset:
616 623 cs.add(r)
617 624 return baseset(cs)
618 625
619 626 def children(repo, subset, x):
620 627 """``children(set)``
621 628 Child changesets of changesets in set.
622 629 """
623 630 s = getset(repo, fullreposet(repo), x)
624 631 cs = _children(repo, subset, s)
625 632 return subset & cs
626 633
627 634 def closed(repo, subset, x):
628 635 """``closed()``
629 636 Changeset is closed.
630 637 """
631 638 # i18n: "closed" is a keyword
632 639 getargs(x, 0, 0, _("closed takes no arguments"))
633 640 return subset.filter(lambda r: repo[r].closesbranch())
634 641
635 642 def contains(repo, subset, x):
636 643 """``contains(pattern)``
637 644 The revision's manifest contains a file matching pattern (but might not
638 645 modify it). See :hg:`help patterns` for information about file patterns.
639 646
640 647 The pattern without explicit kind like ``glob:`` is expected to be
641 648 relative to the current directory and match against a file exactly
642 649 for efficiency.
643 650 """
644 651 # i18n: "contains" is a keyword
645 652 pat = getstring(x, _("contains requires a pattern"))
646 653
647 654 def matches(x):
648 655 if not matchmod.patkind(pat):
649 656 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
650 657 if pats in repo[x]:
651 658 return True
652 659 else:
653 660 c = repo[x]
654 661 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
655 662 for f in c.manifest():
656 663 if m(f):
657 664 return True
658 665 return False
659 666
660 667 return subset.filter(matches)
661 668
662 669 def converted(repo, subset, x):
663 670 """``converted([id])``
664 671 Changesets converted from the given identifier in the old repository if
665 672 present, or all converted changesets if no identifier is specified.
666 673 """
667 674
668 675 # There is exactly no chance of resolving the revision, so do a simple
669 676 # string compare and hope for the best
670 677
671 678 rev = None
672 679 # i18n: "converted" is a keyword
673 680 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
674 681 if l:
675 682 # i18n: "converted" is a keyword
676 683 rev = getstring(l[0], _('converted requires a revision'))
677 684
678 685 def _matchvalue(r):
679 686 source = repo[r].extra().get('convert_revision', None)
680 687 return source is not None and (rev is None or source.startswith(rev))
681 688
682 689 return subset.filter(lambda r: _matchvalue(r))
683 690
684 691 def date(repo, subset, x):
685 692 """``date(interval)``
686 693 Changesets within the interval, see :hg:`help dates`.
687 694 """
688 695 # i18n: "date" is a keyword
689 696 ds = getstring(x, _("date requires a string"))
690 697 dm = util.matchdate(ds)
691 698 return subset.filter(lambda x: dm(repo[x].date()[0]))
692 699
693 700 def desc(repo, subset, x):
694 701 """``desc(string)``
695 702 Search commit message for string. The match is case-insensitive.
696 703 """
697 704 # i18n: "desc" is a keyword
698 705 ds = encoding.lower(getstring(x, _("desc requires a string")))
699 706
700 707 def matches(x):
701 708 c = repo[x]
702 709 return ds in encoding.lower(c.description())
703 710
704 711 return subset.filter(matches)
705 712
706 713 def _descendants(repo, subset, x, followfirst=False):
707 714 roots = getset(repo, fullreposet(repo), x)
708 715 if not roots:
709 716 return baseset()
710 717 s = _revdescendants(repo, roots, followfirst)
711 718
712 719 # Both sets need to be ascending in order to lazily return the union
713 720 # in the correct order.
714 721 base = subset & roots
715 722 desc = subset & s
716 723 result = base + desc
717 724 if subset.isascending():
718 725 result.sort()
719 726 elif subset.isdescending():
720 727 result.sort(reverse=True)
721 728 else:
722 729 result = subset & result
723 730 return result
724 731
725 732 def descendants(repo, subset, x):
726 733 """``descendants(set)``
727 734 Changesets which are descendants of changesets in set.
728 735 """
729 736 return _descendants(repo, subset, x)
730 737
731 738 def _firstdescendants(repo, subset, x):
732 739 # ``_firstdescendants(set)``
733 740 # Like ``descendants(set)`` but follows only the first parents.
734 741 return _descendants(repo, subset, x, followfirst=True)
735 742
736 743 def destination(repo, subset, x):
737 744 """``destination([set])``
738 745 Changesets that were created by a graft, transplant or rebase operation,
739 746 with the given revisions specified as the source. Omitting the optional set
740 747 is the same as passing all().
741 748 """
742 749 if x is not None:
743 750 sources = getset(repo, fullreposet(repo), x)
744 751 else:
745 752 sources = fullreposet(repo)
746 753
747 754 dests = set()
748 755
749 756 # subset contains all of the possible destinations that can be returned, so
750 757 # iterate over them and see if their source(s) were provided in the arg set.
751 758 # Even if the immediate src of r is not in the arg set, src's source (or
752 759 # further back) may be. Scanning back further than the immediate src allows
753 760 # transitive transplants and rebases to yield the same results as transitive
754 761 # grafts.
755 762 for r in subset:
756 763 src = _getrevsource(repo, r)
757 764 lineage = None
758 765
759 766 while src is not None:
760 767 if lineage is None:
761 768 lineage = list()
762 769
763 770 lineage.append(r)
764 771
765 772 # The visited lineage is a match if the current source is in the arg
766 773 # set. Since every candidate dest is visited by way of iterating
767 774 # subset, any dests further back in the lineage will be tested by a
768 775 # different iteration over subset. Likewise, if the src was already
769 776 # selected, the current lineage can be selected without going back
770 777 # further.
771 778 if src in sources or src in dests:
772 779 dests.update(lineage)
773 780 break
774 781
775 782 r = src
776 783 src = _getrevsource(repo, r)
777 784
778 785 return subset.filter(dests.__contains__)
779 786
780 787 def divergent(repo, subset, x):
781 788 """``divergent()``
782 789 Final successors of changesets with an alternative set of final successors.
783 790 """
784 791 # i18n: "divergent" is a keyword
785 792 getargs(x, 0, 0, _("divergent takes no arguments"))
786 793 divergent = obsmod.getrevs(repo, 'divergent')
787 794 return subset & divergent
788 795
789 796 def draft(repo, subset, x):
790 797 """``draft()``
791 798 Changeset in draft phase."""
792 799 # i18n: "draft" is a keyword
793 800 getargs(x, 0, 0, _("draft takes no arguments"))
794 801 phase = repo._phasecache.phase
795 802 target = phases.draft
796 803 condition = lambda r: phase(repo, r) == target
797 804 return subset.filter(condition, cache=False)
798 805
799 806 def extinct(repo, subset, x):
800 807 """``extinct()``
801 808 Obsolete changesets with obsolete descendants only.
802 809 """
803 810 # i18n: "extinct" is a keyword
804 811 getargs(x, 0, 0, _("extinct takes no arguments"))
805 812 extincts = obsmod.getrevs(repo, 'extinct')
806 813 return subset & extincts
807 814
808 815 def extra(repo, subset, x):
809 816 """``extra(label, [value])``
810 817 Changesets with the given label in the extra metadata, with the given
811 818 optional value.
812 819
813 820 If `value` starts with `re:`, the remainder of the value is treated as
814 821 a regular expression. To match a value that actually starts with `re:`,
815 822 use the prefix `literal:`.
816 823 """
817 824
818 825 # i18n: "extra" is a keyword
819 826 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
820 827 # i18n: "extra" is a keyword
821 828 label = getstring(l[0], _('first argument to extra must be a string'))
822 829 value = None
823 830
824 831 if len(l) > 1:
825 832 # i18n: "extra" is a keyword
826 833 value = getstring(l[1], _('second argument to extra must be a string'))
827 834 kind, value, matcher = _stringmatcher(value)
828 835
829 836 def _matchvalue(r):
830 837 extra = repo[r].extra()
831 838 return label in extra and (value is None or matcher(extra[label]))
832 839
833 840 return subset.filter(lambda r: _matchvalue(r))
834 841
835 842 def filelog(repo, subset, x):
836 843 """``filelog(pattern)``
837 844 Changesets connected to the specified filelog.
838 845
839 846 For performance reasons, visits only revisions mentioned in the file-level
840 847 filelog, rather than filtering through all changesets (much faster, but
841 848 doesn't include deletes or duplicate changes). For a slower, more accurate
842 849 result, use ``file()``.
843 850
844 851 The pattern without explicit kind like ``glob:`` is expected to be
845 852 relative to the current directory and match against a file exactly
846 853 for efficiency.
847 854
848 855 If some linkrev points to revisions filtered by the current repoview, we'll
849 856 work around it to return a non-filtered value.
850 857 """
851 858
852 859 # i18n: "filelog" is a keyword
853 860 pat = getstring(x, _("filelog requires a pattern"))
854 861 s = set()
855 862 cl = repo.changelog
856 863
857 864 if not matchmod.patkind(pat):
858 865 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
859 866 files = [f]
860 867 else:
861 868 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
862 869 files = (f for f in repo[None] if m(f))
863 870
864 871 for f in files:
865 872 backrevref = {} # final value for: filerev -> changerev
866 873 lowestchild = {} # lowest known filerev child of a filerev
867 874 delayed = [] # filerev with filtered linkrev, for post-processing
868 875 lowesthead = None # cache for manifest content of all head revisions
869 876 fl = repo.file(f)
870 877 for fr in list(fl):
871 878 rev = fl.linkrev(fr)
872 879 if rev not in cl:
873 880 # changerev pointed in linkrev is filtered
874 881 # record it for post processing.
875 882 delayed.append((fr, rev))
876 883 continue
877 884 for p in fl.parentrevs(fr):
878 885 if 0 <= p and p not in lowestchild:
879 886 lowestchild[p] = fr
880 887 backrevref[fr] = rev
881 888 s.add(rev)
882 889
883 890 # Post-processing of all filerevs we skipped because they were
884 891 # filtered. If such filerevs have known and unfiltered children, this
885 892 # means they have an unfiltered appearance out there. We'll use linkrev
886 893 # adjustment to find one of these appearances. The lowest known child
887 894 # will be used as a starting point because it is the best upper-bound we
888 895 # have.
889 896 #
890 897 # This approach will fail when an unfiltered but linkrev-shadowed
891 898 # appearance exists in a head changeset without unfiltered filerev
892 899 # children anywhere.
893 900 while delayed:
894 901 # must be a descending iteration. To slowly fill lowest child
895 902 # information that is of potential use by the next item.
896 903 fr, rev = delayed.pop()
897 904 lkr = rev
898 905
899 906 child = lowestchild.get(fr)
900 907
901 908 if child is None:
902 909 # search for existence of this file revision in a head revision.
903 910 # There are three possibilities:
904 911 # - the revision exists in a head and we can find an
905 912 # introduction from there,
906 913 # - the revision does not exist in a head because it has been
907 914 # changed since its introduction: we would have found a child
908 915 # and be in the other 'else' clause,
909 916 # - all versions of the revision are hidden.
910 917 if lowesthead is None:
911 918 lowesthead = {}
912 919 for h in repo.heads():
913 920 fnode = repo[h].manifest().get(f)
914 921 if fnode is not None:
915 922 lowesthead[fl.rev(fnode)] = h
916 923 headrev = lowesthead.get(fr)
917 924 if headrev is None:
918 925 # content is nowhere unfiltered
919 926 continue
920 927 rev = repo[headrev][f].introrev()
921 928 else:
922 929 # the lowest known child is a good upper bound
923 930 childcrev = backrevref[child]
924 931 # XXX this does not guarantee returning the lowest
925 932 # introduction of this revision, but this gives a
926 933 # result which is a good start and will fit in most
927 934 # cases. We probably need to fix the multiple
928 935 # introductions case properly (report each
929 936 # introduction, even for identical file revisions)
930 937 # once and for all at some point anyway.
931 938 for p in repo[childcrev][f].parents():
932 939 if p.filerev() == fr:
933 940 rev = p.rev()
934 941 break
935 942 if rev == lkr: # no shadowed entry found
936 943 # XXX This should never happen unless some manifest points
937 944 # to biggish file revisions (like a revision that uses a
938 945 # parent that never appears in the manifest ancestors)
939 946 continue
940 947
941 948 # Fill the data for the next iteration.
942 949 for p in fl.parentrevs(fr):
943 950 if 0 <= p and p not in lowestchild:
944 951 lowestchild[p] = fr
945 952 backrevref[fr] = rev
946 953 s.add(rev)
947 954
948 955 return subset & s
949 956
950 957 def first(repo, subset, x):
951 958 """``first(set, [n])``
952 959 An alias for limit().
953 960 """
954 961 return limit(repo, subset, x)
955 962
956 963 def _follow(repo, subset, x, name, followfirst=False):
957 964 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
958 965 c = repo['.']
959 966 if l:
960 967 x = getstring(l[0], _("%s expected a filename") % name)
961 968 if x in c:
962 969 cx = c[x]
963 970 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
964 971 # include the revision responsible for the most recent version
965 972 s.add(cx.introrev())
966 973 else:
967 974 return baseset()
968 975 else:
969 976 s = _revancestors(repo, baseset([c.rev()]), followfirst)
970 977
971 978 return subset & s
972 979
973 980 def follow(repo, subset, x):
974 981 """``follow([file])``
975 982 An alias for ``::.`` (ancestors of the working directory's first parent).
976 983 If a filename is specified, the history of the given file is followed,
977 984 including copies.
978 985 """
979 986 return _follow(repo, subset, x, 'follow')
980 987
981 988 def _followfirst(repo, subset, x):
982 989 # ``followfirst([file])``
983 990 # Like ``follow([file])`` but follows only the first parent of
984 991 # every revision or file revision.
985 992 return _follow(repo, subset, x, '_followfirst', followfirst=True)
986 993
987 994 def getall(repo, subset, x):
988 995 """``all()``
989 996 All changesets, the same as ``0:tip``.
990 997 """
991 998 # i18n: "all" is a keyword
992 999 getargs(x, 0, 0, _("all takes no arguments"))
993 1000 return subset & spanset(repo) # drop "null" if any
994 1001
995 1002 def grep(repo, subset, x):
996 1003 """``grep(regex)``
997 1004 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
998 1005 to ensure special escape characters are handled correctly. Unlike
999 1006 ``keyword(string)``, the match is case-sensitive.
1000 1007 """
1001 1008 try:
1002 1009 # i18n: "grep" is a keyword
1003 1010 gr = re.compile(getstring(x, _("grep requires a string")))
1004 1011 except re.error, e:
1005 1012 raise error.ParseError(_('invalid match pattern: %s') % e)
1006 1013
1007 1014 def matches(x):
1008 1015 c = repo[x]
1009 1016 for e in c.files() + [c.user(), c.description()]:
1010 1017 if gr.search(e):
1011 1018 return True
1012 1019 return False
1013 1020
1014 1021 return subset.filter(matches)
1015 1022
1016 1023 def _matchfiles(repo, subset, x):
1017 1024 # _matchfiles takes a revset list of prefixed arguments:
1018 1025 #
1019 1026 # [p:foo, i:bar, x:baz]
1020 1027 #
1021 1028 # builds a match object from them and filters subset. Allowed
1022 1029 # prefixes are 'p:' for regular patterns, 'i:' for include
1023 1030 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1024 1031 # a revision identifier, or the empty string to reference the
1025 1032 # working directory, from which the match object is
1026 1033 # initialized. Use 'd:' to set the default matching mode, default
1027 1034 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1028 1035
1029 1036 # i18n: "_matchfiles" is a keyword
1030 1037 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
1031 1038 pats, inc, exc = [], [], []
1032 1039 rev, default = None, None
1033 1040 for arg in l:
1034 1041 # i18n: "_matchfiles" is a keyword
1035 1042 s = getstring(arg, _("_matchfiles requires string arguments"))
1036 1043 prefix, value = s[:2], s[2:]
1037 1044 if prefix == 'p:':
1038 1045 pats.append(value)
1039 1046 elif prefix == 'i:':
1040 1047 inc.append(value)
1041 1048 elif prefix == 'x:':
1042 1049 exc.append(value)
1043 1050 elif prefix == 'r:':
1044 1051 if rev is not None:
1045 1052 # i18n: "_matchfiles" is a keyword
1046 1053 raise error.ParseError(_('_matchfiles expected at most one '
1047 1054 'revision'))
1048 1055 if value != '': # empty means working directory; leave rev as None
1049 1056 rev = value
1050 1057 elif prefix == 'd:':
1051 1058 if default is not None:
1052 1059 # i18n: "_matchfiles" is a keyword
1053 1060 raise error.ParseError(_('_matchfiles expected at most one '
1054 1061 'default mode'))
1055 1062 default = value
1056 1063 else:
1057 1064 # i18n: "_matchfiles" is a keyword
1058 1065 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
1059 1066 if not default:
1060 1067 default = 'glob'
1061 1068
1062 1069 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
1063 1070 exclude=exc, ctx=repo[rev], default=default)
1064 1071
1065 1072 def matches(x):
1066 1073 for f in repo[x].files():
1067 1074 if m(f):
1068 1075 return True
1069 1076 return False
1070 1077
1071 1078 return subset.filter(matches)
1072 1079
1073 1080 def hasfile(repo, subset, x):
1074 1081 """``file(pattern)``
1075 1082 Changesets affecting files matched by pattern.
1076 1083
1077 1084 For a faster but less accurate result, consider using ``filelog()``
1078 1085 instead.
1079 1086
1080 1087 This predicate uses ``glob:`` as the default kind of pattern.
1081 1088 """
1082 1089 # i18n: "file" is a keyword
1083 1090 pat = getstring(x, _("file requires a pattern"))
1084 1091 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1085 1092
1086 1093 def head(repo, subset, x):
1087 1094 """``head()``
1088 1095 Changeset is a named branch head.
1089 1096 """
1090 1097 # i18n: "head" is a keyword
1091 1098 getargs(x, 0, 0, _("head takes no arguments"))
1092 1099 hs = set()
1093 1100 for b, ls in repo.branchmap().iteritems():
1094 1101 hs.update(repo[h].rev() for h in ls)
1102 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
1103 # necessary to ensure we preserve the order in subset.
1095 1104 return baseset(hs).filter(subset.__contains__)
1096 1105
1097 1106 def heads(repo, subset, x):
1098 1107 """``heads(set)``
1099 1108 Members of set with no children in set.
1100 1109 """
1101 1110 s = getset(repo, subset, x)
1102 1111 ps = parents(repo, subset, x)
1103 1112 return s - ps
1104 1113
1105 1114 def hidden(repo, subset, x):
1106 1115 """``hidden()``
1107 1116 Hidden changesets.
1108 1117 """
1109 1118 # i18n: "hidden" is a keyword
1110 1119 getargs(x, 0, 0, _("hidden takes no arguments"))
1111 1120 hiddenrevs = repoview.filterrevs(repo, 'visible')
1112 1121 return subset & hiddenrevs
1113 1122
1114 1123 def keyword(repo, subset, x):
1115 1124 """``keyword(string)``
1116 1125 Search commit message, user name, and names of changed files for
1117 1126 string. The match is case-insensitive.
1118 1127 """
1119 1128 # i18n: "keyword" is a keyword
1120 1129 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1121 1130
1122 1131 def matches(r):
1123 1132 c = repo[r]
1124 1133 return any(kw in encoding.lower(t) for t in c.files() + [c.user(),
1125 1134 c.description()])
1126 1135
1127 1136 return subset.filter(matches)
1128 1137
1129 1138 def limit(repo, subset, x):
1130 1139 """``limit(set, [n])``
1131 1140 First n members of set, defaulting to 1.
1132 1141 """
1133 1142 # i18n: "limit" is a keyword
1134 1143 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
1135 1144 try:
1136 1145 lim = 1
1137 1146 if len(l) == 2:
1138 1147 # i18n: "limit" is a keyword
1139 1148 lim = int(getstring(l[1], _("limit requires a number")))
1140 1149 except (TypeError, ValueError):
1141 1150 # i18n: "limit" is a keyword
1142 1151 raise error.ParseError(_("limit expects a number"))
1143 1152 ss = subset
1144 1153 os = getset(repo, fullreposet(repo), l[0])
1145 1154 result = []
1146 1155 it = iter(os)
1147 1156 for x in xrange(lim):
1148 1157 y = next(it, None)
1149 1158 if y is None:
1150 1159 break
1151 1160 elif y in ss:
1152 1161 result.append(y)
1153 1162 return baseset(result)
1154 1163
1155 1164 def last(repo, subset, x):
1156 1165 """``last(set, [n])``
1157 1166 Last n members of set, defaulting to 1.
1158 1167 """
1159 1168 # i18n: "last" is a keyword
1160 1169 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1161 1170 try:
1162 1171 lim = 1
1163 1172 if len(l) == 2:
1164 1173 # i18n: "last" is a keyword
1165 1174 lim = int(getstring(l[1], _("last requires a number")))
1166 1175 except (TypeError, ValueError):
1167 1176 # i18n: "last" is a keyword
1168 1177 raise error.ParseError(_("last expects a number"))
1169 1178 ss = subset
1170 1179 os = getset(repo, fullreposet(repo), l[0])
1171 1180 os.reverse()
1172 1181 result = []
1173 1182 it = iter(os)
1174 1183 for x in xrange(lim):
1175 1184 y = next(it, None)
1176 1185 if y is None:
1177 1186 break
1178 1187 elif y in ss:
1179 1188 result.append(y)
1180 1189 return baseset(result)
1181 1190
1182 1191 def maxrev(repo, subset, x):
1183 1192 """``max(set)``
1184 1193 Changeset with highest revision number in set.
1185 1194 """
1186 1195 os = getset(repo, fullreposet(repo), x)
1187 1196 if os:
1188 1197 m = os.max()
1189 1198 if m in subset:
1190 1199 return baseset([m])
1191 1200 return baseset()
1192 1201
1193 1202 def merge(repo, subset, x):
1194 1203 """``merge()``
1195 1204 Changeset is a merge changeset.
1196 1205 """
1197 1206 # i18n: "merge" is a keyword
1198 1207 getargs(x, 0, 0, _("merge takes no arguments"))
1199 1208 cl = repo.changelog
1200 1209 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1201 1210
1202 1211 def branchpoint(repo, subset, x):
1203 1212 """``branchpoint()``
1204 1213 Changesets with more than one child.
1205 1214 """
1206 1215 # i18n: "branchpoint" is a keyword
1207 1216 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1208 1217 cl = repo.changelog
1209 1218 if not subset:
1210 1219 return baseset()
1211 1220 baserev = min(subset)
1212 1221 parentscount = [0]*(len(repo) - baserev)
1213 1222 for r in cl.revs(start=baserev + 1):
1214 1223 for p in cl.parentrevs(r):
1215 1224 if p >= baserev:
1216 1225 parentscount[p - baserev] += 1
1217 1226 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1218 1227
1219 1228 def minrev(repo, subset, x):
1220 1229 """``min(set)``
1221 1230 Changeset with lowest revision number in set.
1222 1231 """
1223 1232 os = getset(repo, fullreposet(repo), x)
1224 1233 if os:
1225 1234 m = os.min()
1226 1235 if m in subset:
1227 1236 return baseset([m])
1228 1237 return baseset()
1229 1238
1230 1239 def modifies(repo, subset, x):
1231 1240 """``modifies(pattern)``
1232 1241 Changesets modifying files matched by pattern.
1233 1242
1234 1243 The pattern without explicit kind like ``glob:`` is expected to be
1235 1244 relative to the current directory and match against a file or a
1236 1245 directory.
1237 1246 """
1238 1247 # i18n: "modifies" is a keyword
1239 1248 pat = getstring(x, _("modifies requires a pattern"))
1240 1249 return checkstatus(repo, subset, pat, 0)
1241 1250
1242 1251 def named(repo, subset, x):
1243 1252 """``named(namespace)``
1244 1253 The changesets in a given namespace.
1245 1254
1246 1255 If `namespace` starts with `re:`, the remainder of the string is treated as
1247 1256 a regular expression. To match a namespace that actually starts with `re:`,
1248 1257 use the prefix `literal:`.
1249 1258 """
1250 1259 # i18n: "named" is a keyword
1251 1260 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1252 1261
1253 1262 ns = getstring(args[0],
1254 1263 # i18n: "named" is a keyword
1255 1264 _('the argument to named must be a string'))
1256 1265 kind, pattern, matcher = _stringmatcher(ns)
1257 1266 namespaces = set()
1258 1267 if kind == 'literal':
1259 1268 if pattern not in repo.names:
1260 1269 raise error.RepoLookupError(_("namespace '%s' does not exist")
1261 1270 % ns)
1262 1271 namespaces.add(repo.names[pattern])
1263 1272 else:
1264 1273 for name, ns in repo.names.iteritems():
1265 1274 if matcher(name):
1266 1275 namespaces.add(ns)
1267 1276 if not namespaces:
1268 1277 raise error.RepoLookupError(_("no namespace exists"
1269 1278 " that match '%s'") % pattern)
1270 1279
1271 1280 names = set()
1272 1281 for ns in namespaces:
1273 1282 for name in ns.listnames(repo):
1274 1283 if name not in ns.deprecated:
1275 1284 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1276 1285
1277 1286 names -= set([node.nullrev])
1278 1287 return subset & names
1279 1288
1280 1289 def node_(repo, subset, x):
1281 1290 """``id(string)``
1282 1291 Revision non-ambiguously specified by the given hex string prefix.
1283 1292 """
1284 1293 # i18n: "id" is a keyword
1285 1294 l = getargs(x, 1, 1, _("id requires one argument"))
1286 1295 # i18n: "id" is a keyword
1287 1296 n = getstring(l[0], _("id requires a string"))
1288 1297 if len(n) == 40:
1289 1298 try:
1290 1299 rn = repo.changelog.rev(node.bin(n))
1291 1300 except (LookupError, TypeError):
1292 1301 rn = None
1293 1302 else:
1294 1303 rn = None
1295 1304 pm = repo.changelog._partialmatch(n)
1296 1305 if pm is not None:
1297 1306 rn = repo.changelog.rev(pm)
1298 1307
1299 1308 if rn is None:
1300 1309 return baseset()
1301 1310 result = baseset([rn])
1302 1311 return result & subset
1303 1312
1304 1313 def obsolete(repo, subset, x):
1305 1314 """``obsolete()``
1306 1315 Mutable changeset with a newer version."""
1307 1316 # i18n: "obsolete" is a keyword
1308 1317 getargs(x, 0, 0, _("obsolete takes no arguments"))
1309 1318 obsoletes = obsmod.getrevs(repo, 'obsolete')
1310 1319 return subset & obsoletes
1311 1320
1312 1321 def only(repo, subset, x):
1313 1322 """``only(set, [set])``
1314 1323 Changesets that are ancestors of the first set that are not ancestors
1315 1324 of any other head in the repo. If a second set is specified, the result
1316 1325 is ancestors of the first set that are not ancestors of the second set
1317 1326 (i.e. ::<set1> - ::<set2>).
1318 1327 """
1319 1328 cl = repo.changelog
1320 1329 # i18n: "only" is a keyword
1321 1330 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1322 1331 include = getset(repo, fullreposet(repo), args[0])
1323 1332 if len(args) == 1:
1324 1333 if not include:
1325 1334 return baseset()
1326 1335
1327 1336 descendants = set(_revdescendants(repo, include, False))
1328 1337 exclude = [rev for rev in cl.headrevs()
1329 1338 if not rev in descendants and not rev in include]
1330 1339 else:
1331 1340 exclude = getset(repo, fullreposet(repo), args[1])
1332 1341
1333 1342 results = set(cl.findmissingrevs(common=exclude, heads=include))
1334 1343 return subset & results
1335 1344
1336 1345 def origin(repo, subset, x):
1337 1346 """``origin([set])``
1338 1347 Changesets that were specified as a source for the grafts, transplants or
1339 1348 rebases that created the given revisions. Omitting the optional set is the
1340 1349 same as passing all(). If a changeset created by these operations is itself
1341 1350 specified as a source for one of these operations, only the source changeset
1342 1351 for the first operation is selected.
1343 1352 """
1344 1353 if x is not None:
1345 1354 dests = getset(repo, fullreposet(repo), x)
1346 1355 else:
1347 1356 dests = fullreposet(repo)
1348 1357
1349 1358 def _firstsrc(rev):
1350 1359 src = _getrevsource(repo, rev)
1351 1360 if src is None:
1352 1361 return None
1353 1362
1354 1363 while True:
1355 1364 prev = _getrevsource(repo, src)
1356 1365
1357 1366 if prev is None:
1358 1367 return src
1359 1368 src = prev
1360 1369
1361 1370 o = set([_firstsrc(r) for r in dests])
1362 1371 o -= set([None])
1363 1372 return subset & o
1364 1373
1365 1374 def outgoing(repo, subset, x):
1366 1375 """``outgoing([path])``
1367 1376 Changesets not found in the specified destination repository, or the
1368 1377 default push location.
1369 1378 """
1370 1379 # Avoid cycles.
1371 1380 import discovery
1372 1381 import hg
1373 1382 # i18n: "outgoing" is a keyword
1374 1383 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1375 1384 # i18n: "outgoing" is a keyword
1376 1385 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1377 1386 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1378 1387 dest, branches = hg.parseurl(dest)
1379 1388 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1380 1389 if revs:
1381 1390 revs = [repo.lookup(rev) for rev in revs]
1382 1391 other = hg.peer(repo, {}, dest)
1383 1392 repo.ui.pushbuffer()
1384 1393 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1385 1394 repo.ui.popbuffer()
1386 1395 cl = repo.changelog
1387 1396 o = set([cl.rev(r) for r in outgoing.missing])
1388 1397 return subset & o
1389 1398
1390 1399 def p1(repo, subset, x):
1391 1400 """``p1([set])``
1392 1401 First parent of changesets in set, or the working directory.
1393 1402 """
1394 1403 if x is None:
1395 1404 p = repo[x].p1().rev()
1396 1405 if p >= 0:
1397 1406 return subset & baseset([p])
1398 1407 return baseset()
1399 1408
1400 1409 ps = set()
1401 1410 cl = repo.changelog
1402 1411 for r in getset(repo, fullreposet(repo), x):
1403 1412 ps.add(cl.parentrevs(r)[0])
1404 1413 ps -= set([node.nullrev])
1405 1414 return subset & ps
1406 1415
1407 1416 def p2(repo, subset, x):
1408 1417 """``p2([set])``
1409 1418 Second parent of changesets in set, or the working directory.
1410 1419 """
1411 1420 if x is None:
1412 1421 ps = repo[x].parents()
1413 1422 try:
1414 1423 p = ps[1].rev()
1415 1424 if p >= 0:
1416 1425 return subset & baseset([p])
1417 1426 return baseset()
1418 1427 except IndexError:
1419 1428 return baseset()
1420 1429
1421 1430 ps = set()
1422 1431 cl = repo.changelog
1423 1432 for r in getset(repo, fullreposet(repo), x):
1424 1433 ps.add(cl.parentrevs(r)[1])
1425 1434 ps -= set([node.nullrev])
1426 1435 return subset & ps
1427 1436
1428 1437 def parents(repo, subset, x):
1429 1438 """``parents([set])``
1430 1439 The set of all parents for all changesets in set, or the working directory.
1431 1440 """
1432 1441 if x is None:
1433 1442 ps = set(p.rev() for p in repo[x].parents())
1434 1443 else:
1435 1444 ps = set()
1436 1445 cl = repo.changelog
1437 1446 for r in getset(repo, fullreposet(repo), x):
1438 1447 ps.update(cl.parentrevs(r))
1439 1448 ps -= set([node.nullrev])
1440 1449 return subset & ps
1441 1450
1442 1451 def parentspec(repo, subset, x, n):
1443 1452 """``set^0``
1444 1453 The set.
1445 1454 ``set^1`` (or ``set^``), ``set^2``
1446 1455 First or second parent, respectively, of all changesets in set.
1447 1456 """
1448 1457 try:
1449 1458 n = int(n[1])
1450 1459 if n not in (0, 1, 2):
1451 1460 raise ValueError
1452 1461 except (TypeError, ValueError):
1453 1462 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1454 1463 ps = set()
1455 1464 cl = repo.changelog
1456 1465 for r in getset(repo, fullreposet(repo), x):
1457 1466 if n == 0:
1458 1467 ps.add(r)
1459 1468 elif n == 1:
1460 1469 ps.add(cl.parentrevs(r)[0])
1461 1470 elif n == 2:
1462 1471 parents = cl.parentrevs(r)
1463 1472 if len(parents) > 1:
1464 1473 ps.add(parents[1])
1465 1474 return subset & ps
1466 1475
1467 1476 def present(repo, subset, x):
1468 1477 """``present(set)``
1469 1478 An empty set, if any revision in set isn't found; otherwise,
1470 1479 all revisions in set.
1471 1480
1472 1481 If any of specified revisions is not present in the local repository,
1473 1482 the query is normally aborted. But this predicate allows the query
1474 1483 to continue even in such cases.
1475 1484 """
1476 1485 try:
1477 1486 return getset(repo, subset, x)
1478 1487 except error.RepoLookupError:
1479 1488 return baseset()
1480 1489
1481 1490 # for internal use
1482 1491 def _notpublic(repo, subset, x):
1483 1492 getargs(x, 0, 0, "_notpublic takes no arguments")
1484 1493 if repo._phasecache._phasesets:
1485 1494 s = set()
1486 1495 for u in repo._phasecache._phasesets[1:]:
1487 1496 s.update(u)
1488 1497 return subset & s
1489 1498 else:
1490 1499 phase = repo._phasecache.phase
1491 1500 target = phases.public
1492 1501 condition = lambda r: phase(repo, r) != target
1493 1502 return subset.filter(condition, cache=False)
1494 1503
1495 1504 def public(repo, subset, x):
1496 1505 """``public()``
1497 1506 Changeset in public phase."""
1498 1507 # i18n: "public" is a keyword
1499 1508 getargs(x, 0, 0, _("public takes no arguments"))
1500 1509 phase = repo._phasecache.phase
1501 1510 target = phases.public
1502 1511 condition = lambda r: phase(repo, r) == target
1503 1512 return subset.filter(condition, cache=False)
1504 1513
1505 1514 def remote(repo, subset, x):
1506 1515 """``remote([id [,path]])``
1507 1516 Local revision that corresponds to the given identifier in a
1508 1517 remote repository, if present. Here, the '.' identifier is a
1509 1518 synonym for the current local branch.
1510 1519 """
1511 1520
1512 1521 import hg # avoid start-up nasties
1513 1522 # i18n: "remote" is a keyword
1514 1523 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1515 1524
1516 1525 q = '.'
1517 1526 if len(l) > 0:
1518 1527 # i18n: "remote" is a keyword
1519 1528 q = getstring(l[0], _("remote requires a string id"))
1520 1529 if q == '.':
1521 1530 q = repo['.'].branch()
1522 1531
1523 1532 dest = ''
1524 1533 if len(l) > 1:
1525 1534 # i18n: "remote" is a keyword
1526 1535 dest = getstring(l[1], _("remote requires a repository path"))
1527 1536 dest = repo.ui.expandpath(dest or 'default')
1528 1537 dest, branches = hg.parseurl(dest)
1529 1538 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1530 1539 if revs:
1531 1540 revs = [repo.lookup(rev) for rev in revs]
1532 1541 other = hg.peer(repo, {}, dest)
1533 1542 n = other.lookup(q)
1534 1543 if n in repo:
1535 1544 r = repo[n].rev()
1536 1545 if r in subset:
1537 1546 return baseset([r])
1538 1547 return baseset()
1539 1548
1540 1549 def removes(repo, subset, x):
1541 1550 """``removes(pattern)``
1542 1551 Changesets which remove files matching pattern.
1543 1552
1544 1553 The pattern without explicit kind like ``glob:`` is expected to be
1545 1554 relative to the current directory and match against a file or a
1546 1555 directory.
1547 1556 """
1548 1557 # i18n: "removes" is a keyword
1549 1558 pat = getstring(x, _("removes requires a pattern"))
1550 1559 return checkstatus(repo, subset, pat, 2)
1551 1560
1552 1561 def rev(repo, subset, x):
1553 1562 """``rev(number)``
1554 1563 Revision with the given numeric identifier.
1555 1564 """
1556 1565 # i18n: "rev" is a keyword
1557 1566 l = getargs(x, 1, 1, _("rev requires one argument"))
1558 1567 try:
1559 1568 # i18n: "rev" is a keyword
1560 1569 l = int(getstring(l[0], _("rev requires a number")))
1561 1570 except (TypeError, ValueError):
1562 1571 # i18n: "rev" is a keyword
1563 1572 raise error.ParseError(_("rev expects a number"))
1564 1573 if l not in repo.changelog and l != node.nullrev:
1565 1574 return baseset()
1566 1575 return subset & baseset([l])
1567 1576
1568 1577 def matching(repo, subset, x):
1569 1578 """``matching(revision [, field])``
1570 1579 Changesets in which a given set of fields match the set of fields in the
1571 1580 selected revision or set.
1572 1581
1573 1582 To match more than one field pass the list of fields to match separated
1574 1583 by spaces (e.g. ``author description``).
1575 1584
1576 1585 Valid fields are most regular revision fields and some special fields.
1577 1586
1578 1587 Regular revision fields are ``description``, ``author``, ``branch``,
1579 1588 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1580 1589 and ``diff``.
1581 1590 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1582 1591 contents of the revision. Two revisions matching their ``diff`` will
1583 1592 also match their ``files``.
1584 1593
1585 1594 Special fields are ``summary`` and ``metadata``:
1586 1595 ``summary`` matches the first line of the description.
1587 1596 ``metadata`` is equivalent to matching ``description user date``
1588 1597 (i.e. it matches the main metadata fields).
1589 1598
1590 1599 ``metadata`` is the default field which is used when no fields are
1591 1600 specified. You can match more than one field at a time.
1592 1601 """
1593 1602 # i18n: "matching" is a keyword
1594 1603 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1595 1604
1596 1605 revs = getset(repo, fullreposet(repo), l[0])
1597 1606
1598 1607 fieldlist = ['metadata']
1599 1608 if len(l) > 1:
1600 1609 fieldlist = getstring(l[1],
1601 1610 # i18n: "matching" is a keyword
1602 1611 _("matching requires a string "
1603 1612 "as its second argument")).split()
1604 1613
1605 1614 # Make sure that there are no repeated fields,
1606 1615 # expand the 'special' 'metadata' field type
1607 1616 # and check the 'files' whenever we check the 'diff'
1608 1617 fields = []
1609 1618 for field in fieldlist:
1610 1619 if field == 'metadata':
1611 1620 fields += ['user', 'description', 'date']
1612 1621 elif field == 'diff':
1613 1622 # a revision matching the diff must also match the files
1614 1623 # since matching the diff is very costly, make sure to
1615 1624 # also match the files first
1616 1625 fields += ['files', 'diff']
1617 1626 else:
1618 1627 if field == 'author':
1619 1628 field = 'user'
1620 1629 fields.append(field)
1621 1630 fields = set(fields)
1622 1631 if 'summary' in fields and 'description' in fields:
1623 1632 # If a revision matches its description it also matches its summary
1624 1633 fields.discard('summary')
1625 1634
1626 1635 # We may want to match more than one field
1627 1636 # Not all fields take the same amount of time to be matched
1628 1637 # Sort the selected fields in order of increasing matching cost
1629 1638 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1630 1639 'files', 'description', 'substate', 'diff']
1631 1640 def fieldkeyfunc(f):
1632 1641 try:
1633 1642 return fieldorder.index(f)
1634 1643 except ValueError:
1635 1644 # assume an unknown field is very costly
1636 1645 return len(fieldorder)
1637 1646 fields = list(fields)
1638 1647 fields.sort(key=fieldkeyfunc)
1639 1648
1640 1649 # Each field will be matched with its own "getfield" function
1641 1650 # which will be added to the getfieldfuncs array of functions
1642 1651 getfieldfuncs = []
1643 1652 _funcs = {
1644 1653 'user': lambda r: repo[r].user(),
1645 1654 'branch': lambda r: repo[r].branch(),
1646 1655 'date': lambda r: repo[r].date(),
1647 1656 'description': lambda r: repo[r].description(),
1648 1657 'files': lambda r: repo[r].files(),
1649 1658 'parents': lambda r: repo[r].parents(),
1650 1659 'phase': lambda r: repo[r].phase(),
1651 1660 'substate': lambda r: repo[r].substate,
1652 1661 'summary': lambda r: repo[r].description().splitlines()[0],
1653 1662 'diff': lambda r: list(repo[r].diff(git=True),)
1654 1663 }
1655 1664 for info in fields:
1656 1665 getfield = _funcs.get(info, None)
1657 1666 if getfield is None:
1658 1667 raise error.ParseError(
1659 1668 # i18n: "matching" is a keyword
1660 1669 _("unexpected field name passed to matching: %s") % info)
1661 1670 getfieldfuncs.append(getfield)
1662 1671 # convert the getfield array of functions into a "getinfo" function
1663 1672 # which returns an array of field values (or a single value if there
1664 1673 # is only one field to match)
1665 1674 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1666 1675
1667 1676 def matches(x):
1668 1677 for rev in revs:
1669 1678 target = getinfo(rev)
1670 1679 match = True
1671 1680 for n, f in enumerate(getfieldfuncs):
1672 1681 if target[n] != f(x):
1673 1682 match = False
1674 1683 if match:
1675 1684 return True
1676 1685 return False
1677 1686
1678 1687 return subset.filter(matches)
1679 1688
1680 1689 def reverse(repo, subset, x):
1681 1690 """``reverse(set)``
1682 1691 Reverse order of set.
1683 1692 """
1684 1693 l = getset(repo, subset, x)
1685 1694 l.reverse()
1686 1695 return l
1687 1696
1688 1697 def roots(repo, subset, x):
1689 1698 """``roots(set)``
1690 1699 Changesets in set with no parent changeset in set.
1691 1700 """
1692 1701 s = getset(repo, fullreposet(repo), x)
1693 1702 subset = subset & s# baseset([r for r in s if r in subset])
1694 1703 cs = _children(repo, subset, s)
1695 1704 return subset - cs
1696 1705
1697 1706 def secret(repo, subset, x):
1698 1707 """``secret()``
1699 1708 Changeset in secret phase."""
1700 1709 # i18n: "secret" is a keyword
1701 1710 getargs(x, 0, 0, _("secret takes no arguments"))
1702 1711 phase = repo._phasecache.phase
1703 1712 target = phases.secret
1704 1713 condition = lambda r: phase(repo, r) == target
1705 1714 return subset.filter(condition, cache=False)
1706 1715
1707 1716 def sort(repo, subset, x):
1708 1717 """``sort(set[, [-]key...])``
1709 1718 Sort set by keys. The default sort order is ascending, specify a key
1710 1719 as ``-key`` to sort in descending order.
1711 1720
1712 1721 The keys can be:
1713 1722
1714 1723 - ``rev`` for the revision number,
1715 1724 - ``branch`` for the branch name,
1716 1725 - ``desc`` for the commit message (description),
1717 1726 - ``user`` for user name (``author`` can be used as an alias),
1718 1727 - ``date`` for the commit date
1719 1728 """
1720 1729 # i18n: "sort" is a keyword
1721 1730 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1722 1731 keys = "rev"
1723 1732 if len(l) == 2:
1724 1733 # i18n: "sort" is a keyword
1725 1734 keys = getstring(l[1], _("sort spec must be a string"))
1726 1735
1727 1736 s = l[0]
1728 1737 keys = keys.split()
1729 1738 l = []
1730 1739 def invert(s):
1731 1740 return "".join(chr(255 - ord(c)) for c in s)
1732 1741 revs = getset(repo, subset, s)
1733 1742 if keys == ["rev"]:
1734 1743 revs.sort()
1735 1744 return revs
1736 1745 elif keys == ["-rev"]:
1737 1746 revs.sort(reverse=True)
1738 1747 return revs
1739 1748 for r in revs:
1740 1749 c = repo[r]
1741 1750 e = []
1742 1751 for k in keys:
1743 1752 if k == 'rev':
1744 1753 e.append(r)
1745 1754 elif k == '-rev':
1746 1755 e.append(-r)
1747 1756 elif k == 'branch':
1748 1757 e.append(c.branch())
1749 1758 elif k == '-branch':
1750 1759 e.append(invert(c.branch()))
1751 1760 elif k == 'desc':
1752 1761 e.append(c.description())
1753 1762 elif k == '-desc':
1754 1763 e.append(invert(c.description()))
1755 1764 elif k in 'user author':
1756 1765 e.append(c.user())
1757 1766 elif k in '-user -author':
1758 1767 e.append(invert(c.user()))
1759 1768 elif k == 'date':
1760 1769 e.append(c.date()[0])
1761 1770 elif k == '-date':
1762 1771 e.append(-c.date()[0])
1763 1772 else:
1764 1773 raise error.ParseError(_("unknown sort key %r") % k)
1765 1774 e.append(r)
1766 1775 l.append(e)
1767 1776 l.sort()
1768 1777 return baseset([e[-1] for e in l])
1769 1778
1770 1779 def subrepo(repo, subset, x):
1771 1780 """``subrepo([pattern])``
1772 1781 Changesets that add, modify or remove the given subrepo. If no subrepo
1773 1782 pattern is named, any subrepo changes are returned.
1774 1783 """
1775 1784 # i18n: "subrepo" is a keyword
1776 1785 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
1777 1786 if len(args) != 0:
1778 1787 pat = getstring(args[0], _("subrepo requires a pattern"))
1779 1788
1780 1789 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
1781 1790
1782 1791 def submatches(names):
1783 1792 k, p, m = _stringmatcher(pat)
1784 1793 for name in names:
1785 1794 if m(name):
1786 1795 yield name
1787 1796
1788 1797 def matches(x):
1789 1798 c = repo[x]
1790 1799 s = repo.status(c.p1().node(), c.node(), match=m)
1791 1800
1792 1801 if len(args) == 0:
1793 1802 return s.added or s.modified or s.removed
1794 1803
1795 1804 if s.added:
1796 1805 return any(submatches(c.substate.keys()))
1797 1806
1798 1807 if s.modified:
1799 1808 subs = set(c.p1().substate.keys())
1800 1809 subs.update(c.substate.keys())
1801 1810
1802 1811 for path in submatches(subs):
1803 1812 if c.p1().substate.get(path) != c.substate.get(path):
1804 1813 return True
1805 1814
1806 1815 if s.removed:
1807 1816 return any(submatches(c.p1().substate.keys()))
1808 1817
1809 1818 return False
1810 1819
1811 1820 return subset.filter(matches)
1812 1821
1813 1822 def _stringmatcher(pattern):
1814 1823 """
1815 1824 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1816 1825 returns the matcher name, pattern, and matcher function.
1817 1826 missing or unknown prefixes are treated as literal matches.
1818 1827
1819 1828 helper for tests:
1820 1829 >>> def test(pattern, *tests):
1821 1830 ... kind, pattern, matcher = _stringmatcher(pattern)
1822 1831 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1823 1832
1824 1833 exact matching (no prefix):
1825 1834 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1826 1835 ('literal', 'abcdefg', [False, False, True])
1827 1836
1828 1837 regex matching ('re:' prefix)
1829 1838 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1830 1839 ('re', 'a.+b', [False, False, True])
1831 1840
1832 1841 force exact matches ('literal:' prefix)
1833 1842 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1834 1843 ('literal', 're:foobar', [False, True])
1835 1844
1836 1845 unknown prefixes are ignored and treated as literals
1837 1846 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1838 1847 ('literal', 'foo:bar', [False, False, True])
1839 1848 """
1840 1849 if pattern.startswith('re:'):
1841 1850 pattern = pattern[3:]
1842 1851 try:
1843 1852 regex = re.compile(pattern)
1844 1853 except re.error, e:
1845 1854 raise error.ParseError(_('invalid regular expression: %s')
1846 1855 % e)
1847 1856 return 're', pattern, regex.search
1848 1857 elif pattern.startswith('literal:'):
1849 1858 pattern = pattern[8:]
1850 1859 return 'literal', pattern, pattern.__eq__
1851 1860
1852 1861 def _substringmatcher(pattern):
1853 1862 kind, pattern, matcher = _stringmatcher(pattern)
1854 1863 if kind == 'literal':
1855 1864 matcher = lambda s: pattern in s
1856 1865 return kind, pattern, matcher
1857 1866
1858 1867 def tag(repo, subset, x):
1859 1868 """``tag([name])``
1860 1869 The specified tag by name, or all tagged revisions if no name is given.
1861 1870
1862 1871 If `name` starts with `re:`, the remainder of the name is treated as
1863 1872 a regular expression. To match a tag that actually starts with `re:`,
1864 1873 use the prefix `literal:`.
1865 1874 """
1866 1875 # i18n: "tag" is a keyword
1867 1876 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1868 1877 cl = repo.changelog
1869 1878 if args:
1870 1879 pattern = getstring(args[0],
1871 1880 # i18n: "tag" is a keyword
1872 1881 _('the argument to tag must be a string'))
1873 1882 kind, pattern, matcher = _stringmatcher(pattern)
1874 1883 if kind == 'literal':
1875 1884 # avoid resolving all tags
1876 1885 tn = repo._tagscache.tags.get(pattern, None)
1877 1886 if tn is None:
1878 1887 raise error.RepoLookupError(_("tag '%s' does not exist")
1879 1888 % pattern)
1880 1889 s = set([repo[tn].rev()])
1881 1890 else:
1882 1891 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1883 1892 else:
1884 1893 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1885 1894 return subset & s
1886 1895
1887 1896 def tagged(repo, subset, x):
1888 1897 return tag(repo, subset, x)
1889 1898
1890 1899 def unstable(repo, subset, x):
1891 1900 """``unstable()``
1892 1901 Non-obsolete changesets with obsolete ancestors.
1893 1902 """
1894 1903 # i18n: "unstable" is a keyword
1895 1904 getargs(x, 0, 0, _("unstable takes no arguments"))
1896 1905 unstables = obsmod.getrevs(repo, 'unstable')
1897 1906 return subset & unstables
1898 1907
1899 1908
1900 1909 def user(repo, subset, x):
1901 1910 """``user(string)``
1902 1911 User name contains string. The match is case-insensitive.
1903 1912
1904 1913 If `string` starts with `re:`, the remainder of the string is treated as
1905 1914 a regular expression. To match a user that actually contains `re:`, use
1906 1915 the prefix `literal:`.
1907 1916 """
1908 1917 return author(repo, subset, x)
1909 1918
1910 1919 # experimental
1911 1920 def wdir(repo, subset, x):
1912 1921 # i18n: "wdir" is a keyword
1913 1922 getargs(x, 0, 0, _("wdir takes no arguments"))
1914 1923 if None in subset or isinstance(subset, fullreposet):
1915 1924 return baseset([None])
1916 1925 return baseset()
1917 1926
1918 1927 # for internal use
1919 1928 def _list(repo, subset, x):
1920 1929 s = getstring(x, "internal error")
1921 1930 if not s:
1922 1931 return baseset()
1923 1932 # remove duplicates here. it's difficult for caller to deduplicate sets
1924 1933 # because different symbols can point to the same rev.
1925 1934 cl = repo.changelog
1926 1935 ls = []
1927 1936 seen = set()
1928 1937 for t in s.split('\0'):
1929 1938 try:
1930 1939 # fast path for integer revision
1931 1940 r = int(t)
1932 1941 if str(r) != t or r not in cl:
1933 1942 raise ValueError
1934 1943 except ValueError:
1935 1944 r = repo[t].rev()
1936 1945 if r in seen:
1937 1946 continue
1938 1947 if (r in subset
1939 1948 or r == node.nullrev and isinstance(subset, fullreposet)):
1940 1949 ls.append(r)
1941 1950 seen.add(r)
1942 1951 return baseset(ls)
1943 1952
1944 1953 # for internal use
1945 1954 def _intlist(repo, subset, x):
1946 1955 s = getstring(x, "internal error")
1947 1956 if not s:
1948 1957 return baseset()
1949 1958 ls = [int(r) for r in s.split('\0')]
1950 1959 s = subset
1951 1960 return baseset([r for r in ls if r in s])
1952 1961
1953 1962 # for internal use
1954 1963 def _hexlist(repo, subset, x):
1955 1964 s = getstring(x, "internal error")
1956 1965 if not s:
1957 1966 return baseset()
1958 1967 cl = repo.changelog
1959 1968 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
1960 1969 s = subset
1961 1970 return baseset([r for r in ls if r in s])
1962 1971
1963 1972 symbols = {
1964 1973 "adds": adds,
1965 1974 "all": getall,
1966 1975 "ancestor": ancestor,
1967 1976 "ancestors": ancestors,
1968 1977 "_firstancestors": _firstancestors,
1969 1978 "author": author,
1970 1979 "bisect": bisect,
1971 1980 "bisected": bisected,
1972 1981 "bookmark": bookmark,
1973 1982 "branch": branch,
1974 1983 "branchpoint": branchpoint,
1975 1984 "bumped": bumped,
1976 1985 "bundle": bundle,
1977 1986 "children": children,
1978 1987 "closed": closed,
1979 1988 "contains": contains,
1980 1989 "converted": converted,
1981 1990 "date": date,
1982 1991 "desc": desc,
1983 1992 "descendants": descendants,
1984 1993 "_firstdescendants": _firstdescendants,
1985 1994 "destination": destination,
1986 1995 "divergent": divergent,
1987 1996 "draft": draft,
1988 1997 "extinct": extinct,
1989 1998 "extra": extra,
1990 1999 "file": hasfile,
1991 2000 "filelog": filelog,
1992 2001 "first": first,
1993 2002 "follow": follow,
1994 2003 "_followfirst": _followfirst,
1995 2004 "grep": grep,
1996 2005 "head": head,
1997 2006 "heads": heads,
1998 2007 "hidden": hidden,
1999 2008 "id": node_,
2000 2009 "keyword": keyword,
2001 2010 "last": last,
2002 2011 "limit": limit,
2003 2012 "_matchfiles": _matchfiles,
2004 2013 "max": maxrev,
2005 2014 "merge": merge,
2006 2015 "min": minrev,
2007 2016 "modifies": modifies,
2008 2017 "named": named,
2009 2018 "obsolete": obsolete,
2010 2019 "only": only,
2011 2020 "origin": origin,
2012 2021 "outgoing": outgoing,
2013 2022 "p1": p1,
2014 2023 "p2": p2,
2015 2024 "parents": parents,
2016 2025 "present": present,
2017 2026 "public": public,
2018 2027 "_notpublic": _notpublic,
2019 2028 "remote": remote,
2020 2029 "removes": removes,
2021 2030 "rev": rev,
2022 2031 "reverse": reverse,
2023 2032 "roots": roots,
2024 2033 "sort": sort,
2025 2034 "secret": secret,
2026 2035 "subrepo": subrepo,
2027 2036 "matching": matching,
2028 2037 "tag": tag,
2029 2038 "tagged": tagged,
2030 2039 "user": user,
2031 2040 "unstable": unstable,
2032 2041 "wdir": wdir,
2033 2042 "_list": _list,
2034 2043 "_intlist": _intlist,
2035 2044 "_hexlist": _hexlist,
2036 2045 }
2037 2046
2038 2047 # symbols which can't be used for a DoS attack for any given input
2039 2048 # (e.g. those which accept regexes as plain strings shouldn't be included)
2040 2049 # functions that just return a lot of changesets (like all) don't count here
2041 2050 safesymbols = set([
2042 2051 "adds",
2043 2052 "all",
2044 2053 "ancestor",
2045 2054 "ancestors",
2046 2055 "_firstancestors",
2047 2056 "author",
2048 2057 "bisect",
2049 2058 "bisected",
2050 2059 "bookmark",
2051 2060 "branch",
2052 2061 "branchpoint",
2053 2062 "bumped",
2054 2063 "bundle",
2055 2064 "children",
2056 2065 "closed",
2057 2066 "converted",
2058 2067 "date",
2059 2068 "desc",
2060 2069 "descendants",
2061 2070 "_firstdescendants",
2062 2071 "destination",
2063 2072 "divergent",
2064 2073 "draft",
2065 2074 "extinct",
2066 2075 "extra",
2067 2076 "file",
2068 2077 "filelog",
2069 2078 "first",
2070 2079 "follow",
2071 2080 "_followfirst",
2072 2081 "head",
2073 2082 "heads",
2074 2083 "hidden",
2075 2084 "id",
2076 2085 "keyword",
2077 2086 "last",
2078 2087 "limit",
2079 2088 "_matchfiles",
2080 2089 "max",
2081 2090 "merge",
2082 2091 "min",
2083 2092 "modifies",
2084 2093 "obsolete",
2085 2094 "only",
2086 2095 "origin",
2087 2096 "outgoing",
2088 2097 "p1",
2089 2098 "p2",
2090 2099 "parents",
2091 2100 "present",
2092 2101 "public",
2093 2102 "_notpublic",
2094 2103 "remote",
2095 2104 "removes",
2096 2105 "rev",
2097 2106 "reverse",
2098 2107 "roots",
2099 2108 "sort",
2100 2109 "secret",
2101 2110 "matching",
2102 2111 "tag",
2103 2112 "tagged",
2104 2113 "user",
2105 2114 "unstable",
2106 2115 "wdir",
2107 2116 "_list",
2108 2117 "_intlist",
2109 2118 "_hexlist",
2110 2119 ])
2111 2120
2112 2121 methods = {
2113 2122 "range": rangeset,
2114 2123 "dagrange": dagrange,
2115 2124 "string": stringset,
2116 2125 "symbol": stringset,
2117 2126 "and": andset,
2118 2127 "or": orset,
2119 2128 "not": notset,
2120 2129 "list": listset,
2121 2130 "func": func,
2122 2131 "ancestor": ancestorspec,
2123 2132 "parent": parentspec,
2124 2133 "parentpost": p1,
2125 2134 }
2126 2135
2127 2136 def optimize(x, small):
2128 2137 if x is None:
2129 2138 return 0, x
2130 2139
2131 2140 smallbonus = 1
2132 2141 if small:
2133 2142 smallbonus = .5
2134 2143
2135 2144 op = x[0]
2136 2145 if op == 'minus':
2137 2146 return optimize(('and', x[1], ('not', x[2])), small)
2138 2147 elif op == 'only':
2139 2148 return optimize(('func', ('symbol', 'only'),
2140 2149 ('list', x[1], x[2])), small)
2141 2150 elif op == 'onlypost':
2142 2151 return optimize(('func', ('symbol', 'only'), x[1]), small)
2143 2152 elif op == 'dagrangepre':
2144 2153 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2145 2154 elif op == 'dagrangepost':
2146 2155 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2147 2156 elif op == 'rangepre':
2148 2157 return optimize(('range', ('string', '0'), x[1]), small)
2149 2158 elif op == 'rangepost':
2150 2159 return optimize(('range', x[1], ('string', 'tip')), small)
2151 2160 elif op == 'negate':
2152 2161 return optimize(('string',
2153 2162 '-' + getstring(x[1], _("can't negate that"))), small)
2154 2163 elif op in 'string symbol negate':
2155 2164 return smallbonus, x # single revisions are small
2156 2165 elif op == 'and':
2157 2166 wa, ta = optimize(x[1], True)
2158 2167 wb, tb = optimize(x[2], True)
2159 2168
2160 2169 # (::x and not ::y)/(not ::y and ::x) have a fast path
2161 2170 def isonly(revs, bases):
2162 2171 return (
2163 2172 revs[0] == 'func'
2164 2173 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2165 2174 and bases[0] == 'not'
2166 2175 and bases[1][0] == 'func'
2167 2176 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2168 2177
2169 2178 w = min(wa, wb)
2170 2179 if isonly(ta, tb):
2171 2180 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2172 2181 if isonly(tb, ta):
2173 2182 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2174 2183
2175 2184 if wa > wb:
2176 2185 return w, (op, tb, ta)
2177 2186 return w, (op, ta, tb)
2178 2187 elif op == 'or':
2179 2188 # fast path for machine-generated expression, that is likely to have
2180 2189 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
2181 2190 ws, ts, ss = [], [], []
2182 2191 def flushss():
2183 2192 if not ss:
2184 2193 return
2185 2194 if len(ss) == 1:
2186 2195 w, t = ss[0]
2187 2196 else:
2188 2197 s = '\0'.join(t[1] for w, t in ss)
2189 2198 y = ('func', ('symbol', '_list'), ('string', s))
2190 2199 w, t = optimize(y, False)
2191 2200 ws.append(w)
2192 2201 ts.append(t)
2193 2202 del ss[:]
2194 2203 for y in x[1:]:
2195 2204 w, t = optimize(y, False)
2196 2205 if t[0] == 'string' or t[0] == 'symbol':
2197 2206 ss.append((w, t))
2198 2207 continue
2199 2208 flushss()
2200 2209 ws.append(w)
2201 2210 ts.append(t)
2202 2211 flushss()
2203 2212 if len(ts) == 1:
2204 2213 return ws[0], ts[0] # 'or' operation is fully optimized out
2205 2214 # we can't reorder trees by weight because it would change the order.
2206 2215 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
2207 2216 # ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0]))
2208 2217 return max(ws), (op,) + tuple(ts)
2209 2218 elif op == 'not':
2210 2219 # Optimize not public() to _notpublic() because we have a fast version
2211 2220 if x[1] == ('func', ('symbol', 'public'), None):
2212 2221 newsym = ('func', ('symbol', '_notpublic'), None)
2213 2222 o = optimize(newsym, not small)
2214 2223 return o[0], o[1]
2215 2224 else:
2216 2225 o = optimize(x[1], not small)
2217 2226 return o[0], (op, o[1])
2218 2227 elif op == 'parentpost':
2219 2228 o = optimize(x[1], small)
2220 2229 return o[0], (op, o[1])
2221 2230 elif op == 'group':
2222 2231 return optimize(x[1], small)
2223 2232 elif op in 'dagrange range list parent ancestorspec':
2224 2233 if op == 'parent':
2225 2234 # x^:y means (x^) : y, not x ^ (:y)
2226 2235 post = ('parentpost', x[1])
2227 2236 if x[2][0] == 'dagrangepre':
2228 2237 return optimize(('dagrange', post, x[2][1]), small)
2229 2238 elif x[2][0] == 'rangepre':
2230 2239 return optimize(('range', post, x[2][1]), small)
2231 2240
2232 2241 wa, ta = optimize(x[1], small)
2233 2242 wb, tb = optimize(x[2], small)
2234 2243 return wa + wb, (op, ta, tb)
2235 2244 elif op == 'func':
2236 2245 f = getstring(x[1], _("not a symbol"))
2237 2246 wa, ta = optimize(x[2], small)
2238 2247 if f in ("author branch closed date desc file grep keyword "
2239 2248 "outgoing user"):
2240 2249 w = 10 # slow
2241 2250 elif f in "modifies adds removes":
2242 2251 w = 30 # slower
2243 2252 elif f == "contains":
2244 2253 w = 100 # very slow
2245 2254 elif f == "ancestor":
2246 2255 w = 1 * smallbonus
2247 2256 elif f in "reverse limit first _intlist":
2248 2257 w = 0
2249 2258 elif f in "sort":
2250 2259 w = 10 # assume most sorts look at changelog
2251 2260 else:
2252 2261 w = 1
2253 2262 return w + wa, (op, x[1], ta)
2254 2263 return 1, x
2255 2264
2256 2265 _aliasarg = ('func', ('symbol', '_aliasarg'))
2257 2266 def _getaliasarg(tree):
2258 2267 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
2259 2268 return X, None otherwise.
2260 2269 """
2261 2270 if (len(tree) == 3 and tree[:2] == _aliasarg
2262 2271 and tree[2][0] == 'string'):
2263 2272 return tree[2][1]
2264 2273 return None
2265 2274
2266 2275 def _checkaliasarg(tree, known=None):
2267 2276 """Check tree contains no _aliasarg construct or only ones which
2268 2277 value is in known. Used to avoid alias placeholders injection.
2269 2278 """
2270 2279 if isinstance(tree, tuple):
2271 2280 arg = _getaliasarg(tree)
2272 2281 if arg is not None and (not known or arg not in known):
2273 2282 raise error.UnknownIdentifier('_aliasarg', [])
2274 2283 for t in tree:
2275 2284 _checkaliasarg(t, known)
2276 2285
2277 2286 # the set of valid characters for the initial letter of symbols in
2278 2287 # alias declarations and definitions
2279 2288 _aliassyminitletters = set(c for c in [chr(i) for i in xrange(256)]
2280 2289 if c.isalnum() or c in '._@$' or ord(c) > 127)
2281 2290
2282 2291 def _tokenizealias(program, lookup=None):
2283 2292 """Parse alias declaration/definition into a stream of tokens
2284 2293
2285 2294 This allows symbol names to use also ``$`` as an initial letter
2286 2295 (for backward compatibility), and callers of this function should
2287 2296 examine whether ``$`` is used also for unexpected symbols or not.
2288 2297 """
2289 2298 return tokenize(program, lookup=lookup,
2290 2299 syminitletters=_aliassyminitletters)
2291 2300
2292 2301 def _parsealiasdecl(decl):
2293 2302 """Parse alias declaration ``decl``
2294 2303
2295 2304 This returns ``(name, tree, args, errorstr)`` tuple:
2296 2305
2297 2306 - ``name``: of declared alias (may be ``decl`` itself at error)
2298 2307 - ``tree``: parse result (or ``None`` at error)
2299 2308 - ``args``: list of alias argument names (or None for symbol declaration)
2300 2309 - ``errorstr``: detail about detected error (or None)
2301 2310
2302 2311 >>> _parsealiasdecl('foo')
2303 2312 ('foo', ('symbol', 'foo'), None, None)
2304 2313 >>> _parsealiasdecl('$foo')
2305 2314 ('$foo', None, None, "'$' not for alias arguments")
2306 2315 >>> _parsealiasdecl('foo::bar')
2307 2316 ('foo::bar', None, None, 'invalid format')
2308 2317 >>> _parsealiasdecl('foo bar')
2309 2318 ('foo bar', None, None, 'at 4: invalid token')
2310 2319 >>> _parsealiasdecl('foo()')
2311 2320 ('foo', ('func', ('symbol', 'foo')), [], None)
2312 2321 >>> _parsealiasdecl('$foo()')
2313 2322 ('$foo()', None, None, "'$' not for alias arguments")
2314 2323 >>> _parsealiasdecl('foo($1, $2)')
2315 2324 ('foo', ('func', ('symbol', 'foo')), ['$1', '$2'], None)
2316 2325 >>> _parsealiasdecl('foo(bar_bar, baz.baz)')
2317 2326 ('foo', ('func', ('symbol', 'foo')), ['bar_bar', 'baz.baz'], None)
2318 2327 >>> _parsealiasdecl('foo($1, $2, nested($1, $2))')
2319 2328 ('foo($1, $2, nested($1, $2))', None, None, 'invalid argument list')
2320 2329 >>> _parsealiasdecl('foo(bar($1, $2))')
2321 2330 ('foo(bar($1, $2))', None, None, 'invalid argument list')
2322 2331 >>> _parsealiasdecl('foo("string")')
2323 2332 ('foo("string")', None, None, 'invalid argument list')
2324 2333 >>> _parsealiasdecl('foo($1, $2')
2325 2334 ('foo($1, $2', None, None, 'at 10: unexpected token: end')
2326 2335 >>> _parsealiasdecl('foo("string')
2327 2336 ('foo("string', None, None, 'at 5: unterminated string')
2328 2337 >>> _parsealiasdecl('foo($1, $2, $1)')
2329 2338 ('foo', None, None, 'argument names collide with each other')
2330 2339 """
2331 2340 p = parser.parser(_tokenizealias, elements)
2332 2341 try:
2333 2342 tree, pos = p.parse(decl)
2334 2343 if (pos != len(decl)):
2335 2344 raise error.ParseError(_('invalid token'), pos)
2336 2345
2337 2346 if isvalidsymbol(tree):
2338 2347 # "name = ...." style
2339 2348 name = getsymbol(tree)
2340 2349 if name.startswith('$'):
2341 2350 return (decl, None, None, _("'$' not for alias arguments"))
2342 2351 return (name, ('symbol', name), None, None)
2343 2352
2344 2353 if isvalidfunc(tree):
2345 2354 # "name(arg, ....) = ...." style
2346 2355 name = getfuncname(tree)
2347 2356 if name.startswith('$'):
2348 2357 return (decl, None, None, _("'$' not for alias arguments"))
2349 2358 args = []
2350 2359 for arg in getfuncargs(tree):
2351 2360 if not isvalidsymbol(arg):
2352 2361 return (decl, None, None, _("invalid argument list"))
2353 2362 args.append(getsymbol(arg))
2354 2363 if len(args) != len(set(args)):
2355 2364 return (name, None, None,
2356 2365 _("argument names collide with each other"))
2357 2366 return (name, ('func', ('symbol', name)), args, None)
2358 2367
2359 2368 return (decl, None, None, _("invalid format"))
2360 2369 except error.ParseError, inst:
2361 2370 return (decl, None, None, parseerrordetail(inst))
2362 2371
2363 2372 def _parsealiasdefn(defn, args):
2364 2373 """Parse alias definition ``defn``
2365 2374
2366 2375 This function also replaces alias argument references in the
2367 2376 specified definition by ``_aliasarg(ARGNAME)``.
2368 2377
2369 2378 ``args`` is a list of alias argument names, or None if the alias
2370 2379 is declared as a symbol.
2371 2380
2372 2381 This returns "tree" as parsing result.
2373 2382
2374 2383 >>> args = ['$1', '$2', 'foo']
2375 2384 >>> print prettyformat(_parsealiasdefn('$1 or foo', args))
2376 2385 (or
2377 2386 (func
2378 2387 ('symbol', '_aliasarg')
2379 2388 ('string', '$1'))
2380 2389 (func
2381 2390 ('symbol', '_aliasarg')
2382 2391 ('string', 'foo')))
2383 2392 >>> try:
2384 2393 ... _parsealiasdefn('$1 or $bar', args)
2385 2394 ... except error.ParseError, inst:
2386 2395 ... print parseerrordetail(inst)
2387 2396 at 6: '$' not for alias arguments
2388 2397 >>> args = ['$1', '$10', 'foo']
2389 2398 >>> print prettyformat(_parsealiasdefn('$10 or foobar', args))
2390 2399 (or
2391 2400 (func
2392 2401 ('symbol', '_aliasarg')
2393 2402 ('string', '$10'))
2394 2403 ('symbol', 'foobar'))
2395 2404 >>> print prettyformat(_parsealiasdefn('"$1" or "foo"', args))
2396 2405 (or
2397 2406 ('string', '$1')
2398 2407 ('string', 'foo'))
2399 2408 """
2400 2409 def tokenizedefn(program, lookup=None):
2401 2410 if args:
2402 2411 argset = set(args)
2403 2412 else:
2404 2413 argset = set()
2405 2414
2406 2415 for t, value, pos in _tokenizealias(program, lookup=lookup):
2407 2416 if t == 'symbol':
2408 2417 if value in argset:
2409 2418 # emulate tokenization of "_aliasarg('ARGNAME')":
2410 2419 # "_aliasarg()" is an unknown symbol only used separate
2411 2420 # alias argument placeholders from regular strings.
2412 2421 yield ('symbol', '_aliasarg', pos)
2413 2422 yield ('(', None, pos)
2414 2423 yield ('string', value, pos)
2415 2424 yield (')', None, pos)
2416 2425 continue
2417 2426 elif value.startswith('$'):
2418 2427 raise error.ParseError(_("'$' not for alias arguments"),
2419 2428 pos)
2420 2429 yield (t, value, pos)
2421 2430
2422 2431 p = parser.parser(tokenizedefn, elements)
2423 2432 tree, pos = p.parse(defn)
2424 2433 if pos != len(defn):
2425 2434 raise error.ParseError(_('invalid token'), pos)
2426 2435 return parser.simplifyinfixops(tree, ('or',))
2427 2436
2428 2437 class revsetalias(object):
2429 2438 # whether own `error` information is already shown or not.
2430 2439 # this avoids showing same warning multiple times at each `findaliases`.
2431 2440 warned = False
2432 2441
2433 2442 def __init__(self, name, value):
2434 2443 '''Aliases like:
2435 2444
2436 2445 h = heads(default)
2437 2446 b($1) = ancestors($1) - ancestors(default)
2438 2447 '''
2439 2448 self.name, self.tree, self.args, self.error = _parsealiasdecl(name)
2440 2449 if self.error:
2441 2450 self.error = _('failed to parse the declaration of revset alias'
2442 2451 ' "%s": %s') % (self.name, self.error)
2443 2452 return
2444 2453
2445 2454 try:
2446 2455 self.replacement = _parsealiasdefn(value, self.args)
2447 2456 # Check for placeholder injection
2448 2457 _checkaliasarg(self.replacement, self.args)
2449 2458 except error.ParseError, inst:
2450 2459 self.error = _('failed to parse the definition of revset alias'
2451 2460 ' "%s": %s') % (self.name, parseerrordetail(inst))
2452 2461
2453 2462 def _getalias(aliases, tree):
2454 2463 """If tree looks like an unexpanded alias, return it. Return None
2455 2464 otherwise.
2456 2465 """
2457 2466 if isinstance(tree, tuple) and tree:
2458 2467 if tree[0] == 'symbol' and len(tree) == 2:
2459 2468 name = tree[1]
2460 2469 alias = aliases.get(name)
2461 2470 if alias and alias.args is None and alias.tree == tree:
2462 2471 return alias
2463 2472 if tree[0] == 'func' and len(tree) > 1:
2464 2473 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
2465 2474 name = tree[1][1]
2466 2475 alias = aliases.get(name)
2467 2476 if alias and alias.args is not None and alias.tree == tree[:2]:
2468 2477 return alias
2469 2478 return None
2470 2479
2471 2480 def _expandargs(tree, args):
2472 2481 """Replace _aliasarg instances with the substitution value of the
2473 2482 same name in args, recursively.
2474 2483 """
2475 2484 if not tree or not isinstance(tree, tuple):
2476 2485 return tree
2477 2486 arg = _getaliasarg(tree)
2478 2487 if arg is not None:
2479 2488 return args[arg]
2480 2489 return tuple(_expandargs(t, args) for t in tree)
2481 2490
2482 2491 def _expandaliases(aliases, tree, expanding, cache):
2483 2492 """Expand aliases in tree, recursively.
2484 2493
2485 2494 'aliases' is a dictionary mapping user defined aliases to
2486 2495 revsetalias objects.
2487 2496 """
2488 2497 if not isinstance(tree, tuple):
2489 2498 # Do not expand raw strings
2490 2499 return tree
2491 2500 alias = _getalias(aliases, tree)
2492 2501 if alias is not None:
2493 2502 if alias.error:
2494 2503 raise util.Abort(alias.error)
2495 2504 if alias in expanding:
2496 2505 raise error.ParseError(_('infinite expansion of revset alias "%s" '
2497 2506 'detected') % alias.name)
2498 2507 expanding.append(alias)
2499 2508 if alias.name not in cache:
2500 2509 cache[alias.name] = _expandaliases(aliases, alias.replacement,
2501 2510 expanding, cache)
2502 2511 result = cache[alias.name]
2503 2512 expanding.pop()
2504 2513 if alias.args is not None:
2505 2514 l = getlist(tree[2])
2506 2515 if len(l) != len(alias.args):
2507 2516 raise error.ParseError(
2508 2517 _('invalid number of arguments: %s') % len(l))
2509 2518 l = [_expandaliases(aliases, a, [], cache) for a in l]
2510 2519 result = _expandargs(result, dict(zip(alias.args, l)))
2511 2520 else:
2512 2521 result = tuple(_expandaliases(aliases, t, expanding, cache)
2513 2522 for t in tree)
2514 2523 return result
2515 2524
2516 2525 def findaliases(ui, tree, showwarning=None):
2517 2526 _checkaliasarg(tree)
2518 2527 aliases = {}
2519 2528 for k, v in ui.configitems('revsetalias'):
2520 2529 alias = revsetalias(k, v)
2521 2530 aliases[alias.name] = alias
2522 2531 tree = _expandaliases(aliases, tree, [], {})
2523 2532 if showwarning:
2524 2533 # warn about problematic (but not referred) aliases
2525 2534 for name, alias in sorted(aliases.iteritems()):
2526 2535 if alias.error and not alias.warned:
2527 2536 showwarning(_('warning: %s\n') % (alias.error))
2528 2537 alias.warned = True
2529 2538 return tree
2530 2539
2531 2540 def foldconcat(tree):
2532 2541 """Fold elements to be concatenated by `##`
2533 2542 """
2534 2543 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2535 2544 return tree
2536 2545 if tree[0] == '_concat':
2537 2546 pending = [tree]
2538 2547 l = []
2539 2548 while pending:
2540 2549 e = pending.pop()
2541 2550 if e[0] == '_concat':
2542 2551 pending.extend(reversed(e[1:]))
2543 2552 elif e[0] in ('string', 'symbol'):
2544 2553 l.append(e[1])
2545 2554 else:
2546 2555 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2547 2556 raise error.ParseError(msg)
2548 2557 return ('string', ''.join(l))
2549 2558 else:
2550 2559 return tuple(foldconcat(t) for t in tree)
2551 2560
2552 2561 def parse(spec, lookup=None):
2553 2562 p = parser.parser(tokenize, elements)
2554 2563 tree, pos = p.parse(spec, lookup=lookup)
2555 2564 if pos != len(spec):
2556 2565 raise error.ParseError(_("invalid token"), pos)
2557 2566 return parser.simplifyinfixops(tree, ('or',))
2558 2567
2559 2568 def posttreebuilthook(tree, repo):
2560 2569 # hook for extensions to execute code on the optimized tree
2561 2570 pass
2562 2571
2563 2572 def match(ui, spec, repo=None):
2564 2573 if not spec:
2565 2574 raise error.ParseError(_("empty query"))
2566 2575 lookup = None
2567 2576 if repo:
2568 2577 lookup = repo.__contains__
2569 2578 tree = parse(spec, lookup)
2570 2579 if ui:
2571 2580 tree = findaliases(ui, tree, showwarning=ui.warn)
2572 2581 tree = foldconcat(tree)
2573 2582 weight, tree = optimize(tree, True)
2574 2583 posttreebuilthook(tree, repo)
2575 2584 def mfunc(repo, subset=None):
2576 2585 if subset is None:
2577 2586 subset = fullreposet(repo)
2578 2587 if util.safehasattr(subset, 'isascending'):
2579 2588 result = getset(repo, subset, tree)
2580 2589 else:
2581 2590 result = getset(repo, baseset(subset), tree)
2582 2591 return result
2583 2592 return mfunc
2584 2593
2585 2594 def formatspec(expr, *args):
2586 2595 '''
2587 2596 This is a convenience function for using revsets internally, and
2588 2597 escapes arguments appropriately. Aliases are intentionally ignored
2589 2598 so that intended expression behavior isn't accidentally subverted.
2590 2599
2591 2600 Supported arguments:
2592 2601
2593 2602 %r = revset expression, parenthesized
2594 2603 %d = int(arg), no quoting
2595 2604 %s = string(arg), escaped and single-quoted
2596 2605 %b = arg.branch(), escaped and single-quoted
2597 2606 %n = hex(arg), single-quoted
2598 2607 %% = a literal '%'
2599 2608
2600 2609 Prefixing the type with 'l' specifies a parenthesized list of that type.
2601 2610
2602 2611 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2603 2612 '(10 or 11):: and ((this()) or (that()))'
2604 2613 >>> formatspec('%d:: and not %d::', 10, 20)
2605 2614 '10:: and not 20::'
2606 2615 >>> formatspec('%ld or %ld', [], [1])
2607 2616 "_list('') or 1"
2608 2617 >>> formatspec('keyword(%s)', 'foo\\xe9')
2609 2618 "keyword('foo\\\\xe9')"
2610 2619 >>> b = lambda: 'default'
2611 2620 >>> b.branch = b
2612 2621 >>> formatspec('branch(%b)', b)
2613 2622 "branch('default')"
2614 2623 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2615 2624 "root(_list('a\\x00b\\x00c\\x00d'))"
2616 2625 '''
2617 2626
2618 2627 def quote(s):
2619 2628 return repr(str(s))
2620 2629
2621 2630 def argtype(c, arg):
2622 2631 if c == 'd':
2623 2632 return str(int(arg))
2624 2633 elif c == 's':
2625 2634 return quote(arg)
2626 2635 elif c == 'r':
2627 2636 parse(arg) # make sure syntax errors are confined
2628 2637 return '(%s)' % arg
2629 2638 elif c == 'n':
2630 2639 return quote(node.hex(arg))
2631 2640 elif c == 'b':
2632 2641 return quote(arg.branch())
2633 2642
2634 2643 def listexp(s, t):
2635 2644 l = len(s)
2636 2645 if l == 0:
2637 2646 return "_list('')"
2638 2647 elif l == 1:
2639 2648 return argtype(t, s[0])
2640 2649 elif t == 'd':
2641 2650 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2642 2651 elif t == 's':
2643 2652 return "_list('%s')" % "\0".join(s)
2644 2653 elif t == 'n':
2645 2654 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2646 2655 elif t == 'b':
2647 2656 return "_list('%s')" % "\0".join(a.branch() for a in s)
2648 2657
2649 2658 m = l // 2
2650 2659 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2651 2660
2652 2661 ret = ''
2653 2662 pos = 0
2654 2663 arg = 0
2655 2664 while pos < len(expr):
2656 2665 c = expr[pos]
2657 2666 if c == '%':
2658 2667 pos += 1
2659 2668 d = expr[pos]
2660 2669 if d == '%':
2661 2670 ret += d
2662 2671 elif d in 'dsnbr':
2663 2672 ret += argtype(d, args[arg])
2664 2673 arg += 1
2665 2674 elif d == 'l':
2666 2675 # a list of some type
2667 2676 pos += 1
2668 2677 d = expr[pos]
2669 2678 ret += listexp(list(args[arg]), d)
2670 2679 arg += 1
2671 2680 else:
2672 2681 raise util.Abort('unexpected revspec format character %s' % d)
2673 2682 else:
2674 2683 ret += c
2675 2684 pos += 1
2676 2685
2677 2686 return ret
2678 2687
2679 2688 def prettyformat(tree):
2680 2689 return parser.prettyformat(tree, ('string', 'symbol'))
2681 2690
2682 2691 def depth(tree):
2683 2692 if isinstance(tree, tuple):
2684 2693 return max(map(depth, tree)) + 1
2685 2694 else:
2686 2695 return 0
2687 2696
2688 2697 def funcsused(tree):
2689 2698 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2690 2699 return set()
2691 2700 else:
2692 2701 funcs = set()
2693 2702 for s in tree[1:]:
2694 2703 funcs |= funcsused(s)
2695 2704 if tree[0] == 'func':
2696 2705 funcs.add(tree[1][1])
2697 2706 return funcs
2698 2707
2699 2708 class abstractsmartset(object):
2700 2709
2701 2710 def __nonzero__(self):
2702 2711 """True if the smartset is not empty"""
2703 2712 raise NotImplementedError()
2704 2713
2705 2714 def __contains__(self, rev):
2706 2715 """provide fast membership testing"""
2707 2716 raise NotImplementedError()
2708 2717
2709 2718 def __iter__(self):
2710 2719 """iterate the set in the order it is supposed to be iterated"""
2711 2720 raise NotImplementedError()
2712 2721
2713 2722 # Attributes containing a function to perform a fast iteration in a given
2714 2723 # direction. A smartset can have none, one, or both defined.
2715 2724 #
2716 2725 # Default value is None instead of a function returning None to avoid
2717 2726 # initializing an iterator just for testing if a fast method exists.
2718 2727 fastasc = None
2719 2728 fastdesc = None
2720 2729
2721 2730 def isascending(self):
2722 2731 """True if the set will iterate in ascending order"""
2723 2732 raise NotImplementedError()
2724 2733
2725 2734 def isdescending(self):
2726 2735 """True if the set will iterate in descending order"""
2727 2736 raise NotImplementedError()
2728 2737
2729 2738 def min(self):
2730 2739 """return the minimum element in the set"""
2731 2740 if self.fastasc is not None:
2732 2741 for r in self.fastasc():
2733 2742 return r
2734 2743 raise ValueError('arg is an empty sequence')
2735 2744 return min(self)
2736 2745
2737 2746 def max(self):
2738 2747 """return the maximum element in the set"""
2739 2748 if self.fastdesc is not None:
2740 2749 for r in self.fastdesc():
2741 2750 return r
2742 2751 raise ValueError('arg is an empty sequence')
2743 2752 return max(self)
2744 2753
2745 2754 def first(self):
2746 2755 """return the first element in the set (user iteration perspective)
2747 2756
2748 2757 Return None if the set is empty"""
2749 2758 raise NotImplementedError()
2750 2759
2751 2760 def last(self):
2752 2761 """return the last element in the set (user iteration perspective)
2753 2762
2754 2763 Return None if the set is empty"""
2755 2764 raise NotImplementedError()
2756 2765
2757 2766 def __len__(self):
2758 2767 """return the length of the smartsets
2759 2768
2760 2769 This can be expensive on smartset that could be lazy otherwise."""
2761 2770 raise NotImplementedError()
2762 2771
2763 2772 def reverse(self):
2764 2773 """reverse the expected iteration order"""
2765 2774 raise NotImplementedError()
2766 2775
2767 2776 def sort(self, reverse=True):
2768 2777 """get the set to iterate in an ascending or descending order"""
2769 2778 raise NotImplementedError()
2770 2779
2771 2780 def __and__(self, other):
2772 2781 """Returns a new object with the intersection of the two collections.
2773 2782
2774 2783 This is part of the mandatory API for smartset."""
2775 2784 if isinstance(other, fullreposet):
2776 2785 return self
2777 2786 return self.filter(other.__contains__, cache=False)
2778 2787
2779 2788 def __add__(self, other):
2780 2789 """Returns a new object with the union of the two collections.
2781 2790
2782 2791 This is part of the mandatory API for smartset."""
2783 2792 return addset(self, other)
2784 2793
2785 2794 def __sub__(self, other):
2786 2795 """Returns a new object with the substraction of the two collections.
2787 2796
2788 2797 This is part of the mandatory API for smartset."""
2789 2798 c = other.__contains__
2790 2799 return self.filter(lambda r: not c(r), cache=False)
2791 2800
2792 2801 def filter(self, condition, cache=True):
2793 2802 """Returns this smartset filtered by condition as a new smartset.
2794 2803
2795 2804 `condition` is a callable which takes a revision number and returns a
2796 2805 boolean.
2797 2806
2798 2807 This is part of the mandatory API for smartset."""
2799 2808 # builtin cannot be cached. but do not needs to
2800 2809 if cache and util.safehasattr(condition, 'func_code'):
2801 2810 condition = util.cachefunc(condition)
2802 2811 return filteredset(self, condition)
2803 2812
2804 2813 class baseset(abstractsmartset):
2805 2814 """Basic data structure that represents a revset and contains the basic
2806 2815 operation that it should be able to perform.
2807 2816
2808 2817 Every method in this class should be implemented by any smartset class.
2809 2818 """
2810 2819 def __init__(self, data=()):
2811 2820 if not isinstance(data, list):
2812 2821 data = list(data)
2813 2822 self._list = data
2814 2823 self._ascending = None
2815 2824
2816 2825 @util.propertycache
2817 2826 def _set(self):
2818 2827 return set(self._list)
2819 2828
2820 2829 @util.propertycache
2821 2830 def _asclist(self):
2822 2831 asclist = self._list[:]
2823 2832 asclist.sort()
2824 2833 return asclist
2825 2834
2826 2835 def __iter__(self):
2827 2836 if self._ascending is None:
2828 2837 return iter(self._list)
2829 2838 elif self._ascending:
2830 2839 return iter(self._asclist)
2831 2840 else:
2832 2841 return reversed(self._asclist)
2833 2842
2834 2843 def fastasc(self):
2835 2844 return iter(self._asclist)
2836 2845
2837 2846 def fastdesc(self):
2838 2847 return reversed(self._asclist)
2839 2848
2840 2849 @util.propertycache
2841 2850 def __contains__(self):
2842 2851 return self._set.__contains__
2843 2852
2844 2853 def __nonzero__(self):
2845 2854 return bool(self._list)
2846 2855
2847 2856 def sort(self, reverse=False):
2848 2857 self._ascending = not bool(reverse)
2849 2858
2850 2859 def reverse(self):
2851 2860 if self._ascending is None:
2852 2861 self._list.reverse()
2853 2862 else:
2854 2863 self._ascending = not self._ascending
2855 2864
2856 2865 def __len__(self):
2857 2866 return len(self._list)
2858 2867
2859 2868 def isascending(self):
2860 2869 """Returns True if the collection is ascending order, False if not.
2861 2870
2862 2871 This is part of the mandatory API for smartset."""
2863 2872 if len(self) <= 1:
2864 2873 return True
2865 2874 return self._ascending is not None and self._ascending
2866 2875
2867 2876 def isdescending(self):
2868 2877 """Returns True if the collection is descending order, False if not.
2869 2878
2870 2879 This is part of the mandatory API for smartset."""
2871 2880 if len(self) <= 1:
2872 2881 return True
2873 2882 return self._ascending is not None and not self._ascending
2874 2883
2875 2884 def first(self):
2876 2885 if self:
2877 2886 if self._ascending is None:
2878 2887 return self._list[0]
2879 2888 elif self._ascending:
2880 2889 return self._asclist[0]
2881 2890 else:
2882 2891 return self._asclist[-1]
2883 2892 return None
2884 2893
2885 2894 def last(self):
2886 2895 if self:
2887 2896 if self._ascending is None:
2888 2897 return self._list[-1]
2889 2898 elif self._ascending:
2890 2899 return self._asclist[-1]
2891 2900 else:
2892 2901 return self._asclist[0]
2893 2902 return None
2894 2903
2895 2904 def __repr__(self):
2896 2905 d = {None: '', False: '-', True: '+'}[self._ascending]
2897 2906 return '<%s%s %r>' % (type(self).__name__, d, self._list)
2898 2907
2899 2908 class filteredset(abstractsmartset):
2900 2909 """Duck type for baseset class which iterates lazily over the revisions in
2901 2910 the subset and contains a function which tests for membership in the
2902 2911 revset
2903 2912 """
2904 2913 def __init__(self, subset, condition=lambda x: True):
2905 2914 """
2906 2915 condition: a function that decide whether a revision in the subset
2907 2916 belongs to the revset or not.
2908 2917 """
2909 2918 self._subset = subset
2910 2919 self._condition = condition
2911 2920 self._cache = {}
2912 2921
2913 2922 def __contains__(self, x):
2914 2923 c = self._cache
2915 2924 if x not in c:
2916 2925 v = c[x] = x in self._subset and self._condition(x)
2917 2926 return v
2918 2927 return c[x]
2919 2928
2920 2929 def __iter__(self):
2921 2930 return self._iterfilter(self._subset)
2922 2931
2923 2932 def _iterfilter(self, it):
2924 2933 cond = self._condition
2925 2934 for x in it:
2926 2935 if cond(x):
2927 2936 yield x
2928 2937
2929 2938 @property
2930 2939 def fastasc(self):
2931 2940 it = self._subset.fastasc
2932 2941 if it is None:
2933 2942 return None
2934 2943 return lambda: self._iterfilter(it())
2935 2944
2936 2945 @property
2937 2946 def fastdesc(self):
2938 2947 it = self._subset.fastdesc
2939 2948 if it is None:
2940 2949 return None
2941 2950 return lambda: self._iterfilter(it())
2942 2951
2943 2952 def __nonzero__(self):
2944 2953 for r in self:
2945 2954 return True
2946 2955 return False
2947 2956
2948 2957 def __len__(self):
2949 2958 # Basic implementation to be changed in future patches.
2950 2959 l = baseset([r for r in self])
2951 2960 return len(l)
2952 2961
2953 2962 def sort(self, reverse=False):
2954 2963 self._subset.sort(reverse=reverse)
2955 2964
2956 2965 def reverse(self):
2957 2966 self._subset.reverse()
2958 2967
2959 2968 def isascending(self):
2960 2969 return self._subset.isascending()
2961 2970
2962 2971 def isdescending(self):
2963 2972 return self._subset.isdescending()
2964 2973
2965 2974 def first(self):
2966 2975 for x in self:
2967 2976 return x
2968 2977 return None
2969 2978
2970 2979 def last(self):
2971 2980 it = None
2972 2981 if self._subset.isascending:
2973 2982 it = self.fastdesc
2974 2983 elif self._subset.isdescending:
2975 2984 it = self.fastdesc
2976 2985 if it is None:
2977 2986 # slowly consume everything. This needs improvement
2978 2987 it = lambda: reversed(list(self))
2979 2988 for x in it():
2980 2989 return x
2981 2990 return None
2982 2991
2983 2992 def __repr__(self):
2984 2993 return '<%s %r>' % (type(self).__name__, self._subset)
2985 2994
2986 2995 # this function will be removed, or merged to addset or orset, when
2987 2996 # - scmutil.revrange() can be rewritten to not combine calculated smartsets
2988 2997 # - or addset can handle more than two sets without balanced tree
2989 2998 def _combinesets(subsets):
2990 2999 """Create balanced tree of addsets representing union of given sets"""
2991 3000 if not subsets:
2992 3001 return baseset()
2993 3002 if len(subsets) == 1:
2994 3003 return subsets[0]
2995 3004 p = len(subsets) // 2
2996 3005 xs = _combinesets(subsets[:p])
2997 3006 ys = _combinesets(subsets[p:])
2998 3007 return addset(xs, ys)
2999 3008
3000 3009 def _iterordered(ascending, iter1, iter2):
3001 3010 """produce an ordered iteration from two iterators with the same order
3002 3011
3003 3012 The ascending is used to indicated the iteration direction.
3004 3013 """
3005 3014 choice = max
3006 3015 if ascending:
3007 3016 choice = min
3008 3017
3009 3018 val1 = None
3010 3019 val2 = None
3011 3020 try:
3012 3021 # Consume both iterators in an ordered way until one is empty
3013 3022 while True:
3014 3023 if val1 is None:
3015 3024 val1 = iter1.next()
3016 3025 if val2 is None:
3017 3026 val2 = iter2.next()
3018 3027 next = choice(val1, val2)
3019 3028 yield next
3020 3029 if val1 == next:
3021 3030 val1 = None
3022 3031 if val2 == next:
3023 3032 val2 = None
3024 3033 except StopIteration:
3025 3034 # Flush any remaining values and consume the other one
3026 3035 it = iter2
3027 3036 if val1 is not None:
3028 3037 yield val1
3029 3038 it = iter1
3030 3039 elif val2 is not None:
3031 3040 # might have been equality and both are empty
3032 3041 yield val2
3033 3042 for val in it:
3034 3043 yield val
3035 3044
3036 3045 class addset(abstractsmartset):
3037 3046 """Represent the addition of two sets
3038 3047
3039 3048 Wrapper structure for lazily adding two structures without losing much
3040 3049 performance on the __contains__ method
3041 3050
3042 3051 If the ascending attribute is set, that means the two structures are
3043 3052 ordered in either an ascending or descending way. Therefore, we can add
3044 3053 them maintaining the order by iterating over both at the same time
3045 3054
3046 3055 >>> xs = baseset([0, 3, 2])
3047 3056 >>> ys = baseset([5, 2, 4])
3048 3057
3049 3058 >>> rs = addset(xs, ys)
3050 3059 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
3051 3060 (True, True, False, True, 0, 4)
3052 3061 >>> rs = addset(xs, baseset([]))
3053 3062 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
3054 3063 (True, True, False, 0, 2)
3055 3064 >>> rs = addset(baseset([]), baseset([]))
3056 3065 >>> bool(rs), 0 in rs, rs.first(), rs.last()
3057 3066 (False, False, None, None)
3058 3067
3059 3068 iterate unsorted:
3060 3069 >>> rs = addset(xs, ys)
3061 3070 >>> [x for x in rs] # without _genlist
3062 3071 [0, 3, 2, 5, 4]
3063 3072 >>> assert not rs._genlist
3064 3073 >>> len(rs)
3065 3074 5
3066 3075 >>> [x for x in rs] # with _genlist
3067 3076 [0, 3, 2, 5, 4]
3068 3077 >>> assert rs._genlist
3069 3078
3070 3079 iterate ascending:
3071 3080 >>> rs = addset(xs, ys, ascending=True)
3072 3081 >>> [x for x in rs], [x for x in rs.fastasc()] # without _asclist
3073 3082 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3074 3083 >>> assert not rs._asclist
3075 3084 >>> len(rs)
3076 3085 5
3077 3086 >>> [x for x in rs], [x for x in rs.fastasc()]
3078 3087 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3079 3088 >>> assert rs._asclist
3080 3089
3081 3090 iterate descending:
3082 3091 >>> rs = addset(xs, ys, ascending=False)
3083 3092 >>> [x for x in rs], [x for x in rs.fastdesc()] # without _asclist
3084 3093 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3085 3094 >>> assert not rs._asclist
3086 3095 >>> len(rs)
3087 3096 5
3088 3097 >>> [x for x in rs], [x for x in rs.fastdesc()]
3089 3098 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3090 3099 >>> assert rs._asclist
3091 3100
3092 3101 iterate ascending without fastasc:
3093 3102 >>> rs = addset(xs, generatorset(ys), ascending=True)
3094 3103 >>> assert rs.fastasc is None
3095 3104 >>> [x for x in rs]
3096 3105 [0, 2, 3, 4, 5]
3097 3106
3098 3107 iterate descending without fastdesc:
3099 3108 >>> rs = addset(generatorset(xs), ys, ascending=False)
3100 3109 >>> assert rs.fastdesc is None
3101 3110 >>> [x for x in rs]
3102 3111 [5, 4, 3, 2, 0]
3103 3112 """
3104 3113 def __init__(self, revs1, revs2, ascending=None):
3105 3114 self._r1 = revs1
3106 3115 self._r2 = revs2
3107 3116 self._iter = None
3108 3117 self._ascending = ascending
3109 3118 self._genlist = None
3110 3119 self._asclist = None
3111 3120
3112 3121 def __len__(self):
3113 3122 return len(self._list)
3114 3123
3115 3124 def __nonzero__(self):
3116 3125 return bool(self._r1) or bool(self._r2)
3117 3126
3118 3127 @util.propertycache
3119 3128 def _list(self):
3120 3129 if not self._genlist:
3121 3130 self._genlist = baseset(iter(self))
3122 3131 return self._genlist
3123 3132
3124 3133 def __iter__(self):
3125 3134 """Iterate over both collections without repeating elements
3126 3135
3127 3136 If the ascending attribute is not set, iterate over the first one and
3128 3137 then over the second one checking for membership on the first one so we
3129 3138 dont yield any duplicates.
3130 3139
3131 3140 If the ascending attribute is set, iterate over both collections at the
3132 3141 same time, yielding only one value at a time in the given order.
3133 3142 """
3134 3143 if self._ascending is None:
3135 3144 if self._genlist:
3136 3145 return iter(self._genlist)
3137 3146 def arbitraryordergen():
3138 3147 for r in self._r1:
3139 3148 yield r
3140 3149 inr1 = self._r1.__contains__
3141 3150 for r in self._r2:
3142 3151 if not inr1(r):
3143 3152 yield r
3144 3153 return arbitraryordergen()
3145 3154 # try to use our own fast iterator if it exists
3146 3155 self._trysetasclist()
3147 3156 if self._ascending:
3148 3157 attr = 'fastasc'
3149 3158 else:
3150 3159 attr = 'fastdesc'
3151 3160 it = getattr(self, attr)
3152 3161 if it is not None:
3153 3162 return it()
3154 3163 # maybe half of the component supports fast
3155 3164 # get iterator for _r1
3156 3165 iter1 = getattr(self._r1, attr)
3157 3166 if iter1 is None:
3158 3167 # let's avoid side effect (not sure it matters)
3159 3168 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
3160 3169 else:
3161 3170 iter1 = iter1()
3162 3171 # get iterator for _r2
3163 3172 iter2 = getattr(self._r2, attr)
3164 3173 if iter2 is None:
3165 3174 # let's avoid side effect (not sure it matters)
3166 3175 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
3167 3176 else:
3168 3177 iter2 = iter2()
3169 3178 return _iterordered(self._ascending, iter1, iter2)
3170 3179
3171 3180 def _trysetasclist(self):
3172 3181 """populate the _asclist attribute if possible and necessary"""
3173 3182 if self._genlist is not None and self._asclist is None:
3174 3183 self._asclist = sorted(self._genlist)
3175 3184
3176 3185 @property
3177 3186 def fastasc(self):
3178 3187 self._trysetasclist()
3179 3188 if self._asclist is not None:
3180 3189 return self._asclist.__iter__
3181 3190 iter1 = self._r1.fastasc
3182 3191 iter2 = self._r2.fastasc
3183 3192 if None in (iter1, iter2):
3184 3193 return None
3185 3194 return lambda: _iterordered(True, iter1(), iter2())
3186 3195
3187 3196 @property
3188 3197 def fastdesc(self):
3189 3198 self._trysetasclist()
3190 3199 if self._asclist is not None:
3191 3200 return self._asclist.__reversed__
3192 3201 iter1 = self._r1.fastdesc
3193 3202 iter2 = self._r2.fastdesc
3194 3203 if None in (iter1, iter2):
3195 3204 return None
3196 3205 return lambda: _iterordered(False, iter1(), iter2())
3197 3206
3198 3207 def __contains__(self, x):
3199 3208 return x in self._r1 or x in self._r2
3200 3209
3201 3210 def sort(self, reverse=False):
3202 3211 """Sort the added set
3203 3212
3204 3213 For this we use the cached list with all the generated values and if we
3205 3214 know they are ascending or descending we can sort them in a smart way.
3206 3215 """
3207 3216 self._ascending = not reverse
3208 3217
3209 3218 def isascending(self):
3210 3219 return self._ascending is not None and self._ascending
3211 3220
3212 3221 def isdescending(self):
3213 3222 return self._ascending is not None and not self._ascending
3214 3223
3215 3224 def reverse(self):
3216 3225 if self._ascending is None:
3217 3226 self._list.reverse()
3218 3227 else:
3219 3228 self._ascending = not self._ascending
3220 3229
3221 3230 def first(self):
3222 3231 for x in self:
3223 3232 return x
3224 3233 return None
3225 3234
3226 3235 def last(self):
3227 3236 self.reverse()
3228 3237 val = self.first()
3229 3238 self.reverse()
3230 3239 return val
3231 3240
3232 3241 def __repr__(self):
3233 3242 d = {None: '', False: '-', True: '+'}[self._ascending]
3234 3243 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3235 3244
3236 3245 class generatorset(abstractsmartset):
3237 3246 """Wrap a generator for lazy iteration
3238 3247
3239 3248 Wrapper structure for generators that provides lazy membership and can
3240 3249 be iterated more than once.
3241 3250 When asked for membership it generates values until either it finds the
3242 3251 requested one or has gone through all the elements in the generator
3243 3252 """
3244 3253 def __init__(self, gen, iterasc=None):
3245 3254 """
3246 3255 gen: a generator producing the values for the generatorset.
3247 3256 """
3248 3257 self._gen = gen
3249 3258 self._asclist = None
3250 3259 self._cache = {}
3251 3260 self._genlist = []
3252 3261 self._finished = False
3253 3262 self._ascending = True
3254 3263 if iterasc is not None:
3255 3264 if iterasc:
3256 3265 self.fastasc = self._iterator
3257 3266 self.__contains__ = self._asccontains
3258 3267 else:
3259 3268 self.fastdesc = self._iterator
3260 3269 self.__contains__ = self._desccontains
3261 3270
3262 3271 def __nonzero__(self):
3263 3272 # Do not use 'for r in self' because it will enforce the iteration
3264 3273 # order (default ascending), possibly unrolling a whole descending
3265 3274 # iterator.
3266 3275 if self._genlist:
3267 3276 return True
3268 3277 for r in self._consumegen():
3269 3278 return True
3270 3279 return False
3271 3280
3272 3281 def __contains__(self, x):
3273 3282 if x in self._cache:
3274 3283 return self._cache[x]
3275 3284
3276 3285 # Use new values only, as existing values would be cached.
3277 3286 for l in self._consumegen():
3278 3287 if l == x:
3279 3288 return True
3280 3289
3281 3290 self._cache[x] = False
3282 3291 return False
3283 3292
3284 3293 def _asccontains(self, x):
3285 3294 """version of contains optimised for ascending generator"""
3286 3295 if x in self._cache:
3287 3296 return self._cache[x]
3288 3297
3289 3298 # Use new values only, as existing values would be cached.
3290 3299 for l in self._consumegen():
3291 3300 if l == x:
3292 3301 return True
3293 3302 if l > x:
3294 3303 break
3295 3304
3296 3305 self._cache[x] = False
3297 3306 return False
3298 3307
3299 3308 def _desccontains(self, x):
3300 3309 """version of contains optimised for descending generator"""
3301 3310 if x in self._cache:
3302 3311 return self._cache[x]
3303 3312
3304 3313 # Use new values only, as existing values would be cached.
3305 3314 for l in self._consumegen():
3306 3315 if l == x:
3307 3316 return True
3308 3317 if l < x:
3309 3318 break
3310 3319
3311 3320 self._cache[x] = False
3312 3321 return False
3313 3322
3314 3323 def __iter__(self):
3315 3324 if self._ascending:
3316 3325 it = self.fastasc
3317 3326 else:
3318 3327 it = self.fastdesc
3319 3328 if it is not None:
3320 3329 return it()
3321 3330 # we need to consume the iterator
3322 3331 for x in self._consumegen():
3323 3332 pass
3324 3333 # recall the same code
3325 3334 return iter(self)
3326 3335
3327 3336 def _iterator(self):
3328 3337 if self._finished:
3329 3338 return iter(self._genlist)
3330 3339
3331 3340 # We have to use this complex iteration strategy to allow multiple
3332 3341 # iterations at the same time. We need to be able to catch revision
3333 3342 # removed from _consumegen and added to genlist in another instance.
3334 3343 #
3335 3344 # Getting rid of it would provide an about 15% speed up on this
3336 3345 # iteration.
3337 3346 genlist = self._genlist
3338 3347 nextrev = self._consumegen().next
3339 3348 _len = len # cache global lookup
3340 3349 def gen():
3341 3350 i = 0
3342 3351 while True:
3343 3352 if i < _len(genlist):
3344 3353 yield genlist[i]
3345 3354 else:
3346 3355 yield nextrev()
3347 3356 i += 1
3348 3357 return gen()
3349 3358
3350 3359 def _consumegen(self):
3351 3360 cache = self._cache
3352 3361 genlist = self._genlist.append
3353 3362 for item in self._gen:
3354 3363 cache[item] = True
3355 3364 genlist(item)
3356 3365 yield item
3357 3366 if not self._finished:
3358 3367 self._finished = True
3359 3368 asc = self._genlist[:]
3360 3369 asc.sort()
3361 3370 self._asclist = asc
3362 3371 self.fastasc = asc.__iter__
3363 3372 self.fastdesc = asc.__reversed__
3364 3373
3365 3374 def __len__(self):
3366 3375 for x in self._consumegen():
3367 3376 pass
3368 3377 return len(self._genlist)
3369 3378
3370 3379 def sort(self, reverse=False):
3371 3380 self._ascending = not reverse
3372 3381
3373 3382 def reverse(self):
3374 3383 self._ascending = not self._ascending
3375 3384
3376 3385 def isascending(self):
3377 3386 return self._ascending
3378 3387
3379 3388 def isdescending(self):
3380 3389 return not self._ascending
3381 3390
3382 3391 def first(self):
3383 3392 if self._ascending:
3384 3393 it = self.fastasc
3385 3394 else:
3386 3395 it = self.fastdesc
3387 3396 if it is None:
3388 3397 # we need to consume all and try again
3389 3398 for x in self._consumegen():
3390 3399 pass
3391 3400 return self.first()
3392 3401 return next(it(), None)
3393 3402
3394 3403 def last(self):
3395 3404 if self._ascending:
3396 3405 it = self.fastdesc
3397 3406 else:
3398 3407 it = self.fastasc
3399 3408 if it is None:
3400 3409 # we need to consume all and try again
3401 3410 for x in self._consumegen():
3402 3411 pass
3403 3412 return self.first()
3404 3413 return next(it(), None)
3405 3414
3406 3415 def __repr__(self):
3407 3416 d = {False: '-', True: '+'}[self._ascending]
3408 3417 return '<%s%s>' % (type(self).__name__, d)
3409 3418
3410 3419 class spanset(abstractsmartset):
3411 3420 """Duck type for baseset class which represents a range of revisions and
3412 3421 can work lazily and without having all the range in memory
3413 3422
3414 3423 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3415 3424 notable points:
3416 3425 - when x < y it will be automatically descending,
3417 3426 - revision filtered with this repoview will be skipped.
3418 3427
3419 3428 """
3420 3429 def __init__(self, repo, start=0, end=None):
3421 3430 """
3422 3431 start: first revision included the set
3423 3432 (default to 0)
3424 3433 end: first revision excluded (last+1)
3425 3434 (default to len(repo)
3426 3435
3427 3436 Spanset will be descending if `end` < `start`.
3428 3437 """
3429 3438 if end is None:
3430 3439 end = len(repo)
3431 3440 self._ascending = start <= end
3432 3441 if not self._ascending:
3433 3442 start, end = end + 1, start +1
3434 3443 self._start = start
3435 3444 self._end = end
3436 3445 self._hiddenrevs = repo.changelog.filteredrevs
3437 3446
3438 3447 def sort(self, reverse=False):
3439 3448 self._ascending = not reverse
3440 3449
3441 3450 def reverse(self):
3442 3451 self._ascending = not self._ascending
3443 3452
3444 3453 def _iterfilter(self, iterrange):
3445 3454 s = self._hiddenrevs
3446 3455 for r in iterrange:
3447 3456 if r not in s:
3448 3457 yield r
3449 3458
3450 3459 def __iter__(self):
3451 3460 if self._ascending:
3452 3461 return self.fastasc()
3453 3462 else:
3454 3463 return self.fastdesc()
3455 3464
3456 3465 def fastasc(self):
3457 3466 iterrange = xrange(self._start, self._end)
3458 3467 if self._hiddenrevs:
3459 3468 return self._iterfilter(iterrange)
3460 3469 return iter(iterrange)
3461 3470
3462 3471 def fastdesc(self):
3463 3472 iterrange = xrange(self._end - 1, self._start - 1, -1)
3464 3473 if self._hiddenrevs:
3465 3474 return self._iterfilter(iterrange)
3466 3475 return iter(iterrange)
3467 3476
3468 3477 def __contains__(self, rev):
3469 3478 hidden = self._hiddenrevs
3470 3479 return ((self._start <= rev < self._end)
3471 3480 and not (hidden and rev in hidden))
3472 3481
3473 3482 def __nonzero__(self):
3474 3483 for r in self:
3475 3484 return True
3476 3485 return False
3477 3486
3478 3487 def __len__(self):
3479 3488 if not self._hiddenrevs:
3480 3489 return abs(self._end - self._start)
3481 3490 else:
3482 3491 count = 0
3483 3492 start = self._start
3484 3493 end = self._end
3485 3494 for rev in self._hiddenrevs:
3486 3495 if (end < rev <= start) or (start <= rev < end):
3487 3496 count += 1
3488 3497 return abs(self._end - self._start) - count
3489 3498
3490 3499 def isascending(self):
3491 3500 return self._ascending
3492 3501
3493 3502 def isdescending(self):
3494 3503 return not self._ascending
3495 3504
3496 3505 def first(self):
3497 3506 if self._ascending:
3498 3507 it = self.fastasc
3499 3508 else:
3500 3509 it = self.fastdesc
3501 3510 for x in it():
3502 3511 return x
3503 3512 return None
3504 3513
3505 3514 def last(self):
3506 3515 if self._ascending:
3507 3516 it = self.fastdesc
3508 3517 else:
3509 3518 it = self.fastasc
3510 3519 for x in it():
3511 3520 return x
3512 3521 return None
3513 3522
3514 3523 def __repr__(self):
3515 3524 d = {False: '-', True: '+'}[self._ascending]
3516 3525 return '<%s%s %d:%d>' % (type(self).__name__, d,
3517 3526 self._start, self._end - 1)
3518 3527
3519 3528 class fullreposet(spanset):
3520 3529 """a set containing all revisions in the repo
3521 3530
3522 3531 This class exists to host special optimization and magic to handle virtual
3523 3532 revisions such as "null".
3524 3533 """
3525 3534
3526 3535 def __init__(self, repo):
3527 3536 super(fullreposet, self).__init__(repo)
3528 3537
3529 3538 def __and__(self, other):
3530 3539 """As self contains the whole repo, all of the other set should also be
3531 3540 in self. Therefore `self & other = other`.
3532 3541
3533 3542 This boldly assumes the other contains valid revs only.
3534 3543 """
3535 3544 # other not a smartset, make is so
3536 3545 if not util.safehasattr(other, 'isascending'):
3537 3546 # filter out hidden revision
3538 3547 # (this boldly assumes all smartset are pure)
3539 3548 #
3540 3549 # `other` was used with "&", let's assume this is a set like
3541 3550 # object.
3542 3551 other = baseset(other - self._hiddenrevs)
3543 3552
3544 3553 # XXX As fullreposet is also used as bootstrap, this is wrong.
3545 3554 #
3546 3555 # With a giveme312() revset returning [3,1,2], this makes
3547 3556 # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
3548 3557 # We cannot just drop it because other usage still need to sort it:
3549 3558 # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
3550 3559 #
3551 3560 # There is also some faulty revset implementations that rely on it
3552 3561 # (eg: children as of its state in e8075329c5fb)
3553 3562 #
3554 3563 # When we fix the two points above we can move this into the if clause
3555 3564 other.sort(reverse=self.isdescending())
3556 3565 return other
3557 3566
3558 3567 def prettyformatset(revs):
3559 3568 lines = []
3560 3569 rs = repr(revs)
3561 3570 p = 0
3562 3571 while p < len(rs):
3563 3572 q = rs.find('<', p + 1)
3564 3573 if q < 0:
3565 3574 q = len(rs)
3566 3575 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3567 3576 assert l >= 0
3568 3577 lines.append((l, rs[p:q].rstrip()))
3569 3578 p = q
3570 3579 return '\n'.join(' ' * l + s for l, s in lines)
3571 3580
3572 3581 # tell hggettext to extract docstrings from these functions:
3573 3582 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now