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