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