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