##// END OF EJS Templates
revset: fix order of nested 'range' expression (BC)...
Yuya Nishihara -
r29944:5f56a3b9 default
parent child Browse files
Show More
@@ -1,3830 +1,3830 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 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
382 # necessary to ensure we preserve the order in subset.
383 #
384 # This has performance implication, carrying the sorting over when possible
385 # would be more efficient.
386 return r & subset
381
382 if order == defineorder:
383 return r & subset
384 else:
385 # carrying the sorting over when possible would be more efficient
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 1836 @predicate('reverse(set)', safe=True)
1837 1837 def reverse(repo, subset, x):
1838 1838 """Reverse order of set.
1839 1839 """
1840 1840 l = getset(repo, subset, x)
1841 1841 l.reverse()
1842 1842 return l
1843 1843
1844 1844 @predicate('roots(set)', safe=True)
1845 1845 def roots(repo, subset, x):
1846 1846 """Changesets in set with no parent changeset in set.
1847 1847 """
1848 1848 s = getset(repo, fullreposet(repo), x)
1849 1849 parents = repo.changelog.parentrevs
1850 1850 def filter(r):
1851 1851 for p in parents(r):
1852 1852 if 0 <= p and p in s:
1853 1853 return False
1854 1854 return True
1855 1855 return subset & s.filter(filter, condrepr='<roots>')
1856 1856
1857 1857 _sortkeyfuncs = {
1858 1858 'rev': lambda c: c.rev(),
1859 1859 'branch': lambda c: c.branch(),
1860 1860 'desc': lambda c: c.description(),
1861 1861 'user': lambda c: c.user(),
1862 1862 'author': lambda c: c.user(),
1863 1863 'date': lambda c: c.date()[0],
1864 1864 }
1865 1865
1866 1866 def _getsortargs(x):
1867 1867 """Parse sort options into (set, [(key, reverse)], opts)"""
1868 1868 args = getargsdict(x, 'sort', 'set keys topo.firstbranch')
1869 1869 if 'set' not in args:
1870 1870 # i18n: "sort" is a keyword
1871 1871 raise error.ParseError(_('sort requires one or two arguments'))
1872 1872 keys = "rev"
1873 1873 if 'keys' in args:
1874 1874 # i18n: "sort" is a keyword
1875 1875 keys = getstring(args['keys'], _("sort spec must be a string"))
1876 1876
1877 1877 keyflags = []
1878 1878 for k in keys.split():
1879 1879 fk = k
1880 1880 reverse = (k[0] == '-')
1881 1881 if reverse:
1882 1882 k = k[1:]
1883 1883 if k not in _sortkeyfuncs and k != 'topo':
1884 1884 raise error.ParseError(_("unknown sort key %r") % fk)
1885 1885 keyflags.append((k, reverse))
1886 1886
1887 1887 if len(keyflags) > 1 and any(k == 'topo' for k, reverse in keyflags):
1888 1888 # i18n: "topo" is a keyword
1889 1889 raise error.ParseError(_('topo sort order cannot be combined '
1890 1890 'with other sort keys'))
1891 1891
1892 1892 opts = {}
1893 1893 if 'topo.firstbranch' in args:
1894 1894 if any(k == 'topo' for k, reverse in keyflags):
1895 1895 opts['topo.firstbranch'] = args['topo.firstbranch']
1896 1896 else:
1897 1897 # i18n: "topo" and "topo.firstbranch" are keywords
1898 1898 raise error.ParseError(_('topo.firstbranch can only be used '
1899 1899 'when using the topo sort key'))
1900 1900
1901 1901 return args['set'], keyflags, opts
1902 1902
1903 1903 @predicate('sort(set[, [-]key... [, ...]])', safe=True)
1904 1904 def sort(repo, subset, x):
1905 1905 """Sort set by keys. The default sort order is ascending, specify a key
1906 1906 as ``-key`` to sort in descending order.
1907 1907
1908 1908 The keys can be:
1909 1909
1910 1910 - ``rev`` for the revision number,
1911 1911 - ``branch`` for the branch name,
1912 1912 - ``desc`` for the commit message (description),
1913 1913 - ``user`` for user name (``author`` can be used as an alias),
1914 1914 - ``date`` for the commit date
1915 1915 - ``topo`` for a reverse topographical sort
1916 1916
1917 1917 The ``topo`` sort order cannot be combined with other sort keys. This sort
1918 1918 takes one optional argument, ``topo.firstbranch``, which takes a revset that
1919 1919 specifies what topographical branches to prioritize in the sort.
1920 1920
1921 1921 """
1922 1922 s, keyflags, opts = _getsortargs(x)
1923 1923 revs = getset(repo, subset, s)
1924 1924
1925 1925 if not keyflags:
1926 1926 return revs
1927 1927 if len(keyflags) == 1 and keyflags[0][0] == "rev":
1928 1928 revs.sort(reverse=keyflags[0][1])
1929 1929 return revs
1930 1930 elif keyflags[0][0] == "topo":
1931 1931 firstbranch = ()
1932 1932 if 'topo.firstbranch' in opts:
1933 1933 firstbranch = getset(repo, subset, opts['topo.firstbranch'])
1934 1934 revs = baseset(_toposort(revs, repo.changelog.parentrevs, firstbranch),
1935 1935 istopo=True)
1936 1936 if keyflags[0][1]:
1937 1937 revs.reverse()
1938 1938 return revs
1939 1939
1940 1940 # sort() is guaranteed to be stable
1941 1941 ctxs = [repo[r] for r in revs]
1942 1942 for k, reverse in reversed(keyflags):
1943 1943 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
1944 1944 return baseset([c.rev() for c in ctxs])
1945 1945
1946 1946 def _toposort(revs, parentsfunc, firstbranch=()):
1947 1947 """Yield revisions from heads to roots one (topo) branch at a time.
1948 1948
1949 1949 This function aims to be used by a graph generator that wishes to minimize
1950 1950 the number of parallel branches and their interleaving.
1951 1951
1952 1952 Example iteration order (numbers show the "true" order in a changelog):
1953 1953
1954 1954 o 4
1955 1955 |
1956 1956 o 1
1957 1957 |
1958 1958 | o 3
1959 1959 | |
1960 1960 | o 2
1961 1961 |/
1962 1962 o 0
1963 1963
1964 1964 Note that the ancestors of merges are understood by the current
1965 1965 algorithm to be on the same branch. This means no reordering will
1966 1966 occur behind a merge.
1967 1967 """
1968 1968
1969 1969 ### Quick summary of the algorithm
1970 1970 #
1971 1971 # This function is based around a "retention" principle. We keep revisions
1972 1972 # in memory until we are ready to emit a whole branch that immediately
1973 1973 # "merges" into an existing one. This reduces the number of parallel
1974 1974 # branches with interleaved revisions.
1975 1975 #
1976 1976 # During iteration revs are split into two groups:
1977 1977 # A) revision already emitted
1978 1978 # B) revision in "retention". They are stored as different subgroups.
1979 1979 #
1980 1980 # for each REV, we do the following logic:
1981 1981 #
1982 1982 # 1) if REV is a parent of (A), we will emit it. If there is a
1983 1983 # retention group ((B) above) that is blocked on REV being
1984 1984 # available, we emit all the revisions out of that retention
1985 1985 # group first.
1986 1986 #
1987 1987 # 2) else, we'll search for a subgroup in (B) awaiting for REV to be
1988 1988 # available, if such subgroup exist, we add REV to it and the subgroup is
1989 1989 # now awaiting for REV.parents() to be available.
1990 1990 #
1991 1991 # 3) finally if no such group existed in (B), we create a new subgroup.
1992 1992 #
1993 1993 #
1994 1994 # To bootstrap the algorithm, we emit the tipmost revision (which
1995 1995 # puts it in group (A) from above).
1996 1996
1997 1997 revs.sort(reverse=True)
1998 1998
1999 1999 # Set of parents of revision that have been emitted. They can be considered
2000 2000 # unblocked as the graph generator is already aware of them so there is no
2001 2001 # need to delay the revisions that reference them.
2002 2002 #
2003 2003 # If someone wants to prioritize a branch over the others, pre-filling this
2004 2004 # set will force all other branches to wait until this branch is ready to be
2005 2005 # emitted.
2006 2006 unblocked = set(firstbranch)
2007 2007
2008 2008 # list of groups waiting to be displayed, each group is defined by:
2009 2009 #
2010 2010 # (revs: lists of revs waiting to be displayed,
2011 2011 # blocked: set of that cannot be displayed before those in 'revs')
2012 2012 #
2013 2013 # The second value ('blocked') correspond to parents of any revision in the
2014 2014 # group ('revs') that is not itself contained in the group. The main idea
2015 2015 # of this algorithm is to delay as much as possible the emission of any
2016 2016 # revision. This means waiting for the moment we are about to display
2017 2017 # these parents to display the revs in a group.
2018 2018 #
2019 2019 # This first implementation is smart until it encounters a merge: it will
2020 2020 # emit revs as soon as any parent is about to be emitted and can grow an
2021 2021 # arbitrary number of revs in 'blocked'. In practice this mean we properly
2022 2022 # retains new branches but gives up on any special ordering for ancestors
2023 2023 # of merges. The implementation can be improved to handle this better.
2024 2024 #
2025 2025 # The first subgroup is special. It corresponds to all the revision that
2026 2026 # were already emitted. The 'revs' lists is expected to be empty and the
2027 2027 # 'blocked' set contains the parents revisions of already emitted revision.
2028 2028 #
2029 2029 # You could pre-seed the <parents> set of groups[0] to a specific
2030 2030 # changesets to select what the first emitted branch should be.
2031 2031 groups = [([], unblocked)]
2032 2032 pendingheap = []
2033 2033 pendingset = set()
2034 2034
2035 2035 heapq.heapify(pendingheap)
2036 2036 heappop = heapq.heappop
2037 2037 heappush = heapq.heappush
2038 2038 for currentrev in revs:
2039 2039 # Heap works with smallest element, we want highest so we invert
2040 2040 if currentrev not in pendingset:
2041 2041 heappush(pendingheap, -currentrev)
2042 2042 pendingset.add(currentrev)
2043 2043 # iterates on pending rev until after the current rev have been
2044 2044 # processed.
2045 2045 rev = None
2046 2046 while rev != currentrev:
2047 2047 rev = -heappop(pendingheap)
2048 2048 pendingset.remove(rev)
2049 2049
2050 2050 # Seek for a subgroup blocked, waiting for the current revision.
2051 2051 matching = [i for i, g in enumerate(groups) if rev in g[1]]
2052 2052
2053 2053 if matching:
2054 2054 # The main idea is to gather together all sets that are blocked
2055 2055 # on the same revision.
2056 2056 #
2057 2057 # Groups are merged when a common blocking ancestor is
2058 2058 # observed. For example, given two groups:
2059 2059 #
2060 2060 # revs [5, 4] waiting for 1
2061 2061 # revs [3, 2] waiting for 1
2062 2062 #
2063 2063 # These two groups will be merged when we process
2064 2064 # 1. In theory, we could have merged the groups when
2065 2065 # we added 2 to the group it is now in (we could have
2066 2066 # noticed the groups were both blocked on 1 then), but
2067 2067 # the way it works now makes the algorithm simpler.
2068 2068 #
2069 2069 # We also always keep the oldest subgroup first. We can
2070 2070 # probably improve the behavior by having the longest set
2071 2071 # first. That way, graph algorithms could minimise the length
2072 2072 # of parallel lines their drawing. This is currently not done.
2073 2073 targetidx = matching.pop(0)
2074 2074 trevs, tparents = groups[targetidx]
2075 2075 for i in matching:
2076 2076 gr = groups[i]
2077 2077 trevs.extend(gr[0])
2078 2078 tparents |= gr[1]
2079 2079 # delete all merged subgroups (except the one we kept)
2080 2080 # (starting from the last subgroup for performance and
2081 2081 # sanity reasons)
2082 2082 for i in reversed(matching):
2083 2083 del groups[i]
2084 2084 else:
2085 2085 # This is a new head. We create a new subgroup for it.
2086 2086 targetidx = len(groups)
2087 2087 groups.append(([], set([rev])))
2088 2088
2089 2089 gr = groups[targetidx]
2090 2090
2091 2091 # We now add the current nodes to this subgroups. This is done
2092 2092 # after the subgroup merging because all elements from a subgroup
2093 2093 # that relied on this rev must precede it.
2094 2094 #
2095 2095 # we also update the <parents> set to include the parents of the
2096 2096 # new nodes.
2097 2097 if rev == currentrev: # only display stuff in rev
2098 2098 gr[0].append(rev)
2099 2099 gr[1].remove(rev)
2100 2100 parents = [p for p in parentsfunc(rev) if p > node.nullrev]
2101 2101 gr[1].update(parents)
2102 2102 for p in parents:
2103 2103 if p not in pendingset:
2104 2104 pendingset.add(p)
2105 2105 heappush(pendingheap, -p)
2106 2106
2107 2107 # Look for a subgroup to display
2108 2108 #
2109 2109 # When unblocked is empty (if clause), we were not waiting for any
2110 2110 # revisions during the first iteration (if no priority was given) or
2111 2111 # if we emitted a whole disconnected set of the graph (reached a
2112 2112 # root). In that case we arbitrarily take the oldest known
2113 2113 # subgroup. The heuristic could probably be better.
2114 2114 #
2115 2115 # Otherwise (elif clause) if the subgroup is blocked on
2116 2116 # a revision we just emitted, we can safely emit it as
2117 2117 # well.
2118 2118 if not unblocked:
2119 2119 if len(groups) > 1: # display other subset
2120 2120 targetidx = 1
2121 2121 gr = groups[1]
2122 2122 elif not gr[1] & unblocked:
2123 2123 gr = None
2124 2124
2125 2125 if gr is not None:
2126 2126 # update the set of awaited revisions with the one from the
2127 2127 # subgroup
2128 2128 unblocked |= gr[1]
2129 2129 # output all revisions in the subgroup
2130 2130 for r in gr[0]:
2131 2131 yield r
2132 2132 # delete the subgroup that you just output
2133 2133 # unless it is groups[0] in which case you just empty it.
2134 2134 if targetidx:
2135 2135 del groups[targetidx]
2136 2136 else:
2137 2137 gr[0][:] = []
2138 2138 # Check if we have some subgroup waiting for revisions we are not going to
2139 2139 # iterate over
2140 2140 for g in groups:
2141 2141 for r in g[0]:
2142 2142 yield r
2143 2143
2144 2144 @predicate('subrepo([pattern])')
2145 2145 def subrepo(repo, subset, x):
2146 2146 """Changesets that add, modify or remove the given subrepo. If no subrepo
2147 2147 pattern is named, any subrepo changes are returned.
2148 2148 """
2149 2149 # i18n: "subrepo" is a keyword
2150 2150 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
2151 2151 pat = None
2152 2152 if len(args) != 0:
2153 2153 pat = getstring(args[0], _("subrepo requires a pattern"))
2154 2154
2155 2155 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
2156 2156
2157 2157 def submatches(names):
2158 2158 k, p, m = util.stringmatcher(pat)
2159 2159 for name in names:
2160 2160 if m(name):
2161 2161 yield name
2162 2162
2163 2163 def matches(x):
2164 2164 c = repo[x]
2165 2165 s = repo.status(c.p1().node(), c.node(), match=m)
2166 2166
2167 2167 if pat is None:
2168 2168 return s.added or s.modified or s.removed
2169 2169
2170 2170 if s.added:
2171 2171 return any(submatches(c.substate.keys()))
2172 2172
2173 2173 if s.modified:
2174 2174 subs = set(c.p1().substate.keys())
2175 2175 subs.update(c.substate.keys())
2176 2176
2177 2177 for path in submatches(subs):
2178 2178 if c.p1().substate.get(path) != c.substate.get(path):
2179 2179 return True
2180 2180
2181 2181 if s.removed:
2182 2182 return any(submatches(c.p1().substate.keys()))
2183 2183
2184 2184 return False
2185 2185
2186 2186 return subset.filter(matches, condrepr=('<subrepo %r>', pat))
2187 2187
2188 2188 def _substringmatcher(pattern):
2189 2189 kind, pattern, matcher = util.stringmatcher(pattern)
2190 2190 if kind == 'literal':
2191 2191 matcher = lambda s: pattern in s
2192 2192 return kind, pattern, matcher
2193 2193
2194 2194 @predicate('tag([name])', safe=True)
2195 2195 def tag(repo, subset, x):
2196 2196 """The specified tag by name, or all tagged revisions if no name is given.
2197 2197
2198 2198 If `name` starts with `re:`, the remainder of the name is treated as
2199 2199 a regular expression. To match a tag that actually starts with `re:`,
2200 2200 use the prefix `literal:`.
2201 2201 """
2202 2202 # i18n: "tag" is a keyword
2203 2203 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
2204 2204 cl = repo.changelog
2205 2205 if args:
2206 2206 pattern = getstring(args[0],
2207 2207 # i18n: "tag" is a keyword
2208 2208 _('the argument to tag must be a string'))
2209 2209 kind, pattern, matcher = util.stringmatcher(pattern)
2210 2210 if kind == 'literal':
2211 2211 # avoid resolving all tags
2212 2212 tn = repo._tagscache.tags.get(pattern, None)
2213 2213 if tn is None:
2214 2214 raise error.RepoLookupError(_("tag '%s' does not exist")
2215 2215 % pattern)
2216 2216 s = set([repo[tn].rev()])
2217 2217 else:
2218 2218 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
2219 2219 else:
2220 2220 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
2221 2221 return subset & s
2222 2222
2223 2223 @predicate('tagged', safe=True)
2224 2224 def tagged(repo, subset, x):
2225 2225 return tag(repo, subset, x)
2226 2226
2227 2227 @predicate('unstable()', safe=True)
2228 2228 def unstable(repo, subset, x):
2229 2229 """Non-obsolete changesets with obsolete ancestors.
2230 2230 """
2231 2231 # i18n: "unstable" is a keyword
2232 2232 getargs(x, 0, 0, _("unstable takes no arguments"))
2233 2233 unstables = obsmod.getrevs(repo, 'unstable')
2234 2234 return subset & unstables
2235 2235
2236 2236
2237 2237 @predicate('user(string)', safe=True)
2238 2238 def user(repo, subset, x):
2239 2239 """User name contains string. The match is case-insensitive.
2240 2240
2241 2241 If `string` starts with `re:`, the remainder of the string is treated as
2242 2242 a regular expression. To match a user that actually contains `re:`, use
2243 2243 the prefix `literal:`.
2244 2244 """
2245 2245 return author(repo, subset, x)
2246 2246
2247 2247 # experimental
2248 2248 @predicate('wdir', safe=True)
2249 2249 def wdir(repo, subset, x):
2250 2250 # i18n: "wdir" is a keyword
2251 2251 getargs(x, 0, 0, _("wdir takes no arguments"))
2252 2252 if node.wdirrev in subset or isinstance(subset, fullreposet):
2253 2253 return baseset([node.wdirrev])
2254 2254 return baseset()
2255 2255
2256 2256 def _orderedlist(repo, subset, x):
2257 2257 s = getstring(x, "internal error")
2258 2258 if not s:
2259 2259 return baseset()
2260 2260 # remove duplicates here. it's difficult for caller to deduplicate sets
2261 2261 # because different symbols can point to the same rev.
2262 2262 cl = repo.changelog
2263 2263 ls = []
2264 2264 seen = set()
2265 2265 for t in s.split('\0'):
2266 2266 try:
2267 2267 # fast path for integer revision
2268 2268 r = int(t)
2269 2269 if str(r) != t or r not in cl:
2270 2270 raise ValueError
2271 2271 revs = [r]
2272 2272 except ValueError:
2273 2273 revs = stringset(repo, subset, t)
2274 2274
2275 2275 for r in revs:
2276 2276 if r in seen:
2277 2277 continue
2278 2278 if (r in subset
2279 2279 or r == node.nullrev and isinstance(subset, fullreposet)):
2280 2280 ls.append(r)
2281 2281 seen.add(r)
2282 2282 return baseset(ls)
2283 2283
2284 2284 # for internal use
2285 2285 @predicate('_list', safe=True, takeorder=True)
2286 2286 def _list(repo, subset, x, order):
2287 2287 if order == followorder:
2288 2288 # slow path to take the subset order
2289 2289 return subset & _orderedlist(repo, fullreposet(repo), x)
2290 2290 else:
2291 2291 return _orderedlist(repo, subset, x)
2292 2292
2293 2293 def _orderedintlist(repo, subset, x):
2294 2294 s = getstring(x, "internal error")
2295 2295 if not s:
2296 2296 return baseset()
2297 2297 ls = [int(r) for r in s.split('\0')]
2298 2298 s = subset
2299 2299 return baseset([r for r in ls if r in s])
2300 2300
2301 2301 # for internal use
2302 2302 @predicate('_intlist', safe=True, takeorder=True)
2303 2303 def _intlist(repo, subset, x, order):
2304 2304 if order == followorder:
2305 2305 # slow path to take the subset order
2306 2306 return subset & _orderedintlist(repo, fullreposet(repo), x)
2307 2307 else:
2308 2308 return _orderedintlist(repo, subset, x)
2309 2309
2310 2310 def _orderedhexlist(repo, subset, x):
2311 2311 s = getstring(x, "internal error")
2312 2312 if not s:
2313 2313 return baseset()
2314 2314 cl = repo.changelog
2315 2315 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2316 2316 s = subset
2317 2317 return baseset([r for r in ls if r in s])
2318 2318
2319 2319 # for internal use
2320 2320 @predicate('_hexlist', safe=True, takeorder=True)
2321 2321 def _hexlist(repo, subset, x, order):
2322 2322 if order == followorder:
2323 2323 # slow path to take the subset order
2324 2324 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2325 2325 else:
2326 2326 return _orderedhexlist(repo, subset, x)
2327 2327
2328 2328 methods = {
2329 2329 "range": rangeset,
2330 2330 "dagrange": dagrange,
2331 2331 "string": stringset,
2332 2332 "symbol": stringset,
2333 2333 "and": andset,
2334 2334 "or": orset,
2335 2335 "not": notset,
2336 2336 "difference": differenceset,
2337 2337 "list": listset,
2338 2338 "keyvalue": keyvaluepair,
2339 2339 "func": func,
2340 2340 "ancestor": ancestorspec,
2341 2341 "parent": parentspec,
2342 2342 "parentpost": parentpost,
2343 2343 }
2344 2344
2345 2345 # Constants for ordering requirement, used in _analyze():
2346 2346 #
2347 2347 # If 'define', any nested functions and operations can change the ordering of
2348 2348 # the entries in the set. If 'follow', any nested functions and operations
2349 2349 # should take the ordering specified by the first operand to the '&' operator.
2350 2350 #
2351 2351 # For instance,
2352 2352 #
2353 2353 # X & (Y | Z)
2354 2354 # ^ ^^^^^^^
2355 2355 # | follow
2356 2356 # define
2357 2357 #
2358 2358 # will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
2359 2359 # of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
2360 2360 #
2361 2361 # 'any' means the order doesn't matter. For instance,
2362 2362 #
2363 2363 # X & !Y
2364 2364 # ^
2365 2365 # any
2366 2366 #
2367 2367 # 'y()' can either enforce its ordering requirement or take the ordering
2368 2368 # specified by 'x()' because 'not()' doesn't care the order.
2369 2369 #
2370 2370 # Transition of ordering requirement:
2371 2371 #
2372 2372 # 1. starts with 'define'
2373 2373 # 2. shifts to 'follow' by 'x & y'
2374 2374 # 3. changes back to 'define' on function call 'f(x)' or function-like
2375 2375 # operation 'x (f) y' because 'f' may have its own ordering requirement
2376 2376 # for 'x' and 'y' (e.g. 'first(x)')
2377 2377 #
2378 2378 anyorder = 'any' # don't care the order
2379 2379 defineorder = 'define' # should define the order
2380 2380 followorder = 'follow' # must follow the current order
2381 2381
2382 2382 # transition table for 'x & y', from the current expression 'x' to 'y'
2383 2383 _tofolloworder = {
2384 2384 anyorder: anyorder,
2385 2385 defineorder: followorder,
2386 2386 followorder: followorder,
2387 2387 }
2388 2388
2389 2389 def _matchonly(revs, bases):
2390 2390 """
2391 2391 >>> f = lambda *args: _matchonly(*map(parse, args))
2392 2392 >>> f('ancestors(A)', 'not ancestors(B)')
2393 2393 ('list', ('symbol', 'A'), ('symbol', 'B'))
2394 2394 """
2395 2395 if (revs is not None
2396 2396 and revs[0] == 'func'
2397 2397 and getsymbol(revs[1]) == 'ancestors'
2398 2398 and bases is not None
2399 2399 and bases[0] == 'not'
2400 2400 and bases[1][0] == 'func'
2401 2401 and getsymbol(bases[1][1]) == 'ancestors'):
2402 2402 return ('list', revs[2], bases[1][2])
2403 2403
2404 2404 def _fixops(x):
2405 2405 """Rewrite raw parsed tree to resolve ambiguous syntax which cannot be
2406 2406 handled well by our simple top-down parser"""
2407 2407 if not isinstance(x, tuple):
2408 2408 return x
2409 2409
2410 2410 op = x[0]
2411 2411 if op == 'parent':
2412 2412 # x^:y means (x^) : y, not x ^ (:y)
2413 2413 # x^: means (x^) :, not x ^ (:)
2414 2414 post = ('parentpost', x[1])
2415 2415 if x[2][0] == 'dagrangepre':
2416 2416 return _fixops(('dagrange', post, x[2][1]))
2417 2417 elif x[2][0] == 'rangepre':
2418 2418 return _fixops(('range', post, x[2][1]))
2419 2419 elif x[2][0] == 'rangeall':
2420 2420 return _fixops(('rangepost', post))
2421 2421 elif op == 'or':
2422 2422 # make number of arguments deterministic:
2423 2423 # x + y + z -> (or x y z) -> (or (list x y z))
2424 2424 return (op, _fixops(('list',) + x[1:]))
2425 2425
2426 2426 return (op,) + tuple(_fixops(y) for y in x[1:])
2427 2427
2428 2428 def _analyze(x, order):
2429 2429 if x is None:
2430 2430 return x
2431 2431
2432 2432 op = x[0]
2433 2433 if op == 'minus':
2434 2434 return _analyze(('and', x[1], ('not', x[2])), order)
2435 2435 elif op == 'only':
2436 2436 t = ('func', ('symbol', 'only'), ('list', x[1], x[2]))
2437 2437 return _analyze(t, order)
2438 2438 elif op == 'onlypost':
2439 2439 return _analyze(('func', ('symbol', 'only'), x[1]), order)
2440 2440 elif op == 'dagrangepre':
2441 2441 return _analyze(('func', ('symbol', 'ancestors'), x[1]), order)
2442 2442 elif op == 'dagrangepost':
2443 2443 return _analyze(('func', ('symbol', 'descendants'), x[1]), order)
2444 2444 elif op == 'rangeall':
2445 2445 return _analyze(('range', ('string', '0'), ('string', 'tip')), order)
2446 2446 elif op == 'rangepre':
2447 2447 return _analyze(('range', ('string', '0'), x[1]), order)
2448 2448 elif op == 'rangepost':
2449 2449 return _analyze(('range', x[1], ('string', 'tip')), order)
2450 2450 elif op == 'negate':
2451 2451 s = getstring(x[1], _("can't negate that"))
2452 2452 return _analyze(('string', '-' + s), order)
2453 2453 elif op in ('string', 'symbol'):
2454 2454 return x
2455 2455 elif op == 'and':
2456 2456 ta = _analyze(x[1], order)
2457 2457 tb = _analyze(x[2], _tofolloworder[order])
2458 2458 return (op, ta, tb, order)
2459 2459 elif op == 'or':
2460 2460 return (op, _analyze(x[1], order), order)
2461 2461 elif op == 'not':
2462 2462 return (op, _analyze(x[1], anyorder), order)
2463 2463 elif op == 'parentpost':
2464 2464 return (op, _analyze(x[1], defineorder), order)
2465 2465 elif op == 'group':
2466 2466 return _analyze(x[1], order)
2467 2467 elif op in ('dagrange', 'range', 'parent', 'ancestor'):
2468 2468 ta = _analyze(x[1], defineorder)
2469 2469 tb = _analyze(x[2], defineorder)
2470 2470 return (op, ta, tb, order)
2471 2471 elif op == 'list':
2472 2472 return (op,) + tuple(_analyze(y, order) for y in x[1:])
2473 2473 elif op == 'keyvalue':
2474 2474 return (op, x[1], _analyze(x[2], order))
2475 2475 elif op == 'func':
2476 2476 f = getsymbol(x[1])
2477 2477 d = defineorder
2478 2478 if f == 'present':
2479 2479 # 'present(set)' is known to return the argument set with no
2480 2480 # modification, so forward the current order to its argument
2481 2481 d = order
2482 2482 return (op, x[1], _analyze(x[2], d), order)
2483 2483 raise ValueError('invalid operator %r' % op)
2484 2484
2485 2485 def analyze(x, order=defineorder):
2486 2486 """Transform raw parsed tree to evaluatable tree which can be fed to
2487 2487 optimize() or getset()
2488 2488
2489 2489 All pseudo operations should be mapped to real operations or functions
2490 2490 defined in methods or symbols table respectively.
2491 2491
2492 2492 'order' specifies how the current expression 'x' is ordered (see the
2493 2493 constants defined above.)
2494 2494 """
2495 2495 return _analyze(x, order)
2496 2496
2497 2497 def _optimize(x, small):
2498 2498 if x is None:
2499 2499 return 0, x
2500 2500
2501 2501 smallbonus = 1
2502 2502 if small:
2503 2503 smallbonus = .5
2504 2504
2505 2505 op = x[0]
2506 2506 if op in ('string', 'symbol'):
2507 2507 return smallbonus, x # single revisions are small
2508 2508 elif op == 'and':
2509 2509 wa, ta = _optimize(x[1], True)
2510 2510 wb, tb = _optimize(x[2], True)
2511 2511 order = x[3]
2512 2512 w = min(wa, wb)
2513 2513
2514 2514 # (::x and not ::y)/(not ::y and ::x) have a fast path
2515 2515 tm = _matchonly(ta, tb) or _matchonly(tb, ta)
2516 2516 if tm:
2517 2517 return w, ('func', ('symbol', 'only'), tm, order)
2518 2518
2519 2519 if tb is not None and tb[0] == 'not':
2520 2520 return wa, ('difference', ta, tb[1], order)
2521 2521
2522 2522 if wa > wb:
2523 2523 return w, (op, tb, ta, order)
2524 2524 return w, (op, ta, tb, order)
2525 2525 elif op == 'or':
2526 2526 # fast path for machine-generated expression, that is likely to have
2527 2527 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
2528 2528 order = x[2]
2529 2529 ws, ts, ss = [], [], []
2530 2530 def flushss():
2531 2531 if not ss:
2532 2532 return
2533 2533 if len(ss) == 1:
2534 2534 w, t = ss[0]
2535 2535 else:
2536 2536 s = '\0'.join(t[1] for w, t in ss)
2537 2537 y = ('func', ('symbol', '_list'), ('string', s), order)
2538 2538 w, t = _optimize(y, False)
2539 2539 ws.append(w)
2540 2540 ts.append(t)
2541 2541 del ss[:]
2542 2542 for y in getlist(x[1]):
2543 2543 w, t = _optimize(y, False)
2544 2544 if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
2545 2545 ss.append((w, t))
2546 2546 continue
2547 2547 flushss()
2548 2548 ws.append(w)
2549 2549 ts.append(t)
2550 2550 flushss()
2551 2551 if len(ts) == 1:
2552 2552 return ws[0], ts[0] # 'or' operation is fully optimized out
2553 2553 # we can't reorder trees by weight because it would change the order.
2554 2554 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
2555 2555 # ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0]))
2556 2556 return max(ws), (op, ('list',) + tuple(ts), order)
2557 2557 elif op == 'not':
2558 2558 # Optimize not public() to _notpublic() because we have a fast version
2559 2559 if x[1][:3] == ('func', ('symbol', 'public'), None):
2560 2560 order = x[1][3]
2561 2561 newsym = ('func', ('symbol', '_notpublic'), None, order)
2562 2562 o = _optimize(newsym, not small)
2563 2563 return o[0], o[1]
2564 2564 else:
2565 2565 o = _optimize(x[1], not small)
2566 2566 order = x[2]
2567 2567 return o[0], (op, o[1], order)
2568 2568 elif op == 'parentpost':
2569 2569 o = _optimize(x[1], small)
2570 2570 order = x[2]
2571 2571 return o[0], (op, o[1], order)
2572 2572 elif op in ('dagrange', 'range', 'parent', 'ancestor'):
2573 2573 wa, ta = _optimize(x[1], small)
2574 2574 wb, tb = _optimize(x[2], small)
2575 2575 order = x[3]
2576 2576 return wa + wb, (op, ta, tb, order)
2577 2577 elif op == 'list':
2578 2578 ws, ts = zip(*(_optimize(y, small) for y in x[1:]))
2579 2579 return sum(ws), (op,) + ts
2580 2580 elif op == 'keyvalue':
2581 2581 w, t = _optimize(x[2], small)
2582 2582 return w, (op, x[1], t)
2583 2583 elif op == 'func':
2584 2584 f = getsymbol(x[1])
2585 2585 wa, ta = _optimize(x[2], small)
2586 2586 if f in ('author', 'branch', 'closed', 'date', 'desc', 'file', 'grep',
2587 2587 'keyword', 'outgoing', 'user'):
2588 2588 w = 10 # slow
2589 2589 elif f in ('modifies', 'adds', 'removes'):
2590 2590 w = 30 # slower
2591 2591 elif f == "contains":
2592 2592 w = 100 # very slow
2593 2593 elif f == "ancestor":
2594 2594 w = 1 * smallbonus
2595 2595 elif f in ('reverse', 'limit', 'first', '_intlist'):
2596 2596 w = 0
2597 2597 elif f == "sort":
2598 2598 w = 10 # assume most sorts look at changelog
2599 2599 else:
2600 2600 w = 1
2601 2601 order = x[3]
2602 2602 return w + wa, (op, x[1], ta, order)
2603 2603 raise ValueError('invalid operator %r' % op)
2604 2604
2605 2605 def optimize(tree):
2606 2606 """Optimize evaluatable tree
2607 2607
2608 2608 All pseudo operations should be transformed beforehand.
2609 2609 """
2610 2610 _weight, newtree = _optimize(tree, small=True)
2611 2611 return newtree
2612 2612
2613 2613 # the set of valid characters for the initial letter of symbols in
2614 2614 # alias declarations and definitions
2615 2615 _aliassyminitletters = set(c for c in [chr(i) for i in xrange(256)]
2616 2616 if c.isalnum() or c in '._@$' or ord(c) > 127)
2617 2617
2618 2618 def _parsewith(spec, lookup=None, syminitletters=None):
2619 2619 """Generate a parse tree of given spec with given tokenizing options
2620 2620
2621 2621 >>> _parsewith('foo($1)', syminitletters=_aliassyminitletters)
2622 2622 ('func', ('symbol', 'foo'), ('symbol', '$1'))
2623 2623 >>> _parsewith('$1')
2624 2624 Traceback (most recent call last):
2625 2625 ...
2626 2626 ParseError: ("syntax error in revset '$1'", 0)
2627 2627 >>> _parsewith('foo bar')
2628 2628 Traceback (most recent call last):
2629 2629 ...
2630 2630 ParseError: ('invalid token', 4)
2631 2631 """
2632 2632 p = parser.parser(elements)
2633 2633 tree, pos = p.parse(tokenize(spec, lookup=lookup,
2634 2634 syminitletters=syminitletters))
2635 2635 if pos != len(spec):
2636 2636 raise error.ParseError(_('invalid token'), pos)
2637 2637 return _fixops(parser.simplifyinfixops(tree, ('list', 'or')))
2638 2638
2639 2639 class _aliasrules(parser.basealiasrules):
2640 2640 """Parsing and expansion rule set of revset aliases"""
2641 2641 _section = _('revset alias')
2642 2642
2643 2643 @staticmethod
2644 2644 def _parse(spec):
2645 2645 """Parse alias declaration/definition ``spec``
2646 2646
2647 2647 This allows symbol names to use also ``$`` as an initial letter
2648 2648 (for backward compatibility), and callers of this function should
2649 2649 examine whether ``$`` is used also for unexpected symbols or not.
2650 2650 """
2651 2651 return _parsewith(spec, syminitletters=_aliassyminitletters)
2652 2652
2653 2653 @staticmethod
2654 2654 def _trygetfunc(tree):
2655 2655 if tree[0] == 'func' and tree[1][0] == 'symbol':
2656 2656 return tree[1][1], getlist(tree[2])
2657 2657
2658 2658 def expandaliases(ui, tree):
2659 2659 aliases = _aliasrules.buildmap(ui.configitems('revsetalias'))
2660 2660 tree = _aliasrules.expand(aliases, tree)
2661 2661 # warn about problematic (but not referred) aliases
2662 2662 for name, alias in sorted(aliases.iteritems()):
2663 2663 if alias.error and not alias.warned:
2664 2664 ui.warn(_('warning: %s\n') % (alias.error))
2665 2665 alias.warned = True
2666 2666 return tree
2667 2667
2668 2668 def foldconcat(tree):
2669 2669 """Fold elements to be concatenated by `##`
2670 2670 """
2671 2671 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2672 2672 return tree
2673 2673 if tree[0] == '_concat':
2674 2674 pending = [tree]
2675 2675 l = []
2676 2676 while pending:
2677 2677 e = pending.pop()
2678 2678 if e[0] == '_concat':
2679 2679 pending.extend(reversed(e[1:]))
2680 2680 elif e[0] in ('string', 'symbol'):
2681 2681 l.append(e[1])
2682 2682 else:
2683 2683 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2684 2684 raise error.ParseError(msg)
2685 2685 return ('string', ''.join(l))
2686 2686 else:
2687 2687 return tuple(foldconcat(t) for t in tree)
2688 2688
2689 2689 def parse(spec, lookup=None):
2690 2690 return _parsewith(spec, lookup=lookup)
2691 2691
2692 2692 def posttreebuilthook(tree, repo):
2693 2693 # hook for extensions to execute code on the optimized tree
2694 2694 pass
2695 2695
2696 2696 def match(ui, spec, repo=None):
2697 2697 """Create a matcher for a single revision spec."""
2698 2698 return matchany(ui, [spec], repo=repo)
2699 2699
2700 2700 def matchany(ui, specs, repo=None):
2701 2701 """Create a matcher that will include any revisions matching one of the
2702 2702 given specs"""
2703 2703 if not specs:
2704 2704 def mfunc(repo, subset=None):
2705 2705 return baseset()
2706 2706 return mfunc
2707 2707 if not all(specs):
2708 2708 raise error.ParseError(_("empty query"))
2709 2709 lookup = None
2710 2710 if repo:
2711 2711 lookup = repo.__contains__
2712 2712 if len(specs) == 1:
2713 2713 tree = parse(specs[0], lookup)
2714 2714 else:
2715 2715 tree = ('or', ('list',) + tuple(parse(s, lookup) for s in specs))
2716 2716
2717 2717 if ui:
2718 2718 tree = expandaliases(ui, tree)
2719 2719 tree = foldconcat(tree)
2720 2720 tree = analyze(tree)
2721 2721 tree = optimize(tree)
2722 2722 posttreebuilthook(tree, repo)
2723 2723 return makematcher(tree)
2724 2724
2725 2725 def makematcher(tree):
2726 2726 """Create a matcher from an evaluatable tree"""
2727 2727 def mfunc(repo, subset=None):
2728 2728 if subset is None:
2729 2729 subset = fullreposet(repo)
2730 2730 if util.safehasattr(subset, 'isascending'):
2731 2731 result = getset(repo, subset, tree)
2732 2732 else:
2733 2733 result = getset(repo, baseset(subset), tree)
2734 2734 return result
2735 2735 return mfunc
2736 2736
2737 2737 def formatspec(expr, *args):
2738 2738 '''
2739 2739 This is a convenience function for using revsets internally, and
2740 2740 escapes arguments appropriately. Aliases are intentionally ignored
2741 2741 so that intended expression behavior isn't accidentally subverted.
2742 2742
2743 2743 Supported arguments:
2744 2744
2745 2745 %r = revset expression, parenthesized
2746 2746 %d = int(arg), no quoting
2747 2747 %s = string(arg), escaped and single-quoted
2748 2748 %b = arg.branch(), escaped and single-quoted
2749 2749 %n = hex(arg), single-quoted
2750 2750 %% = a literal '%'
2751 2751
2752 2752 Prefixing the type with 'l' specifies a parenthesized list of that type.
2753 2753
2754 2754 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2755 2755 '(10 or 11):: and ((this()) or (that()))'
2756 2756 >>> formatspec('%d:: and not %d::', 10, 20)
2757 2757 '10:: and not 20::'
2758 2758 >>> formatspec('%ld or %ld', [], [1])
2759 2759 "_list('') or 1"
2760 2760 >>> formatspec('keyword(%s)', 'foo\\xe9')
2761 2761 "keyword('foo\\\\xe9')"
2762 2762 >>> b = lambda: 'default'
2763 2763 >>> b.branch = b
2764 2764 >>> formatspec('branch(%b)', b)
2765 2765 "branch('default')"
2766 2766 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2767 2767 "root(_list('a\\x00b\\x00c\\x00d'))"
2768 2768 '''
2769 2769
2770 2770 def quote(s):
2771 2771 return repr(str(s))
2772 2772
2773 2773 def argtype(c, arg):
2774 2774 if c == 'd':
2775 2775 return str(int(arg))
2776 2776 elif c == 's':
2777 2777 return quote(arg)
2778 2778 elif c == 'r':
2779 2779 parse(arg) # make sure syntax errors are confined
2780 2780 return '(%s)' % arg
2781 2781 elif c == 'n':
2782 2782 return quote(node.hex(arg))
2783 2783 elif c == 'b':
2784 2784 return quote(arg.branch())
2785 2785
2786 2786 def listexp(s, t):
2787 2787 l = len(s)
2788 2788 if l == 0:
2789 2789 return "_list('')"
2790 2790 elif l == 1:
2791 2791 return argtype(t, s[0])
2792 2792 elif t == 'd':
2793 2793 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2794 2794 elif t == 's':
2795 2795 return "_list('%s')" % "\0".join(s)
2796 2796 elif t == 'n':
2797 2797 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2798 2798 elif t == 'b':
2799 2799 return "_list('%s')" % "\0".join(a.branch() for a in s)
2800 2800
2801 2801 m = l // 2
2802 2802 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2803 2803
2804 2804 ret = ''
2805 2805 pos = 0
2806 2806 arg = 0
2807 2807 while pos < len(expr):
2808 2808 c = expr[pos]
2809 2809 if c == '%':
2810 2810 pos += 1
2811 2811 d = expr[pos]
2812 2812 if d == '%':
2813 2813 ret += d
2814 2814 elif d in 'dsnbr':
2815 2815 ret += argtype(d, args[arg])
2816 2816 arg += 1
2817 2817 elif d == 'l':
2818 2818 # a list of some type
2819 2819 pos += 1
2820 2820 d = expr[pos]
2821 2821 ret += listexp(list(args[arg]), d)
2822 2822 arg += 1
2823 2823 else:
2824 2824 raise error.Abort(_('unexpected revspec format character %s')
2825 2825 % d)
2826 2826 else:
2827 2827 ret += c
2828 2828 pos += 1
2829 2829
2830 2830 return ret
2831 2831
2832 2832 def prettyformat(tree):
2833 2833 return parser.prettyformat(tree, ('string', 'symbol'))
2834 2834
2835 2835 def depth(tree):
2836 2836 if isinstance(tree, tuple):
2837 2837 return max(map(depth, tree)) + 1
2838 2838 else:
2839 2839 return 0
2840 2840
2841 2841 def funcsused(tree):
2842 2842 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2843 2843 return set()
2844 2844 else:
2845 2845 funcs = set()
2846 2846 for s in tree[1:]:
2847 2847 funcs |= funcsused(s)
2848 2848 if tree[0] == 'func':
2849 2849 funcs.add(tree[1][1])
2850 2850 return funcs
2851 2851
2852 2852 def _formatsetrepr(r):
2853 2853 """Format an optional printable representation of a set
2854 2854
2855 2855 ======== =================================
2856 2856 type(r) example
2857 2857 ======== =================================
2858 2858 tuple ('<not %r>', other)
2859 2859 str '<branch closed>'
2860 2860 callable lambda: '<branch %r>' % sorted(b)
2861 2861 object other
2862 2862 ======== =================================
2863 2863 """
2864 2864 if r is None:
2865 2865 return ''
2866 2866 elif isinstance(r, tuple):
2867 2867 return r[0] % r[1:]
2868 2868 elif isinstance(r, str):
2869 2869 return r
2870 2870 elif callable(r):
2871 2871 return r()
2872 2872 else:
2873 2873 return repr(r)
2874 2874
2875 2875 class abstractsmartset(object):
2876 2876
2877 2877 def __nonzero__(self):
2878 2878 """True if the smartset is not empty"""
2879 2879 raise NotImplementedError()
2880 2880
2881 2881 def __contains__(self, rev):
2882 2882 """provide fast membership testing"""
2883 2883 raise NotImplementedError()
2884 2884
2885 2885 def __iter__(self):
2886 2886 """iterate the set in the order it is supposed to be iterated"""
2887 2887 raise NotImplementedError()
2888 2888
2889 2889 # Attributes containing a function to perform a fast iteration in a given
2890 2890 # direction. A smartset can have none, one, or both defined.
2891 2891 #
2892 2892 # Default value is None instead of a function returning None to avoid
2893 2893 # initializing an iterator just for testing if a fast method exists.
2894 2894 fastasc = None
2895 2895 fastdesc = None
2896 2896
2897 2897 def isascending(self):
2898 2898 """True if the set will iterate in ascending order"""
2899 2899 raise NotImplementedError()
2900 2900
2901 2901 def isdescending(self):
2902 2902 """True if the set will iterate in descending order"""
2903 2903 raise NotImplementedError()
2904 2904
2905 2905 def istopo(self):
2906 2906 """True if the set will iterate in topographical order"""
2907 2907 raise NotImplementedError()
2908 2908
2909 2909 @util.cachefunc
2910 2910 def min(self):
2911 2911 """return the minimum element in the set"""
2912 2912 if self.fastasc is not None:
2913 2913 for r in self.fastasc():
2914 2914 return r
2915 2915 raise ValueError('arg is an empty sequence')
2916 2916 return min(self)
2917 2917
2918 2918 @util.cachefunc
2919 2919 def max(self):
2920 2920 """return the maximum element in the set"""
2921 2921 if self.fastdesc is not None:
2922 2922 for r in self.fastdesc():
2923 2923 return r
2924 2924 raise ValueError('arg is an empty sequence')
2925 2925 return max(self)
2926 2926
2927 2927 def first(self):
2928 2928 """return the first element in the set (user iteration perspective)
2929 2929
2930 2930 Return None if the set is empty"""
2931 2931 raise NotImplementedError()
2932 2932
2933 2933 def last(self):
2934 2934 """return the last element in the set (user iteration perspective)
2935 2935
2936 2936 Return None if the set is empty"""
2937 2937 raise NotImplementedError()
2938 2938
2939 2939 def __len__(self):
2940 2940 """return the length of the smartsets
2941 2941
2942 2942 This can be expensive on smartset that could be lazy otherwise."""
2943 2943 raise NotImplementedError()
2944 2944
2945 2945 def reverse(self):
2946 2946 """reverse the expected iteration order"""
2947 2947 raise NotImplementedError()
2948 2948
2949 2949 def sort(self, reverse=True):
2950 2950 """get the set to iterate in an ascending or descending order"""
2951 2951 raise NotImplementedError()
2952 2952
2953 2953 def __and__(self, other):
2954 2954 """Returns a new object with the intersection of the two collections.
2955 2955
2956 2956 This is part of the mandatory API for smartset."""
2957 2957 if isinstance(other, fullreposet):
2958 2958 return self
2959 2959 return self.filter(other.__contains__, condrepr=other, cache=False)
2960 2960
2961 2961 def __add__(self, other):
2962 2962 """Returns a new object with the union of the two collections.
2963 2963
2964 2964 This is part of the mandatory API for smartset."""
2965 2965 return addset(self, other)
2966 2966
2967 2967 def __sub__(self, other):
2968 2968 """Returns a new object with the substraction of the two collections.
2969 2969
2970 2970 This is part of the mandatory API for smartset."""
2971 2971 c = other.__contains__
2972 2972 return self.filter(lambda r: not c(r), condrepr=('<not %r>', other),
2973 2973 cache=False)
2974 2974
2975 2975 def filter(self, condition, condrepr=None, cache=True):
2976 2976 """Returns this smartset filtered by condition as a new smartset.
2977 2977
2978 2978 `condition` is a callable which takes a revision number and returns a
2979 2979 boolean. Optional `condrepr` provides a printable representation of
2980 2980 the given `condition`.
2981 2981
2982 2982 This is part of the mandatory API for smartset."""
2983 2983 # builtin cannot be cached. but do not needs to
2984 2984 if cache and util.safehasattr(condition, 'func_code'):
2985 2985 condition = util.cachefunc(condition)
2986 2986 return filteredset(self, condition, condrepr)
2987 2987
2988 2988 class baseset(abstractsmartset):
2989 2989 """Basic data structure that represents a revset and contains the basic
2990 2990 operation that it should be able to perform.
2991 2991
2992 2992 Every method in this class should be implemented by any smartset class.
2993 2993 """
2994 2994 def __init__(self, data=(), datarepr=None, istopo=False):
2995 2995 """
2996 2996 datarepr: a tuple of (format, obj, ...), a function or an object that
2997 2997 provides a printable representation of the given data.
2998 2998 """
2999 2999 self._ascending = None
3000 3000 self._istopo = istopo
3001 3001 if not isinstance(data, list):
3002 3002 if isinstance(data, set):
3003 3003 self._set = data
3004 3004 # set has no order we pick one for stability purpose
3005 3005 self._ascending = True
3006 3006 data = list(data)
3007 3007 self._list = data
3008 3008 self._datarepr = datarepr
3009 3009
3010 3010 @util.propertycache
3011 3011 def _set(self):
3012 3012 return set(self._list)
3013 3013
3014 3014 @util.propertycache
3015 3015 def _asclist(self):
3016 3016 asclist = self._list[:]
3017 3017 asclist.sort()
3018 3018 return asclist
3019 3019
3020 3020 def __iter__(self):
3021 3021 if self._ascending is None:
3022 3022 return iter(self._list)
3023 3023 elif self._ascending:
3024 3024 return iter(self._asclist)
3025 3025 else:
3026 3026 return reversed(self._asclist)
3027 3027
3028 3028 def fastasc(self):
3029 3029 return iter(self._asclist)
3030 3030
3031 3031 def fastdesc(self):
3032 3032 return reversed(self._asclist)
3033 3033
3034 3034 @util.propertycache
3035 3035 def __contains__(self):
3036 3036 return self._set.__contains__
3037 3037
3038 3038 def __nonzero__(self):
3039 3039 return bool(self._list)
3040 3040
3041 3041 def sort(self, reverse=False):
3042 3042 self._ascending = not bool(reverse)
3043 3043 self._istopo = False
3044 3044
3045 3045 def reverse(self):
3046 3046 if self._ascending is None:
3047 3047 self._list.reverse()
3048 3048 else:
3049 3049 self._ascending = not self._ascending
3050 3050 self._istopo = False
3051 3051
3052 3052 def __len__(self):
3053 3053 return len(self._list)
3054 3054
3055 3055 def isascending(self):
3056 3056 """Returns True if the collection is ascending order, False if not.
3057 3057
3058 3058 This is part of the mandatory API for smartset."""
3059 3059 if len(self) <= 1:
3060 3060 return True
3061 3061 return self._ascending is not None and self._ascending
3062 3062
3063 3063 def isdescending(self):
3064 3064 """Returns True if the collection is descending order, False if not.
3065 3065
3066 3066 This is part of the mandatory API for smartset."""
3067 3067 if len(self) <= 1:
3068 3068 return True
3069 3069 return self._ascending is not None and not self._ascending
3070 3070
3071 3071 def istopo(self):
3072 3072 """Is the collection is in topographical order or not.
3073 3073
3074 3074 This is part of the mandatory API for smartset."""
3075 3075 if len(self) <= 1:
3076 3076 return True
3077 3077 return self._istopo
3078 3078
3079 3079 def first(self):
3080 3080 if self:
3081 3081 if self._ascending is None:
3082 3082 return self._list[0]
3083 3083 elif self._ascending:
3084 3084 return self._asclist[0]
3085 3085 else:
3086 3086 return self._asclist[-1]
3087 3087 return None
3088 3088
3089 3089 def last(self):
3090 3090 if self:
3091 3091 if self._ascending is None:
3092 3092 return self._list[-1]
3093 3093 elif self._ascending:
3094 3094 return self._asclist[-1]
3095 3095 else:
3096 3096 return self._asclist[0]
3097 3097 return None
3098 3098
3099 3099 def __repr__(self):
3100 3100 d = {None: '', False: '-', True: '+'}[self._ascending]
3101 3101 s = _formatsetrepr(self._datarepr)
3102 3102 if not s:
3103 3103 l = self._list
3104 3104 # if _list has been built from a set, it might have a different
3105 3105 # order from one python implementation to another.
3106 3106 # We fallback to the sorted version for a stable output.
3107 3107 if self._ascending is not None:
3108 3108 l = self._asclist
3109 3109 s = repr(l)
3110 3110 return '<%s%s %s>' % (type(self).__name__, d, s)
3111 3111
3112 3112 class filteredset(abstractsmartset):
3113 3113 """Duck type for baseset class which iterates lazily over the revisions in
3114 3114 the subset and contains a function which tests for membership in the
3115 3115 revset
3116 3116 """
3117 3117 def __init__(self, subset, condition=lambda x: True, condrepr=None):
3118 3118 """
3119 3119 condition: a function that decide whether a revision in the subset
3120 3120 belongs to the revset or not.
3121 3121 condrepr: a tuple of (format, obj, ...), a function or an object that
3122 3122 provides a printable representation of the given condition.
3123 3123 """
3124 3124 self._subset = subset
3125 3125 self._condition = condition
3126 3126 self._condrepr = condrepr
3127 3127
3128 3128 def __contains__(self, x):
3129 3129 return x in self._subset and self._condition(x)
3130 3130
3131 3131 def __iter__(self):
3132 3132 return self._iterfilter(self._subset)
3133 3133
3134 3134 def _iterfilter(self, it):
3135 3135 cond = self._condition
3136 3136 for x in it:
3137 3137 if cond(x):
3138 3138 yield x
3139 3139
3140 3140 @property
3141 3141 def fastasc(self):
3142 3142 it = self._subset.fastasc
3143 3143 if it is None:
3144 3144 return None
3145 3145 return lambda: self._iterfilter(it())
3146 3146
3147 3147 @property
3148 3148 def fastdesc(self):
3149 3149 it = self._subset.fastdesc
3150 3150 if it is None:
3151 3151 return None
3152 3152 return lambda: self._iterfilter(it())
3153 3153
3154 3154 def __nonzero__(self):
3155 3155 fast = None
3156 3156 candidates = [self.fastasc if self.isascending() else None,
3157 3157 self.fastdesc if self.isdescending() else None,
3158 3158 self.fastasc,
3159 3159 self.fastdesc]
3160 3160 for candidate in candidates:
3161 3161 if candidate is not None:
3162 3162 fast = candidate
3163 3163 break
3164 3164
3165 3165 if fast is not None:
3166 3166 it = fast()
3167 3167 else:
3168 3168 it = self
3169 3169
3170 3170 for r in it:
3171 3171 return True
3172 3172 return False
3173 3173
3174 3174 def __len__(self):
3175 3175 # Basic implementation to be changed in future patches.
3176 3176 # until this gets improved, we use generator expression
3177 3177 # here, since list compr is free to call __len__ again
3178 3178 # causing infinite recursion
3179 3179 l = baseset(r for r in self)
3180 3180 return len(l)
3181 3181
3182 3182 def sort(self, reverse=False):
3183 3183 self._subset.sort(reverse=reverse)
3184 3184
3185 3185 def reverse(self):
3186 3186 self._subset.reverse()
3187 3187
3188 3188 def isascending(self):
3189 3189 return self._subset.isascending()
3190 3190
3191 3191 def isdescending(self):
3192 3192 return self._subset.isdescending()
3193 3193
3194 3194 def istopo(self):
3195 3195 return self._subset.istopo()
3196 3196
3197 3197 def first(self):
3198 3198 for x in self:
3199 3199 return x
3200 3200 return None
3201 3201
3202 3202 def last(self):
3203 3203 it = None
3204 3204 if self.isascending():
3205 3205 it = self.fastdesc
3206 3206 elif self.isdescending():
3207 3207 it = self.fastasc
3208 3208 if it is not None:
3209 3209 for x in it():
3210 3210 return x
3211 3211 return None #empty case
3212 3212 else:
3213 3213 x = None
3214 3214 for x in self:
3215 3215 pass
3216 3216 return x
3217 3217
3218 3218 def __repr__(self):
3219 3219 xs = [repr(self._subset)]
3220 3220 s = _formatsetrepr(self._condrepr)
3221 3221 if s:
3222 3222 xs.append(s)
3223 3223 return '<%s %s>' % (type(self).__name__, ', '.join(xs))
3224 3224
3225 3225 def _iterordered(ascending, iter1, iter2):
3226 3226 """produce an ordered iteration from two iterators with the same order
3227 3227
3228 3228 The ascending is used to indicated the iteration direction.
3229 3229 """
3230 3230 choice = max
3231 3231 if ascending:
3232 3232 choice = min
3233 3233
3234 3234 val1 = None
3235 3235 val2 = None
3236 3236 try:
3237 3237 # Consume both iterators in an ordered way until one is empty
3238 3238 while True:
3239 3239 if val1 is None:
3240 3240 val1 = next(iter1)
3241 3241 if val2 is None:
3242 3242 val2 = next(iter2)
3243 3243 n = choice(val1, val2)
3244 3244 yield n
3245 3245 if val1 == n:
3246 3246 val1 = None
3247 3247 if val2 == n:
3248 3248 val2 = None
3249 3249 except StopIteration:
3250 3250 # Flush any remaining values and consume the other one
3251 3251 it = iter2
3252 3252 if val1 is not None:
3253 3253 yield val1
3254 3254 it = iter1
3255 3255 elif val2 is not None:
3256 3256 # might have been equality and both are empty
3257 3257 yield val2
3258 3258 for val in it:
3259 3259 yield val
3260 3260
3261 3261 class addset(abstractsmartset):
3262 3262 """Represent the addition of two sets
3263 3263
3264 3264 Wrapper structure for lazily adding two structures without losing much
3265 3265 performance on the __contains__ method
3266 3266
3267 3267 If the ascending attribute is set, that means the two structures are
3268 3268 ordered in either an ascending or descending way. Therefore, we can add
3269 3269 them maintaining the order by iterating over both at the same time
3270 3270
3271 3271 >>> xs = baseset([0, 3, 2])
3272 3272 >>> ys = baseset([5, 2, 4])
3273 3273
3274 3274 >>> rs = addset(xs, ys)
3275 3275 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
3276 3276 (True, True, False, True, 0, 4)
3277 3277 >>> rs = addset(xs, baseset([]))
3278 3278 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
3279 3279 (True, True, False, 0, 2)
3280 3280 >>> rs = addset(baseset([]), baseset([]))
3281 3281 >>> bool(rs), 0 in rs, rs.first(), rs.last()
3282 3282 (False, False, None, None)
3283 3283
3284 3284 iterate unsorted:
3285 3285 >>> rs = addset(xs, ys)
3286 3286 >>> # (use generator because pypy could call len())
3287 3287 >>> list(x for x in rs) # without _genlist
3288 3288 [0, 3, 2, 5, 4]
3289 3289 >>> assert not rs._genlist
3290 3290 >>> len(rs)
3291 3291 5
3292 3292 >>> [x for x in rs] # with _genlist
3293 3293 [0, 3, 2, 5, 4]
3294 3294 >>> assert rs._genlist
3295 3295
3296 3296 iterate ascending:
3297 3297 >>> rs = addset(xs, ys, ascending=True)
3298 3298 >>> # (use generator because pypy could call len())
3299 3299 >>> list(x for x in rs), list(x for x in rs.fastasc()) # without _asclist
3300 3300 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3301 3301 >>> assert not rs._asclist
3302 3302 >>> len(rs)
3303 3303 5
3304 3304 >>> [x for x in rs], [x for x in rs.fastasc()]
3305 3305 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3306 3306 >>> assert rs._asclist
3307 3307
3308 3308 iterate descending:
3309 3309 >>> rs = addset(xs, ys, ascending=False)
3310 3310 >>> # (use generator because pypy could call len())
3311 3311 >>> list(x for x in rs), list(x for x in rs.fastdesc()) # without _asclist
3312 3312 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3313 3313 >>> assert not rs._asclist
3314 3314 >>> len(rs)
3315 3315 5
3316 3316 >>> [x for x in rs], [x for x in rs.fastdesc()]
3317 3317 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3318 3318 >>> assert rs._asclist
3319 3319
3320 3320 iterate ascending without fastasc:
3321 3321 >>> rs = addset(xs, generatorset(ys), ascending=True)
3322 3322 >>> assert rs.fastasc is None
3323 3323 >>> [x for x in rs]
3324 3324 [0, 2, 3, 4, 5]
3325 3325
3326 3326 iterate descending without fastdesc:
3327 3327 >>> rs = addset(generatorset(xs), ys, ascending=False)
3328 3328 >>> assert rs.fastdesc is None
3329 3329 >>> [x for x in rs]
3330 3330 [5, 4, 3, 2, 0]
3331 3331 """
3332 3332 def __init__(self, revs1, revs2, ascending=None):
3333 3333 self._r1 = revs1
3334 3334 self._r2 = revs2
3335 3335 self._iter = None
3336 3336 self._ascending = ascending
3337 3337 self._genlist = None
3338 3338 self._asclist = None
3339 3339
3340 3340 def __len__(self):
3341 3341 return len(self._list)
3342 3342
3343 3343 def __nonzero__(self):
3344 3344 return bool(self._r1) or bool(self._r2)
3345 3345
3346 3346 @util.propertycache
3347 3347 def _list(self):
3348 3348 if not self._genlist:
3349 3349 self._genlist = baseset(iter(self))
3350 3350 return self._genlist
3351 3351
3352 3352 def __iter__(self):
3353 3353 """Iterate over both collections without repeating elements
3354 3354
3355 3355 If the ascending attribute is not set, iterate over the first one and
3356 3356 then over the second one checking for membership on the first one so we
3357 3357 dont yield any duplicates.
3358 3358
3359 3359 If the ascending attribute is set, iterate over both collections at the
3360 3360 same time, yielding only one value at a time in the given order.
3361 3361 """
3362 3362 if self._ascending is None:
3363 3363 if self._genlist:
3364 3364 return iter(self._genlist)
3365 3365 def arbitraryordergen():
3366 3366 for r in self._r1:
3367 3367 yield r
3368 3368 inr1 = self._r1.__contains__
3369 3369 for r in self._r2:
3370 3370 if not inr1(r):
3371 3371 yield r
3372 3372 return arbitraryordergen()
3373 3373 # try to use our own fast iterator if it exists
3374 3374 self._trysetasclist()
3375 3375 if self._ascending:
3376 3376 attr = 'fastasc'
3377 3377 else:
3378 3378 attr = 'fastdesc'
3379 3379 it = getattr(self, attr)
3380 3380 if it is not None:
3381 3381 return it()
3382 3382 # maybe half of the component supports fast
3383 3383 # get iterator for _r1
3384 3384 iter1 = getattr(self._r1, attr)
3385 3385 if iter1 is None:
3386 3386 # let's avoid side effect (not sure it matters)
3387 3387 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
3388 3388 else:
3389 3389 iter1 = iter1()
3390 3390 # get iterator for _r2
3391 3391 iter2 = getattr(self._r2, attr)
3392 3392 if iter2 is None:
3393 3393 # let's avoid side effect (not sure it matters)
3394 3394 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
3395 3395 else:
3396 3396 iter2 = iter2()
3397 3397 return _iterordered(self._ascending, iter1, iter2)
3398 3398
3399 3399 def _trysetasclist(self):
3400 3400 """populate the _asclist attribute if possible and necessary"""
3401 3401 if self._genlist is not None and self._asclist is None:
3402 3402 self._asclist = sorted(self._genlist)
3403 3403
3404 3404 @property
3405 3405 def fastasc(self):
3406 3406 self._trysetasclist()
3407 3407 if self._asclist is not None:
3408 3408 return self._asclist.__iter__
3409 3409 iter1 = self._r1.fastasc
3410 3410 iter2 = self._r2.fastasc
3411 3411 if None in (iter1, iter2):
3412 3412 return None
3413 3413 return lambda: _iterordered(True, iter1(), iter2())
3414 3414
3415 3415 @property
3416 3416 def fastdesc(self):
3417 3417 self._trysetasclist()
3418 3418 if self._asclist is not None:
3419 3419 return self._asclist.__reversed__
3420 3420 iter1 = self._r1.fastdesc
3421 3421 iter2 = self._r2.fastdesc
3422 3422 if None in (iter1, iter2):
3423 3423 return None
3424 3424 return lambda: _iterordered(False, iter1(), iter2())
3425 3425
3426 3426 def __contains__(self, x):
3427 3427 return x in self._r1 or x in self._r2
3428 3428
3429 3429 def sort(self, reverse=False):
3430 3430 """Sort the added set
3431 3431
3432 3432 For this we use the cached list with all the generated values and if we
3433 3433 know they are ascending or descending we can sort them in a smart way.
3434 3434 """
3435 3435 self._ascending = not reverse
3436 3436
3437 3437 def isascending(self):
3438 3438 return self._ascending is not None and self._ascending
3439 3439
3440 3440 def isdescending(self):
3441 3441 return self._ascending is not None and not self._ascending
3442 3442
3443 3443 def istopo(self):
3444 3444 # not worth the trouble asserting if the two sets combined are still
3445 3445 # in topographical order. Use the sort() predicate to explicitly sort
3446 3446 # again instead.
3447 3447 return False
3448 3448
3449 3449 def reverse(self):
3450 3450 if self._ascending is None:
3451 3451 self._list.reverse()
3452 3452 else:
3453 3453 self._ascending = not self._ascending
3454 3454
3455 3455 def first(self):
3456 3456 for x in self:
3457 3457 return x
3458 3458 return None
3459 3459
3460 3460 def last(self):
3461 3461 self.reverse()
3462 3462 val = self.first()
3463 3463 self.reverse()
3464 3464 return val
3465 3465
3466 3466 def __repr__(self):
3467 3467 d = {None: '', False: '-', True: '+'}[self._ascending]
3468 3468 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3469 3469
3470 3470 class generatorset(abstractsmartset):
3471 3471 """Wrap a generator for lazy iteration
3472 3472
3473 3473 Wrapper structure for generators that provides lazy membership and can
3474 3474 be iterated more than once.
3475 3475 When asked for membership it generates values until either it finds the
3476 3476 requested one or has gone through all the elements in the generator
3477 3477 """
3478 3478 def __init__(self, gen, iterasc=None):
3479 3479 """
3480 3480 gen: a generator producing the values for the generatorset.
3481 3481 """
3482 3482 self._gen = gen
3483 3483 self._asclist = None
3484 3484 self._cache = {}
3485 3485 self._genlist = []
3486 3486 self._finished = False
3487 3487 self._ascending = True
3488 3488 if iterasc is not None:
3489 3489 if iterasc:
3490 3490 self.fastasc = self._iterator
3491 3491 self.__contains__ = self._asccontains
3492 3492 else:
3493 3493 self.fastdesc = self._iterator
3494 3494 self.__contains__ = self._desccontains
3495 3495
3496 3496 def __nonzero__(self):
3497 3497 # Do not use 'for r in self' because it will enforce the iteration
3498 3498 # order (default ascending), possibly unrolling a whole descending
3499 3499 # iterator.
3500 3500 if self._genlist:
3501 3501 return True
3502 3502 for r in self._consumegen():
3503 3503 return True
3504 3504 return False
3505 3505
3506 3506 def __contains__(self, x):
3507 3507 if x in self._cache:
3508 3508 return self._cache[x]
3509 3509
3510 3510 # Use new values only, as existing values would be cached.
3511 3511 for l in self._consumegen():
3512 3512 if l == x:
3513 3513 return True
3514 3514
3515 3515 self._cache[x] = False
3516 3516 return False
3517 3517
3518 3518 def _asccontains(self, x):
3519 3519 """version of contains optimised for ascending generator"""
3520 3520 if x in self._cache:
3521 3521 return self._cache[x]
3522 3522
3523 3523 # Use new values only, as existing values would be cached.
3524 3524 for l in self._consumegen():
3525 3525 if l == x:
3526 3526 return True
3527 3527 if l > x:
3528 3528 break
3529 3529
3530 3530 self._cache[x] = False
3531 3531 return False
3532 3532
3533 3533 def _desccontains(self, x):
3534 3534 """version of contains optimised for descending generator"""
3535 3535 if x in self._cache:
3536 3536 return self._cache[x]
3537 3537
3538 3538 # Use new values only, as existing values would be cached.
3539 3539 for l in self._consumegen():
3540 3540 if l == x:
3541 3541 return True
3542 3542 if l < x:
3543 3543 break
3544 3544
3545 3545 self._cache[x] = False
3546 3546 return False
3547 3547
3548 3548 def __iter__(self):
3549 3549 if self._ascending:
3550 3550 it = self.fastasc
3551 3551 else:
3552 3552 it = self.fastdesc
3553 3553 if it is not None:
3554 3554 return it()
3555 3555 # we need to consume the iterator
3556 3556 for x in self._consumegen():
3557 3557 pass
3558 3558 # recall the same code
3559 3559 return iter(self)
3560 3560
3561 3561 def _iterator(self):
3562 3562 if self._finished:
3563 3563 return iter(self._genlist)
3564 3564
3565 3565 # We have to use this complex iteration strategy to allow multiple
3566 3566 # iterations at the same time. We need to be able to catch revision
3567 3567 # removed from _consumegen and added to genlist in another instance.
3568 3568 #
3569 3569 # Getting rid of it would provide an about 15% speed up on this
3570 3570 # iteration.
3571 3571 genlist = self._genlist
3572 3572 nextrev = self._consumegen().next
3573 3573 _len = len # cache global lookup
3574 3574 def gen():
3575 3575 i = 0
3576 3576 while True:
3577 3577 if i < _len(genlist):
3578 3578 yield genlist[i]
3579 3579 else:
3580 3580 yield nextrev()
3581 3581 i += 1
3582 3582 return gen()
3583 3583
3584 3584 def _consumegen(self):
3585 3585 cache = self._cache
3586 3586 genlist = self._genlist.append
3587 3587 for item in self._gen:
3588 3588 cache[item] = True
3589 3589 genlist(item)
3590 3590 yield item
3591 3591 if not self._finished:
3592 3592 self._finished = True
3593 3593 asc = self._genlist[:]
3594 3594 asc.sort()
3595 3595 self._asclist = asc
3596 3596 self.fastasc = asc.__iter__
3597 3597 self.fastdesc = asc.__reversed__
3598 3598
3599 3599 def __len__(self):
3600 3600 for x in self._consumegen():
3601 3601 pass
3602 3602 return len(self._genlist)
3603 3603
3604 3604 def sort(self, reverse=False):
3605 3605 self._ascending = not reverse
3606 3606
3607 3607 def reverse(self):
3608 3608 self._ascending = not self._ascending
3609 3609
3610 3610 def isascending(self):
3611 3611 return self._ascending
3612 3612
3613 3613 def isdescending(self):
3614 3614 return not self._ascending
3615 3615
3616 3616 def istopo(self):
3617 3617 # not worth the trouble asserting if the two sets combined are still
3618 3618 # in topographical order. Use the sort() predicate to explicitly sort
3619 3619 # again instead.
3620 3620 return False
3621 3621
3622 3622 def first(self):
3623 3623 if self._ascending:
3624 3624 it = self.fastasc
3625 3625 else:
3626 3626 it = self.fastdesc
3627 3627 if it is None:
3628 3628 # we need to consume all and try again
3629 3629 for x in self._consumegen():
3630 3630 pass
3631 3631 return self.first()
3632 3632 return next(it(), None)
3633 3633
3634 3634 def last(self):
3635 3635 if self._ascending:
3636 3636 it = self.fastdesc
3637 3637 else:
3638 3638 it = self.fastasc
3639 3639 if it is None:
3640 3640 # we need to consume all and try again
3641 3641 for x in self._consumegen():
3642 3642 pass
3643 3643 return self.first()
3644 3644 return next(it(), None)
3645 3645
3646 3646 def __repr__(self):
3647 3647 d = {False: '-', True: '+'}[self._ascending]
3648 3648 return '<%s%s>' % (type(self).__name__, d)
3649 3649
3650 3650 class spanset(abstractsmartset):
3651 3651 """Duck type for baseset class which represents a range of revisions and
3652 3652 can work lazily and without having all the range in memory
3653 3653
3654 3654 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3655 3655 notable points:
3656 3656 - when x < y it will be automatically descending,
3657 3657 - revision filtered with this repoview will be skipped.
3658 3658
3659 3659 """
3660 3660 def __init__(self, repo, start=0, end=None):
3661 3661 """
3662 3662 start: first revision included the set
3663 3663 (default to 0)
3664 3664 end: first revision excluded (last+1)
3665 3665 (default to len(repo)
3666 3666
3667 3667 Spanset will be descending if `end` < `start`.
3668 3668 """
3669 3669 if end is None:
3670 3670 end = len(repo)
3671 3671 self._ascending = start <= end
3672 3672 if not self._ascending:
3673 3673 start, end = end + 1, start +1
3674 3674 self._start = start
3675 3675 self._end = end
3676 3676 self._hiddenrevs = repo.changelog.filteredrevs
3677 3677
3678 3678 def sort(self, reverse=False):
3679 3679 self._ascending = not reverse
3680 3680
3681 3681 def reverse(self):
3682 3682 self._ascending = not self._ascending
3683 3683
3684 3684 def istopo(self):
3685 3685 # not worth the trouble asserting if the two sets combined are still
3686 3686 # in topographical order. Use the sort() predicate to explicitly sort
3687 3687 # again instead.
3688 3688 return False
3689 3689
3690 3690 def _iterfilter(self, iterrange):
3691 3691 s = self._hiddenrevs
3692 3692 for r in iterrange:
3693 3693 if r not in s:
3694 3694 yield r
3695 3695
3696 3696 def __iter__(self):
3697 3697 if self._ascending:
3698 3698 return self.fastasc()
3699 3699 else:
3700 3700 return self.fastdesc()
3701 3701
3702 3702 def fastasc(self):
3703 3703 iterrange = xrange(self._start, self._end)
3704 3704 if self._hiddenrevs:
3705 3705 return self._iterfilter(iterrange)
3706 3706 return iter(iterrange)
3707 3707
3708 3708 def fastdesc(self):
3709 3709 iterrange = xrange(self._end - 1, self._start - 1, -1)
3710 3710 if self._hiddenrevs:
3711 3711 return self._iterfilter(iterrange)
3712 3712 return iter(iterrange)
3713 3713
3714 3714 def __contains__(self, rev):
3715 3715 hidden = self._hiddenrevs
3716 3716 return ((self._start <= rev < self._end)
3717 3717 and not (hidden and rev in hidden))
3718 3718
3719 3719 def __nonzero__(self):
3720 3720 for r in self:
3721 3721 return True
3722 3722 return False
3723 3723
3724 3724 def __len__(self):
3725 3725 if not self._hiddenrevs:
3726 3726 return abs(self._end - self._start)
3727 3727 else:
3728 3728 count = 0
3729 3729 start = self._start
3730 3730 end = self._end
3731 3731 for rev in self._hiddenrevs:
3732 3732 if (end < rev <= start) or (start <= rev < end):
3733 3733 count += 1
3734 3734 return abs(self._end - self._start) - count
3735 3735
3736 3736 def isascending(self):
3737 3737 return self._ascending
3738 3738
3739 3739 def isdescending(self):
3740 3740 return not self._ascending
3741 3741
3742 3742 def first(self):
3743 3743 if self._ascending:
3744 3744 it = self.fastasc
3745 3745 else:
3746 3746 it = self.fastdesc
3747 3747 for x in it():
3748 3748 return x
3749 3749 return None
3750 3750
3751 3751 def last(self):
3752 3752 if self._ascending:
3753 3753 it = self.fastdesc
3754 3754 else:
3755 3755 it = self.fastasc
3756 3756 for x in it():
3757 3757 return x
3758 3758 return None
3759 3759
3760 3760 def __repr__(self):
3761 3761 d = {False: '-', True: '+'}[self._ascending]
3762 3762 return '<%s%s %d:%d>' % (type(self).__name__, d,
3763 3763 self._start, self._end - 1)
3764 3764
3765 3765 class fullreposet(spanset):
3766 3766 """a set containing all revisions in the repo
3767 3767
3768 3768 This class exists to host special optimization and magic to handle virtual
3769 3769 revisions such as "null".
3770 3770 """
3771 3771
3772 3772 def __init__(self, repo):
3773 3773 super(fullreposet, self).__init__(repo)
3774 3774
3775 3775 def __and__(self, other):
3776 3776 """As self contains the whole repo, all of the other set should also be
3777 3777 in self. Therefore `self & other = other`.
3778 3778
3779 3779 This boldly assumes the other contains valid revs only.
3780 3780 """
3781 3781 # other not a smartset, make is so
3782 3782 if not util.safehasattr(other, 'isascending'):
3783 3783 # filter out hidden revision
3784 3784 # (this boldly assumes all smartset are pure)
3785 3785 #
3786 3786 # `other` was used with "&", let's assume this is a set like
3787 3787 # object.
3788 3788 other = baseset(other - self._hiddenrevs)
3789 3789
3790 3790 # XXX As fullreposet is also used as bootstrap, this is wrong.
3791 3791 #
3792 3792 # With a giveme312() revset returning [3,1,2], this makes
3793 3793 # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
3794 3794 # We cannot just drop it because other usage still need to sort it:
3795 3795 # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
3796 3796 #
3797 3797 # There is also some faulty revset implementations that rely on it
3798 3798 # (eg: children as of its state in e8075329c5fb)
3799 3799 #
3800 3800 # When we fix the two points above we can move this into the if clause
3801 3801 other.sort(reverse=self.isdescending())
3802 3802 return other
3803 3803
3804 3804 def prettyformatset(revs):
3805 3805 lines = []
3806 3806 rs = repr(revs)
3807 3807 p = 0
3808 3808 while p < len(rs):
3809 3809 q = rs.find('<', p + 1)
3810 3810 if q < 0:
3811 3811 q = len(rs)
3812 3812 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3813 3813 assert l >= 0
3814 3814 lines.append((l, rs[p:q].rstrip()))
3815 3815 p = q
3816 3816 return '\n'.join(' ' * l + s for l, s in lines)
3817 3817
3818 3818 def loadpredicate(ui, extname, registrarobj):
3819 3819 """Load revset predicates from specified registrarobj
3820 3820 """
3821 3821 for name, func in registrarobj._table.iteritems():
3822 3822 symbols[name] = func
3823 3823 if func._safe:
3824 3824 safesymbols.add(name)
3825 3825
3826 3826 # load built-in predicates explicitly to setup safesymbols
3827 3827 loadpredicate(None, None, predicate)
3828 3828
3829 3829 # tell hggettext to extract docstrings from these functions:
3830 3830 i18nfunctions = symbols.values()
@@ -1,3540 +1,3569 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 'x:y' takes ordering parameter into account:
1259
1260 $ try -p optimized '3:0 & 0:3 & not 2:1'
1261 * optimized:
1262 (difference
1263 (and
1264 (range
1265 ('symbol', '3')
1266 ('symbol', '0')
1267 define)
1268 (range
1269 ('symbol', '0')
1270 ('symbol', '3')
1271 follow)
1272 define)
1273 (range
1274 ('symbol', '2')
1275 ('symbol', '1')
1276 any)
1277 define)
1278 * set:
1279 <filteredset
1280 <filteredset
1281 <spanset- 0:3>,
1282 <spanset+ 0:3>>,
1283 <not
1284 <spanset+ 1:2>>>
1285 3
1286 0
1287
1258 1288 'a + b', which is optimized to '_list(a b)', should take the ordering of
1259 1289 the left expression:
1260 1290
1261 1291 $ try --optimize '2:0 & (0 + 1 + 2)'
1262 1292 (and
1263 1293 (range
1264 1294 ('symbol', '2')
1265 1295 ('symbol', '0'))
1266 1296 (group
1267 1297 (or
1268 1298 (list
1269 1299 ('symbol', '0')
1270 1300 ('symbol', '1')
1271 1301 ('symbol', '2')))))
1272 1302 * optimized:
1273 1303 (and
1274 1304 (range
1275 1305 ('symbol', '2')
1276 1306 ('symbol', '0')
1277 1307 define)
1278 1308 (func
1279 1309 ('symbol', '_list')
1280 1310 ('string', '0\x001\x002')
1281 1311 follow)
1282 1312 define)
1283 1313 * set:
1284 1314 <filteredset
1285 1315 <spanset- 0:2>,
1286 1316 <baseset [0, 1, 2]>>
1287 1317 2
1288 1318 1
1289 1319 0
1290 1320
1291 1321 'A + B' should take the ordering of the left expression:
1292 1322
1293 1323 $ try --optimize '2:0 & (0:1 + 2)'
1294 1324 (and
1295 1325 (range
1296 1326 ('symbol', '2')
1297 1327 ('symbol', '0'))
1298 1328 (group
1299 1329 (or
1300 1330 (list
1301 1331 (range
1302 1332 ('symbol', '0')
1303 1333 ('symbol', '1'))
1304 1334 ('symbol', '2')))))
1305 1335 * optimized:
1306 1336 (and
1307 1337 (range
1308 1338 ('symbol', '2')
1309 1339 ('symbol', '0')
1310 1340 define)
1311 1341 (or
1312 1342 (list
1313 1343 (range
1314 1344 ('symbol', '0')
1315 1345 ('symbol', '1')
1316 1346 follow)
1317 1347 ('symbol', '2'))
1318 1348 follow)
1319 1349 define)
1320 1350 * set:
1321 1351 <filteredset
1322 1352 <spanset- 0:2>,
1323 1353 <addset
1324 1354 <spanset+ 0:1>,
1325 1355 <baseset [2]>>>
1326 1356 2
1327 1357 1
1328 1358 0
1329 1359
1330 1360 '_intlist(a b)' should behave like 'a + b':
1331 1361
1332 1362 $ trylist --optimize '2:0 & %ld' 0 1 2
1333 1363 (and
1334 1364 (range
1335 1365 ('symbol', '2')
1336 1366 ('symbol', '0'))
1337 1367 (func
1338 1368 ('symbol', '_intlist')
1339 1369 ('string', '0\x001\x002')))
1340 1370 * optimized:
1341 1371 (and
1342 1372 (func
1343 1373 ('symbol', '_intlist')
1344 1374 ('string', '0\x001\x002')
1345 1375 follow)
1346 1376 (range
1347 1377 ('symbol', '2')
1348 1378 ('symbol', '0')
1349 1379 define)
1350 1380 define)
1351 1381 * set:
1352 1382 <filteredset
1353 1383 <spanset- 0:2>,
1354 1384 <baseset+ [0, 1, 2]>>
1355 1385 2
1356 1386 1
1357 1387 0
1358 1388
1359 1389 $ trylist --optimize '%ld & 2:0' 0 2 1
1360 1390 (and
1361 1391 (func
1362 1392 ('symbol', '_intlist')
1363 1393 ('string', '0\x002\x001'))
1364 1394 (range
1365 1395 ('symbol', '2')
1366 1396 ('symbol', '0')))
1367 1397 * optimized:
1368 1398 (and
1369 1399 (func
1370 1400 ('symbol', '_intlist')
1371 1401 ('string', '0\x002\x001')
1372 1402 define)
1373 1403 (range
1374 1404 ('symbol', '2')
1375 1405 ('symbol', '0')
1376 1406 follow)
1377 1407 define)
1378 1408 * set:
1379 1409 <filteredset
1380 <spanset- 0:2>,
1381 <baseset [0, 2, 1]>>
1410 <baseset [0, 2, 1]>,
1411 <spanset- 0:2>>
1412 0
1382 1413 2
1383 1414 1
1384 0
1385 BROKEN: should be '0 2 1'
1386 1415
1387 1416 '_hexlist(a b)' should behave like 'a + b':
1388 1417
1389 1418 $ trylist --optimize --bin '2:0 & %ln' `hg log -T '{node} ' -r0:2`
1390 1419 (and
1391 1420 (range
1392 1421 ('symbol', '2')
1393 1422 ('symbol', '0'))
1394 1423 (func
1395 1424 ('symbol', '_hexlist')
1396 1425 ('string', '*'))) (glob)
1397 1426 * optimized:
1398 1427 (and
1399 1428 (range
1400 1429 ('symbol', '2')
1401 1430 ('symbol', '0')
1402 1431 define)
1403 1432 (func
1404 1433 ('symbol', '_hexlist')
1405 1434 ('string', '*') (glob)
1406 1435 follow)
1407 1436 define)
1408 1437 * set:
1409 1438 <filteredset
1410 1439 <spanset- 0:2>,
1411 1440 <baseset [0, 1, 2]>>
1412 1441 2
1413 1442 1
1414 1443 0
1415 1444
1416 1445 $ trylist --optimize --bin '%ln & 2:0' `hg log -T '{node} ' -r0+2+1`
1417 1446 (and
1418 1447 (func
1419 1448 ('symbol', '_hexlist')
1420 1449 ('string', '*')) (glob)
1421 1450 (range
1422 1451 ('symbol', '2')
1423 1452 ('symbol', '0')))
1424 1453 * optimized:
1425 1454 (and
1426 1455 (range
1427 1456 ('symbol', '2')
1428 1457 ('symbol', '0')
1429 1458 follow)
1430 1459 (func
1431 1460 ('symbol', '_hexlist')
1432 1461 ('string', '*') (glob)
1433 1462 define)
1434 1463 define)
1435 1464 * set:
1436 1465 <baseset [0, 2, 1]>
1437 1466 0
1438 1467 2
1439 1468 1
1440 1469
1441 1470 '_list' should not go through the slow follow-order path if order doesn't
1442 1471 matter:
1443 1472
1444 1473 $ try -p optimized '2:0 & not (0 + 1)'
1445 1474 * optimized:
1446 1475 (difference
1447 1476 (range
1448 1477 ('symbol', '2')
1449 1478 ('symbol', '0')
1450 1479 define)
1451 1480 (func
1452 1481 ('symbol', '_list')
1453 1482 ('string', '0\x001')
1454 1483 any)
1455 1484 define)
1456 1485 * set:
1457 1486 <filteredset
1458 1487 <spanset- 0:2>,
1459 1488 <not
1460 1489 <baseset [0, 1]>>>
1461 1490 2
1462 1491
1463 1492 $ try -p optimized '2:0 & not (0:2 & (0 + 1))'
1464 1493 * optimized:
1465 1494 (difference
1466 1495 (range
1467 1496 ('symbol', '2')
1468 1497 ('symbol', '0')
1469 1498 define)
1470 1499 (and
1471 1500 (range
1472 1501 ('symbol', '0')
1473 1502 ('symbol', '2')
1474 1503 any)
1475 1504 (func
1476 1505 ('symbol', '_list')
1477 1506 ('string', '0\x001')
1478 1507 any)
1479 1508 any)
1480 1509 define)
1481 1510 * set:
1482 1511 <filteredset
1483 1512 <spanset- 0:2>,
1484 1513 <not
1485 1514 <baseset [0, 1]>>>
1486 1515 2
1487 1516
1488 1517 because 'present()' does nothing other than suppressing an error, the
1489 1518 ordering requirement should be forwarded to the nested expression
1490 1519
1491 1520 $ try -p optimized 'present(2 + 0 + 1)'
1492 1521 * optimized:
1493 1522 (func
1494 1523 ('symbol', 'present')
1495 1524 (func
1496 1525 ('symbol', '_list')
1497 1526 ('string', '2\x000\x001')
1498 1527 define)
1499 1528 define)
1500 1529 * set:
1501 1530 <baseset [2, 0, 1]>
1502 1531 2
1503 1532 0
1504 1533 1
1505 1534
1506 1535 $ try --optimize '2:0 & present(0 + 1 + 2)'
1507 1536 (and
1508 1537 (range
1509 1538 ('symbol', '2')
1510 1539 ('symbol', '0'))
1511 1540 (func
1512 1541 ('symbol', 'present')
1513 1542 (or
1514 1543 (list
1515 1544 ('symbol', '0')
1516 1545 ('symbol', '1')
1517 1546 ('symbol', '2')))))
1518 1547 * optimized:
1519 1548 (and
1520 1549 (range
1521 1550 ('symbol', '2')
1522 1551 ('symbol', '0')
1523 1552 define)
1524 1553 (func
1525 1554 ('symbol', 'present')
1526 1555 (func
1527 1556 ('symbol', '_list')
1528 1557 ('string', '0\x001\x002')
1529 1558 follow)
1530 1559 follow)
1531 1560 define)
1532 1561 * set:
1533 1562 <filteredset
1534 1563 <spanset- 0:2>,
1535 1564 <baseset [0, 1, 2]>>
1536 1565 2
1537 1566 1
1538 1567 0
1539 1568
1540 1569 'reverse()' should take effect only if it is the outermost expression:
1541 1570
1542 1571 $ try --optimize '0:2 & reverse(all())'
1543 1572 (and
1544 1573 (range
1545 1574 ('symbol', '0')
1546 1575 ('symbol', '2'))
1547 1576 (func
1548 1577 ('symbol', 'reverse')
1549 1578 (func
1550 1579 ('symbol', 'all')
1551 1580 None)))
1552 1581 * optimized:
1553 1582 (and
1554 1583 (range
1555 1584 ('symbol', '0')
1556 1585 ('symbol', '2')
1557 1586 define)
1558 1587 (func
1559 1588 ('symbol', 'reverse')
1560 1589 (func
1561 1590 ('symbol', 'all')
1562 1591 None
1563 1592 define)
1564 1593 follow)
1565 1594 define)
1566 1595 * set:
1567 1596 <filteredset
1568 1597 <spanset- 0:2>,
1569 1598 <spanset+ 0:9>>
1570 1599 2
1571 1600 1
1572 1601 0
1573 1602 BROKEN: should be '0 1 2'
1574 1603
1575 1604 'sort()' should take effect only if it is the outermost expression:
1576 1605
1577 1606 $ try --optimize '0:2 & sort(all(), -rev)'
1578 1607 (and
1579 1608 (range
1580 1609 ('symbol', '0')
1581 1610 ('symbol', '2'))
1582 1611 (func
1583 1612 ('symbol', 'sort')
1584 1613 (list
1585 1614 (func
1586 1615 ('symbol', 'all')
1587 1616 None)
1588 1617 (negate
1589 1618 ('symbol', 'rev')))))
1590 1619 * optimized:
1591 1620 (and
1592 1621 (range
1593 1622 ('symbol', '0')
1594 1623 ('symbol', '2')
1595 1624 define)
1596 1625 (func
1597 1626 ('symbol', 'sort')
1598 1627 (list
1599 1628 (func
1600 1629 ('symbol', 'all')
1601 1630 None
1602 1631 define)
1603 1632 ('string', '-rev'))
1604 1633 follow)
1605 1634 define)
1606 1635 * set:
1607 1636 <filteredset
1608 1637 <spanset- 0:2>,
1609 1638 <spanset+ 0:9>>
1610 1639 2
1611 1640 1
1612 1641 0
1613 1642 BROKEN: should be '0 1 2'
1614 1643
1615 1644 for 'A & f(B)', 'B' should not be affected by the order of 'A':
1616 1645
1617 1646 $ try --optimize '2:0 & first(1 + 0 + 2)'
1618 1647 (and
1619 1648 (range
1620 1649 ('symbol', '2')
1621 1650 ('symbol', '0'))
1622 1651 (func
1623 1652 ('symbol', 'first')
1624 1653 (or
1625 1654 (list
1626 1655 ('symbol', '1')
1627 1656 ('symbol', '0')
1628 1657 ('symbol', '2')))))
1629 1658 * optimized:
1630 1659 (and
1631 1660 (range
1632 1661 ('symbol', '2')
1633 1662 ('symbol', '0')
1634 1663 define)
1635 1664 (func
1636 1665 ('symbol', 'first')
1637 1666 (func
1638 1667 ('symbol', '_list')
1639 1668 ('string', '1\x000\x002')
1640 1669 define)
1641 1670 follow)
1642 1671 define)
1643 1672 * set:
1644 1673 <baseset
1645 1674 <limit n=1, offset=0,
1646 1675 <spanset- 0:2>,
1647 1676 <baseset [1, 0, 2]>>>
1648 1677 1
1649 1678
1650 1679 $ try --optimize '2:0 & not last(0 + 2 + 1)'
1651 1680 (and
1652 1681 (range
1653 1682 ('symbol', '2')
1654 1683 ('symbol', '0'))
1655 1684 (not
1656 1685 (func
1657 1686 ('symbol', 'last')
1658 1687 (or
1659 1688 (list
1660 1689 ('symbol', '0')
1661 1690 ('symbol', '2')
1662 1691 ('symbol', '1'))))))
1663 1692 * optimized:
1664 1693 (difference
1665 1694 (range
1666 1695 ('symbol', '2')
1667 1696 ('symbol', '0')
1668 1697 define)
1669 1698 (func
1670 1699 ('symbol', 'last')
1671 1700 (func
1672 1701 ('symbol', '_list')
1673 1702 ('string', '0\x002\x001')
1674 1703 define)
1675 1704 any)
1676 1705 define)
1677 1706 * set:
1678 1707 <filteredset
1679 1708 <spanset- 0:2>,
1680 1709 <not
1681 1710 <baseset
1682 1711 <last n=1,
1683 1712 <fullreposet+ 0:9>,
1684 1713 <baseset [1, 2, 0]>>>>>
1685 1714 2
1686 1715 0
1687 1716
1688 1717 for 'A & (op)(B)', 'B' should not be affected by the order of 'A':
1689 1718
1690 1719 $ try --optimize '2:0 & (1 + 0 + 2):(0 + 2 + 1)'
1691 1720 (and
1692 1721 (range
1693 1722 ('symbol', '2')
1694 1723 ('symbol', '0'))
1695 1724 (range
1696 1725 (group
1697 1726 (or
1698 1727 (list
1699 1728 ('symbol', '1')
1700 1729 ('symbol', '0')
1701 1730 ('symbol', '2'))))
1702 1731 (group
1703 1732 (or
1704 1733 (list
1705 1734 ('symbol', '0')
1706 1735 ('symbol', '2')
1707 1736 ('symbol', '1'))))))
1708 1737 * optimized:
1709 1738 (and
1710 1739 (range
1711 1740 ('symbol', '2')
1712 1741 ('symbol', '0')
1713 1742 define)
1714 1743 (range
1715 1744 (func
1716 1745 ('symbol', '_list')
1717 1746 ('string', '1\x000\x002')
1718 1747 define)
1719 1748 (func
1720 1749 ('symbol', '_list')
1721 1750 ('string', '0\x002\x001')
1722 1751 define)
1723 1752 follow)
1724 1753 define)
1725 1754 * set:
1726 1755 <filteredset
1727 <baseset [1]>,
1728 <spanset- 0:2>>
1756 <spanset- 0:2>,
1757 <baseset [1]>>
1729 1758 1
1730 1759
1731 1760 'A & B' can be rewritten as 'B & A' by weight, but that's fine as long as
1732 1761 the ordering rule is determined before the rewrite; in this example,
1733 1762 'B' follows the order of the initial set, which is the same order as 'A'
1734 1763 since 'A' also follows the order:
1735 1764
1736 1765 $ try --optimize 'contains("glob:*") & (2 + 0 + 1)'
1737 1766 (and
1738 1767 (func
1739 1768 ('symbol', 'contains')
1740 1769 ('string', 'glob:*'))
1741 1770 (group
1742 1771 (or
1743 1772 (list
1744 1773 ('symbol', '2')
1745 1774 ('symbol', '0')
1746 1775 ('symbol', '1')))))
1747 1776 * optimized:
1748 1777 (and
1749 1778 (func
1750 1779 ('symbol', '_list')
1751 1780 ('string', '2\x000\x001')
1752 1781 follow)
1753 1782 (func
1754 1783 ('symbol', 'contains')
1755 1784 ('string', 'glob:*')
1756 1785 define)
1757 1786 define)
1758 1787 * set:
1759 1788 <filteredset
1760 1789 <baseset+ [0, 1, 2]>,
1761 1790 <contains 'glob:*'>>
1762 1791 0
1763 1792 1
1764 1793 2
1765 1794
1766 1795 and in this example, 'A & B' is rewritten as 'B & A', but 'A' overrides
1767 1796 the order appropriately:
1768 1797
1769 1798 $ try --optimize 'reverse(contains("glob:*")) & (0 + 2 + 1)'
1770 1799 (and
1771 1800 (func
1772 1801 ('symbol', 'reverse')
1773 1802 (func
1774 1803 ('symbol', 'contains')
1775 1804 ('string', 'glob:*')))
1776 1805 (group
1777 1806 (or
1778 1807 (list
1779 1808 ('symbol', '0')
1780 1809 ('symbol', '2')
1781 1810 ('symbol', '1')))))
1782 1811 * optimized:
1783 1812 (and
1784 1813 (func
1785 1814 ('symbol', '_list')
1786 1815 ('string', '0\x002\x001')
1787 1816 follow)
1788 1817 (func
1789 1818 ('symbol', 'reverse')
1790 1819 (func
1791 1820 ('symbol', 'contains')
1792 1821 ('string', 'glob:*')
1793 1822 define)
1794 1823 define)
1795 1824 define)
1796 1825 * set:
1797 1826 <filteredset
1798 1827 <baseset- [0, 1, 2]>,
1799 1828 <contains 'glob:*'>>
1800 1829 2
1801 1830 1
1802 1831 0
1803 1832
1804 1833 test sort revset
1805 1834 --------------------------------------------
1806 1835
1807 1836 test when adding two unordered revsets
1808 1837
1809 1838 $ log 'sort(keyword(issue) or modifies(b))'
1810 1839 4
1811 1840 6
1812 1841
1813 1842 test when sorting a reversed collection in the same way it is
1814 1843
1815 1844 $ log 'sort(reverse(all()), -rev)'
1816 1845 9
1817 1846 8
1818 1847 7
1819 1848 6
1820 1849 5
1821 1850 4
1822 1851 3
1823 1852 2
1824 1853 1
1825 1854 0
1826 1855
1827 1856 test when sorting a reversed collection
1828 1857
1829 1858 $ log 'sort(reverse(all()), rev)'
1830 1859 0
1831 1860 1
1832 1861 2
1833 1862 3
1834 1863 4
1835 1864 5
1836 1865 6
1837 1866 7
1838 1867 8
1839 1868 9
1840 1869
1841 1870
1842 1871 test sorting two sorted collections in different orders
1843 1872
1844 1873 $ log 'sort(outgoing() or reverse(removes(a)), rev)'
1845 1874 2
1846 1875 6
1847 1876 8
1848 1877 9
1849 1878
1850 1879 test sorting two sorted collections in different orders backwards
1851 1880
1852 1881 $ log 'sort(outgoing() or reverse(removes(a)), -rev)'
1853 1882 9
1854 1883 8
1855 1884 6
1856 1885 2
1857 1886
1858 1887 test empty sort key which is noop
1859 1888
1860 1889 $ log 'sort(0 + 2 + 1, "")'
1861 1890 0
1862 1891 2
1863 1892 1
1864 1893
1865 1894 test invalid sort keys
1866 1895
1867 1896 $ log 'sort(all(), -invalid)'
1868 1897 hg: parse error: unknown sort key '-invalid'
1869 1898 [255]
1870 1899
1871 1900 $ cd ..
1872 1901
1873 1902 test sorting by multiple keys including variable-length strings
1874 1903
1875 1904 $ hg init sorting
1876 1905 $ cd sorting
1877 1906 $ cat <<EOF >> .hg/hgrc
1878 1907 > [ui]
1879 1908 > logtemplate = '{rev} {branch|p5}{desc|p5}{author|p5}{date|hgdate}\n'
1880 1909 > [templatealias]
1881 1910 > p5(s) = pad(s, 5)
1882 1911 > EOF
1883 1912 $ hg branch -qf b12
1884 1913 $ hg ci -m m111 -u u112 -d '111 10800'
1885 1914 $ hg branch -qf b11
1886 1915 $ hg ci -m m12 -u u111 -d '112 7200'
1887 1916 $ hg branch -qf b111
1888 1917 $ hg ci -m m11 -u u12 -d '111 3600'
1889 1918 $ hg branch -qf b112
1890 1919 $ hg ci -m m111 -u u11 -d '120 0'
1891 1920 $ hg branch -qf b111
1892 1921 $ hg ci -m m112 -u u111 -d '110 14400'
1893 1922 created new head
1894 1923
1895 1924 compare revisions (has fast path):
1896 1925
1897 1926 $ hg log -r 'sort(all(), rev)'
1898 1927 0 b12 m111 u112 111 10800
1899 1928 1 b11 m12 u111 112 7200
1900 1929 2 b111 m11 u12 111 3600
1901 1930 3 b112 m111 u11 120 0
1902 1931 4 b111 m112 u111 110 14400
1903 1932
1904 1933 $ hg log -r 'sort(all(), -rev)'
1905 1934 4 b111 m112 u111 110 14400
1906 1935 3 b112 m111 u11 120 0
1907 1936 2 b111 m11 u12 111 3600
1908 1937 1 b11 m12 u111 112 7200
1909 1938 0 b12 m111 u112 111 10800
1910 1939
1911 1940 compare variable-length strings (issue5218):
1912 1941
1913 1942 $ hg log -r 'sort(all(), branch)'
1914 1943 1 b11 m12 u111 112 7200
1915 1944 2 b111 m11 u12 111 3600
1916 1945 4 b111 m112 u111 110 14400
1917 1946 3 b112 m111 u11 120 0
1918 1947 0 b12 m111 u112 111 10800
1919 1948
1920 1949 $ hg log -r 'sort(all(), -branch)'
1921 1950 0 b12 m111 u112 111 10800
1922 1951 3 b112 m111 u11 120 0
1923 1952 2 b111 m11 u12 111 3600
1924 1953 4 b111 m112 u111 110 14400
1925 1954 1 b11 m12 u111 112 7200
1926 1955
1927 1956 $ hg log -r 'sort(all(), desc)'
1928 1957 2 b111 m11 u12 111 3600
1929 1958 0 b12 m111 u112 111 10800
1930 1959 3 b112 m111 u11 120 0
1931 1960 4 b111 m112 u111 110 14400
1932 1961 1 b11 m12 u111 112 7200
1933 1962
1934 1963 $ hg log -r 'sort(all(), -desc)'
1935 1964 1 b11 m12 u111 112 7200
1936 1965 4 b111 m112 u111 110 14400
1937 1966 0 b12 m111 u112 111 10800
1938 1967 3 b112 m111 u11 120 0
1939 1968 2 b111 m11 u12 111 3600
1940 1969
1941 1970 $ hg log -r 'sort(all(), user)'
1942 1971 3 b112 m111 u11 120 0
1943 1972 1 b11 m12 u111 112 7200
1944 1973 4 b111 m112 u111 110 14400
1945 1974 0 b12 m111 u112 111 10800
1946 1975 2 b111 m11 u12 111 3600
1947 1976
1948 1977 $ hg log -r 'sort(all(), -user)'
1949 1978 2 b111 m11 u12 111 3600
1950 1979 0 b12 m111 u112 111 10800
1951 1980 1 b11 m12 u111 112 7200
1952 1981 4 b111 m112 u111 110 14400
1953 1982 3 b112 m111 u11 120 0
1954 1983
1955 1984 compare dates (tz offset should have no effect):
1956 1985
1957 1986 $ hg log -r 'sort(all(), date)'
1958 1987 4 b111 m112 u111 110 14400
1959 1988 0 b12 m111 u112 111 10800
1960 1989 2 b111 m11 u12 111 3600
1961 1990 1 b11 m12 u111 112 7200
1962 1991 3 b112 m111 u11 120 0
1963 1992
1964 1993 $ hg log -r 'sort(all(), -date)'
1965 1994 3 b112 m111 u11 120 0
1966 1995 1 b11 m12 u111 112 7200
1967 1996 0 b12 m111 u112 111 10800
1968 1997 2 b111 m11 u12 111 3600
1969 1998 4 b111 m112 u111 110 14400
1970 1999
1971 2000 be aware that 'sort(x, -k)' is not exactly the same as 'reverse(sort(x, k))'
1972 2001 because '-k' reverses the comparison, not the list itself:
1973 2002
1974 2003 $ hg log -r 'sort(0 + 2, date)'
1975 2004 0 b12 m111 u112 111 10800
1976 2005 2 b111 m11 u12 111 3600
1977 2006
1978 2007 $ hg log -r 'sort(0 + 2, -date)'
1979 2008 0 b12 m111 u112 111 10800
1980 2009 2 b111 m11 u12 111 3600
1981 2010
1982 2011 $ hg log -r 'reverse(sort(0 + 2, date))'
1983 2012 2 b111 m11 u12 111 3600
1984 2013 0 b12 m111 u112 111 10800
1985 2014
1986 2015 sort by multiple keys:
1987 2016
1988 2017 $ hg log -r 'sort(all(), "branch -rev")'
1989 2018 1 b11 m12 u111 112 7200
1990 2019 4 b111 m112 u111 110 14400
1991 2020 2 b111 m11 u12 111 3600
1992 2021 3 b112 m111 u11 120 0
1993 2022 0 b12 m111 u112 111 10800
1994 2023
1995 2024 $ hg log -r 'sort(all(), "-desc -date")'
1996 2025 1 b11 m12 u111 112 7200
1997 2026 4 b111 m112 u111 110 14400
1998 2027 3 b112 m111 u11 120 0
1999 2028 0 b12 m111 u112 111 10800
2000 2029 2 b111 m11 u12 111 3600
2001 2030
2002 2031 $ hg log -r 'sort(all(), "user -branch date rev")'
2003 2032 3 b112 m111 u11 120 0
2004 2033 4 b111 m112 u111 110 14400
2005 2034 1 b11 m12 u111 112 7200
2006 2035 0 b12 m111 u112 111 10800
2007 2036 2 b111 m11 u12 111 3600
2008 2037
2009 2038 toposort prioritises graph branches
2010 2039
2011 2040 $ hg up 2
2012 2041 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
2013 2042 $ touch a
2014 2043 $ hg addremove
2015 2044 adding a
2016 2045 $ hg ci -m 't1' -u 'tu' -d '130 0'
2017 2046 created new head
2018 2047 $ echo 'a' >> a
2019 2048 $ hg ci -m 't2' -u 'tu' -d '130 0'
2020 2049 $ hg book book1
2021 2050 $ hg up 4
2022 2051 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
2023 2052 (leaving bookmark book1)
2024 2053 $ touch a
2025 2054 $ hg addremove
2026 2055 adding a
2027 2056 $ hg ci -m 't3' -u 'tu' -d '130 0'
2028 2057
2029 2058 $ hg log -r 'sort(all(), topo)'
2030 2059 7 b111 t3 tu 130 0
2031 2060 4 b111 m112 u111 110 14400
2032 2061 3 b112 m111 u11 120 0
2033 2062 6 b111 t2 tu 130 0
2034 2063 5 b111 t1 tu 130 0
2035 2064 2 b111 m11 u12 111 3600
2036 2065 1 b11 m12 u111 112 7200
2037 2066 0 b12 m111 u112 111 10800
2038 2067
2039 2068 $ hg log -r 'sort(all(), -topo)'
2040 2069 0 b12 m111 u112 111 10800
2041 2070 1 b11 m12 u111 112 7200
2042 2071 2 b111 m11 u12 111 3600
2043 2072 5 b111 t1 tu 130 0
2044 2073 6 b111 t2 tu 130 0
2045 2074 3 b112 m111 u11 120 0
2046 2075 4 b111 m112 u111 110 14400
2047 2076 7 b111 t3 tu 130 0
2048 2077
2049 2078 $ hg log -r 'sort(all(), topo, topo.firstbranch=book1)'
2050 2079 6 b111 t2 tu 130 0
2051 2080 5 b111 t1 tu 130 0
2052 2081 7 b111 t3 tu 130 0
2053 2082 4 b111 m112 u111 110 14400
2054 2083 3 b112 m111 u11 120 0
2055 2084 2 b111 m11 u12 111 3600
2056 2085 1 b11 m12 u111 112 7200
2057 2086 0 b12 m111 u112 111 10800
2058 2087
2059 2088 topographical sorting can't be combined with other sort keys, and you can't
2060 2089 use the topo.firstbranch option when topo sort is not active:
2061 2090
2062 2091 $ hg log -r 'sort(all(), "topo user")'
2063 2092 hg: parse error: topo sort order cannot be combined with other sort keys
2064 2093 [255]
2065 2094
2066 2095 $ hg log -r 'sort(all(), user, topo.firstbranch=book1)'
2067 2096 hg: parse error: topo.firstbranch can only be used when using the topo sort key
2068 2097 [255]
2069 2098
2070 2099 topo.firstbranch should accept any kind of expressions:
2071 2100
2072 2101 $ hg log -r 'sort(0, topo, topo.firstbranch=(book1))'
2073 2102 0 b12 m111 u112 111 10800
2074 2103
2075 2104 $ cd ..
2076 2105 $ cd repo
2077 2106
2078 2107 test subtracting something from an addset
2079 2108
2080 2109 $ log '(outgoing() or removes(a)) - removes(a)'
2081 2110 8
2082 2111 9
2083 2112
2084 2113 test intersecting something with an addset
2085 2114
2086 2115 $ log 'parents(outgoing() or removes(a))'
2087 2116 1
2088 2117 4
2089 2118 5
2090 2119 8
2091 2120
2092 2121 test that `or` operation combines elements in the right order:
2093 2122
2094 2123 $ log '3:4 or 2:5'
2095 2124 3
2096 2125 4
2097 2126 2
2098 2127 5
2099 2128 $ log '3:4 or 5:2'
2100 2129 3
2101 2130 4
2102 2131 5
2103 2132 2
2104 2133 $ log 'sort(3:4 or 2:5)'
2105 2134 2
2106 2135 3
2107 2136 4
2108 2137 5
2109 2138 $ log 'sort(3:4 or 5:2)'
2110 2139 2
2111 2140 3
2112 2141 4
2113 2142 5
2114 2143
2115 2144 test that more than one `-r`s are combined in the right order and deduplicated:
2116 2145
2117 2146 $ hg log -T '{rev}\n' -r 3 -r 3 -r 4 -r 5:2 -r 'ancestors(4)'
2118 2147 3
2119 2148 4
2120 2149 5
2121 2150 2
2122 2151 0
2123 2152 1
2124 2153
2125 2154 test that `or` operation skips duplicated revisions from right-hand side
2126 2155
2127 2156 $ try 'reverse(1::5) or ancestors(4)'
2128 2157 (or
2129 2158 (list
2130 2159 (func
2131 2160 ('symbol', 'reverse')
2132 2161 (dagrange
2133 2162 ('symbol', '1')
2134 2163 ('symbol', '5')))
2135 2164 (func
2136 2165 ('symbol', 'ancestors')
2137 2166 ('symbol', '4'))))
2138 2167 * set:
2139 2168 <addset
2140 2169 <baseset- [1, 3, 5]>,
2141 2170 <generatorset+>>
2142 2171 5
2143 2172 3
2144 2173 1
2145 2174 0
2146 2175 2
2147 2176 4
2148 2177 $ try 'sort(ancestors(4) or reverse(1::5))'
2149 2178 (func
2150 2179 ('symbol', 'sort')
2151 2180 (or
2152 2181 (list
2153 2182 (func
2154 2183 ('symbol', 'ancestors')
2155 2184 ('symbol', '4'))
2156 2185 (func
2157 2186 ('symbol', 'reverse')
2158 2187 (dagrange
2159 2188 ('symbol', '1')
2160 2189 ('symbol', '5'))))))
2161 2190 * set:
2162 2191 <addset+
2163 2192 <generatorset+>,
2164 2193 <baseset- [1, 3, 5]>>
2165 2194 0
2166 2195 1
2167 2196 2
2168 2197 3
2169 2198 4
2170 2199 5
2171 2200
2172 2201 test optimization of trivial `or` operation
2173 2202
2174 2203 $ try --optimize '0|(1)|"2"|-2|tip|null'
2175 2204 (or
2176 2205 (list
2177 2206 ('symbol', '0')
2178 2207 (group
2179 2208 ('symbol', '1'))
2180 2209 ('string', '2')
2181 2210 (negate
2182 2211 ('symbol', '2'))
2183 2212 ('symbol', 'tip')
2184 2213 ('symbol', 'null')))
2185 2214 * optimized:
2186 2215 (func
2187 2216 ('symbol', '_list')
2188 2217 ('string', '0\x001\x002\x00-2\x00tip\x00null')
2189 2218 define)
2190 2219 * set:
2191 2220 <baseset [0, 1, 2, 8, 9, -1]>
2192 2221 0
2193 2222 1
2194 2223 2
2195 2224 8
2196 2225 9
2197 2226 -1
2198 2227
2199 2228 $ try --optimize '0|1|2:3'
2200 2229 (or
2201 2230 (list
2202 2231 ('symbol', '0')
2203 2232 ('symbol', '1')
2204 2233 (range
2205 2234 ('symbol', '2')
2206 2235 ('symbol', '3'))))
2207 2236 * optimized:
2208 2237 (or
2209 2238 (list
2210 2239 (func
2211 2240 ('symbol', '_list')
2212 2241 ('string', '0\x001')
2213 2242 define)
2214 2243 (range
2215 2244 ('symbol', '2')
2216 2245 ('symbol', '3')
2217 2246 define))
2218 2247 define)
2219 2248 * set:
2220 2249 <addset
2221 2250 <baseset [0, 1]>,
2222 2251 <spanset+ 2:3>>
2223 2252 0
2224 2253 1
2225 2254 2
2226 2255 3
2227 2256
2228 2257 $ try --optimize '0:1|2|3:4|5|6'
2229 2258 (or
2230 2259 (list
2231 2260 (range
2232 2261 ('symbol', '0')
2233 2262 ('symbol', '1'))
2234 2263 ('symbol', '2')
2235 2264 (range
2236 2265 ('symbol', '3')
2237 2266 ('symbol', '4'))
2238 2267 ('symbol', '5')
2239 2268 ('symbol', '6')))
2240 2269 * optimized:
2241 2270 (or
2242 2271 (list
2243 2272 (range
2244 2273 ('symbol', '0')
2245 2274 ('symbol', '1')
2246 2275 define)
2247 2276 ('symbol', '2')
2248 2277 (range
2249 2278 ('symbol', '3')
2250 2279 ('symbol', '4')
2251 2280 define)
2252 2281 (func
2253 2282 ('symbol', '_list')
2254 2283 ('string', '5\x006')
2255 2284 define))
2256 2285 define)
2257 2286 * set:
2258 2287 <addset
2259 2288 <addset
2260 2289 <spanset+ 0:1>,
2261 2290 <baseset [2]>>,
2262 2291 <addset
2263 2292 <spanset+ 3:4>,
2264 2293 <baseset [5, 6]>>>
2265 2294 0
2266 2295 1
2267 2296 2
2268 2297 3
2269 2298 4
2270 2299 5
2271 2300 6
2272 2301
2273 2302 unoptimized `or` looks like this
2274 2303
2275 2304 $ try --no-optimized -p analyzed '0|1|2|3|4'
2276 2305 * analyzed:
2277 2306 (or
2278 2307 (list
2279 2308 ('symbol', '0')
2280 2309 ('symbol', '1')
2281 2310 ('symbol', '2')
2282 2311 ('symbol', '3')
2283 2312 ('symbol', '4'))
2284 2313 define)
2285 2314 * set:
2286 2315 <addset
2287 2316 <addset
2288 2317 <baseset [0]>,
2289 2318 <baseset [1]>>,
2290 2319 <addset
2291 2320 <baseset [2]>,
2292 2321 <addset
2293 2322 <baseset [3]>,
2294 2323 <baseset [4]>>>>
2295 2324 0
2296 2325 1
2297 2326 2
2298 2327 3
2299 2328 4
2300 2329
2301 2330 test that `_list` should be narrowed by provided `subset`
2302 2331
2303 2332 $ log '0:2 and (null|1|2|3)'
2304 2333 1
2305 2334 2
2306 2335
2307 2336 test that `_list` should remove duplicates
2308 2337
2309 2338 $ log '0|1|2|1|2|-1|tip'
2310 2339 0
2311 2340 1
2312 2341 2
2313 2342 9
2314 2343
2315 2344 test unknown revision in `_list`
2316 2345
2317 2346 $ log '0|unknown'
2318 2347 abort: unknown revision 'unknown'!
2319 2348 [255]
2320 2349
2321 2350 test integer range in `_list`
2322 2351
2323 2352 $ log '-1|-10'
2324 2353 9
2325 2354 0
2326 2355
2327 2356 $ log '-10|-11'
2328 2357 abort: unknown revision '-11'!
2329 2358 [255]
2330 2359
2331 2360 $ log '9|10'
2332 2361 abort: unknown revision '10'!
2333 2362 [255]
2334 2363
2335 2364 test '0000' != '0' in `_list`
2336 2365
2337 2366 $ log '0|0000'
2338 2367 0
2339 2368 -1
2340 2369
2341 2370 test ',' in `_list`
2342 2371 $ log '0,1'
2343 2372 hg: parse error: can't use a list in this context
2344 2373 (see hg help "revsets.x or y")
2345 2374 [255]
2346 2375 $ try '0,1,2'
2347 2376 (list
2348 2377 ('symbol', '0')
2349 2378 ('symbol', '1')
2350 2379 ('symbol', '2'))
2351 2380 hg: parse error: can't use a list in this context
2352 2381 (see hg help "revsets.x or y")
2353 2382 [255]
2354 2383
2355 2384 test that chained `or` operations make balanced addsets
2356 2385
2357 2386 $ try '0:1|1:2|2:3|3:4|4:5'
2358 2387 (or
2359 2388 (list
2360 2389 (range
2361 2390 ('symbol', '0')
2362 2391 ('symbol', '1'))
2363 2392 (range
2364 2393 ('symbol', '1')
2365 2394 ('symbol', '2'))
2366 2395 (range
2367 2396 ('symbol', '2')
2368 2397 ('symbol', '3'))
2369 2398 (range
2370 2399 ('symbol', '3')
2371 2400 ('symbol', '4'))
2372 2401 (range
2373 2402 ('symbol', '4')
2374 2403 ('symbol', '5'))))
2375 2404 * set:
2376 2405 <addset
2377 2406 <addset
2378 2407 <spanset+ 0:1>,
2379 2408 <spanset+ 1:2>>,
2380 2409 <addset
2381 2410 <spanset+ 2:3>,
2382 2411 <addset
2383 2412 <spanset+ 3:4>,
2384 2413 <spanset+ 4:5>>>>
2385 2414 0
2386 2415 1
2387 2416 2
2388 2417 3
2389 2418 4
2390 2419 5
2391 2420
2392 2421 no crash by empty group "()" while optimizing `or` operations
2393 2422
2394 2423 $ try --optimize '0|()'
2395 2424 (or
2396 2425 (list
2397 2426 ('symbol', '0')
2398 2427 (group
2399 2428 None)))
2400 2429 * optimized:
2401 2430 (or
2402 2431 (list
2403 2432 ('symbol', '0')
2404 2433 None)
2405 2434 define)
2406 2435 hg: parse error: missing argument
2407 2436 [255]
2408 2437
2409 2438 test that chained `or` operations never eat up stack (issue4624)
2410 2439 (uses `0:1` instead of `0` to avoid future optimization of trivial revisions)
2411 2440
2412 2441 $ hg log -T '{rev}\n' -r `python -c "print '+'.join(['0:1'] * 500)"`
2413 2442 0
2414 2443 1
2415 2444
2416 2445 test that repeated `-r` options never eat up stack (issue4565)
2417 2446 (uses `-r 0::1` to avoid possible optimization at old-style parser)
2418 2447
2419 2448 $ hg log -T '{rev}\n' `python -c "for i in xrange(500): print '-r 0::1 ',"`
2420 2449 0
2421 2450 1
2422 2451
2423 2452 check that conversion to only works
2424 2453 $ try --optimize '::3 - ::1'
2425 2454 (minus
2426 2455 (dagrangepre
2427 2456 ('symbol', '3'))
2428 2457 (dagrangepre
2429 2458 ('symbol', '1')))
2430 2459 * optimized:
2431 2460 (func
2432 2461 ('symbol', 'only')
2433 2462 (list
2434 2463 ('symbol', '3')
2435 2464 ('symbol', '1'))
2436 2465 define)
2437 2466 * set:
2438 2467 <baseset+ [3]>
2439 2468 3
2440 2469 $ try --optimize 'ancestors(1) - ancestors(3)'
2441 2470 (minus
2442 2471 (func
2443 2472 ('symbol', 'ancestors')
2444 2473 ('symbol', '1'))
2445 2474 (func
2446 2475 ('symbol', 'ancestors')
2447 2476 ('symbol', '3')))
2448 2477 * optimized:
2449 2478 (func
2450 2479 ('symbol', 'only')
2451 2480 (list
2452 2481 ('symbol', '1')
2453 2482 ('symbol', '3'))
2454 2483 define)
2455 2484 * set:
2456 2485 <baseset+ []>
2457 2486 $ try --optimize 'not ::2 and ::6'
2458 2487 (and
2459 2488 (not
2460 2489 (dagrangepre
2461 2490 ('symbol', '2')))
2462 2491 (dagrangepre
2463 2492 ('symbol', '6')))
2464 2493 * optimized:
2465 2494 (func
2466 2495 ('symbol', 'only')
2467 2496 (list
2468 2497 ('symbol', '6')
2469 2498 ('symbol', '2'))
2470 2499 define)
2471 2500 * set:
2472 2501 <baseset+ [3, 4, 5, 6]>
2473 2502 3
2474 2503 4
2475 2504 5
2476 2505 6
2477 2506 $ try --optimize 'ancestors(6) and not ancestors(4)'
2478 2507 (and
2479 2508 (func
2480 2509 ('symbol', 'ancestors')
2481 2510 ('symbol', '6'))
2482 2511 (not
2483 2512 (func
2484 2513 ('symbol', 'ancestors')
2485 2514 ('symbol', '4'))))
2486 2515 * optimized:
2487 2516 (func
2488 2517 ('symbol', 'only')
2489 2518 (list
2490 2519 ('symbol', '6')
2491 2520 ('symbol', '4'))
2492 2521 define)
2493 2522 * set:
2494 2523 <baseset+ [3, 5, 6]>
2495 2524 3
2496 2525 5
2497 2526 6
2498 2527
2499 2528 no crash by empty group "()" while optimizing to "only()"
2500 2529
2501 2530 $ try --optimize '::1 and ()'
2502 2531 (and
2503 2532 (dagrangepre
2504 2533 ('symbol', '1'))
2505 2534 (group
2506 2535 None))
2507 2536 * optimized:
2508 2537 (and
2509 2538 None
2510 2539 (func
2511 2540 ('symbol', 'ancestors')
2512 2541 ('symbol', '1')
2513 2542 define)
2514 2543 define)
2515 2544 hg: parse error: missing argument
2516 2545 [255]
2517 2546
2518 2547 invalid function call should not be optimized to only()
2519 2548
2520 2549 $ log '"ancestors"(6) and not ancestors(4)'
2521 2550 hg: parse error: not a symbol
2522 2551 [255]
2523 2552
2524 2553 $ log 'ancestors(6) and not "ancestors"(4)'
2525 2554 hg: parse error: not a symbol
2526 2555 [255]
2527 2556
2528 2557 we can use patterns when searching for tags
2529 2558
2530 2559 $ log 'tag("1..*")'
2531 2560 abort: tag '1..*' does not exist!
2532 2561 [255]
2533 2562 $ log 'tag("re:1..*")'
2534 2563 6
2535 2564 $ log 'tag("re:[0-9].[0-9]")'
2536 2565 6
2537 2566 $ log 'tag("literal:1.0")'
2538 2567 6
2539 2568 $ log 'tag("re:0..*")'
2540 2569
2541 2570 $ log 'tag(unknown)'
2542 2571 abort: tag 'unknown' does not exist!
2543 2572 [255]
2544 2573 $ log 'tag("re:unknown")'
2545 2574 $ log 'present(tag("unknown"))'
2546 2575 $ log 'present(tag("re:unknown"))'
2547 2576 $ log 'branch(unknown)'
2548 2577 abort: unknown revision 'unknown'!
2549 2578 [255]
2550 2579 $ log 'branch("literal:unknown")'
2551 2580 abort: branch 'unknown' does not exist!
2552 2581 [255]
2553 2582 $ log 'branch("re:unknown")'
2554 2583 $ log 'present(branch("unknown"))'
2555 2584 $ log 'present(branch("re:unknown"))'
2556 2585 $ log 'user(bob)'
2557 2586 2
2558 2587
2559 2588 $ log '4::8'
2560 2589 4
2561 2590 8
2562 2591 $ log '4:8'
2563 2592 4
2564 2593 5
2565 2594 6
2566 2595 7
2567 2596 8
2568 2597
2569 2598 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
2570 2599 4
2571 2600 2
2572 2601 5
2573 2602
2574 2603 $ log 'not 0 and 0:2'
2575 2604 1
2576 2605 2
2577 2606 $ log 'not 1 and 0:2'
2578 2607 0
2579 2608 2
2580 2609 $ log 'not 2 and 0:2'
2581 2610 0
2582 2611 1
2583 2612 $ log '(1 and 2)::'
2584 2613 $ log '(1 and 2):'
2585 2614 $ log '(1 and 2):3'
2586 2615 $ log 'sort(head(), -rev)'
2587 2616 9
2588 2617 7
2589 2618 6
2590 2619 5
2591 2620 4
2592 2621 3
2593 2622 2
2594 2623 1
2595 2624 0
2596 2625 $ log '4::8 - 8'
2597 2626 4
2598 2627
2599 2628 matching() should preserve the order of the input set:
2600 2629
2601 2630 $ log '(2 or 3 or 1) and matching(1 or 2 or 3)'
2602 2631 2
2603 2632 3
2604 2633 1
2605 2634
2606 2635 $ log 'named("unknown")'
2607 2636 abort: namespace 'unknown' does not exist!
2608 2637 [255]
2609 2638 $ log 'named("re:unknown")'
2610 2639 abort: no namespace exists that match 'unknown'!
2611 2640 [255]
2612 2641 $ log 'present(named("unknown"))'
2613 2642 $ log 'present(named("re:unknown"))'
2614 2643
2615 2644 $ log 'tag()'
2616 2645 6
2617 2646 $ log 'named("tags")'
2618 2647 6
2619 2648
2620 2649 issue2437
2621 2650
2622 2651 $ log '3 and p1(5)'
2623 2652 3
2624 2653 $ log '4 and p2(6)'
2625 2654 4
2626 2655 $ log '1 and parents(:2)'
2627 2656 1
2628 2657 $ log '2 and children(1:)'
2629 2658 2
2630 2659 $ log 'roots(all()) or roots(all())'
2631 2660 0
2632 2661 $ hg debugrevspec 'roots(all()) or roots(all())'
2633 2662 0
2634 2663 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
2635 2664 9
2636 2665 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
2637 2666 4
2638 2667
2639 2668 issue2654: report a parse error if the revset was not completely parsed
2640 2669
2641 2670 $ log '1 OR 2'
2642 2671 hg: parse error at 2: invalid token
2643 2672 [255]
2644 2673
2645 2674 or operator should preserve ordering:
2646 2675 $ log 'reverse(2::4) or tip'
2647 2676 4
2648 2677 2
2649 2678 9
2650 2679
2651 2680 parentrevspec
2652 2681
2653 2682 $ log 'merge()^0'
2654 2683 6
2655 2684 $ log 'merge()^'
2656 2685 5
2657 2686 $ log 'merge()^1'
2658 2687 5
2659 2688 $ log 'merge()^2'
2660 2689 4
2661 2690 $ log 'merge()^^'
2662 2691 3
2663 2692 $ log 'merge()^1^'
2664 2693 3
2665 2694 $ log 'merge()^^^'
2666 2695 1
2667 2696
2668 2697 $ log 'merge()~0'
2669 2698 6
2670 2699 $ log 'merge()~1'
2671 2700 5
2672 2701 $ log 'merge()~2'
2673 2702 3
2674 2703 $ log 'merge()~2^1'
2675 2704 1
2676 2705 $ log 'merge()~3'
2677 2706 1
2678 2707
2679 2708 $ log '(-3:tip)^'
2680 2709 4
2681 2710 6
2682 2711 8
2683 2712
2684 2713 $ log 'tip^foo'
2685 2714 hg: parse error: ^ expects a number 0, 1, or 2
2686 2715 [255]
2687 2716
2688 2717 Bogus function gets suggestions
2689 2718 $ log 'add()'
2690 2719 hg: parse error: unknown identifier: add
2691 2720 (did you mean adds?)
2692 2721 [255]
2693 2722 $ log 'added()'
2694 2723 hg: parse error: unknown identifier: added
2695 2724 (did you mean adds?)
2696 2725 [255]
2697 2726 $ log 'remo()'
2698 2727 hg: parse error: unknown identifier: remo
2699 2728 (did you mean one of remote, removes?)
2700 2729 [255]
2701 2730 $ log 'babar()'
2702 2731 hg: parse error: unknown identifier: babar
2703 2732 [255]
2704 2733
2705 2734 Bogus function with a similar internal name doesn't suggest the internal name
2706 2735 $ log 'matches()'
2707 2736 hg: parse error: unknown identifier: matches
2708 2737 (did you mean matching?)
2709 2738 [255]
2710 2739
2711 2740 Undocumented functions aren't suggested as similar either
2712 2741 $ log 'wdir2()'
2713 2742 hg: parse error: unknown identifier: wdir2
2714 2743 [255]
2715 2744
2716 2745 multiple revspecs
2717 2746
2718 2747 $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
2719 2748 8
2720 2749 9
2721 2750 4
2722 2751 5
2723 2752 6
2724 2753 7
2725 2754
2726 2755 test usage in revpair (with "+")
2727 2756
2728 2757 (real pair)
2729 2758
2730 2759 $ hg diff -r 'tip^^' -r 'tip'
2731 2760 diff -r 2326846efdab -r 24286f4ae135 .hgtags
2732 2761 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2733 2762 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
2734 2763 @@ -0,0 +1,1 @@
2735 2764 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
2736 2765 $ hg diff -r 'tip^^::tip'
2737 2766 diff -r 2326846efdab -r 24286f4ae135 .hgtags
2738 2767 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2739 2768 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
2740 2769 @@ -0,0 +1,1 @@
2741 2770 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
2742 2771
2743 2772 (single rev)
2744 2773
2745 2774 $ hg diff -r 'tip^' -r 'tip^'
2746 2775 $ hg diff -r 'tip^:tip^'
2747 2776
2748 2777 (single rev that does not looks like a range)
2749 2778
2750 2779 $ hg diff -r 'tip^::tip^ or tip^'
2751 2780 diff -r d5d0dcbdc4d9 .hgtags
2752 2781 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2753 2782 +++ b/.hgtags * (glob)
2754 2783 @@ -0,0 +1,1 @@
2755 2784 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
2756 2785 $ hg diff -r 'tip^ or tip^'
2757 2786 diff -r d5d0dcbdc4d9 .hgtags
2758 2787 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2759 2788 +++ b/.hgtags * (glob)
2760 2789 @@ -0,0 +1,1 @@
2761 2790 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
2762 2791
2763 2792 (no rev)
2764 2793
2765 2794 $ hg diff -r 'author("babar") or author("celeste")'
2766 2795 abort: empty revision range
2767 2796 [255]
2768 2797
2769 2798 aliases:
2770 2799
2771 2800 $ echo '[revsetalias]' >> .hg/hgrc
2772 2801 $ echo 'm = merge()' >> .hg/hgrc
2773 2802 (revset aliases can override builtin revsets)
2774 2803 $ echo 'p2($1) = p1($1)' >> .hg/hgrc
2775 2804 $ echo 'sincem = descendants(m)' >> .hg/hgrc
2776 2805 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
2777 2806 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
2778 2807 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
2779 2808
2780 2809 $ try m
2781 2810 ('symbol', 'm')
2782 2811 * expanded:
2783 2812 (func
2784 2813 ('symbol', 'merge')
2785 2814 None)
2786 2815 * set:
2787 2816 <filteredset
2788 2817 <fullreposet+ 0:9>,
2789 2818 <merge>>
2790 2819 6
2791 2820
2792 2821 $ HGPLAIN=1
2793 2822 $ export HGPLAIN
2794 2823 $ try m
2795 2824 ('symbol', 'm')
2796 2825 abort: unknown revision 'm'!
2797 2826 [255]
2798 2827
2799 2828 $ HGPLAINEXCEPT=revsetalias
2800 2829 $ export HGPLAINEXCEPT
2801 2830 $ try m
2802 2831 ('symbol', 'm')
2803 2832 * expanded:
2804 2833 (func
2805 2834 ('symbol', 'merge')
2806 2835 None)
2807 2836 * set:
2808 2837 <filteredset
2809 2838 <fullreposet+ 0:9>,
2810 2839 <merge>>
2811 2840 6
2812 2841
2813 2842 $ unset HGPLAIN
2814 2843 $ unset HGPLAINEXCEPT
2815 2844
2816 2845 $ try 'p2(.)'
2817 2846 (func
2818 2847 ('symbol', 'p2')
2819 2848 ('symbol', '.'))
2820 2849 * expanded:
2821 2850 (func
2822 2851 ('symbol', 'p1')
2823 2852 ('symbol', '.'))
2824 2853 * set:
2825 2854 <baseset+ [8]>
2826 2855 8
2827 2856
2828 2857 $ HGPLAIN=1
2829 2858 $ export HGPLAIN
2830 2859 $ try 'p2(.)'
2831 2860 (func
2832 2861 ('symbol', 'p2')
2833 2862 ('symbol', '.'))
2834 2863 * set:
2835 2864 <baseset+ []>
2836 2865
2837 2866 $ HGPLAINEXCEPT=revsetalias
2838 2867 $ export HGPLAINEXCEPT
2839 2868 $ try 'p2(.)'
2840 2869 (func
2841 2870 ('symbol', 'p2')
2842 2871 ('symbol', '.'))
2843 2872 * expanded:
2844 2873 (func
2845 2874 ('symbol', 'p1')
2846 2875 ('symbol', '.'))
2847 2876 * set:
2848 2877 <baseset+ [8]>
2849 2878 8
2850 2879
2851 2880 $ unset HGPLAIN
2852 2881 $ unset HGPLAINEXCEPT
2853 2882
2854 2883 test alias recursion
2855 2884
2856 2885 $ try sincem
2857 2886 ('symbol', 'sincem')
2858 2887 * expanded:
2859 2888 (func
2860 2889 ('symbol', 'descendants')
2861 2890 (func
2862 2891 ('symbol', 'merge')
2863 2892 None))
2864 2893 * set:
2865 2894 <addset+
2866 2895 <filteredset
2867 2896 <fullreposet+ 0:9>,
2868 2897 <merge>>,
2869 2898 <generatorset+>>
2870 2899 6
2871 2900 7
2872 2901
2873 2902 test infinite recursion
2874 2903
2875 2904 $ echo 'recurse1 = recurse2' >> .hg/hgrc
2876 2905 $ echo 'recurse2 = recurse1' >> .hg/hgrc
2877 2906 $ try recurse1
2878 2907 ('symbol', 'recurse1')
2879 2908 hg: parse error: infinite expansion of revset alias "recurse1" detected
2880 2909 [255]
2881 2910
2882 2911 $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
2883 2912 $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
2884 2913 $ try "level2(level1(1, 2), 3)"
2885 2914 (func
2886 2915 ('symbol', 'level2')
2887 2916 (list
2888 2917 (func
2889 2918 ('symbol', 'level1')
2890 2919 (list
2891 2920 ('symbol', '1')
2892 2921 ('symbol', '2')))
2893 2922 ('symbol', '3')))
2894 2923 * expanded:
2895 2924 (or
2896 2925 (list
2897 2926 ('symbol', '3')
2898 2927 (or
2899 2928 (list
2900 2929 ('symbol', '1')
2901 2930 ('symbol', '2')))))
2902 2931 * set:
2903 2932 <addset
2904 2933 <baseset [3]>,
2905 2934 <baseset [1, 2]>>
2906 2935 3
2907 2936 1
2908 2937 2
2909 2938
2910 2939 test nesting and variable passing
2911 2940
2912 2941 $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
2913 2942 $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
2914 2943 $ echo 'nested3($1) = max($1)' >> .hg/hgrc
2915 2944 $ try 'nested(2:5)'
2916 2945 (func
2917 2946 ('symbol', 'nested')
2918 2947 (range
2919 2948 ('symbol', '2')
2920 2949 ('symbol', '5')))
2921 2950 * expanded:
2922 2951 (func
2923 2952 ('symbol', 'max')
2924 2953 (range
2925 2954 ('symbol', '2')
2926 2955 ('symbol', '5')))
2927 2956 * set:
2928 2957 <baseset
2929 2958 <max
2930 2959 <fullreposet+ 0:9>,
2931 2960 <spanset+ 2:5>>>
2932 2961 5
2933 2962
2934 2963 test chained `or` operations are flattened at parsing phase
2935 2964
2936 2965 $ echo 'chainedorops($1, $2, $3) = $1|$2|$3' >> .hg/hgrc
2937 2966 $ try 'chainedorops(0:1, 1:2, 2:3)'
2938 2967 (func
2939 2968 ('symbol', 'chainedorops')
2940 2969 (list
2941 2970 (range
2942 2971 ('symbol', '0')
2943 2972 ('symbol', '1'))
2944 2973 (range
2945 2974 ('symbol', '1')
2946 2975 ('symbol', '2'))
2947 2976 (range
2948 2977 ('symbol', '2')
2949 2978 ('symbol', '3'))))
2950 2979 * expanded:
2951 2980 (or
2952 2981 (list
2953 2982 (range
2954 2983 ('symbol', '0')
2955 2984 ('symbol', '1'))
2956 2985 (range
2957 2986 ('symbol', '1')
2958 2987 ('symbol', '2'))
2959 2988 (range
2960 2989 ('symbol', '2')
2961 2990 ('symbol', '3'))))
2962 2991 * set:
2963 2992 <addset
2964 2993 <spanset+ 0:1>,
2965 2994 <addset
2966 2995 <spanset+ 1:2>,
2967 2996 <spanset+ 2:3>>>
2968 2997 0
2969 2998 1
2970 2999 2
2971 3000 3
2972 3001
2973 3002 test variable isolation, variable placeholders are rewritten as string
2974 3003 then parsed and matched again as string. Check they do not leak too
2975 3004 far away.
2976 3005
2977 3006 $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
2978 3007 $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
2979 3008 $ try 'callinjection(2:5)'
2980 3009 (func
2981 3010 ('symbol', 'callinjection')
2982 3011 (range
2983 3012 ('symbol', '2')
2984 3013 ('symbol', '5')))
2985 3014 * expanded:
2986 3015 (func
2987 3016 ('symbol', 'descendants')
2988 3017 (func
2989 3018 ('symbol', 'max')
2990 3019 ('string', '$1')))
2991 3020 abort: unknown revision '$1'!
2992 3021 [255]
2993 3022
2994 3023 test scope of alias expansion: 'universe' is expanded prior to 'shadowall(0)',
2995 3024 but 'all()' should never be substituded to '0()'.
2996 3025
2997 3026 $ echo 'universe = all()' >> .hg/hgrc
2998 3027 $ echo 'shadowall(all) = all and universe' >> .hg/hgrc
2999 3028 $ try 'shadowall(0)'
3000 3029 (func
3001 3030 ('symbol', 'shadowall')
3002 3031 ('symbol', '0'))
3003 3032 * expanded:
3004 3033 (and
3005 3034 ('symbol', '0')
3006 3035 (func
3007 3036 ('symbol', 'all')
3008 3037 None))
3009 3038 * set:
3010 3039 <filteredset
3011 3040 <baseset [0]>,
3012 3041 <spanset+ 0:9>>
3013 3042 0
3014 3043
3015 3044 test unknown reference:
3016 3045
3017 3046 $ try "unknownref(0)" --config 'revsetalias.unknownref($1)=$1:$2'
3018 3047 (func
3019 3048 ('symbol', 'unknownref')
3020 3049 ('symbol', '0'))
3021 3050 abort: bad definition of revset alias "unknownref": invalid symbol '$2'
3022 3051 [255]
3023 3052
3024 3053 $ hg debugrevspec --debug --config revsetalias.anotherbadone='branch(' "tip"
3025 3054 ('symbol', 'tip')
3026 3055 warning: bad definition of revset alias "anotherbadone": at 7: not a prefix: end
3027 3056 * set:
3028 3057 <baseset [9]>
3029 3058 9
3030 3059
3031 3060 $ try 'tip'
3032 3061 ('symbol', 'tip')
3033 3062 * set:
3034 3063 <baseset [9]>
3035 3064 9
3036 3065
3037 3066 $ hg debugrevspec --debug --config revsetalias.'bad name'='tip' "tip"
3038 3067 ('symbol', 'tip')
3039 3068 warning: bad declaration of revset alias "bad name": at 4: invalid token
3040 3069 * set:
3041 3070 <baseset [9]>
3042 3071 9
3043 3072 $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc
3044 3073 $ try 'strictreplacing("foo", tip)'
3045 3074 (func
3046 3075 ('symbol', 'strictreplacing')
3047 3076 (list
3048 3077 ('string', 'foo')
3049 3078 ('symbol', 'tip')))
3050 3079 * expanded:
3051 3080 (or
3052 3081 (list
3053 3082 ('symbol', 'tip')
3054 3083 (func
3055 3084 ('symbol', 'desc')
3056 3085 ('string', '$1'))))
3057 3086 * set:
3058 3087 <addset
3059 3088 <baseset [9]>,
3060 3089 <filteredset
3061 3090 <fullreposet+ 0:9>,
3062 3091 <desc '$1'>>>
3063 3092 9
3064 3093
3065 3094 $ try 'd(2:5)'
3066 3095 (func
3067 3096 ('symbol', 'd')
3068 3097 (range
3069 3098 ('symbol', '2')
3070 3099 ('symbol', '5')))
3071 3100 * expanded:
3072 3101 (func
3073 3102 ('symbol', 'reverse')
3074 3103 (func
3075 3104 ('symbol', 'sort')
3076 3105 (list
3077 3106 (range
3078 3107 ('symbol', '2')
3079 3108 ('symbol', '5'))
3080 3109 ('symbol', 'date'))))
3081 3110 * set:
3082 3111 <baseset [4, 5, 3, 2]>
3083 3112 4
3084 3113 5
3085 3114 3
3086 3115 2
3087 3116 $ try 'rs(2 or 3, date)'
3088 3117 (func
3089 3118 ('symbol', 'rs')
3090 3119 (list
3091 3120 (or
3092 3121 (list
3093 3122 ('symbol', '2')
3094 3123 ('symbol', '3')))
3095 3124 ('symbol', 'date')))
3096 3125 * expanded:
3097 3126 (func
3098 3127 ('symbol', 'reverse')
3099 3128 (func
3100 3129 ('symbol', 'sort')
3101 3130 (list
3102 3131 (or
3103 3132 (list
3104 3133 ('symbol', '2')
3105 3134 ('symbol', '3')))
3106 3135 ('symbol', 'date'))))
3107 3136 * set:
3108 3137 <baseset [3, 2]>
3109 3138 3
3110 3139 2
3111 3140 $ try 'rs()'
3112 3141 (func
3113 3142 ('symbol', 'rs')
3114 3143 None)
3115 3144 hg: parse error: invalid number of arguments: 0
3116 3145 [255]
3117 3146 $ try 'rs(2)'
3118 3147 (func
3119 3148 ('symbol', 'rs')
3120 3149 ('symbol', '2'))
3121 3150 hg: parse error: invalid number of arguments: 1
3122 3151 [255]
3123 3152 $ try 'rs(2, data, 7)'
3124 3153 (func
3125 3154 ('symbol', 'rs')
3126 3155 (list
3127 3156 ('symbol', '2')
3128 3157 ('symbol', 'data')
3129 3158 ('symbol', '7')))
3130 3159 hg: parse error: invalid number of arguments: 3
3131 3160 [255]
3132 3161 $ try 'rs4(2 or 3, x, x, date)'
3133 3162 (func
3134 3163 ('symbol', 'rs4')
3135 3164 (list
3136 3165 (or
3137 3166 (list
3138 3167 ('symbol', '2')
3139 3168 ('symbol', '3')))
3140 3169 ('symbol', 'x')
3141 3170 ('symbol', 'x')
3142 3171 ('symbol', 'date')))
3143 3172 * expanded:
3144 3173 (func
3145 3174 ('symbol', 'reverse')
3146 3175 (func
3147 3176 ('symbol', 'sort')
3148 3177 (list
3149 3178 (or
3150 3179 (list
3151 3180 ('symbol', '2')
3152 3181 ('symbol', '3')))
3153 3182 ('symbol', 'date'))))
3154 3183 * set:
3155 3184 <baseset [3, 2]>
3156 3185 3
3157 3186 2
3158 3187
3159 3188 issue4553: check that revset aliases override existing hash prefix
3160 3189
3161 3190 $ hg log -qr e
3162 3191 6:e0cc66ef77e8
3163 3192
3164 3193 $ hg log -qr e --config revsetalias.e="all()"
3165 3194 0:2785f51eece5
3166 3195 1:d75937da8da0
3167 3196 2:5ed5505e9f1c
3168 3197 3:8528aa5637f2
3169 3198 4:2326846efdab
3170 3199 5:904fa392b941
3171 3200 6:e0cc66ef77e8
3172 3201 7:013af1973af4
3173 3202 8:d5d0dcbdc4d9
3174 3203 9:24286f4ae135
3175 3204
3176 3205 $ hg log -qr e: --config revsetalias.e="0"
3177 3206 0:2785f51eece5
3178 3207 1:d75937da8da0
3179 3208 2:5ed5505e9f1c
3180 3209 3:8528aa5637f2
3181 3210 4:2326846efdab
3182 3211 5:904fa392b941
3183 3212 6:e0cc66ef77e8
3184 3213 7:013af1973af4
3185 3214 8:d5d0dcbdc4d9
3186 3215 9:24286f4ae135
3187 3216
3188 3217 $ hg log -qr :e --config revsetalias.e="9"
3189 3218 0:2785f51eece5
3190 3219 1:d75937da8da0
3191 3220 2:5ed5505e9f1c
3192 3221 3:8528aa5637f2
3193 3222 4:2326846efdab
3194 3223 5:904fa392b941
3195 3224 6:e0cc66ef77e8
3196 3225 7:013af1973af4
3197 3226 8:d5d0dcbdc4d9
3198 3227 9:24286f4ae135
3199 3228
3200 3229 $ hg log -qr e:
3201 3230 6:e0cc66ef77e8
3202 3231 7:013af1973af4
3203 3232 8:d5d0dcbdc4d9
3204 3233 9:24286f4ae135
3205 3234
3206 3235 $ hg log -qr :e
3207 3236 0:2785f51eece5
3208 3237 1:d75937da8da0
3209 3238 2:5ed5505e9f1c
3210 3239 3:8528aa5637f2
3211 3240 4:2326846efdab
3212 3241 5:904fa392b941
3213 3242 6:e0cc66ef77e8
3214 3243
3215 3244 issue2549 - correct optimizations
3216 3245
3217 3246 $ try 'limit(1 or 2 or 3, 2) and not 2'
3218 3247 (and
3219 3248 (func
3220 3249 ('symbol', 'limit')
3221 3250 (list
3222 3251 (or
3223 3252 (list
3224 3253 ('symbol', '1')
3225 3254 ('symbol', '2')
3226 3255 ('symbol', '3')))
3227 3256 ('symbol', '2')))
3228 3257 (not
3229 3258 ('symbol', '2')))
3230 3259 * set:
3231 3260 <filteredset
3232 3261 <baseset
3233 3262 <limit n=2, offset=0,
3234 3263 <fullreposet+ 0:9>,
3235 3264 <baseset [1, 2, 3]>>>,
3236 3265 <not
3237 3266 <baseset [2]>>>
3238 3267 1
3239 3268 $ try 'max(1 or 2) and not 2'
3240 3269 (and
3241 3270 (func
3242 3271 ('symbol', 'max')
3243 3272 (or
3244 3273 (list
3245 3274 ('symbol', '1')
3246 3275 ('symbol', '2'))))
3247 3276 (not
3248 3277 ('symbol', '2')))
3249 3278 * set:
3250 3279 <filteredset
3251 3280 <baseset
3252 3281 <max
3253 3282 <fullreposet+ 0:9>,
3254 3283 <baseset [1, 2]>>>,
3255 3284 <not
3256 3285 <baseset [2]>>>
3257 3286 $ try 'min(1 or 2) and not 1'
3258 3287 (and
3259 3288 (func
3260 3289 ('symbol', 'min')
3261 3290 (or
3262 3291 (list
3263 3292 ('symbol', '1')
3264 3293 ('symbol', '2'))))
3265 3294 (not
3266 3295 ('symbol', '1')))
3267 3296 * set:
3268 3297 <filteredset
3269 3298 <baseset
3270 3299 <min
3271 3300 <fullreposet+ 0:9>,
3272 3301 <baseset [1, 2]>>>,
3273 3302 <not
3274 3303 <baseset [1]>>>
3275 3304 $ try 'last(1 or 2, 1) and not 2'
3276 3305 (and
3277 3306 (func
3278 3307 ('symbol', 'last')
3279 3308 (list
3280 3309 (or
3281 3310 (list
3282 3311 ('symbol', '1')
3283 3312 ('symbol', '2')))
3284 3313 ('symbol', '1')))
3285 3314 (not
3286 3315 ('symbol', '2')))
3287 3316 * set:
3288 3317 <filteredset
3289 3318 <baseset
3290 3319 <last n=1,
3291 3320 <fullreposet+ 0:9>,
3292 3321 <baseset [2, 1]>>>,
3293 3322 <not
3294 3323 <baseset [2]>>>
3295 3324
3296 3325 issue4289 - ordering of built-ins
3297 3326 $ hg log -M -q -r 3:2
3298 3327 3:8528aa5637f2
3299 3328 2:5ed5505e9f1c
3300 3329
3301 3330 test revsets started with 40-chars hash (issue3669)
3302 3331
3303 3332 $ ISSUE3669_TIP=`hg tip --template '{node}'`
3304 3333 $ hg log -r "${ISSUE3669_TIP}" --template '{rev}\n'
3305 3334 9
3306 3335 $ hg log -r "${ISSUE3669_TIP}^" --template '{rev}\n'
3307 3336 8
3308 3337
3309 3338 test or-ed indirect predicates (issue3775)
3310 3339
3311 3340 $ log '6 or 6^1' | sort
3312 3341 5
3313 3342 6
3314 3343 $ log '6^1 or 6' | sort
3315 3344 5
3316 3345 6
3317 3346 $ log '4 or 4~1' | sort
3318 3347 2
3319 3348 4
3320 3349 $ log '4~1 or 4' | sort
3321 3350 2
3322 3351 4
3323 3352 $ log '(0 or 2):(4 or 6) or 0 or 6' | sort
3324 3353 0
3325 3354 1
3326 3355 2
3327 3356 3
3328 3357 4
3329 3358 5
3330 3359 6
3331 3360 $ log '0 or 6 or (0 or 2):(4 or 6)' | sort
3332 3361 0
3333 3362 1
3334 3363 2
3335 3364 3
3336 3365 4
3337 3366 5
3338 3367 6
3339 3368
3340 3369 tests for 'remote()' predicate:
3341 3370 #. (csets in remote) (id) (remote)
3342 3371 1. less than local current branch "default"
3343 3372 2. same with local specified "default"
3344 3373 3. more than local specified specified
3345 3374
3346 3375 $ hg clone --quiet -U . ../remote3
3347 3376 $ cd ../remote3
3348 3377 $ hg update -q 7
3349 3378 $ echo r > r
3350 3379 $ hg ci -Aqm 10
3351 3380 $ log 'remote()'
3352 3381 7
3353 3382 $ log 'remote("a-b-c-")'
3354 3383 2
3355 3384 $ cd ../repo
3356 3385 $ log 'remote(".a.b.c.", "../remote3")'
3357 3386
3358 3387 tests for concatenation of strings/symbols by "##"
3359 3388
3360 3389 $ try "278 ## '5f5' ## 1ee ## 'ce5'"
3361 3390 (_concat
3362 3391 (_concat
3363 3392 (_concat
3364 3393 ('symbol', '278')
3365 3394 ('string', '5f5'))
3366 3395 ('symbol', '1ee'))
3367 3396 ('string', 'ce5'))
3368 3397 * concatenated:
3369 3398 ('string', '2785f51eece5')
3370 3399 * set:
3371 3400 <baseset [0]>
3372 3401 0
3373 3402
3374 3403 $ echo 'cat4($1, $2, $3, $4) = $1 ## $2 ## $3 ## $4' >> .hg/hgrc
3375 3404 $ try "cat4(278, '5f5', 1ee, 'ce5')"
3376 3405 (func
3377 3406 ('symbol', 'cat4')
3378 3407 (list
3379 3408 ('symbol', '278')
3380 3409 ('string', '5f5')
3381 3410 ('symbol', '1ee')
3382 3411 ('string', 'ce5')))
3383 3412 * expanded:
3384 3413 (_concat
3385 3414 (_concat
3386 3415 (_concat
3387 3416 ('symbol', '278')
3388 3417 ('string', '5f5'))
3389 3418 ('symbol', '1ee'))
3390 3419 ('string', 'ce5'))
3391 3420 * concatenated:
3392 3421 ('string', '2785f51eece5')
3393 3422 * set:
3394 3423 <baseset [0]>
3395 3424 0
3396 3425
3397 3426 (check concatenation in alias nesting)
3398 3427
3399 3428 $ echo 'cat2($1, $2) = $1 ## $2' >> .hg/hgrc
3400 3429 $ echo 'cat2x2($1, $2, $3, $4) = cat2($1 ## $2, $3 ## $4)' >> .hg/hgrc
3401 3430 $ log "cat2x2(278, '5f5', 1ee, 'ce5')"
3402 3431 0
3403 3432
3404 3433 (check operator priority)
3405 3434
3406 3435 $ echo 'cat2n2($1, $2, $3, $4) = $1 ## $2 or $3 ## $4~2' >> .hg/hgrc
3407 3436 $ log "cat2n2(2785f5, 1eece5, 24286f, 4ae135)"
3408 3437 0
3409 3438 4
3410 3439
3411 3440 $ cd ..
3412 3441
3413 3442 prepare repository that has "default" branches of multiple roots
3414 3443
3415 3444 $ hg init namedbranch
3416 3445 $ cd namedbranch
3417 3446
3418 3447 $ echo default0 >> a
3419 3448 $ hg ci -Aqm0
3420 3449 $ echo default1 >> a
3421 3450 $ hg ci -m1
3422 3451
3423 3452 $ hg branch -q stable
3424 3453 $ echo stable2 >> a
3425 3454 $ hg ci -m2
3426 3455 $ echo stable3 >> a
3427 3456 $ hg ci -m3
3428 3457
3429 3458 $ hg update -q null
3430 3459 $ echo default4 >> a
3431 3460 $ hg ci -Aqm4
3432 3461 $ echo default5 >> a
3433 3462 $ hg ci -m5
3434 3463
3435 3464 "null" revision belongs to "default" branch (issue4683)
3436 3465
3437 3466 $ log 'branch(null)'
3438 3467 0
3439 3468 1
3440 3469 4
3441 3470 5
3442 3471
3443 3472 "null" revision belongs to "default" branch, but it shouldn't appear in set
3444 3473 unless explicitly specified (issue4682)
3445 3474
3446 3475 $ log 'children(branch(default))'
3447 3476 1
3448 3477 2
3449 3478 5
3450 3479
3451 3480 $ cd ..
3452 3481
3453 3482 test author/desc/keyword in problematic encoding
3454 3483 # unicode: cp932:
3455 3484 # u30A2 0x83 0x41(= 'A')
3456 3485 # u30C2 0x83 0x61(= 'a')
3457 3486
3458 3487 $ hg init problematicencoding
3459 3488 $ cd problematicencoding
3460 3489
3461 3490 $ python > setup.sh <<EOF
3462 3491 > print u'''
3463 3492 > echo a > text
3464 3493 > hg add text
3465 3494 > hg --encoding utf-8 commit -u '\u30A2' -m none
3466 3495 > echo b > text
3467 3496 > hg --encoding utf-8 commit -u '\u30C2' -m none
3468 3497 > echo c > text
3469 3498 > hg --encoding utf-8 commit -u none -m '\u30A2'
3470 3499 > echo d > text
3471 3500 > hg --encoding utf-8 commit -u none -m '\u30C2'
3472 3501 > '''.encode('utf-8')
3473 3502 > EOF
3474 3503 $ sh < setup.sh
3475 3504
3476 3505 test in problematic encoding
3477 3506 $ python > test.sh <<EOF
3478 3507 > print u'''
3479 3508 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
3480 3509 > echo ====
3481 3510 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
3482 3511 > echo ====
3483 3512 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
3484 3513 > echo ====
3485 3514 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
3486 3515 > echo ====
3487 3516 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
3488 3517 > echo ====
3489 3518 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
3490 3519 > '''.encode('cp932')
3491 3520 > EOF
3492 3521 $ sh < test.sh
3493 3522 0
3494 3523 ====
3495 3524 1
3496 3525 ====
3497 3526 2
3498 3527 ====
3499 3528 3
3500 3529 ====
3501 3530 0
3502 3531 2
3503 3532 ====
3504 3533 1
3505 3534 3
3506 3535
3507 3536 test error message of bad revset
3508 3537 $ hg log -r 'foo\\'
3509 3538 hg: parse error at 3: syntax error in revset 'foo\\'
3510 3539 [255]
3511 3540
3512 3541 $ cd ..
3513 3542
3514 3543 Test that revset predicate of extension isn't loaded at failure of
3515 3544 loading it
3516 3545
3517 3546 $ cd repo
3518 3547
3519 3548 $ cat <<EOF > $TESTTMP/custompredicate.py
3520 3549 > from mercurial import error, registrar, revset
3521 3550 >
3522 3551 > revsetpredicate = registrar.revsetpredicate()
3523 3552 >
3524 3553 > @revsetpredicate('custom1()')
3525 3554 > def custom1(repo, subset, x):
3526 3555 > return revset.baseset([1])
3527 3556 >
3528 3557 > raise error.Abort('intentional failure of loading extension')
3529 3558 > EOF
3530 3559 $ cat <<EOF > .hg/hgrc
3531 3560 > [extensions]
3532 3561 > custompredicate = $TESTTMP/custompredicate.py
3533 3562 > EOF
3534 3563
3535 3564 $ hg debugrevspec "custom1()"
3536 3565 *** failed to import extension custompredicate from $TESTTMP/custompredicate.py: intentional failure of loading extension
3537 3566 hg: parse error: unknown identifier: custom1
3538 3567 [255]
3539 3568
3540 3569 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now