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