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