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