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