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