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