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