##// END OF EJS Templates
revset: add partial support for ancestor(wdir())...
Yuya Nishihara -
r38541:54d7aaa2 default
parent child Browse files
Show More
@@ -1,2247 +1,2248
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 re
11 11
12 12 from .i18n import _
13 13 from . import (
14 14 dagop,
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 obsutil,
23 23 pathutil,
24 24 phases,
25 25 pycompat,
26 26 registrar,
27 27 repoview,
28 28 revsetlang,
29 29 scmutil,
30 30 smartset,
31 31 stack as stackmod,
32 32 util,
33 33 )
34 34 from .utils import (
35 35 dateutil,
36 36 stringutil,
37 37 )
38 38
39 39 # helpers for processing parsed tree
40 40 getsymbol = revsetlang.getsymbol
41 41 getstring = revsetlang.getstring
42 42 getinteger = revsetlang.getinteger
43 43 getboolean = revsetlang.getboolean
44 44 getlist = revsetlang.getlist
45 45 getrange = revsetlang.getrange
46 46 getargs = revsetlang.getargs
47 47 getargsdict = revsetlang.getargsdict
48 48
49 49 baseset = smartset.baseset
50 50 generatorset = smartset.generatorset
51 51 spanset = smartset.spanset
52 52 fullreposet = smartset.fullreposet
53 53
54 54 # Constants for ordering requirement, used in getset():
55 55 #
56 56 # If 'define', any nested functions and operations MAY change the ordering of
57 57 # the entries in the set (but if changes the ordering, it MUST ALWAYS change
58 58 # it). If 'follow', any nested functions and operations MUST take the ordering
59 59 # specified by the first operand to the '&' operator.
60 60 #
61 61 # For instance,
62 62 #
63 63 # X & (Y | Z)
64 64 # ^ ^^^^^^^
65 65 # | follow
66 66 # define
67 67 #
68 68 # will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
69 69 # of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
70 70 #
71 71 # 'any' means the order doesn't matter. For instance,
72 72 #
73 73 # (X & !Y) | ancestors(Z)
74 74 # ^ ^
75 75 # any any
76 76 #
77 77 # For 'X & !Y', 'X' decides the order and 'Y' is subtracted from 'X', so the
78 78 # order of 'Y' does not matter. For 'ancestors(Z)', Z's order does not matter
79 79 # since 'ancestors' does not care about the order of its argument.
80 80 #
81 81 # Currently, most revsets do not care about the order, so 'define' is
82 82 # equivalent to 'follow' for them, and the resulting order is based on the
83 83 # 'subset' parameter passed down to them:
84 84 #
85 85 # m = revset.match(...)
86 86 # m(repo, subset, order=defineorder)
87 87 # ^^^^^^
88 88 # For most revsets, 'define' means using the order this subset provides
89 89 #
90 90 # There are a few revsets that always redefine the order if 'define' is
91 91 # specified: 'sort(X)', 'reverse(X)', 'x:y'.
92 92 anyorder = 'any' # don't care the order, could be even random-shuffled
93 93 defineorder = 'define' # ALWAYS redefine, or ALWAYS follow the current order
94 94 followorder = 'follow' # MUST follow the current order
95 95
96 96 # helpers
97 97
98 98 def getset(repo, subset, x, order=defineorder):
99 99 if not x:
100 100 raise error.ParseError(_("missing argument"))
101 101 return methods[x[0]](repo, subset, *x[1:], order=order)
102 102
103 103 def _getrevsource(repo, r):
104 104 extra = repo[r].extra()
105 105 for label in ('source', 'transplant_source', 'rebase_source'):
106 106 if label in extra:
107 107 try:
108 108 return repo[extra[label]].rev()
109 109 except error.RepoLookupError:
110 110 pass
111 111 return None
112 112
113 113 def _sortedb(xs):
114 114 return sorted(util.rapply(pycompat.maybebytestr, xs))
115 115
116 116 # operator methods
117 117
118 118 def stringset(repo, subset, x, order):
119 119 if not x:
120 120 raise error.ParseError(_("empty string is not a valid revision"))
121 121 x = scmutil.intrev(scmutil.revsymbol(repo, x))
122 122 if (x in subset
123 123 or x == node.nullrev and isinstance(subset, fullreposet)):
124 124 return baseset([x])
125 125 return baseset()
126 126
127 127 def rangeset(repo, subset, x, y, order):
128 128 m = getset(repo, fullreposet(repo), x)
129 129 n = getset(repo, fullreposet(repo), y)
130 130
131 131 if not m or not n:
132 132 return baseset()
133 133 return _makerangeset(repo, subset, m.first(), n.last(), order)
134 134
135 135 def rangeall(repo, subset, x, order):
136 136 assert x is None
137 137 return _makerangeset(repo, subset, 0, repo.changelog.tiprev(), order)
138 138
139 139 def rangepre(repo, subset, y, order):
140 140 # ':y' can't be rewritten to '0:y' since '0' may be hidden
141 141 n = getset(repo, fullreposet(repo), y)
142 142 if not n:
143 143 return baseset()
144 144 return _makerangeset(repo, subset, 0, n.last(), order)
145 145
146 146 def rangepost(repo, subset, x, order):
147 147 m = getset(repo, fullreposet(repo), x)
148 148 if not m:
149 149 return baseset()
150 150 return _makerangeset(repo, subset, m.first(), repo.changelog.tiprev(),
151 151 order)
152 152
153 153 def _makerangeset(repo, subset, m, n, order):
154 154 if m == n:
155 155 r = baseset([m])
156 156 elif n == node.wdirrev:
157 157 r = spanset(repo, m, len(repo)) + baseset([n])
158 158 elif m == node.wdirrev:
159 159 r = baseset([m]) + spanset(repo, repo.changelog.tiprev(), n - 1)
160 160 elif m < n:
161 161 r = spanset(repo, m, n + 1)
162 162 else:
163 163 r = spanset(repo, m, n - 1)
164 164
165 165 if order == defineorder:
166 166 return r & subset
167 167 else:
168 168 # carrying the sorting over when possible would be more efficient
169 169 return subset & r
170 170
171 171 def dagrange(repo, subset, x, y, order):
172 172 r = fullreposet(repo)
173 173 xs = dagop.reachableroots(repo, getset(repo, r, x), getset(repo, r, y),
174 174 includepath=True)
175 175 return subset & xs
176 176
177 177 def andset(repo, subset, x, y, order):
178 178 if order == anyorder:
179 179 yorder = anyorder
180 180 else:
181 181 yorder = followorder
182 182 return getset(repo, getset(repo, subset, x, order), y, yorder)
183 183
184 184 def andsmallyset(repo, subset, x, y, order):
185 185 # 'andsmally(x, y)' is equivalent to 'and(x, y)', but faster when y is small
186 186 if order == anyorder:
187 187 yorder = anyorder
188 188 else:
189 189 yorder = followorder
190 190 return getset(repo, getset(repo, subset, y, yorder), x, order)
191 191
192 192 def differenceset(repo, subset, x, y, order):
193 193 return getset(repo, subset, x, order) - getset(repo, subset, y, anyorder)
194 194
195 195 def _orsetlist(repo, subset, xs, order):
196 196 assert xs
197 197 if len(xs) == 1:
198 198 return getset(repo, subset, xs[0], order)
199 199 p = len(xs) // 2
200 200 a = _orsetlist(repo, subset, xs[:p], order)
201 201 b = _orsetlist(repo, subset, xs[p:], order)
202 202 return a + b
203 203
204 204 def orset(repo, subset, x, order):
205 205 xs = getlist(x)
206 206 if not xs:
207 207 return baseset()
208 208 if order == followorder:
209 209 # slow path to take the subset order
210 210 return subset & _orsetlist(repo, fullreposet(repo), xs, anyorder)
211 211 else:
212 212 return _orsetlist(repo, subset, xs, order)
213 213
214 214 def notset(repo, subset, x, order):
215 215 return subset - getset(repo, subset, x, anyorder)
216 216
217 217 def relationset(repo, subset, x, y, order):
218 218 raise error.ParseError(_("can't use a relation in this context"))
219 219
220 220 def relsubscriptset(repo, subset, x, y, z, order):
221 221 # this is pretty basic implementation of 'x#y[z]' operator, still
222 222 # experimental so undocumented. see the wiki for further ideas.
223 223 # https://www.mercurial-scm.org/wiki/RevsetOperatorPlan
224 224 rel = getsymbol(y)
225 225 n = getinteger(z, _("relation subscript must be an integer"))
226 226
227 227 # TODO: perhaps this should be a table of relation functions
228 228 if rel in ('g', 'generations'):
229 229 # TODO: support range, rewrite tests, and drop startdepth argument
230 230 # from ancestors() and descendants() predicates
231 231 if n <= 0:
232 232 n = -n
233 233 return _ancestors(repo, subset, x, startdepth=n, stopdepth=n + 1)
234 234 else:
235 235 return _descendants(repo, subset, x, startdepth=n, stopdepth=n + 1)
236 236
237 237 raise error.UnknownIdentifier(rel, ['generations'])
238 238
239 239 def subscriptset(repo, subset, x, y, order):
240 240 raise error.ParseError(_("can't use a subscript in this context"))
241 241
242 242 def listset(repo, subset, *xs, **opts):
243 243 raise error.ParseError(_("can't use a list in this context"),
244 244 hint=_('see hg help "revsets.x or y"'))
245 245
246 246 def keyvaluepair(repo, subset, k, v, order):
247 247 raise error.ParseError(_("can't use a key-value pair in this context"))
248 248
249 249 def func(repo, subset, a, b, order):
250 250 f = getsymbol(a)
251 251 if f in symbols:
252 252 func = symbols[f]
253 253 if getattr(func, '_takeorder', False):
254 254 return func(repo, subset, b, order)
255 255 return func(repo, subset, b)
256 256
257 257 keep = lambda fn: getattr(fn, '__doc__', None) is not None
258 258
259 259 syms = [s for (s, fn) in symbols.items() if keep(fn)]
260 260 raise error.UnknownIdentifier(f, syms)
261 261
262 262 # functions
263 263
264 264 # symbols are callables like:
265 265 # fn(repo, subset, x)
266 266 # with:
267 267 # repo - current repository instance
268 268 # subset - of revisions to be examined
269 269 # x - argument in tree form
270 270 symbols = revsetlang.symbols
271 271
272 272 # symbols which can't be used for a DoS attack for any given input
273 273 # (e.g. those which accept regexes as plain strings shouldn't be included)
274 274 # functions that just return a lot of changesets (like all) don't count here
275 275 safesymbols = set()
276 276
277 277 predicate = registrar.revsetpredicate()
278 278
279 279 @predicate('_destupdate')
280 280 def _destupdate(repo, subset, x):
281 281 # experimental revset for update destination
282 282 args = getargsdict(x, 'limit', 'clean')
283 283 return subset & baseset([destutil.destupdate(repo,
284 284 **pycompat.strkwargs(args))[0]])
285 285
286 286 @predicate('_destmerge')
287 287 def _destmerge(repo, subset, x):
288 288 # experimental revset for merge destination
289 289 sourceset = None
290 290 if x is not None:
291 291 sourceset = getset(repo, fullreposet(repo), x)
292 292 return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
293 293
294 294 @predicate('adds(pattern)', safe=True, weight=30)
295 295 def adds(repo, subset, x):
296 296 """Changesets that add a file matching pattern.
297 297
298 298 The pattern without explicit kind like ``glob:`` is expected to be
299 299 relative to the current directory and match against a file or a
300 300 directory.
301 301 """
302 302 # i18n: "adds" is a keyword
303 303 pat = getstring(x, _("adds requires a pattern"))
304 304 return checkstatus(repo, subset, pat, 1)
305 305
306 306 @predicate('ancestor(*changeset)', safe=True, weight=0.5)
307 307 def ancestor(repo, subset, x):
308 308 """A greatest common ancestor of the changesets.
309 309
310 310 Accepts 0 or more changesets.
311 311 Will return empty list when passed no args.
312 312 Greatest common ancestor of a single changeset is that changeset.
313 313 """
314 314 reviter = iter(orset(repo, fullreposet(repo), x, order=anyorder))
315 315 try:
316 316 anc = repo[next(reviter)]
317 317 except StopIteration:
318 318 return baseset()
319 319 for r in reviter:
320 320 anc = anc.ancestor(repo[r])
321 321
322 if anc.rev() in subset:
323 return baseset([anc.rev()])
322 r = scmutil.intrev(anc)
323 if r in subset:
324 return baseset([r])
324 325 return baseset()
325 326
326 327 def _ancestors(repo, subset, x, followfirst=False, startdepth=None,
327 328 stopdepth=None):
328 329 heads = getset(repo, fullreposet(repo), x)
329 330 if not heads:
330 331 return baseset()
331 332 s = dagop.revancestors(repo, heads, followfirst, startdepth, stopdepth)
332 333 return subset & s
333 334
334 335 @predicate('ancestors(set[, depth])', safe=True)
335 336 def ancestors(repo, subset, x):
336 337 """Changesets that are ancestors of changesets in set, including the
337 338 given changesets themselves.
338 339
339 340 If depth is specified, the result only includes changesets up to
340 341 the specified generation.
341 342 """
342 343 # startdepth is for internal use only until we can decide the UI
343 344 args = getargsdict(x, 'ancestors', 'set depth startdepth')
344 345 if 'set' not in args:
345 346 # i18n: "ancestors" is a keyword
346 347 raise error.ParseError(_('ancestors takes at least 1 argument'))
347 348 startdepth = stopdepth = None
348 349 if 'startdepth' in args:
349 350 n = getinteger(args['startdepth'],
350 351 "ancestors expects an integer startdepth")
351 352 if n < 0:
352 353 raise error.ParseError("negative startdepth")
353 354 startdepth = n
354 355 if 'depth' in args:
355 356 # i18n: "ancestors" is a keyword
356 357 n = getinteger(args['depth'], _("ancestors expects an integer depth"))
357 358 if n < 0:
358 359 raise error.ParseError(_("negative depth"))
359 360 stopdepth = n + 1
360 361 return _ancestors(repo, subset, args['set'],
361 362 startdepth=startdepth, stopdepth=stopdepth)
362 363
363 364 @predicate('_firstancestors', safe=True)
364 365 def _firstancestors(repo, subset, x):
365 366 # ``_firstancestors(set)``
366 367 # Like ``ancestors(set)`` but follows only the first parents.
367 368 return _ancestors(repo, subset, x, followfirst=True)
368 369
369 370 def _childrenspec(repo, subset, x, n, order):
370 371 """Changesets that are the Nth child of a changeset
371 372 in set.
372 373 """
373 374 cs = set()
374 375 for r in getset(repo, fullreposet(repo), x):
375 376 for i in range(n):
376 377 c = repo[r].children()
377 378 if len(c) == 0:
378 379 break
379 380 if len(c) > 1:
380 381 raise error.RepoLookupError(
381 382 _("revision in set has more than one child"))
382 383 r = c[0].rev()
383 384 else:
384 385 cs.add(r)
385 386 return subset & cs
386 387
387 388 def ancestorspec(repo, subset, x, n, order):
388 389 """``set~n``
389 390 Changesets that are the Nth ancestor (first parents only) of a changeset
390 391 in set.
391 392 """
392 393 n = getinteger(n, _("~ expects a number"))
393 394 if n < 0:
394 395 # children lookup
395 396 return _childrenspec(repo, subset, x, -n, order)
396 397 ps = set()
397 398 cl = repo.changelog
398 399 for r in getset(repo, fullreposet(repo), x):
399 400 for i in range(n):
400 401 try:
401 402 r = cl.parentrevs(r)[0]
402 403 except error.WdirUnsupported:
403 404 r = repo[r].parents()[0].rev()
404 405 ps.add(r)
405 406 return subset & ps
406 407
407 408 @predicate('author(string)', safe=True, weight=10)
408 409 def author(repo, subset, x):
409 410 """Alias for ``user(string)``.
410 411 """
411 412 # i18n: "author" is a keyword
412 413 n = getstring(x, _("author requires a string"))
413 414 kind, pattern, matcher = _substringmatcher(n, casesensitive=False)
414 415 return subset.filter(lambda x: matcher(repo[x].user()),
415 416 condrepr=('<user %r>', n))
416 417
417 418 @predicate('bisect(string)', safe=True)
418 419 def bisect(repo, subset, x):
419 420 """Changesets marked in the specified bisect status:
420 421
421 422 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
422 423 - ``goods``, ``bads`` : csets topologically good/bad
423 424 - ``range`` : csets taking part in the bisection
424 425 - ``pruned`` : csets that are goods, bads or skipped
425 426 - ``untested`` : csets whose fate is yet unknown
426 427 - ``ignored`` : csets ignored due to DAG topology
427 428 - ``current`` : the cset currently being bisected
428 429 """
429 430 # i18n: "bisect" is a keyword
430 431 status = getstring(x, _("bisect requires a string")).lower()
431 432 state = set(hbisect.get(repo, status))
432 433 return subset & state
433 434
434 435 # Backward-compatibility
435 436 # - no help entry so that we do not advertise it any more
436 437 @predicate('bisected', safe=True)
437 438 def bisected(repo, subset, x):
438 439 return bisect(repo, subset, x)
439 440
440 441 @predicate('bookmark([name])', safe=True)
441 442 def bookmark(repo, subset, x):
442 443 """The named bookmark or all bookmarks.
443 444
444 445 Pattern matching is supported for `name`. See :hg:`help revisions.patterns`.
445 446 """
446 447 # i18n: "bookmark" is a keyword
447 448 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
448 449 if args:
449 450 bm = getstring(args[0],
450 451 # i18n: "bookmark" is a keyword
451 452 _('the argument to bookmark must be a string'))
452 453 kind, pattern, matcher = stringutil.stringmatcher(bm)
453 454 bms = set()
454 455 if kind == 'literal':
455 456 bmrev = repo._bookmarks.get(pattern, None)
456 457 if not bmrev:
457 458 raise error.RepoLookupError(_("bookmark '%s' does not exist")
458 459 % pattern)
459 460 bms.add(repo[bmrev].rev())
460 461 else:
461 462 matchrevs = set()
462 463 for name, bmrev in repo._bookmarks.iteritems():
463 464 if matcher(name):
464 465 matchrevs.add(bmrev)
465 466 if not matchrevs:
466 467 raise error.RepoLookupError(_("no bookmarks exist"
467 468 " that match '%s'") % pattern)
468 469 for bmrev in matchrevs:
469 470 bms.add(repo[bmrev].rev())
470 471 else:
471 472 bms = {repo[r].rev() for r in repo._bookmarks.values()}
472 473 bms -= {node.nullrev}
473 474 return subset & bms
474 475
475 476 @predicate('branch(string or set)', safe=True, weight=10)
476 477 def branch(repo, subset, x):
477 478 """
478 479 All changesets belonging to the given branch or the branches of the given
479 480 changesets.
480 481
481 482 Pattern matching is supported for `string`. See
482 483 :hg:`help revisions.patterns`.
483 484 """
484 485 getbi = repo.revbranchcache().branchinfo
485 486 def getbranch(r):
486 487 try:
487 488 return getbi(r)[0]
488 489 except error.WdirUnsupported:
489 490 return repo[r].branch()
490 491
491 492 try:
492 493 b = getstring(x, '')
493 494 except error.ParseError:
494 495 # not a string, but another revspec, e.g. tip()
495 496 pass
496 497 else:
497 498 kind, pattern, matcher = stringutil.stringmatcher(b)
498 499 if kind == 'literal':
499 500 # note: falls through to the revspec case if no branch with
500 501 # this name exists and pattern kind is not specified explicitly
501 502 if pattern in repo.branchmap():
502 503 return subset.filter(lambda r: matcher(getbranch(r)),
503 504 condrepr=('<branch %r>', b))
504 505 if b.startswith('literal:'):
505 506 raise error.RepoLookupError(_("branch '%s' does not exist")
506 507 % pattern)
507 508 else:
508 509 return subset.filter(lambda r: matcher(getbranch(r)),
509 510 condrepr=('<branch %r>', b))
510 511
511 512 s = getset(repo, fullreposet(repo), x)
512 513 b = set()
513 514 for r in s:
514 515 b.add(getbranch(r))
515 516 c = s.__contains__
516 517 return subset.filter(lambda r: c(r) or getbranch(r) in b,
517 518 condrepr=lambda: '<branch %r>' % _sortedb(b))
518 519
519 520 @predicate('phasedivergent()', safe=True)
520 521 def phasedivergent(repo, subset, x):
521 522 """Mutable changesets marked as successors of public changesets.
522 523
523 524 Only non-public and non-obsolete changesets can be `phasedivergent`.
524 525 (EXPERIMENTAL)
525 526 """
526 527 # i18n: "phasedivergent" is a keyword
527 528 getargs(x, 0, 0, _("phasedivergent takes no arguments"))
528 529 phasedivergent = obsmod.getrevs(repo, 'phasedivergent')
529 530 return subset & phasedivergent
530 531
531 532 @predicate('bundle()', safe=True)
532 533 def bundle(repo, subset, x):
533 534 """Changesets in the bundle.
534 535
535 536 Bundle must be specified by the -R option."""
536 537
537 538 try:
538 539 bundlerevs = repo.changelog.bundlerevs
539 540 except AttributeError:
540 541 raise error.Abort(_("no bundle provided - specify with -R"))
541 542 return subset & bundlerevs
542 543
543 544 def checkstatus(repo, subset, pat, field):
544 545 hasset = matchmod.patkind(pat) == 'set'
545 546
546 547 mcache = [None]
547 548 def matches(x):
548 549 c = repo[x]
549 550 if not mcache[0] or hasset:
550 551 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
551 552 m = mcache[0]
552 553 fname = None
553 554 if not m.anypats() and len(m.files()) == 1:
554 555 fname = m.files()[0]
555 556 if fname is not None:
556 557 if fname not in c.files():
557 558 return False
558 559 else:
559 560 for f in c.files():
560 561 if m(f):
561 562 break
562 563 else:
563 564 return False
564 565 files = repo.status(c.p1().node(), c.node())[field]
565 566 if fname is not None:
566 567 if fname in files:
567 568 return True
568 569 else:
569 570 for f in files:
570 571 if m(f):
571 572 return True
572 573
573 574 return subset.filter(matches, condrepr=('<status[%r] %r>', field, pat))
574 575
575 576 def _children(repo, subset, parentset):
576 577 if not parentset:
577 578 return baseset()
578 579 cs = set()
579 580 pr = repo.changelog.parentrevs
580 581 minrev = parentset.min()
581 582 nullrev = node.nullrev
582 583 for r in subset:
583 584 if r <= minrev:
584 585 continue
585 586 p1, p2 = pr(r)
586 587 if p1 in parentset:
587 588 cs.add(r)
588 589 if p2 != nullrev and p2 in parentset:
589 590 cs.add(r)
590 591 return baseset(cs)
591 592
592 593 @predicate('children(set)', safe=True)
593 594 def children(repo, subset, x):
594 595 """Child changesets of changesets in set.
595 596 """
596 597 s = getset(repo, fullreposet(repo), x)
597 598 cs = _children(repo, subset, s)
598 599 return subset & cs
599 600
600 601 @predicate('closed()', safe=True, weight=10)
601 602 def closed(repo, subset, x):
602 603 """Changeset is closed.
603 604 """
604 605 # i18n: "closed" is a keyword
605 606 getargs(x, 0, 0, _("closed takes no arguments"))
606 607 return subset.filter(lambda r: repo[r].closesbranch(),
607 608 condrepr='<branch closed>')
608 609
609 610 @predicate('contains(pattern)', weight=100)
610 611 def contains(repo, subset, x):
611 612 """The revision's manifest contains a file matching pattern (but might not
612 613 modify it). See :hg:`help patterns` for information about file patterns.
613 614
614 615 The pattern without explicit kind like ``glob:`` is expected to be
615 616 relative to the current directory and match against a file exactly
616 617 for efficiency.
617 618 """
618 619 # i18n: "contains" is a keyword
619 620 pat = getstring(x, _("contains requires a pattern"))
620 621
621 622 def matches(x):
622 623 if not matchmod.patkind(pat):
623 624 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
624 625 if pats in repo[x]:
625 626 return True
626 627 else:
627 628 c = repo[x]
628 629 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
629 630 for f in c.manifest():
630 631 if m(f):
631 632 return True
632 633 return False
633 634
634 635 return subset.filter(matches, condrepr=('<contains %r>', pat))
635 636
636 637 @predicate('converted([id])', safe=True)
637 638 def converted(repo, subset, x):
638 639 """Changesets converted from the given identifier in the old repository if
639 640 present, or all converted changesets if no identifier is specified.
640 641 """
641 642
642 643 # There is exactly no chance of resolving the revision, so do a simple
643 644 # string compare and hope for the best
644 645
645 646 rev = None
646 647 # i18n: "converted" is a keyword
647 648 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
648 649 if l:
649 650 # i18n: "converted" is a keyword
650 651 rev = getstring(l[0], _('converted requires a revision'))
651 652
652 653 def _matchvalue(r):
653 654 source = repo[r].extra().get('convert_revision', None)
654 655 return source is not None and (rev is None or source.startswith(rev))
655 656
656 657 return subset.filter(lambda r: _matchvalue(r),
657 658 condrepr=('<converted %r>', rev))
658 659
659 660 @predicate('date(interval)', safe=True, weight=10)
660 661 def date(repo, subset, x):
661 662 """Changesets within the interval, see :hg:`help dates`.
662 663 """
663 664 # i18n: "date" is a keyword
664 665 ds = getstring(x, _("date requires a string"))
665 666 dm = dateutil.matchdate(ds)
666 667 return subset.filter(lambda x: dm(repo[x].date()[0]),
667 668 condrepr=('<date %r>', ds))
668 669
669 670 @predicate('desc(string)', safe=True, weight=10)
670 671 def desc(repo, subset, x):
671 672 """Search commit message for string. The match is case-insensitive.
672 673
673 674 Pattern matching is supported for `string`. See
674 675 :hg:`help revisions.patterns`.
675 676 """
676 677 # i18n: "desc" is a keyword
677 678 ds = getstring(x, _("desc requires a string"))
678 679
679 680 kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
680 681
681 682 return subset.filter(lambda r: matcher(repo[r].description()),
682 683 condrepr=('<desc %r>', ds))
683 684
684 685 def _descendants(repo, subset, x, followfirst=False, startdepth=None,
685 686 stopdepth=None):
686 687 roots = getset(repo, fullreposet(repo), x)
687 688 if not roots:
688 689 return baseset()
689 690 s = dagop.revdescendants(repo, roots, followfirst, startdepth, stopdepth)
690 691 return subset & s
691 692
692 693 @predicate('descendants(set[, depth])', safe=True)
693 694 def descendants(repo, subset, x):
694 695 """Changesets which are descendants of changesets in set, including the
695 696 given changesets themselves.
696 697
697 698 If depth is specified, the result only includes changesets up to
698 699 the specified generation.
699 700 """
700 701 # startdepth is for internal use only until we can decide the UI
701 702 args = getargsdict(x, 'descendants', 'set depth startdepth')
702 703 if 'set' not in args:
703 704 # i18n: "descendants" is a keyword
704 705 raise error.ParseError(_('descendants takes at least 1 argument'))
705 706 startdepth = stopdepth = None
706 707 if 'startdepth' in args:
707 708 n = getinteger(args['startdepth'],
708 709 "descendants expects an integer startdepth")
709 710 if n < 0:
710 711 raise error.ParseError("negative startdepth")
711 712 startdepth = n
712 713 if 'depth' in args:
713 714 # i18n: "descendants" is a keyword
714 715 n = getinteger(args['depth'], _("descendants expects an integer depth"))
715 716 if n < 0:
716 717 raise error.ParseError(_("negative depth"))
717 718 stopdepth = n + 1
718 719 return _descendants(repo, subset, args['set'],
719 720 startdepth=startdepth, stopdepth=stopdepth)
720 721
721 722 @predicate('_firstdescendants', safe=True)
722 723 def _firstdescendants(repo, subset, x):
723 724 # ``_firstdescendants(set)``
724 725 # Like ``descendants(set)`` but follows only the first parents.
725 726 return _descendants(repo, subset, x, followfirst=True)
726 727
727 728 @predicate('destination([set])', safe=True, weight=10)
728 729 def destination(repo, subset, x):
729 730 """Changesets that were created by a graft, transplant or rebase operation,
730 731 with the given revisions specified as the source. Omitting the optional set
731 732 is the same as passing all().
732 733 """
733 734 if x is not None:
734 735 sources = getset(repo, fullreposet(repo), x)
735 736 else:
736 737 sources = fullreposet(repo)
737 738
738 739 dests = set()
739 740
740 741 # subset contains all of the possible destinations that can be returned, so
741 742 # iterate over them and see if their source(s) were provided in the arg set.
742 743 # Even if the immediate src of r is not in the arg set, src's source (or
743 744 # further back) may be. Scanning back further than the immediate src allows
744 745 # transitive transplants and rebases to yield the same results as transitive
745 746 # grafts.
746 747 for r in subset:
747 748 src = _getrevsource(repo, r)
748 749 lineage = None
749 750
750 751 while src is not None:
751 752 if lineage is None:
752 753 lineage = list()
753 754
754 755 lineage.append(r)
755 756
756 757 # The visited lineage is a match if the current source is in the arg
757 758 # set. Since every candidate dest is visited by way of iterating
758 759 # subset, any dests further back in the lineage will be tested by a
759 760 # different iteration over subset. Likewise, if the src was already
760 761 # selected, the current lineage can be selected without going back
761 762 # further.
762 763 if src in sources or src in dests:
763 764 dests.update(lineage)
764 765 break
765 766
766 767 r = src
767 768 src = _getrevsource(repo, r)
768 769
769 770 return subset.filter(dests.__contains__,
770 771 condrepr=lambda: '<destination %r>' % _sortedb(dests))
771 772
772 773 @predicate('contentdivergent()', safe=True)
773 774 def contentdivergent(repo, subset, x):
774 775 """
775 776 Final successors of changesets with an alternative set of final
776 777 successors. (EXPERIMENTAL)
777 778 """
778 779 # i18n: "contentdivergent" is a keyword
779 780 getargs(x, 0, 0, _("contentdivergent takes no arguments"))
780 781 contentdivergent = obsmod.getrevs(repo, 'contentdivergent')
781 782 return subset & contentdivergent
782 783
783 784 @predicate('extdata(source)', safe=False, weight=100)
784 785 def extdata(repo, subset, x):
785 786 """Changesets in the specified extdata source. (EXPERIMENTAL)"""
786 787 # i18n: "extdata" is a keyword
787 788 args = getargsdict(x, 'extdata', 'source')
788 789 source = getstring(args.get('source'),
789 790 # i18n: "extdata" is a keyword
790 791 _('extdata takes at least 1 string argument'))
791 792 data = scmutil.extdatasource(repo, source)
792 793 return subset & baseset(data)
793 794
794 795 @predicate('extinct()', safe=True)
795 796 def extinct(repo, subset, x):
796 797 """Obsolete changesets with obsolete descendants only.
797 798 """
798 799 # i18n: "extinct" is a keyword
799 800 getargs(x, 0, 0, _("extinct takes no arguments"))
800 801 extincts = obsmod.getrevs(repo, 'extinct')
801 802 return subset & extincts
802 803
803 804 @predicate('extra(label, [value])', safe=True)
804 805 def extra(repo, subset, x):
805 806 """Changesets with the given label in the extra metadata, with the given
806 807 optional value.
807 808
808 809 Pattern matching is supported for `value`. See
809 810 :hg:`help revisions.patterns`.
810 811 """
811 812 args = getargsdict(x, 'extra', 'label value')
812 813 if 'label' not in args:
813 814 # i18n: "extra" is a keyword
814 815 raise error.ParseError(_('extra takes at least 1 argument'))
815 816 # i18n: "extra" is a keyword
816 817 label = getstring(args['label'], _('first argument to extra must be '
817 818 'a string'))
818 819 value = None
819 820
820 821 if 'value' in args:
821 822 # i18n: "extra" is a keyword
822 823 value = getstring(args['value'], _('second argument to extra must be '
823 824 'a string'))
824 825 kind, value, matcher = stringutil.stringmatcher(value)
825 826
826 827 def _matchvalue(r):
827 828 extra = repo[r].extra()
828 829 return label in extra and (value is None or matcher(extra[label]))
829 830
830 831 return subset.filter(lambda r: _matchvalue(r),
831 832 condrepr=('<extra[%r] %r>', label, value))
832 833
833 834 @predicate('filelog(pattern)', safe=True)
834 835 def filelog(repo, subset, x):
835 836 """Changesets connected to the specified filelog.
836 837
837 838 For performance reasons, visits only revisions mentioned in the file-level
838 839 filelog, rather than filtering through all changesets (much faster, but
839 840 doesn't include deletes or duplicate changes). For a slower, more accurate
840 841 result, use ``file()``.
841 842
842 843 The pattern without explicit kind like ``glob:`` is expected to be
843 844 relative to the current directory and match against a file exactly
844 845 for efficiency.
845 846
846 847 If some linkrev points to revisions filtered by the current repoview, we'll
847 848 work around it to return a non-filtered value.
848 849 """
849 850
850 851 # i18n: "filelog" is a keyword
851 852 pat = getstring(x, _("filelog requires a pattern"))
852 853 s = set()
853 854 cl = repo.changelog
854 855
855 856 if not matchmod.patkind(pat):
856 857 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
857 858 files = [f]
858 859 else:
859 860 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
860 861 files = (f for f in repo[None] if m(f))
861 862
862 863 for f in files:
863 864 fl = repo.file(f)
864 865 known = {}
865 866 scanpos = 0
866 867 for fr in list(fl):
867 868 fn = fl.node(fr)
868 869 if fn in known:
869 870 s.add(known[fn])
870 871 continue
871 872
872 873 lr = fl.linkrev(fr)
873 874 if lr in cl:
874 875 s.add(lr)
875 876 elif scanpos is not None:
876 877 # lowest matching changeset is filtered, scan further
877 878 # ahead in changelog
878 879 start = max(lr, scanpos) + 1
879 880 scanpos = None
880 881 for r in cl.revs(start):
881 882 # minimize parsing of non-matching entries
882 883 if f in cl.revision(r) and f in cl.readfiles(r):
883 884 try:
884 885 # try to use manifest delta fastpath
885 886 n = repo[r].filenode(f)
886 887 if n not in known:
887 888 if n == fn:
888 889 s.add(r)
889 890 scanpos = r
890 891 break
891 892 else:
892 893 known[n] = r
893 894 except error.ManifestLookupError:
894 895 # deletion in changelog
895 896 continue
896 897
897 898 return subset & s
898 899
899 900 @predicate('first(set, [n])', safe=True, takeorder=True, weight=0)
900 901 def first(repo, subset, x, order):
901 902 """An alias for limit().
902 903 """
903 904 return limit(repo, subset, x, order)
904 905
905 906 def _follow(repo, subset, x, name, followfirst=False):
906 907 args = getargsdict(x, name, 'file startrev')
907 908 revs = None
908 909 if 'startrev' in args:
909 910 revs = getset(repo, fullreposet(repo), args['startrev'])
910 911 if 'file' in args:
911 912 x = getstring(args['file'], _("%s expected a pattern") % name)
912 913 if revs is None:
913 914 revs = [None]
914 915 fctxs = []
915 916 for r in revs:
916 917 ctx = mctx = repo[r]
917 918 if r is None:
918 919 ctx = repo['.']
919 920 m = matchmod.match(repo.root, repo.getcwd(), [x],
920 921 ctx=mctx, default='path')
921 922 fctxs.extend(ctx[f].introfilectx() for f in ctx.manifest().walk(m))
922 923 s = dagop.filerevancestors(fctxs, followfirst)
923 924 else:
924 925 if revs is None:
925 926 revs = baseset([repo['.'].rev()])
926 927 s = dagop.revancestors(repo, revs, followfirst)
927 928
928 929 return subset & s
929 930
930 931 @predicate('follow([file[, startrev]])', safe=True)
931 932 def follow(repo, subset, x):
932 933 """
933 934 An alias for ``::.`` (ancestors of the working directory's first parent).
934 935 If file pattern is specified, the histories of files matching given
935 936 pattern in the revision given by startrev are followed, including copies.
936 937 """
937 938 return _follow(repo, subset, x, 'follow')
938 939
939 940 @predicate('_followfirst', safe=True)
940 941 def _followfirst(repo, subset, x):
941 942 # ``followfirst([file[, startrev]])``
942 943 # Like ``follow([file[, startrev]])`` but follows only the first parent
943 944 # of every revisions or files revisions.
944 945 return _follow(repo, subset, x, '_followfirst', followfirst=True)
945 946
946 947 @predicate('followlines(file, fromline:toline[, startrev=., descend=False])',
947 948 safe=True)
948 949 def followlines(repo, subset, x):
949 950 """Changesets modifying `file` in line range ('fromline', 'toline').
950 951
951 952 Line range corresponds to 'file' content at 'startrev' and should hence be
952 953 consistent with file size. If startrev is not specified, working directory's
953 954 parent is used.
954 955
955 956 By default, ancestors of 'startrev' are returned. If 'descend' is True,
956 957 descendants of 'startrev' are returned though renames are (currently) not
957 958 followed in this direction.
958 959 """
959 960 args = getargsdict(x, 'followlines', 'file *lines startrev descend')
960 961 if len(args['lines']) != 1:
961 962 raise error.ParseError(_("followlines requires a line range"))
962 963
963 964 rev = '.'
964 965 if 'startrev' in args:
965 966 revs = getset(repo, fullreposet(repo), args['startrev'])
966 967 if len(revs) != 1:
967 968 raise error.ParseError(
968 969 # i18n: "followlines" is a keyword
969 970 _("followlines expects exactly one revision"))
970 971 rev = revs.last()
971 972
972 973 pat = getstring(args['file'], _("followlines requires a pattern"))
973 974 # i18n: "followlines" is a keyword
974 975 msg = _("followlines expects exactly one file")
975 976 fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
976 977 # i18n: "followlines" is a keyword
977 978 lr = getrange(args['lines'][0], _("followlines expects a line range"))
978 979 fromline, toline = [getinteger(a, _("line range bounds must be integers"))
979 980 for a in lr]
980 981 fromline, toline = util.processlinerange(fromline, toline)
981 982
982 983 fctx = repo[rev].filectx(fname)
983 984 descend = False
984 985 if 'descend' in args:
985 986 descend = getboolean(args['descend'],
986 987 # i18n: "descend" is a keyword
987 988 _("descend argument must be a boolean"))
988 989 if descend:
989 990 rs = generatorset(
990 991 (c.rev() for c, _linerange
991 992 in dagop.blockdescendants(fctx, fromline, toline)),
992 993 iterasc=True)
993 994 else:
994 995 rs = generatorset(
995 996 (c.rev() for c, _linerange
996 997 in dagop.blockancestors(fctx, fromline, toline)),
997 998 iterasc=False)
998 999 return subset & rs
999 1000
1000 1001 @predicate('all()', safe=True)
1001 1002 def getall(repo, subset, x):
1002 1003 """All changesets, the same as ``0:tip``.
1003 1004 """
1004 1005 # i18n: "all" is a keyword
1005 1006 getargs(x, 0, 0, _("all takes no arguments"))
1006 1007 return subset & spanset(repo) # drop "null" if any
1007 1008
1008 1009 @predicate('grep(regex)', weight=10)
1009 1010 def grep(repo, subset, x):
1010 1011 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1011 1012 to ensure special escape characters are handled correctly. Unlike
1012 1013 ``keyword(string)``, the match is case-sensitive.
1013 1014 """
1014 1015 try:
1015 1016 # i18n: "grep" is a keyword
1016 1017 gr = re.compile(getstring(x, _("grep requires a string")))
1017 1018 except re.error as e:
1018 1019 raise error.ParseError(
1019 1020 _('invalid match pattern: %s') % stringutil.forcebytestr(e))
1020 1021
1021 1022 def matches(x):
1022 1023 c = repo[x]
1023 1024 for e in c.files() + [c.user(), c.description()]:
1024 1025 if gr.search(e):
1025 1026 return True
1026 1027 return False
1027 1028
1028 1029 return subset.filter(matches, condrepr=('<grep %r>', gr.pattern))
1029 1030
1030 1031 @predicate('_matchfiles', safe=True)
1031 1032 def _matchfiles(repo, subset, x):
1032 1033 # _matchfiles takes a revset list of prefixed arguments:
1033 1034 #
1034 1035 # [p:foo, i:bar, x:baz]
1035 1036 #
1036 1037 # builds a match object from them and filters subset. Allowed
1037 1038 # prefixes are 'p:' for regular patterns, 'i:' for include
1038 1039 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1039 1040 # a revision identifier, or the empty string to reference the
1040 1041 # working directory, from which the match object is
1041 1042 # initialized. Use 'd:' to set the default matching mode, default
1042 1043 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1043 1044
1044 1045 l = getargs(x, 1, -1, "_matchfiles requires at least one argument")
1045 1046 pats, inc, exc = [], [], []
1046 1047 rev, default = None, None
1047 1048 for arg in l:
1048 1049 s = getstring(arg, "_matchfiles requires string arguments")
1049 1050 prefix, value = s[:2], s[2:]
1050 1051 if prefix == 'p:':
1051 1052 pats.append(value)
1052 1053 elif prefix == 'i:':
1053 1054 inc.append(value)
1054 1055 elif prefix == 'x:':
1055 1056 exc.append(value)
1056 1057 elif prefix == 'r:':
1057 1058 if rev is not None:
1058 1059 raise error.ParseError('_matchfiles expected at most one '
1059 1060 'revision')
1060 1061 if value == '': # empty means working directory
1061 1062 rev = node.wdirrev
1062 1063 else:
1063 1064 rev = value
1064 1065 elif prefix == 'd:':
1065 1066 if default is not None:
1066 1067 raise error.ParseError('_matchfiles expected at most one '
1067 1068 'default mode')
1068 1069 default = value
1069 1070 else:
1070 1071 raise error.ParseError('invalid _matchfiles prefix: %s' % prefix)
1071 1072 if not default:
1072 1073 default = 'glob'
1073 1074 hasset = any(matchmod.patkind(p) == 'set' for p in pats + inc + exc)
1074 1075
1075 1076 mcache = [None]
1076 1077
1077 1078 # This directly read the changelog data as creating changectx for all
1078 1079 # revisions is quite expensive.
1079 1080 getfiles = repo.changelog.readfiles
1080 1081 wdirrev = node.wdirrev
1081 1082 def matches(x):
1082 1083 if x == wdirrev:
1083 1084 files = repo[x].files()
1084 1085 else:
1085 1086 files = getfiles(x)
1086 1087
1087 1088 if not mcache[0] or (hasset and rev is None):
1088 1089 r = x if rev is None else rev
1089 1090 mcache[0] = matchmod.match(repo.root, repo.getcwd(), pats,
1090 1091 include=inc, exclude=exc, ctx=repo[r],
1091 1092 default=default)
1092 1093 m = mcache[0]
1093 1094
1094 1095 for f in files:
1095 1096 if m(f):
1096 1097 return True
1097 1098 return False
1098 1099
1099 1100 return subset.filter(matches,
1100 1101 condrepr=('<matchfiles patterns=%r, include=%r '
1101 1102 'exclude=%r, default=%r, rev=%r>',
1102 1103 pats, inc, exc, default, rev))
1103 1104
1104 1105 @predicate('file(pattern)', safe=True, weight=10)
1105 1106 def hasfile(repo, subset, x):
1106 1107 """Changesets affecting files matched by pattern.
1107 1108
1108 1109 For a faster but less accurate result, consider using ``filelog()``
1109 1110 instead.
1110 1111
1111 1112 This predicate uses ``glob:`` as the default kind of pattern.
1112 1113 """
1113 1114 # i18n: "file" is a keyword
1114 1115 pat = getstring(x, _("file requires a pattern"))
1115 1116 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1116 1117
1117 1118 @predicate('head()', safe=True)
1118 1119 def head(repo, subset, x):
1119 1120 """Changeset is a named branch head.
1120 1121 """
1121 1122 # i18n: "head" is a keyword
1122 1123 getargs(x, 0, 0, _("head takes no arguments"))
1123 1124 hs = set()
1124 1125 cl = repo.changelog
1125 1126 for ls in repo.branchmap().itervalues():
1126 1127 hs.update(cl.rev(h) for h in ls)
1127 1128 return subset & baseset(hs)
1128 1129
1129 1130 @predicate('heads(set)', safe=True, takeorder=True)
1130 1131 def heads(repo, subset, x, order):
1131 1132 """Members of set with no children in set.
1132 1133 """
1133 1134 # argument set should never define order
1134 1135 if order == defineorder:
1135 1136 order = followorder
1136 1137 s = getset(repo, subset, x, order=order)
1137 1138 ps = parents(repo, subset, x)
1138 1139 return s - ps
1139 1140
1140 1141 @predicate('hidden()', safe=True)
1141 1142 def hidden(repo, subset, x):
1142 1143 """Hidden changesets.
1143 1144 """
1144 1145 # i18n: "hidden" is a keyword
1145 1146 getargs(x, 0, 0, _("hidden takes no arguments"))
1146 1147 hiddenrevs = repoview.filterrevs(repo, 'visible')
1147 1148 return subset & hiddenrevs
1148 1149
1149 1150 @predicate('keyword(string)', safe=True, weight=10)
1150 1151 def keyword(repo, subset, x):
1151 1152 """Search commit message, user name, and names of changed files for
1152 1153 string. The match is case-insensitive.
1153 1154
1154 1155 For a regular expression or case sensitive search of these fields, use
1155 1156 ``grep(regex)``.
1156 1157 """
1157 1158 # i18n: "keyword" is a keyword
1158 1159 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1159 1160
1160 1161 def matches(r):
1161 1162 c = repo[r]
1162 1163 return any(kw in encoding.lower(t)
1163 1164 for t in c.files() + [c.user(), c.description()])
1164 1165
1165 1166 return subset.filter(matches, condrepr=('<keyword %r>', kw))
1166 1167
1167 1168 @predicate('limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
1168 1169 def limit(repo, subset, x, order):
1169 1170 """First n members of set, defaulting to 1, starting from offset.
1170 1171 """
1171 1172 args = getargsdict(x, 'limit', 'set n offset')
1172 1173 if 'set' not in args:
1173 1174 # i18n: "limit" is a keyword
1174 1175 raise error.ParseError(_("limit requires one to three arguments"))
1175 1176 # i18n: "limit" is a keyword
1176 1177 lim = getinteger(args.get('n'), _("limit expects a number"), default=1)
1177 1178 if lim < 0:
1178 1179 raise error.ParseError(_("negative number to select"))
1179 1180 # i18n: "limit" is a keyword
1180 1181 ofs = getinteger(args.get('offset'), _("limit expects a number"), default=0)
1181 1182 if ofs < 0:
1182 1183 raise error.ParseError(_("negative offset"))
1183 1184 os = getset(repo, fullreposet(repo), args['set'])
1184 1185 ls = os.slice(ofs, ofs + lim)
1185 1186 if order == followorder and lim > 1:
1186 1187 return subset & ls
1187 1188 return ls & subset
1188 1189
1189 1190 @predicate('last(set, [n])', safe=True, takeorder=True)
1190 1191 def last(repo, subset, x, order):
1191 1192 """Last n members of set, defaulting to 1.
1192 1193 """
1193 1194 # i18n: "last" is a keyword
1194 1195 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1195 1196 lim = 1
1196 1197 if len(l) == 2:
1197 1198 # i18n: "last" is a keyword
1198 1199 lim = getinteger(l[1], _("last expects a number"))
1199 1200 if lim < 0:
1200 1201 raise error.ParseError(_("negative number to select"))
1201 1202 os = getset(repo, fullreposet(repo), l[0])
1202 1203 os.reverse()
1203 1204 ls = os.slice(0, lim)
1204 1205 if order == followorder and lim > 1:
1205 1206 return subset & ls
1206 1207 ls.reverse()
1207 1208 return ls & subset
1208 1209
1209 1210 @predicate('max(set)', safe=True)
1210 1211 def maxrev(repo, subset, x):
1211 1212 """Changeset with highest revision number in set.
1212 1213 """
1213 1214 os = getset(repo, fullreposet(repo), x)
1214 1215 try:
1215 1216 m = os.max()
1216 1217 if m in subset:
1217 1218 return baseset([m], datarepr=('<max %r, %r>', subset, os))
1218 1219 except ValueError:
1219 1220 # os.max() throws a ValueError when the collection is empty.
1220 1221 # Same as python's max().
1221 1222 pass
1222 1223 return baseset(datarepr=('<max %r, %r>', subset, os))
1223 1224
1224 1225 @predicate('merge()', safe=True)
1225 1226 def merge(repo, subset, x):
1226 1227 """Changeset is a merge changeset.
1227 1228 """
1228 1229 # i18n: "merge" is a keyword
1229 1230 getargs(x, 0, 0, _("merge takes no arguments"))
1230 1231 cl = repo.changelog
1231 1232 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1,
1232 1233 condrepr='<merge>')
1233 1234
1234 1235 @predicate('branchpoint()', safe=True)
1235 1236 def branchpoint(repo, subset, x):
1236 1237 """Changesets with more than one child.
1237 1238 """
1238 1239 # i18n: "branchpoint" is a keyword
1239 1240 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1240 1241 cl = repo.changelog
1241 1242 if not subset:
1242 1243 return baseset()
1243 1244 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1244 1245 # (and if it is not, it should.)
1245 1246 baserev = min(subset)
1246 1247 parentscount = [0]*(len(repo) - baserev)
1247 1248 for r in cl.revs(start=baserev + 1):
1248 1249 for p in cl.parentrevs(r):
1249 1250 if p >= baserev:
1250 1251 parentscount[p - baserev] += 1
1251 1252 return subset.filter(lambda r: parentscount[r - baserev] > 1,
1252 1253 condrepr='<branchpoint>')
1253 1254
1254 1255 @predicate('min(set)', safe=True)
1255 1256 def minrev(repo, subset, x):
1256 1257 """Changeset with lowest revision number in set.
1257 1258 """
1258 1259 os = getset(repo, fullreposet(repo), x)
1259 1260 try:
1260 1261 m = os.min()
1261 1262 if m in subset:
1262 1263 return baseset([m], datarepr=('<min %r, %r>', subset, os))
1263 1264 except ValueError:
1264 1265 # os.min() throws a ValueError when the collection is empty.
1265 1266 # Same as python's min().
1266 1267 pass
1267 1268 return baseset(datarepr=('<min %r, %r>', subset, os))
1268 1269
1269 1270 @predicate('modifies(pattern)', safe=True, weight=30)
1270 1271 def modifies(repo, subset, x):
1271 1272 """Changesets modifying files matched by pattern.
1272 1273
1273 1274 The pattern without explicit kind like ``glob:`` is expected to be
1274 1275 relative to the current directory and match against a file or a
1275 1276 directory.
1276 1277 """
1277 1278 # i18n: "modifies" is a keyword
1278 1279 pat = getstring(x, _("modifies requires a pattern"))
1279 1280 return checkstatus(repo, subset, pat, 0)
1280 1281
1281 1282 @predicate('named(namespace)')
1282 1283 def named(repo, subset, x):
1283 1284 """The changesets in a given namespace.
1284 1285
1285 1286 Pattern matching is supported for `namespace`. See
1286 1287 :hg:`help revisions.patterns`.
1287 1288 """
1288 1289 # i18n: "named" is a keyword
1289 1290 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1290 1291
1291 1292 ns = getstring(args[0],
1292 1293 # i18n: "named" is a keyword
1293 1294 _('the argument to named must be a string'))
1294 1295 kind, pattern, matcher = stringutil.stringmatcher(ns)
1295 1296 namespaces = set()
1296 1297 if kind == 'literal':
1297 1298 if pattern not in repo.names:
1298 1299 raise error.RepoLookupError(_("namespace '%s' does not exist")
1299 1300 % ns)
1300 1301 namespaces.add(repo.names[pattern])
1301 1302 else:
1302 1303 for name, ns in repo.names.iteritems():
1303 1304 if matcher(name):
1304 1305 namespaces.add(ns)
1305 1306 if not namespaces:
1306 1307 raise error.RepoLookupError(_("no namespace exists"
1307 1308 " that match '%s'") % pattern)
1308 1309
1309 1310 names = set()
1310 1311 for ns in namespaces:
1311 1312 for name in ns.listnames(repo):
1312 1313 if name not in ns.deprecated:
1313 1314 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1314 1315
1315 1316 names -= {node.nullrev}
1316 1317 return subset & names
1317 1318
1318 1319 @predicate('id(string)', safe=True)
1319 1320 def node_(repo, subset, x):
1320 1321 """Revision non-ambiguously specified by the given hex string prefix.
1321 1322 """
1322 1323 # i18n: "id" is a keyword
1323 1324 l = getargs(x, 1, 1, _("id requires one argument"))
1324 1325 # i18n: "id" is a keyword
1325 1326 n = getstring(l[0], _("id requires a string"))
1326 1327 if len(n) == 40:
1327 1328 try:
1328 1329 rn = repo.changelog.rev(node.bin(n))
1329 1330 except error.WdirUnsupported:
1330 1331 rn = node.wdirrev
1331 1332 except (LookupError, TypeError):
1332 1333 rn = None
1333 1334 else:
1334 1335 rn = None
1335 1336 try:
1336 1337 pm = scmutil.resolvehexnodeidprefix(repo, n)
1337 1338 if pm is not None:
1338 1339 rn = repo.changelog.rev(pm)
1339 1340 except LookupError:
1340 1341 pass
1341 1342 except error.WdirUnsupported:
1342 1343 rn = node.wdirrev
1343 1344
1344 1345 if rn is None:
1345 1346 return baseset()
1346 1347 result = baseset([rn])
1347 1348 return result & subset
1348 1349
1349 1350 @predicate('none()', safe=True)
1350 1351 def none(repo, subset, x):
1351 1352 """No changesets.
1352 1353 """
1353 1354 # i18n: "none" is a keyword
1354 1355 getargs(x, 0, 0, _("none takes no arguments"))
1355 1356 return baseset()
1356 1357
1357 1358 @predicate('obsolete()', safe=True)
1358 1359 def obsolete(repo, subset, x):
1359 1360 """Mutable changeset with a newer version."""
1360 1361 # i18n: "obsolete" is a keyword
1361 1362 getargs(x, 0, 0, _("obsolete takes no arguments"))
1362 1363 obsoletes = obsmod.getrevs(repo, 'obsolete')
1363 1364 return subset & obsoletes
1364 1365
1365 1366 @predicate('only(set, [set])', safe=True)
1366 1367 def only(repo, subset, x):
1367 1368 """Changesets that are ancestors of the first set that are not ancestors
1368 1369 of any other head in the repo. If a second set is specified, the result
1369 1370 is ancestors of the first set that are not ancestors of the second set
1370 1371 (i.e. ::<set1> - ::<set2>).
1371 1372 """
1372 1373 cl = repo.changelog
1373 1374 # i18n: "only" is a keyword
1374 1375 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1375 1376 include = getset(repo, fullreposet(repo), args[0])
1376 1377 if len(args) == 1:
1377 1378 if not include:
1378 1379 return baseset()
1379 1380
1380 1381 descendants = set(dagop.revdescendants(repo, include, False))
1381 1382 exclude = [rev for rev in cl.headrevs()
1382 1383 if not rev in descendants and not rev in include]
1383 1384 else:
1384 1385 exclude = getset(repo, fullreposet(repo), args[1])
1385 1386
1386 1387 results = set(cl.findmissingrevs(common=exclude, heads=include))
1387 1388 # XXX we should turn this into a baseset instead of a set, smartset may do
1388 1389 # some optimizations from the fact this is a baseset.
1389 1390 return subset & results
1390 1391
1391 1392 @predicate('origin([set])', safe=True)
1392 1393 def origin(repo, subset, x):
1393 1394 """
1394 1395 Changesets that were specified as a source for the grafts, transplants or
1395 1396 rebases that created the given revisions. Omitting the optional set is the
1396 1397 same as passing all(). If a changeset created by these operations is itself
1397 1398 specified as a source for one of these operations, only the source changeset
1398 1399 for the first operation is selected.
1399 1400 """
1400 1401 if x is not None:
1401 1402 dests = getset(repo, fullreposet(repo), x)
1402 1403 else:
1403 1404 dests = fullreposet(repo)
1404 1405
1405 1406 def _firstsrc(rev):
1406 1407 src = _getrevsource(repo, rev)
1407 1408 if src is None:
1408 1409 return None
1409 1410
1410 1411 while True:
1411 1412 prev = _getrevsource(repo, src)
1412 1413
1413 1414 if prev is None:
1414 1415 return src
1415 1416 src = prev
1416 1417
1417 1418 o = {_firstsrc(r) for r in dests}
1418 1419 o -= {None}
1419 1420 # XXX we should turn this into a baseset instead of a set, smartset may do
1420 1421 # some optimizations from the fact this is a baseset.
1421 1422 return subset & o
1422 1423
1423 1424 @predicate('outgoing([path])', safe=False, weight=10)
1424 1425 def outgoing(repo, subset, x):
1425 1426 """Changesets not found in the specified destination repository, or the
1426 1427 default push location.
1427 1428 """
1428 1429 # Avoid cycles.
1429 1430 from . import (
1430 1431 discovery,
1431 1432 hg,
1432 1433 )
1433 1434 # i18n: "outgoing" is a keyword
1434 1435 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1435 1436 # i18n: "outgoing" is a keyword
1436 1437 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1437 1438 if not dest:
1438 1439 # ui.paths.getpath() explicitly tests for None, not just a boolean
1439 1440 dest = None
1440 1441 path = repo.ui.paths.getpath(dest, default=('default-push', 'default'))
1441 1442 if not path:
1442 1443 raise error.Abort(_('default repository not configured!'),
1443 1444 hint=_("see 'hg help config.paths'"))
1444 1445 dest = path.pushloc or path.loc
1445 1446 branches = path.branch, []
1446 1447
1447 1448 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1448 1449 if revs:
1449 1450 revs = [repo.lookup(rev) for rev in revs]
1450 1451 other = hg.peer(repo, {}, dest)
1451 1452 repo.ui.pushbuffer()
1452 1453 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1453 1454 repo.ui.popbuffer()
1454 1455 cl = repo.changelog
1455 1456 o = {cl.rev(r) for r in outgoing.missing}
1456 1457 return subset & o
1457 1458
1458 1459 @predicate('p1([set])', safe=True)
1459 1460 def p1(repo, subset, x):
1460 1461 """First parent of changesets in set, or the working directory.
1461 1462 """
1462 1463 if x is None:
1463 1464 p = repo[x].p1().rev()
1464 1465 if p >= 0:
1465 1466 return subset & baseset([p])
1466 1467 return baseset()
1467 1468
1468 1469 ps = set()
1469 1470 cl = repo.changelog
1470 1471 for r in getset(repo, fullreposet(repo), x):
1471 1472 try:
1472 1473 ps.add(cl.parentrevs(r)[0])
1473 1474 except error.WdirUnsupported:
1474 1475 ps.add(repo[r].parents()[0].rev())
1475 1476 ps -= {node.nullrev}
1476 1477 # XXX we should turn this into a baseset instead of a set, smartset may do
1477 1478 # some optimizations from the fact this is a baseset.
1478 1479 return subset & ps
1479 1480
1480 1481 @predicate('p2([set])', safe=True)
1481 1482 def p2(repo, subset, x):
1482 1483 """Second parent of changesets in set, or the working directory.
1483 1484 """
1484 1485 if x is None:
1485 1486 ps = repo[x].parents()
1486 1487 try:
1487 1488 p = ps[1].rev()
1488 1489 if p >= 0:
1489 1490 return subset & baseset([p])
1490 1491 return baseset()
1491 1492 except IndexError:
1492 1493 return baseset()
1493 1494
1494 1495 ps = set()
1495 1496 cl = repo.changelog
1496 1497 for r in getset(repo, fullreposet(repo), x):
1497 1498 try:
1498 1499 ps.add(cl.parentrevs(r)[1])
1499 1500 except error.WdirUnsupported:
1500 1501 parents = repo[r].parents()
1501 1502 if len(parents) == 2:
1502 1503 ps.add(parents[1])
1503 1504 ps -= {node.nullrev}
1504 1505 # XXX we should turn this into a baseset instead of a set, smartset may do
1505 1506 # some optimizations from the fact this is a baseset.
1506 1507 return subset & ps
1507 1508
1508 1509 def parentpost(repo, subset, x, order):
1509 1510 return p1(repo, subset, x)
1510 1511
1511 1512 @predicate('parents([set])', safe=True)
1512 1513 def parents(repo, subset, x):
1513 1514 """
1514 1515 The set of all parents for all changesets in set, or the working directory.
1515 1516 """
1516 1517 if x is None:
1517 1518 ps = set(p.rev() for p in repo[x].parents())
1518 1519 else:
1519 1520 ps = set()
1520 1521 cl = repo.changelog
1521 1522 up = ps.update
1522 1523 parentrevs = cl.parentrevs
1523 1524 for r in getset(repo, fullreposet(repo), x):
1524 1525 try:
1525 1526 up(parentrevs(r))
1526 1527 except error.WdirUnsupported:
1527 1528 up(p.rev() for p in repo[r].parents())
1528 1529 ps -= {node.nullrev}
1529 1530 return subset & ps
1530 1531
1531 1532 def _phase(repo, subset, *targets):
1532 1533 """helper to select all rev in <targets> phases"""
1533 1534 return repo._phasecache.getrevset(repo, targets, subset)
1534 1535
1535 1536 @predicate('draft()', safe=True)
1536 1537 def draft(repo, subset, x):
1537 1538 """Changeset in draft phase."""
1538 1539 # i18n: "draft" is a keyword
1539 1540 getargs(x, 0, 0, _("draft takes no arguments"))
1540 1541 target = phases.draft
1541 1542 return _phase(repo, subset, target)
1542 1543
1543 1544 @predicate('secret()', safe=True)
1544 1545 def secret(repo, subset, x):
1545 1546 """Changeset in secret phase."""
1546 1547 # i18n: "secret" is a keyword
1547 1548 getargs(x, 0, 0, _("secret takes no arguments"))
1548 1549 target = phases.secret
1549 1550 return _phase(repo, subset, target)
1550 1551
1551 1552 @predicate('stack([revs])', safe=True)
1552 1553 def stack(repo, subset, x):
1553 1554 """Experimental revset for the stack of changesets or working directory
1554 1555 parent. (EXPERIMENTAL)
1555 1556 """
1556 1557 if x is None:
1557 1558 stacks = stackmod.getstack(repo, x)
1558 1559 else:
1559 1560 stacks = smartset.baseset([])
1560 1561 for revision in getset(repo, fullreposet(repo), x):
1561 1562 currentstack = stackmod.getstack(repo, revision)
1562 1563 stacks = stacks + currentstack
1563 1564
1564 1565 return subset & stacks
1565 1566
1566 1567 def parentspec(repo, subset, x, n, order):
1567 1568 """``set^0``
1568 1569 The set.
1569 1570 ``set^1`` (or ``set^``), ``set^2``
1570 1571 First or second parent, respectively, of all changesets in set.
1571 1572 """
1572 1573 try:
1573 1574 n = int(n[1])
1574 1575 if n not in (0, 1, 2):
1575 1576 raise ValueError
1576 1577 except (TypeError, ValueError):
1577 1578 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1578 1579 ps = set()
1579 1580 cl = repo.changelog
1580 1581 for r in getset(repo, fullreposet(repo), x):
1581 1582 if n == 0:
1582 1583 ps.add(r)
1583 1584 elif n == 1:
1584 1585 try:
1585 1586 ps.add(cl.parentrevs(r)[0])
1586 1587 except error.WdirUnsupported:
1587 1588 ps.add(repo[r].parents()[0].rev())
1588 1589 else:
1589 1590 try:
1590 1591 parents = cl.parentrevs(r)
1591 1592 if parents[1] != node.nullrev:
1592 1593 ps.add(parents[1])
1593 1594 except error.WdirUnsupported:
1594 1595 parents = repo[r].parents()
1595 1596 if len(parents) == 2:
1596 1597 ps.add(parents[1].rev())
1597 1598 return subset & ps
1598 1599
1599 1600 @predicate('present(set)', safe=True, takeorder=True)
1600 1601 def present(repo, subset, x, order):
1601 1602 """An empty set, if any revision in set isn't found; otherwise,
1602 1603 all revisions in set.
1603 1604
1604 1605 If any of specified revisions is not present in the local repository,
1605 1606 the query is normally aborted. But this predicate allows the query
1606 1607 to continue even in such cases.
1607 1608 """
1608 1609 try:
1609 1610 return getset(repo, subset, x, order)
1610 1611 except error.RepoLookupError:
1611 1612 return baseset()
1612 1613
1613 1614 # for internal use
1614 1615 @predicate('_notpublic', safe=True)
1615 1616 def _notpublic(repo, subset, x):
1616 1617 getargs(x, 0, 0, "_notpublic takes no arguments")
1617 1618 return _phase(repo, subset, phases.draft, phases.secret)
1618 1619
1619 1620 # for internal use
1620 1621 @predicate('_phaseandancestors(phasename, set)', safe=True)
1621 1622 def _phaseandancestors(repo, subset, x):
1622 1623 # equivalent to (phasename() & ancestors(set)) but more efficient
1623 1624 # phasename could be one of 'draft', 'secret', or '_notpublic'
1624 1625 args = getargs(x, 2, 2, "_phaseandancestors requires two arguments")
1625 1626 phasename = getsymbol(args[0])
1626 1627 s = getset(repo, fullreposet(repo), args[1])
1627 1628
1628 1629 draft = phases.draft
1629 1630 secret = phases.secret
1630 1631 phasenamemap = {
1631 1632 '_notpublic': draft,
1632 1633 'draft': draft, # follow secret's ancestors
1633 1634 'secret': secret,
1634 1635 }
1635 1636 if phasename not in phasenamemap:
1636 1637 raise error.ParseError('%r is not a valid phasename' % phasename)
1637 1638
1638 1639 minimalphase = phasenamemap[phasename]
1639 1640 getphase = repo._phasecache.phase
1640 1641
1641 1642 def cutfunc(rev):
1642 1643 return getphase(repo, rev) < minimalphase
1643 1644
1644 1645 revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
1645 1646
1646 1647 if phasename == 'draft': # need to remove secret changesets
1647 1648 revs = revs.filter(lambda r: getphase(repo, r) == draft)
1648 1649 return subset & revs
1649 1650
1650 1651 @predicate('public()', safe=True)
1651 1652 def public(repo, subset, x):
1652 1653 """Changeset in public phase."""
1653 1654 # i18n: "public" is a keyword
1654 1655 getargs(x, 0, 0, _("public takes no arguments"))
1655 1656 return _phase(repo, subset, phases.public)
1656 1657
1657 1658 @predicate('remote([id [,path]])', safe=False)
1658 1659 def remote(repo, subset, x):
1659 1660 """Local revision that corresponds to the given identifier in a
1660 1661 remote repository, if present. Here, the '.' identifier is a
1661 1662 synonym for the current local branch.
1662 1663 """
1663 1664
1664 1665 from . import hg # avoid start-up nasties
1665 1666 # i18n: "remote" is a keyword
1666 1667 l = getargs(x, 0, 2, _("remote takes zero, one, or two arguments"))
1667 1668
1668 1669 q = '.'
1669 1670 if len(l) > 0:
1670 1671 # i18n: "remote" is a keyword
1671 1672 q = getstring(l[0], _("remote requires a string id"))
1672 1673 if q == '.':
1673 1674 q = repo['.'].branch()
1674 1675
1675 1676 dest = ''
1676 1677 if len(l) > 1:
1677 1678 # i18n: "remote" is a keyword
1678 1679 dest = getstring(l[1], _("remote requires a repository path"))
1679 1680 dest = repo.ui.expandpath(dest or 'default')
1680 1681 dest, branches = hg.parseurl(dest)
1681 1682 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1682 1683 if revs:
1683 1684 revs = [repo.lookup(rev) for rev in revs]
1684 1685 other = hg.peer(repo, {}, dest)
1685 1686 n = other.lookup(q)
1686 1687 if n in repo:
1687 1688 r = repo[n].rev()
1688 1689 if r in subset:
1689 1690 return baseset([r])
1690 1691 return baseset()
1691 1692
1692 1693 @predicate('removes(pattern)', safe=True, weight=30)
1693 1694 def removes(repo, subset, x):
1694 1695 """Changesets which remove files matching pattern.
1695 1696
1696 1697 The pattern without explicit kind like ``glob:`` is expected to be
1697 1698 relative to the current directory and match against a file or a
1698 1699 directory.
1699 1700 """
1700 1701 # i18n: "removes" is a keyword
1701 1702 pat = getstring(x, _("removes requires a pattern"))
1702 1703 return checkstatus(repo, subset, pat, 2)
1703 1704
1704 1705 @predicate('rev(number)', safe=True)
1705 1706 def rev(repo, subset, x):
1706 1707 """Revision with the given numeric identifier.
1707 1708 """
1708 1709 # i18n: "rev" is a keyword
1709 1710 l = getargs(x, 1, 1, _("rev requires one argument"))
1710 1711 try:
1711 1712 # i18n: "rev" is a keyword
1712 1713 l = int(getstring(l[0], _("rev requires a number")))
1713 1714 except (TypeError, ValueError):
1714 1715 # i18n: "rev" is a keyword
1715 1716 raise error.ParseError(_("rev expects a number"))
1716 1717 if l not in repo.changelog and l not in (node.nullrev, node.wdirrev):
1717 1718 return baseset()
1718 1719 return subset & baseset([l])
1719 1720
1720 1721 @predicate('matching(revision [, field])', safe=True)
1721 1722 def matching(repo, subset, x):
1722 1723 """Changesets in which a given set of fields match the set of fields in the
1723 1724 selected revision or set.
1724 1725
1725 1726 To match more than one field pass the list of fields to match separated
1726 1727 by spaces (e.g. ``author description``).
1727 1728
1728 1729 Valid fields are most regular revision fields and some special fields.
1729 1730
1730 1731 Regular revision fields are ``description``, ``author``, ``branch``,
1731 1732 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1732 1733 and ``diff``.
1733 1734 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1734 1735 contents of the revision. Two revisions matching their ``diff`` will
1735 1736 also match their ``files``.
1736 1737
1737 1738 Special fields are ``summary`` and ``metadata``:
1738 1739 ``summary`` matches the first line of the description.
1739 1740 ``metadata`` is equivalent to matching ``description user date``
1740 1741 (i.e. it matches the main metadata fields).
1741 1742
1742 1743 ``metadata`` is the default field which is used when no fields are
1743 1744 specified. You can match more than one field at a time.
1744 1745 """
1745 1746 # i18n: "matching" is a keyword
1746 1747 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1747 1748
1748 1749 revs = getset(repo, fullreposet(repo), l[0])
1749 1750
1750 1751 fieldlist = ['metadata']
1751 1752 if len(l) > 1:
1752 1753 fieldlist = getstring(l[1],
1753 1754 # i18n: "matching" is a keyword
1754 1755 _("matching requires a string "
1755 1756 "as its second argument")).split()
1756 1757
1757 1758 # Make sure that there are no repeated fields,
1758 1759 # expand the 'special' 'metadata' field type
1759 1760 # and check the 'files' whenever we check the 'diff'
1760 1761 fields = []
1761 1762 for field in fieldlist:
1762 1763 if field == 'metadata':
1763 1764 fields += ['user', 'description', 'date']
1764 1765 elif field == 'diff':
1765 1766 # a revision matching the diff must also match the files
1766 1767 # since matching the diff is very costly, make sure to
1767 1768 # also match the files first
1768 1769 fields += ['files', 'diff']
1769 1770 else:
1770 1771 if field == 'author':
1771 1772 field = 'user'
1772 1773 fields.append(field)
1773 1774 fields = set(fields)
1774 1775 if 'summary' in fields and 'description' in fields:
1775 1776 # If a revision matches its description it also matches its summary
1776 1777 fields.discard('summary')
1777 1778
1778 1779 # We may want to match more than one field
1779 1780 # Not all fields take the same amount of time to be matched
1780 1781 # Sort the selected fields in order of increasing matching cost
1781 1782 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1782 1783 'files', 'description', 'substate', 'diff']
1783 1784 def fieldkeyfunc(f):
1784 1785 try:
1785 1786 return fieldorder.index(f)
1786 1787 except ValueError:
1787 1788 # assume an unknown field is very costly
1788 1789 return len(fieldorder)
1789 1790 fields = list(fields)
1790 1791 fields.sort(key=fieldkeyfunc)
1791 1792
1792 1793 # Each field will be matched with its own "getfield" function
1793 1794 # which will be added to the getfieldfuncs array of functions
1794 1795 getfieldfuncs = []
1795 1796 _funcs = {
1796 1797 'user': lambda r: repo[r].user(),
1797 1798 'branch': lambda r: repo[r].branch(),
1798 1799 'date': lambda r: repo[r].date(),
1799 1800 'description': lambda r: repo[r].description(),
1800 1801 'files': lambda r: repo[r].files(),
1801 1802 'parents': lambda r: repo[r].parents(),
1802 1803 'phase': lambda r: repo[r].phase(),
1803 1804 'substate': lambda r: repo[r].substate,
1804 1805 'summary': lambda r: repo[r].description().splitlines()[0],
1805 1806 'diff': lambda r: list(repo[r].diff(opts={'git': True}),)
1806 1807 }
1807 1808 for info in fields:
1808 1809 getfield = _funcs.get(info, None)
1809 1810 if getfield is None:
1810 1811 raise error.ParseError(
1811 1812 # i18n: "matching" is a keyword
1812 1813 _("unexpected field name passed to matching: %s") % info)
1813 1814 getfieldfuncs.append(getfield)
1814 1815 # convert the getfield array of functions into a "getinfo" function
1815 1816 # which returns an array of field values (or a single value if there
1816 1817 # is only one field to match)
1817 1818 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1818 1819
1819 1820 def matches(x):
1820 1821 for rev in revs:
1821 1822 target = getinfo(rev)
1822 1823 match = True
1823 1824 for n, f in enumerate(getfieldfuncs):
1824 1825 if target[n] != f(x):
1825 1826 match = False
1826 1827 if match:
1827 1828 return True
1828 1829 return False
1829 1830
1830 1831 return subset.filter(matches, condrepr=('<matching%r %r>', fields, revs))
1831 1832
1832 1833 @predicate('reverse(set)', safe=True, takeorder=True, weight=0)
1833 1834 def reverse(repo, subset, x, order):
1834 1835 """Reverse order of set.
1835 1836 """
1836 1837 l = getset(repo, subset, x, order)
1837 1838 if order == defineorder:
1838 1839 l.reverse()
1839 1840 return l
1840 1841
1841 1842 @predicate('roots(set)', safe=True)
1842 1843 def roots(repo, subset, x):
1843 1844 """Changesets in set with no parent changeset in set.
1844 1845 """
1845 1846 s = getset(repo, fullreposet(repo), x)
1846 1847 parents = repo.changelog.parentrevs
1847 1848 def filter(r):
1848 1849 for p in parents(r):
1849 1850 if 0 <= p and p in s:
1850 1851 return False
1851 1852 return True
1852 1853 return subset & s.filter(filter, condrepr='<roots>')
1853 1854
1854 1855 _sortkeyfuncs = {
1855 1856 'rev': lambda c: c.rev(),
1856 1857 'branch': lambda c: c.branch(),
1857 1858 'desc': lambda c: c.description(),
1858 1859 'user': lambda c: c.user(),
1859 1860 'author': lambda c: c.user(),
1860 1861 'date': lambda c: c.date()[0],
1861 1862 }
1862 1863
1863 1864 def _getsortargs(x):
1864 1865 """Parse sort options into (set, [(key, reverse)], opts)"""
1865 1866 args = getargsdict(x, 'sort', 'set keys topo.firstbranch')
1866 1867 if 'set' not in args:
1867 1868 # i18n: "sort" is a keyword
1868 1869 raise error.ParseError(_('sort requires one or two arguments'))
1869 1870 keys = "rev"
1870 1871 if 'keys' in args:
1871 1872 # i18n: "sort" is a keyword
1872 1873 keys = getstring(args['keys'], _("sort spec must be a string"))
1873 1874
1874 1875 keyflags = []
1875 1876 for k in keys.split():
1876 1877 fk = k
1877 1878 reverse = (k.startswith('-'))
1878 1879 if reverse:
1879 1880 k = k[1:]
1880 1881 if k not in _sortkeyfuncs and k != 'topo':
1881 1882 raise error.ParseError(
1882 1883 _("unknown sort key %r") % pycompat.bytestr(fk))
1883 1884 keyflags.append((k, reverse))
1884 1885
1885 1886 if len(keyflags) > 1 and any(k == 'topo' for k, reverse in keyflags):
1886 1887 # i18n: "topo" is a keyword
1887 1888 raise error.ParseError(_('topo sort order cannot be combined '
1888 1889 'with other sort keys'))
1889 1890
1890 1891 opts = {}
1891 1892 if 'topo.firstbranch' in args:
1892 1893 if any(k == 'topo' for k, reverse in keyflags):
1893 1894 opts['topo.firstbranch'] = args['topo.firstbranch']
1894 1895 else:
1895 1896 # i18n: "topo" and "topo.firstbranch" are keywords
1896 1897 raise error.ParseError(_('topo.firstbranch can only be used '
1897 1898 'when using the topo sort key'))
1898 1899
1899 1900 return args['set'], keyflags, opts
1900 1901
1901 1902 @predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True,
1902 1903 weight=10)
1903 1904 def sort(repo, subset, x, order):
1904 1905 """Sort set by keys. The default sort order is ascending, specify a key
1905 1906 as ``-key`` to sort in descending order.
1906 1907
1907 1908 The keys can be:
1908 1909
1909 1910 - ``rev`` for the revision number,
1910 1911 - ``branch`` for the branch name,
1911 1912 - ``desc`` for the commit message (description),
1912 1913 - ``user`` for user name (``author`` can be used as an alias),
1913 1914 - ``date`` for the commit date
1914 1915 - ``topo`` for a reverse topographical sort
1915 1916
1916 1917 The ``topo`` sort order cannot be combined with other sort keys. This sort
1917 1918 takes one optional argument, ``topo.firstbranch``, which takes a revset that
1918 1919 specifies what topographical branches to prioritize in the sort.
1919 1920
1920 1921 """
1921 1922 s, keyflags, opts = _getsortargs(x)
1922 1923 revs = getset(repo, subset, s, order)
1923 1924
1924 1925 if not keyflags or order != defineorder:
1925 1926 return revs
1926 1927 if len(keyflags) == 1 and keyflags[0][0] == "rev":
1927 1928 revs.sort(reverse=keyflags[0][1])
1928 1929 return revs
1929 1930 elif keyflags[0][0] == "topo":
1930 1931 firstbranch = ()
1931 1932 if 'topo.firstbranch' in opts:
1932 1933 firstbranch = getset(repo, subset, opts['topo.firstbranch'])
1933 1934 revs = baseset(dagop.toposort(revs, repo.changelog.parentrevs,
1934 1935 firstbranch),
1935 1936 istopo=True)
1936 1937 if keyflags[0][1]:
1937 1938 revs.reverse()
1938 1939 return revs
1939 1940
1940 1941 # sort() is guaranteed to be stable
1941 1942 ctxs = [repo[r] for r in revs]
1942 1943 for k, reverse in reversed(keyflags):
1943 1944 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
1944 1945 return baseset([c.rev() for c in ctxs])
1945 1946
1946 1947 @predicate('subrepo([pattern])')
1947 1948 def subrepo(repo, subset, x):
1948 1949 """Changesets that add, modify or remove the given subrepo. If no subrepo
1949 1950 pattern is named, any subrepo changes are returned.
1950 1951 """
1951 1952 # i18n: "subrepo" is a keyword
1952 1953 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
1953 1954 pat = None
1954 1955 if len(args) != 0:
1955 1956 pat = getstring(args[0], _("subrepo requires a pattern"))
1956 1957
1957 1958 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
1958 1959
1959 1960 def submatches(names):
1960 1961 k, p, m = stringutil.stringmatcher(pat)
1961 1962 for name in names:
1962 1963 if m(name):
1963 1964 yield name
1964 1965
1965 1966 def matches(x):
1966 1967 c = repo[x]
1967 1968 s = repo.status(c.p1().node(), c.node(), match=m)
1968 1969
1969 1970 if pat is None:
1970 1971 return s.added or s.modified or s.removed
1971 1972
1972 1973 if s.added:
1973 1974 return any(submatches(c.substate.keys()))
1974 1975
1975 1976 if s.modified:
1976 1977 subs = set(c.p1().substate.keys())
1977 1978 subs.update(c.substate.keys())
1978 1979
1979 1980 for path in submatches(subs):
1980 1981 if c.p1().substate.get(path) != c.substate.get(path):
1981 1982 return True
1982 1983
1983 1984 if s.removed:
1984 1985 return any(submatches(c.p1().substate.keys()))
1985 1986
1986 1987 return False
1987 1988
1988 1989 return subset.filter(matches, condrepr=('<subrepo %r>', pat))
1989 1990
1990 1991 def _mapbynodefunc(repo, s, f):
1991 1992 """(repo, smartset, [node] -> [node]) -> smartset
1992 1993
1993 1994 Helper method to map a smartset to another smartset given a function only
1994 1995 talking about nodes. Handles converting between rev numbers and nodes, and
1995 1996 filtering.
1996 1997 """
1997 1998 cl = repo.unfiltered().changelog
1998 1999 torev = cl.rev
1999 2000 tonode = cl.node
2000 2001 nodemap = cl.nodemap
2001 2002 result = set(torev(n) for n in f(tonode(r) for r in s) if n in nodemap)
2002 2003 return smartset.baseset(result - repo.changelog.filteredrevs)
2003 2004
2004 2005 @predicate('successors(set)', safe=True)
2005 2006 def successors(repo, subset, x):
2006 2007 """All successors for set, including the given set themselves"""
2007 2008 s = getset(repo, fullreposet(repo), x)
2008 2009 f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
2009 2010 d = _mapbynodefunc(repo, s, f)
2010 2011 return subset & d
2011 2012
2012 2013 def _substringmatcher(pattern, casesensitive=True):
2013 2014 kind, pattern, matcher = stringutil.stringmatcher(
2014 2015 pattern, casesensitive=casesensitive)
2015 2016 if kind == 'literal':
2016 2017 if not casesensitive:
2017 2018 pattern = encoding.lower(pattern)
2018 2019 matcher = lambda s: pattern in encoding.lower(s)
2019 2020 else:
2020 2021 matcher = lambda s: pattern in s
2021 2022 return kind, pattern, matcher
2022 2023
2023 2024 @predicate('tag([name])', safe=True)
2024 2025 def tag(repo, subset, x):
2025 2026 """The specified tag by name, or all tagged revisions if no name is given.
2026 2027
2027 2028 Pattern matching is supported for `name`. See
2028 2029 :hg:`help revisions.patterns`.
2029 2030 """
2030 2031 # i18n: "tag" is a keyword
2031 2032 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
2032 2033 cl = repo.changelog
2033 2034 if args:
2034 2035 pattern = getstring(args[0],
2035 2036 # i18n: "tag" is a keyword
2036 2037 _('the argument to tag must be a string'))
2037 2038 kind, pattern, matcher = stringutil.stringmatcher(pattern)
2038 2039 if kind == 'literal':
2039 2040 # avoid resolving all tags
2040 2041 tn = repo._tagscache.tags.get(pattern, None)
2041 2042 if tn is None:
2042 2043 raise error.RepoLookupError(_("tag '%s' does not exist")
2043 2044 % pattern)
2044 2045 s = {repo[tn].rev()}
2045 2046 else:
2046 2047 s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
2047 2048 else:
2048 2049 s = {cl.rev(n) for t, n in repo.tagslist() if t != 'tip'}
2049 2050 return subset & s
2050 2051
2051 2052 @predicate('tagged', safe=True)
2052 2053 def tagged(repo, subset, x):
2053 2054 return tag(repo, subset, x)
2054 2055
2055 2056 @predicate('orphan()', safe=True)
2056 2057 def orphan(repo, subset, x):
2057 2058 """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)
2058 2059 """
2059 2060 # i18n: "orphan" is a keyword
2060 2061 getargs(x, 0, 0, _("orphan takes no arguments"))
2061 2062 orphan = obsmod.getrevs(repo, 'orphan')
2062 2063 return subset & orphan
2063 2064
2064 2065
2065 2066 @predicate('user(string)', safe=True, weight=10)
2066 2067 def user(repo, subset, x):
2067 2068 """User name contains string. The match is case-insensitive.
2068 2069
2069 2070 Pattern matching is supported for `string`. See
2070 2071 :hg:`help revisions.patterns`.
2071 2072 """
2072 2073 return author(repo, subset, x)
2073 2074
2074 2075 @predicate('wdir()', safe=True, weight=0)
2075 2076 def wdir(repo, subset, x):
2076 2077 """Working directory. (EXPERIMENTAL)"""
2077 2078 # i18n: "wdir" is a keyword
2078 2079 getargs(x, 0, 0, _("wdir takes no arguments"))
2079 2080 if node.wdirrev in subset or isinstance(subset, fullreposet):
2080 2081 return baseset([node.wdirrev])
2081 2082 return baseset()
2082 2083
2083 2084 def _orderedlist(repo, subset, x):
2084 2085 s = getstring(x, "internal error")
2085 2086 if not s:
2086 2087 return baseset()
2087 2088 # remove duplicates here. it's difficult for caller to deduplicate sets
2088 2089 # because different symbols can point to the same rev.
2089 2090 cl = repo.changelog
2090 2091 ls = []
2091 2092 seen = set()
2092 2093 for t in s.split('\0'):
2093 2094 try:
2094 2095 # fast path for integer revision
2095 2096 r = int(t)
2096 2097 if ('%d' % r) != t or r not in cl:
2097 2098 raise ValueError
2098 2099 revs = [r]
2099 2100 except ValueError:
2100 2101 revs = stringset(repo, subset, t, defineorder)
2101 2102
2102 2103 for r in revs:
2103 2104 if r in seen:
2104 2105 continue
2105 2106 if (r in subset
2106 2107 or r == node.nullrev and isinstance(subset, fullreposet)):
2107 2108 ls.append(r)
2108 2109 seen.add(r)
2109 2110 return baseset(ls)
2110 2111
2111 2112 # for internal use
2112 2113 @predicate('_list', safe=True, takeorder=True)
2113 2114 def _list(repo, subset, x, order):
2114 2115 if order == followorder:
2115 2116 # slow path to take the subset order
2116 2117 return subset & _orderedlist(repo, fullreposet(repo), x)
2117 2118 else:
2118 2119 return _orderedlist(repo, subset, x)
2119 2120
2120 2121 def _orderedintlist(repo, subset, x):
2121 2122 s = getstring(x, "internal error")
2122 2123 if not s:
2123 2124 return baseset()
2124 2125 ls = [int(r) for r in s.split('\0')]
2125 2126 s = subset
2126 2127 return baseset([r for r in ls if r in s])
2127 2128
2128 2129 # for internal use
2129 2130 @predicate('_intlist', safe=True, takeorder=True, weight=0)
2130 2131 def _intlist(repo, subset, x, order):
2131 2132 if order == followorder:
2132 2133 # slow path to take the subset order
2133 2134 return subset & _orderedintlist(repo, fullreposet(repo), x)
2134 2135 else:
2135 2136 return _orderedintlist(repo, subset, x)
2136 2137
2137 2138 def _orderedhexlist(repo, subset, x):
2138 2139 s = getstring(x, "internal error")
2139 2140 if not s:
2140 2141 return baseset()
2141 2142 cl = repo.changelog
2142 2143 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2143 2144 s = subset
2144 2145 return baseset([r for r in ls if r in s])
2145 2146
2146 2147 # for internal use
2147 2148 @predicate('_hexlist', safe=True, takeorder=True)
2148 2149 def _hexlist(repo, subset, x, order):
2149 2150 if order == followorder:
2150 2151 # slow path to take the subset order
2151 2152 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2152 2153 else:
2153 2154 return _orderedhexlist(repo, subset, x)
2154 2155
2155 2156 methods = {
2156 2157 "range": rangeset,
2157 2158 "rangeall": rangeall,
2158 2159 "rangepre": rangepre,
2159 2160 "rangepost": rangepost,
2160 2161 "dagrange": dagrange,
2161 2162 "string": stringset,
2162 2163 "symbol": stringset,
2163 2164 "and": andset,
2164 2165 "andsmally": andsmallyset,
2165 2166 "or": orset,
2166 2167 "not": notset,
2167 2168 "difference": differenceset,
2168 2169 "relation": relationset,
2169 2170 "relsubscript": relsubscriptset,
2170 2171 "subscript": subscriptset,
2171 2172 "list": listset,
2172 2173 "keyvalue": keyvaluepair,
2173 2174 "func": func,
2174 2175 "ancestor": ancestorspec,
2175 2176 "parent": parentspec,
2176 2177 "parentpost": parentpost,
2177 2178 }
2178 2179
2179 2180 def lookupfn(repo):
2180 2181 return lambda symbol: scmutil.isrevsymbol(repo, symbol)
2181 2182
2182 2183 def match(ui, spec, lookup=None):
2183 2184 """Create a matcher for a single revision spec"""
2184 2185 return matchany(ui, [spec], lookup=lookup)
2185 2186
2186 2187 def matchany(ui, specs, lookup=None, localalias=None):
2187 2188 """Create a matcher that will include any revisions matching one of the
2188 2189 given specs
2189 2190
2190 2191 If lookup function is not None, the parser will first attempt to handle
2191 2192 old-style ranges, which may contain operator characters.
2192 2193
2193 2194 If localalias is not None, it is a dict {name: definitionstring}. It takes
2194 2195 precedence over [revsetalias] config section.
2195 2196 """
2196 2197 if not specs:
2197 2198 def mfunc(repo, subset=None):
2198 2199 return baseset()
2199 2200 return mfunc
2200 2201 if not all(specs):
2201 2202 raise error.ParseError(_("empty query"))
2202 2203 if len(specs) == 1:
2203 2204 tree = revsetlang.parse(specs[0], lookup)
2204 2205 else:
2205 2206 tree = ('or',
2206 2207 ('list',) + tuple(revsetlang.parse(s, lookup) for s in specs))
2207 2208
2208 2209 aliases = []
2209 2210 warn = None
2210 2211 if ui:
2211 2212 aliases.extend(ui.configitems('revsetalias'))
2212 2213 warn = ui.warn
2213 2214 if localalias:
2214 2215 aliases.extend(localalias.items())
2215 2216 if aliases:
2216 2217 tree = revsetlang.expandaliases(tree, aliases, warn=warn)
2217 2218 tree = revsetlang.foldconcat(tree)
2218 2219 tree = revsetlang.analyze(tree)
2219 2220 tree = revsetlang.optimize(tree)
2220 2221 return makematcher(tree)
2221 2222
2222 2223 def makematcher(tree):
2223 2224 """Create a matcher from an evaluatable tree"""
2224 2225 def mfunc(repo, subset=None, order=None):
2225 2226 if order is None:
2226 2227 if subset is None:
2227 2228 order = defineorder # 'x'
2228 2229 else:
2229 2230 order = followorder # 'subset & x'
2230 2231 if subset is None:
2231 2232 subset = fullreposet(repo)
2232 2233 return getset(repo, subset, tree, order)
2233 2234 return mfunc
2234 2235
2235 2236 def loadpredicate(ui, extname, registrarobj):
2236 2237 """Load revset predicates from specified registrarobj
2237 2238 """
2238 2239 for name, func in registrarobj._table.iteritems():
2239 2240 symbols[name] = func
2240 2241 if func._safe:
2241 2242 safesymbols.add(name)
2242 2243
2243 2244 # load built-in predicates explicitly to setup safesymbols
2244 2245 loadpredicate(None, None, predicate)
2245 2246
2246 2247 # tell hggettext to extract docstrings from these functions:
2247 2248 i18nfunctions = symbols.values()
@@ -1,2873 +1,2883
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[b'r3232'] = r3232
20 20 > EOF
21 21 $ cat >> $HGRCPATH << EOF
22 22 > [extensions]
23 23 > drawdag=$TESTDIR/drawdag.py
24 24 > testrevset=$TESTTMP/testrevset.py
25 25 > EOF
26 26
27 27 $ try() {
28 28 > hg debugrevspec --debug "$@"
29 29 > }
30 30
31 31 $ log() {
32 32 > hg log --template '{rev}\n' -r "$1"
33 33 > }
34 34
35 35 extension to build '_intlist()' and '_hexlist()', which is necessary because
36 36 these predicates use '\0' as a separator:
37 37
38 38 $ cat <<EOF > debugrevlistspec.py
39 39 > from __future__ import absolute_import
40 40 > from mercurial import (
41 41 > node as nodemod,
42 42 > registrar,
43 43 > revset,
44 44 > revsetlang,
45 45 > )
46 46 > from mercurial.utils import stringutil
47 47 > cmdtable = {}
48 48 > command = registrar.command(cmdtable)
49 49 > @command(b'debugrevlistspec',
50 50 > [(b'', b'optimize', None, b'print parsed tree after optimizing'),
51 51 > (b'', b'bin', None, b'unhexlify arguments')])
52 52 > def debugrevlistspec(ui, repo, fmt, *args, **opts):
53 53 > if opts['bin']:
54 54 > args = map(nodemod.bin, args)
55 55 > expr = revsetlang.formatspec(fmt, list(args))
56 56 > if ui.verbose:
57 57 > tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
58 58 > ui.note(revsetlang.prettyformat(tree), b"\n")
59 59 > if opts["optimize"]:
60 60 > opttree = revsetlang.optimize(revsetlang.analyze(tree))
61 61 > ui.note(b"* optimized:\n", revsetlang.prettyformat(opttree),
62 62 > b"\n")
63 63 > func = revset.match(ui, expr, lookup=revset.lookupfn(repo))
64 64 > revs = func(repo)
65 65 > if ui.verbose:
66 66 > ui.note(b"* set:\n", stringutil.prettyrepr(revs), b"\n")
67 67 > for c in revs:
68 68 > ui.write(b"%d\n" % c)
69 69 > EOF
70 70 $ cat <<EOF >> $HGRCPATH
71 71 > [extensions]
72 72 > debugrevlistspec = $TESTTMP/debugrevlistspec.py
73 73 > EOF
74 74 $ trylist() {
75 75 > hg debugrevlistspec --debug "$@"
76 76 > }
77 77
78 78 $ hg init repo
79 79 $ cd repo
80 80
81 81 $ echo a > a
82 82 $ hg branch a
83 83 marked working directory as branch a
84 84 (branches are permanent and global, did you want a bookmark?)
85 85 $ hg ci -Aqm0
86 86
87 87 $ echo b > b
88 88 $ hg branch b
89 89 marked working directory as branch b
90 90 $ hg ci -Aqm1
91 91
92 92 $ rm a
93 93 $ hg branch a-b-c-
94 94 marked working directory as branch a-b-c-
95 95 $ hg ci -Aqm2 -u Bob
96 96
97 97 $ hg log -r "extra('branch', 'a-b-c-')" --template '{rev}\n'
98 98 2
99 99 $ hg log -r "extra('branch')" --template '{rev}\n'
100 100 0
101 101 1
102 102 2
103 103 $ hg log -r "extra('branch', 're:a')" --template '{rev} {branch}\n'
104 104 0 a
105 105 2 a-b-c-
106 106
107 107 $ hg co 1
108 108 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 109 $ hg branch +a+b+c+
110 110 marked working directory as branch +a+b+c+
111 111 $ hg ci -Aqm3
112 112
113 113 $ hg co 2 # interleave
114 114 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
115 115 $ echo bb > b
116 116 $ hg branch -- -a-b-c-
117 117 marked working directory as branch -a-b-c-
118 118 $ hg ci -Aqm4 -d "May 12 2005"
119 119
120 120 $ hg co 3
121 121 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
122 122 $ hg branch !a/b/c/
123 123 marked working directory as branch !a/b/c/
124 124 $ hg ci -Aqm"5 bug"
125 125
126 126 $ hg merge 4
127 127 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
128 128 (branch merge, don't forget to commit)
129 129 $ hg branch _a_b_c_
130 130 marked working directory as branch _a_b_c_
131 131 $ hg ci -Aqm"6 issue619"
132 132
133 133 $ hg branch .a.b.c.
134 134 marked working directory as branch .a.b.c.
135 135 $ hg ci -Aqm7
136 136
137 137 $ hg branch all
138 138 marked working directory as branch all
139 139
140 140 $ hg co 4
141 141 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 142 $ hg branch Γ©
143 143 marked working directory as branch \xc3\xa9 (esc)
144 144 $ hg ci -Aqm9
145 145
146 146 $ hg tag -r6 1.0
147 147 $ hg bookmark -r6 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
148 148
149 149 $ hg clone --quiet -U -r 7 . ../remote1
150 150 $ hg clone --quiet -U -r 8 . ../remote2
151 151 $ echo "[paths]" >> .hg/hgrc
152 152 $ echo "default = ../remote1" >> .hg/hgrc
153 153
154 154 trivial
155 155
156 156 $ try 0:1
157 157 (range
158 158 (symbol '0')
159 159 (symbol '1'))
160 160 * set:
161 161 <spanset+ 0:2>
162 162 0
163 163 1
164 164 $ try --optimize :
165 165 (rangeall
166 166 None)
167 167 * optimized:
168 168 (rangeall
169 169 None)
170 170 * set:
171 171 <spanset+ 0:10>
172 172 0
173 173 1
174 174 2
175 175 3
176 176 4
177 177 5
178 178 6
179 179 7
180 180 8
181 181 9
182 182 $ try 3::6
183 183 (dagrange
184 184 (symbol '3')
185 185 (symbol '6'))
186 186 * set:
187 187 <baseset+ [3, 5, 6]>
188 188 3
189 189 5
190 190 6
191 191 $ try '0|1|2'
192 192 (or
193 193 (list
194 194 (symbol '0')
195 195 (symbol '1')
196 196 (symbol '2')))
197 197 * set:
198 198 <baseset [0, 1, 2]>
199 199 0
200 200 1
201 201 2
202 202
203 203 names that should work without quoting
204 204
205 205 $ try a
206 206 (symbol 'a')
207 207 * set:
208 208 <baseset [0]>
209 209 0
210 210 $ try b-a
211 211 (minus
212 212 (symbol 'b')
213 213 (symbol 'a'))
214 214 * set:
215 215 <filteredset
216 216 <baseset [1]>,
217 217 <not
218 218 <baseset [0]>>>
219 219 1
220 220 $ try _a_b_c_
221 221 (symbol '_a_b_c_')
222 222 * set:
223 223 <baseset [6]>
224 224 6
225 225 $ try _a_b_c_-a
226 226 (minus
227 227 (symbol '_a_b_c_')
228 228 (symbol 'a'))
229 229 * set:
230 230 <filteredset
231 231 <baseset [6]>,
232 232 <not
233 233 <baseset [0]>>>
234 234 6
235 235 $ try .a.b.c.
236 236 (symbol '.a.b.c.')
237 237 * set:
238 238 <baseset [7]>
239 239 7
240 240 $ try .a.b.c.-a
241 241 (minus
242 242 (symbol '.a.b.c.')
243 243 (symbol 'a'))
244 244 * set:
245 245 <filteredset
246 246 <baseset [7]>,
247 247 <not
248 248 <baseset [0]>>>
249 249 7
250 250
251 251 names that should be caught by fallback mechanism
252 252
253 253 $ try -- '-a-b-c-'
254 254 (symbol '-a-b-c-')
255 255 * set:
256 256 <baseset [4]>
257 257 4
258 258 $ log -a-b-c-
259 259 4
260 260 $ try '+a+b+c+'
261 261 (symbol '+a+b+c+')
262 262 * set:
263 263 <baseset [3]>
264 264 3
265 265 $ try '+a+b+c+:'
266 266 (rangepost
267 267 (symbol '+a+b+c+'))
268 268 * set:
269 269 <spanset+ 3:10>
270 270 3
271 271 4
272 272 5
273 273 6
274 274 7
275 275 8
276 276 9
277 277 $ try ':+a+b+c+'
278 278 (rangepre
279 279 (symbol '+a+b+c+'))
280 280 * set:
281 281 <spanset+ 0:4>
282 282 0
283 283 1
284 284 2
285 285 3
286 286 $ try -- '-a-b-c-:+a+b+c+'
287 287 (range
288 288 (symbol '-a-b-c-')
289 289 (symbol '+a+b+c+'))
290 290 * set:
291 291 <spanset- 3:5>
292 292 4
293 293 3
294 294 $ log '-a-b-c-:+a+b+c+'
295 295 4
296 296 3
297 297
298 298 $ try -- -a-b-c--a # complains
299 299 (minus
300 300 (minus
301 301 (minus
302 302 (negate
303 303 (symbol 'a'))
304 304 (symbol 'b'))
305 305 (symbol 'c'))
306 306 (negate
307 307 (symbol 'a')))
308 308 abort: unknown revision '-a'!
309 309 [255]
310 310 $ try Γ©
311 311 (symbol '\xc3\xa9')
312 312 * set:
313 313 <baseset [9]>
314 314 9
315 315
316 316 no quoting needed
317 317
318 318 $ log ::a-b-c-
319 319 0
320 320 1
321 321 2
322 322
323 323 quoting needed
324 324
325 325 $ try '"-a-b-c-"-a'
326 326 (minus
327 327 (string '-a-b-c-')
328 328 (symbol 'a'))
329 329 * set:
330 330 <filteredset
331 331 <baseset [4]>,
332 332 <not
333 333 <baseset [0]>>>
334 334 4
335 335
336 336 $ log '1 or 2'
337 337 1
338 338 2
339 339 $ log '1|2'
340 340 1
341 341 2
342 342 $ log '1 and 2'
343 343 $ log '1&2'
344 344 $ try '1&2|3' # precedence - and is higher
345 345 (or
346 346 (list
347 347 (and
348 348 (symbol '1')
349 349 (symbol '2'))
350 350 (symbol '3')))
351 351 * set:
352 352 <addset
353 353 <baseset []>,
354 354 <baseset [3]>>
355 355 3
356 356 $ try '1|2&3'
357 357 (or
358 358 (list
359 359 (symbol '1')
360 360 (and
361 361 (symbol '2')
362 362 (symbol '3'))))
363 363 * set:
364 364 <addset
365 365 <baseset [1]>,
366 366 <baseset []>>
367 367 1
368 368 $ try '1&2&3' # associativity
369 369 (and
370 370 (and
371 371 (symbol '1')
372 372 (symbol '2'))
373 373 (symbol '3'))
374 374 * set:
375 375 <baseset []>
376 376 $ try '1|(2|3)'
377 377 (or
378 378 (list
379 379 (symbol '1')
380 380 (group
381 381 (or
382 382 (list
383 383 (symbol '2')
384 384 (symbol '3'))))))
385 385 * set:
386 386 <addset
387 387 <baseset [1]>,
388 388 <baseset [2, 3]>>
389 389 1
390 390 2
391 391 3
392 392 $ log '1.0' # tag
393 393 6
394 394 $ log 'a' # branch
395 395 0
396 396 $ log '2785f51ee'
397 397 0
398 398 $ log 'date(2005)'
399 399 4
400 400 $ log 'date(this is a test)'
401 401 hg: parse error at 10: unexpected token: symbol
402 402 (date(this is a test)
403 403 ^ here)
404 404 [255]
405 405 $ log 'date()'
406 406 hg: parse error: date requires a string
407 407 [255]
408 408 $ log 'date'
409 409 abort: unknown revision 'date'!
410 410 [255]
411 411 $ log 'date('
412 412 hg: parse error at 5: not a prefix: end
413 413 (date(
414 414 ^ here)
415 415 [255]
416 416 $ log 'date("\xy")'
417 417 hg: parse error: invalid \x escape* (glob)
418 418 [255]
419 419 $ log 'date(tip)'
420 420 hg: parse error: invalid date: 'tip'
421 421 [255]
422 422 $ log '0:date'
423 423 abort: unknown revision 'date'!
424 424 [255]
425 425 $ log '::"date"'
426 426 abort: unknown revision 'date'!
427 427 [255]
428 428 $ hg book date -r 4
429 429 $ log '0:date'
430 430 0
431 431 1
432 432 2
433 433 3
434 434 4
435 435 $ log '::date'
436 436 0
437 437 1
438 438 2
439 439 4
440 440 $ log '::"date"'
441 441 0
442 442 1
443 443 2
444 444 4
445 445 $ log 'date(2005) and 1::'
446 446 4
447 447 $ hg book -d date
448 448
449 449 function name should be a symbol
450 450
451 451 $ log '"date"(2005)'
452 452 hg: parse error: not a symbol
453 453 [255]
454 454
455 455 keyword arguments
456 456
457 457 $ log 'extra(branch, value=a)'
458 458 0
459 459
460 460 $ log 'extra(branch, a, b)'
461 461 hg: parse error: extra takes at most 2 positional arguments
462 462 [255]
463 463 $ log 'extra(a, label=b)'
464 464 hg: parse error: extra got multiple values for keyword argument 'label'
465 465 [255]
466 466 $ log 'extra(label=branch, default)'
467 467 hg: parse error: extra got an invalid argument
468 468 [255]
469 469 $ log 'extra(branch, foo+bar=baz)'
470 470 hg: parse error: extra got an invalid argument
471 471 [255]
472 472 $ log 'extra(unknown=branch)'
473 473 hg: parse error: extra got an unexpected keyword argument 'unknown'
474 474 [255]
475 475
476 476 $ try 'foo=bar|baz'
477 477 (keyvalue
478 478 (symbol 'foo')
479 479 (or
480 480 (list
481 481 (symbol 'bar')
482 482 (symbol 'baz'))))
483 483 hg: parse error: can't use a key-value pair in this context
484 484 [255]
485 485
486 486 right-hand side should be optimized recursively
487 487
488 488 $ try --optimize 'foo=(not public())'
489 489 (keyvalue
490 490 (symbol 'foo')
491 491 (group
492 492 (not
493 493 (func
494 494 (symbol 'public')
495 495 None))))
496 496 * optimized:
497 497 (keyvalue
498 498 (symbol 'foo')
499 499 (func
500 500 (symbol '_notpublic')
501 501 None))
502 502 hg: parse error: can't use a key-value pair in this context
503 503 [255]
504 504
505 505 relation-subscript operator has the highest binding strength (as function call):
506 506
507 507 $ hg debugrevspec -p parsed 'tip:tip^#generations[-1]'
508 508 * parsed:
509 509 (range
510 510 (symbol 'tip')
511 511 (relsubscript
512 512 (parentpost
513 513 (symbol 'tip'))
514 514 (symbol 'generations')
515 515 (negate
516 516 (symbol '1'))))
517 517 9
518 518 8
519 519 7
520 520 6
521 521 5
522 522 4
523 523
524 524 $ hg debugrevspec -p parsed --no-show-revs 'not public()#generations[0]'
525 525 * parsed:
526 526 (not
527 527 (relsubscript
528 528 (func
529 529 (symbol 'public')
530 530 None)
531 531 (symbol 'generations')
532 532 (symbol '0')))
533 533
534 534 left-hand side of relation-subscript operator should be optimized recursively:
535 535
536 536 $ hg debugrevspec -p analyzed -p optimized --no-show-revs \
537 537 > '(not public())#generations[0]'
538 538 * analyzed:
539 539 (relsubscript
540 540 (not
541 541 (func
542 542 (symbol 'public')
543 543 None))
544 544 (symbol 'generations')
545 545 (symbol '0'))
546 546 * optimized:
547 547 (relsubscript
548 548 (func
549 549 (symbol '_notpublic')
550 550 None)
551 551 (symbol 'generations')
552 552 (symbol '0'))
553 553
554 554 resolution of subscript and relation-subscript ternary operators:
555 555
556 556 $ hg debugrevspec -p analyzed 'tip[0]'
557 557 * analyzed:
558 558 (subscript
559 559 (symbol 'tip')
560 560 (symbol '0'))
561 561 hg: parse error: can't use a subscript in this context
562 562 [255]
563 563
564 564 $ hg debugrevspec -p analyzed 'tip#rel[0]'
565 565 * analyzed:
566 566 (relsubscript
567 567 (symbol 'tip')
568 568 (symbol 'rel')
569 569 (symbol '0'))
570 570 hg: parse error: unknown identifier: rel
571 571 [255]
572 572
573 573 $ hg debugrevspec -p analyzed '(tip#rel)[0]'
574 574 * analyzed:
575 575 (subscript
576 576 (relation
577 577 (symbol 'tip')
578 578 (symbol 'rel'))
579 579 (symbol '0'))
580 580 hg: parse error: can't use a subscript in this context
581 581 [255]
582 582
583 583 $ hg debugrevspec -p analyzed 'tip#rel[0][1]'
584 584 * analyzed:
585 585 (subscript
586 586 (relsubscript
587 587 (symbol 'tip')
588 588 (symbol 'rel')
589 589 (symbol '0'))
590 590 (symbol '1'))
591 591 hg: parse error: can't use a subscript in this context
592 592 [255]
593 593
594 594 $ hg debugrevspec -p analyzed 'tip#rel0#rel1[1]'
595 595 * analyzed:
596 596 (relsubscript
597 597 (relation
598 598 (symbol 'tip')
599 599 (symbol 'rel0'))
600 600 (symbol 'rel1')
601 601 (symbol '1'))
602 602 hg: parse error: unknown identifier: rel1
603 603 [255]
604 604
605 605 $ hg debugrevspec -p analyzed 'tip#rel0[0]#rel1[1]'
606 606 * analyzed:
607 607 (relsubscript
608 608 (relsubscript
609 609 (symbol 'tip')
610 610 (symbol 'rel0')
611 611 (symbol '0'))
612 612 (symbol 'rel1')
613 613 (symbol '1'))
614 614 hg: parse error: unknown identifier: rel1
615 615 [255]
616 616
617 617 parse errors of relation, subscript and relation-subscript operators:
618 618
619 619 $ hg debugrevspec '[0]'
620 620 hg: parse error at 0: not a prefix: [
621 621 ([0]
622 622 ^ here)
623 623 [255]
624 624 $ hg debugrevspec '.#'
625 625 hg: parse error at 2: not a prefix: end
626 626 (.#
627 627 ^ here)
628 628 [255]
629 629 $ hg debugrevspec '#rel'
630 630 hg: parse error at 0: not a prefix: #
631 631 (#rel
632 632 ^ here)
633 633 [255]
634 634 $ hg debugrevspec '.#rel[0'
635 635 hg: parse error at 7: unexpected token: end
636 636 (.#rel[0
637 637 ^ here)
638 638 [255]
639 639 $ hg debugrevspec '.]'
640 640 hg: parse error at 1: invalid token
641 641 (.]
642 642 ^ here)
643 643 [255]
644 644
645 645 $ hg debugrevspec '.#generations[a]'
646 646 hg: parse error: relation subscript must be an integer
647 647 [255]
648 648 $ hg debugrevspec '.#generations[1-2]'
649 649 hg: parse error: relation subscript must be an integer
650 650 [255]
651 651
652 652 parsed tree at stages:
653 653
654 654 $ hg debugrevspec -p all '()'
655 655 * parsed:
656 656 (group
657 657 None)
658 658 * expanded:
659 659 (group
660 660 None)
661 661 * concatenated:
662 662 (group
663 663 None)
664 664 * analyzed:
665 665 None
666 666 * optimized:
667 667 None
668 668 hg: parse error: missing argument
669 669 [255]
670 670
671 671 $ hg debugrevspec --no-optimized -p all '()'
672 672 * parsed:
673 673 (group
674 674 None)
675 675 * expanded:
676 676 (group
677 677 None)
678 678 * concatenated:
679 679 (group
680 680 None)
681 681 * analyzed:
682 682 None
683 683 hg: parse error: missing argument
684 684 [255]
685 685
686 686 $ hg debugrevspec -p parsed -p analyzed -p optimized '(0|1)-1'
687 687 * parsed:
688 688 (minus
689 689 (group
690 690 (or
691 691 (list
692 692 (symbol '0')
693 693 (symbol '1'))))
694 694 (symbol '1'))
695 695 * analyzed:
696 696 (and
697 697 (or
698 698 (list
699 699 (symbol '0')
700 700 (symbol '1')))
701 701 (not
702 702 (symbol '1')))
703 703 * optimized:
704 704 (difference
705 705 (func
706 706 (symbol '_list')
707 707 (string '0\x001'))
708 708 (symbol '1'))
709 709 0
710 710
711 711 $ hg debugrevspec -p unknown '0'
712 712 abort: invalid stage name: unknown
713 713 [255]
714 714
715 715 $ hg debugrevspec -p all --optimize '0'
716 716 abort: cannot use --optimize with --show-stage
717 717 [255]
718 718
719 719 verify optimized tree:
720 720
721 721 $ hg debugrevspec --verify '0|1'
722 722
723 723 $ hg debugrevspec --verify -v -p analyzed -p optimized 'r3232() & 2'
724 724 * analyzed:
725 725 (and
726 726 (func
727 727 (symbol 'r3232')
728 728 None)
729 729 (symbol '2'))
730 730 * optimized:
731 731 (andsmally
732 732 (func
733 733 (symbol 'r3232')
734 734 None)
735 735 (symbol '2'))
736 736 * analyzed set:
737 737 <baseset [2]>
738 738 * optimized set:
739 739 <baseset [2, 2]>
740 740 --- analyzed
741 741 +++ optimized
742 742 2
743 743 +2
744 744 [1]
745 745
746 746 $ hg debugrevspec --no-optimized --verify-optimized '0'
747 747 abort: cannot use --verify-optimized with --no-optimized
748 748 [255]
749 749
750 750 Test that symbols only get parsed as functions if there's an opening
751 751 parenthesis.
752 752
753 753 $ hg book only -r 9
754 754 $ log 'only(only)' # Outer "only" is a function, inner "only" is the bookmark
755 755 8
756 756 9
757 757
758 758 ':y' behaves like '0:y', but can't be rewritten as such since the revision '0'
759 759 may be hidden (issue5385)
760 760
761 761 $ try -p parsed -p analyzed ':'
762 762 * parsed:
763 763 (rangeall
764 764 None)
765 765 * analyzed:
766 766 (rangeall
767 767 None)
768 768 * set:
769 769 <spanset+ 0:10>
770 770 0
771 771 1
772 772 2
773 773 3
774 774 4
775 775 5
776 776 6
777 777 7
778 778 8
779 779 9
780 780 $ try -p analyzed ':1'
781 781 * analyzed:
782 782 (rangepre
783 783 (symbol '1'))
784 784 * set:
785 785 <spanset+ 0:2>
786 786 0
787 787 1
788 788 $ try -p analyzed ':(1|2)'
789 789 * analyzed:
790 790 (rangepre
791 791 (or
792 792 (list
793 793 (symbol '1')
794 794 (symbol '2'))))
795 795 * set:
796 796 <spanset+ 0:3>
797 797 0
798 798 1
799 799 2
800 800 $ try -p analyzed ':(1&2)'
801 801 * analyzed:
802 802 (rangepre
803 803 (and
804 804 (symbol '1')
805 805 (symbol '2')))
806 806 * set:
807 807 <baseset []>
808 808
809 809 infix/suffix resolution of ^ operator (issue2884, issue5764):
810 810
811 811 x^:y means (x^):y
812 812
813 813 $ try '1^:2'
814 814 (range
815 815 (parentpost
816 816 (symbol '1'))
817 817 (symbol '2'))
818 818 * set:
819 819 <spanset+ 0:3>
820 820 0
821 821 1
822 822 2
823 823
824 824 $ try '1^::2'
825 825 (dagrange
826 826 (parentpost
827 827 (symbol '1'))
828 828 (symbol '2'))
829 829 * set:
830 830 <baseset+ [0, 1, 2]>
831 831 0
832 832 1
833 833 2
834 834
835 835 $ try '1^..2'
836 836 (dagrange
837 837 (parentpost
838 838 (symbol '1'))
839 839 (symbol '2'))
840 840 * set:
841 841 <baseset+ [0, 1, 2]>
842 842 0
843 843 1
844 844 2
845 845
846 846 $ try '9^:'
847 847 (rangepost
848 848 (parentpost
849 849 (symbol '9')))
850 850 * set:
851 851 <spanset+ 8:10>
852 852 8
853 853 9
854 854
855 855 $ try '9^::'
856 856 (dagrangepost
857 857 (parentpost
858 858 (symbol '9')))
859 859 * set:
860 860 <generatorsetasc+>
861 861 8
862 862 9
863 863
864 864 $ try '9^..'
865 865 (dagrangepost
866 866 (parentpost
867 867 (symbol '9')))
868 868 * set:
869 869 <generatorsetasc+>
870 870 8
871 871 9
872 872
873 873 x^:y should be resolved before omitting group operators
874 874
875 875 $ try '1^(:2)'
876 876 (parent
877 877 (symbol '1')
878 878 (group
879 879 (rangepre
880 880 (symbol '2'))))
881 881 hg: parse error: ^ expects a number 0, 1, or 2
882 882 [255]
883 883
884 884 x^:y should be resolved recursively
885 885
886 886 $ try 'sort(1^:2)'
887 887 (func
888 888 (symbol 'sort')
889 889 (range
890 890 (parentpost
891 891 (symbol '1'))
892 892 (symbol '2')))
893 893 * set:
894 894 <spanset+ 0:3>
895 895 0
896 896 1
897 897 2
898 898
899 899 $ try '(3^:4)^:2'
900 900 (range
901 901 (parentpost
902 902 (group
903 903 (range
904 904 (parentpost
905 905 (symbol '3'))
906 906 (symbol '4'))))
907 907 (symbol '2'))
908 908 * set:
909 909 <spanset+ 0:3>
910 910 0
911 911 1
912 912 2
913 913
914 914 $ try '(3^::4)^::2'
915 915 (dagrange
916 916 (parentpost
917 917 (group
918 918 (dagrange
919 919 (parentpost
920 920 (symbol '3'))
921 921 (symbol '4'))))
922 922 (symbol '2'))
923 923 * set:
924 924 <baseset+ [0, 1, 2]>
925 925 0
926 926 1
927 927 2
928 928
929 929 $ try '(9^:)^:'
930 930 (rangepost
931 931 (parentpost
932 932 (group
933 933 (rangepost
934 934 (parentpost
935 935 (symbol '9'))))))
936 936 * set:
937 937 <spanset+ 4:10>
938 938 4
939 939 5
940 940 6
941 941 7
942 942 8
943 943 9
944 944
945 945 x^ in alias should also be resolved
946 946
947 947 $ try 'A' --config 'revsetalias.A=1^:2'
948 948 (symbol 'A')
949 949 * expanded:
950 950 (range
951 951 (parentpost
952 952 (symbol '1'))
953 953 (symbol '2'))
954 954 * set:
955 955 <spanset+ 0:3>
956 956 0
957 957 1
958 958 2
959 959
960 960 $ try 'A:2' --config 'revsetalias.A=1^'
961 961 (range
962 962 (symbol 'A')
963 963 (symbol '2'))
964 964 * expanded:
965 965 (range
966 966 (parentpost
967 967 (symbol '1'))
968 968 (symbol '2'))
969 969 * set:
970 970 <spanset+ 0:3>
971 971 0
972 972 1
973 973 2
974 974
975 975 but not beyond the boundary of alias expansion, because the resolution should
976 976 be made at the parsing stage
977 977
978 978 $ try '1^A' --config 'revsetalias.A=:2'
979 979 (parent
980 980 (symbol '1')
981 981 (symbol 'A'))
982 982 * expanded:
983 983 (parent
984 984 (symbol '1')
985 985 (rangepre
986 986 (symbol '2')))
987 987 hg: parse error: ^ expects a number 0, 1, or 2
988 988 [255]
989 989
990 990 '::' itself isn't a valid expression
991 991
992 992 $ try '::'
993 993 (dagrangeall
994 994 None)
995 995 hg: parse error: can't use '::' in this context
996 996 [255]
997 997
998 998 ancestor can accept 0 or more arguments
999 999
1000 1000 $ log 'ancestor()'
1001 1001 $ log 'ancestor(1)'
1002 1002 1
1003 1003 $ log 'ancestor(4,5)'
1004 1004 1
1005 1005 $ log 'ancestor(4,5) and 4'
1006 1006 $ log 'ancestor(0,0,1,3)'
1007 1007 0
1008 1008 $ log 'ancestor(3,1,5,3,5,1)'
1009 1009 1
1010 1010 $ log 'ancestor(0,1,3,5)'
1011 1011 0
1012 1012 $ log 'ancestor(1,2,3,4,5)'
1013 1013 1
1014 1014
1015 1015 test ancestors
1016 1016
1017 1017 $ hg log -G -T '{rev}\n' --config experimental.graphshorten=True
1018 1018 @ 9
1019 1019 o 8
1020 1020 | o 7
1021 1021 | o 6
1022 1022 |/|
1023 1023 | o 5
1024 1024 o | 4
1025 1025 | o 3
1026 1026 o | 2
1027 1027 |/
1028 1028 o 1
1029 1029 o 0
1030 1030
1031 1031 $ log 'ancestors(5)'
1032 1032 0
1033 1033 1
1034 1034 3
1035 1035 5
1036 1036 $ log 'ancestor(ancestors(5))'
1037 1037 0
1038 1038 $ log '::r3232()'
1039 1039 0
1040 1040 1
1041 1041 2
1042 1042 3
1043 1043
1044 1044 test ancestors with depth limit
1045 1045
1046 1046 (depth=0 selects the node itself)
1047 1047
1048 1048 $ log 'reverse(ancestors(9, depth=0))'
1049 1049 9
1050 1050
1051 1051 (interleaved: '4' would be missing if heap queue were higher depth first)
1052 1052
1053 1053 $ log 'reverse(ancestors(8:9, depth=1))'
1054 1054 9
1055 1055 8
1056 1056 4
1057 1057
1058 1058 (interleaved: '2' would be missing if heap queue were higher depth first)
1059 1059
1060 1060 $ log 'reverse(ancestors(7+8, depth=2))'
1061 1061 8
1062 1062 7
1063 1063 6
1064 1064 5
1065 1065 4
1066 1066 2
1067 1067
1068 1068 (walk example above by separate queries)
1069 1069
1070 1070 $ log 'reverse(ancestors(8, depth=2)) + reverse(ancestors(7, depth=2))'
1071 1071 8
1072 1072 4
1073 1073 2
1074 1074 7
1075 1075 6
1076 1076 5
1077 1077
1078 1078 (walk 2nd and 3rd ancestors)
1079 1079
1080 1080 $ log 'reverse(ancestors(7, depth=3, startdepth=2))'
1081 1081 5
1082 1082 4
1083 1083 3
1084 1084 2
1085 1085
1086 1086 (interleaved: '4' would be missing if higher-depth ancestors weren't scanned)
1087 1087
1088 1088 $ log 'reverse(ancestors(7+8, depth=2, startdepth=2))'
1089 1089 5
1090 1090 4
1091 1091 2
1092 1092
1093 1093 (note that 'ancestors(x, depth=y, startdepth=z)' does not identical to
1094 1094 'ancestors(x, depth=y) - ancestors(x, depth=z-1)' because a node may have
1095 1095 multiple depths)
1096 1096
1097 1097 $ log 'reverse(ancestors(7+8, depth=2) - ancestors(7+8, depth=1))'
1098 1098 5
1099 1099 2
1100 1100
1101 1101 test bad arguments passed to ancestors()
1102 1102
1103 1103 $ log 'ancestors(., depth=-1)'
1104 1104 hg: parse error: negative depth
1105 1105 [255]
1106 1106 $ log 'ancestors(., depth=foo)'
1107 1107 hg: parse error: ancestors expects an integer depth
1108 1108 [255]
1109 1109
1110 1110 test descendants
1111 1111
1112 1112 $ hg log -G -T '{rev}\n' --config experimental.graphshorten=True
1113 1113 @ 9
1114 1114 o 8
1115 1115 | o 7
1116 1116 | o 6
1117 1117 |/|
1118 1118 | o 5
1119 1119 o | 4
1120 1120 | o 3
1121 1121 o | 2
1122 1122 |/
1123 1123 o 1
1124 1124 o 0
1125 1125
1126 1126 (null is ultimate root and has optimized path)
1127 1127
1128 1128 $ log 'null:4 & descendants(null)'
1129 1129 -1
1130 1130 0
1131 1131 1
1132 1132 2
1133 1133 3
1134 1134 4
1135 1135
1136 1136 (including merge)
1137 1137
1138 1138 $ log ':8 & descendants(2)'
1139 1139 2
1140 1140 4
1141 1141 6
1142 1142 7
1143 1143 8
1144 1144
1145 1145 (multiple roots)
1146 1146
1147 1147 $ log ':8 & descendants(2+5)'
1148 1148 2
1149 1149 4
1150 1150 5
1151 1151 6
1152 1152 7
1153 1153 8
1154 1154
1155 1155 test descendants with depth limit
1156 1156
1157 1157 (depth=0 selects the node itself)
1158 1158
1159 1159 $ log 'descendants(0, depth=0)'
1160 1160 0
1161 1161 $ log 'null: & descendants(null, depth=0)'
1162 1162 -1
1163 1163
1164 1164 (p2 = null should be ignored)
1165 1165
1166 1166 $ log 'null: & descendants(null, depth=2)'
1167 1167 -1
1168 1168 0
1169 1169 1
1170 1170
1171 1171 (multiple paths: depth(6) = (2, 3))
1172 1172
1173 1173 $ log 'descendants(1+3, depth=2)'
1174 1174 1
1175 1175 2
1176 1176 3
1177 1177 4
1178 1178 5
1179 1179 6
1180 1180
1181 1181 (multiple paths: depth(5) = (1, 2), depth(6) = (2, 3))
1182 1182
1183 1183 $ log 'descendants(3+1, depth=2, startdepth=2)'
1184 1184 4
1185 1185 5
1186 1186 6
1187 1187
1188 1188 (multiple depths: depth(6) = (0, 2, 4), search for depth=2)
1189 1189
1190 1190 $ log 'descendants(0+3+6, depth=3, startdepth=1)'
1191 1191 1
1192 1192 2
1193 1193 3
1194 1194 4
1195 1195 5
1196 1196 6
1197 1197 7
1198 1198
1199 1199 (multiple depths: depth(6) = (0, 4), no match)
1200 1200
1201 1201 $ log 'descendants(0+6, depth=3, startdepth=1)'
1202 1202 1
1203 1203 2
1204 1204 3
1205 1205 4
1206 1206 5
1207 1207 7
1208 1208
1209 1209 test ancestors/descendants relation subscript:
1210 1210
1211 1211 $ log 'tip#generations[0]'
1212 1212 9
1213 1213 $ log '.#generations[-1]'
1214 1214 8
1215 1215 $ log '.#g[(-1)]'
1216 1216 8
1217 1217
1218 1218 $ hg debugrevspec -p parsed 'roots(:)#g[2]'
1219 1219 * parsed:
1220 1220 (relsubscript
1221 1221 (func
1222 1222 (symbol 'roots')
1223 1223 (rangeall
1224 1224 None))
1225 1225 (symbol 'g')
1226 1226 (symbol '2'))
1227 1227 2
1228 1228 3
1229 1229
1230 1230 test author
1231 1231
1232 1232 $ log 'author(bob)'
1233 1233 2
1234 1234 $ log 'author("re:bob|test")'
1235 1235 0
1236 1236 1
1237 1237 2
1238 1238 3
1239 1239 4
1240 1240 5
1241 1241 6
1242 1242 7
1243 1243 8
1244 1244 9
1245 1245 $ log 'author(r"re:\S")'
1246 1246 0
1247 1247 1
1248 1248 2
1249 1249 3
1250 1250 4
1251 1251 5
1252 1252 6
1253 1253 7
1254 1254 8
1255 1255 9
1256 1256 $ log 'branch(Γ©)'
1257 1257 8
1258 1258 9
1259 1259 $ log 'branch(a)'
1260 1260 0
1261 1261 $ hg log -r 'branch("re:a")' --template '{rev} {branch}\n'
1262 1262 0 a
1263 1263 2 a-b-c-
1264 1264 3 +a+b+c+
1265 1265 4 -a-b-c-
1266 1266 5 !a/b/c/
1267 1267 6 _a_b_c_
1268 1268 7 .a.b.c.
1269 1269 $ log 'children(ancestor(4,5))'
1270 1270 2
1271 1271 3
1272 1272
1273 1273 $ log 'children(4)'
1274 1274 6
1275 1275 8
1276 1276 $ log 'children(null)'
1277 1277 0
1278 1278
1279 1279 $ log 'closed()'
1280 1280 $ log 'contains(a)'
1281 1281 0
1282 1282 1
1283 1283 3
1284 1284 5
1285 1285 $ log 'contains("../repo/a")'
1286 1286 0
1287 1287 1
1288 1288 3
1289 1289 5
1290 1290 $ log 'desc(B)'
1291 1291 5
1292 1292 $ hg log -r 'desc(r"re:S?u")' --template "{rev} {desc|firstline}\n"
1293 1293 5 5 bug
1294 1294 6 6 issue619
1295 1295 $ log 'descendants(2 or 3)'
1296 1296 2
1297 1297 3
1298 1298 4
1299 1299 5
1300 1300 6
1301 1301 7
1302 1302 8
1303 1303 9
1304 1304 $ log 'file("b*")'
1305 1305 1
1306 1306 4
1307 1307 $ log 'filelog("b")'
1308 1308 1
1309 1309 4
1310 1310 $ log 'filelog("../repo/b")'
1311 1311 1
1312 1312 4
1313 1313 $ log 'follow()'
1314 1314 0
1315 1315 1
1316 1316 2
1317 1317 4
1318 1318 8
1319 1319 9
1320 1320 $ log 'grep("issue\d+")'
1321 1321 6
1322 1322 $ try 'grep("(")' # invalid regular expression
1323 1323 (func
1324 1324 (symbol 'grep')
1325 1325 (string '('))
1326 1326 hg: parse error: invalid match pattern: (unbalanced parenthesis|missing \),.*) (re)
1327 1327 [255]
1328 1328 $ try 'grep("\bissue\d+")'
1329 1329 (func
1330 1330 (symbol 'grep')
1331 1331 (string '\x08issue\\d+'))
1332 1332 * set:
1333 1333 <filteredset
1334 1334 <fullreposet+ 0:10>,
1335 1335 <grep '\x08issue\\d+'>>
1336 1336 $ try 'grep(r"\bissue\d+")'
1337 1337 (func
1338 1338 (symbol 'grep')
1339 1339 (string '\\bissue\\d+'))
1340 1340 * set:
1341 1341 <filteredset
1342 1342 <fullreposet+ 0:10>,
1343 1343 <grep '\\bissue\\d+'>>
1344 1344 6
1345 1345 $ try 'grep(r"\")'
1346 1346 hg: parse error at 7: unterminated string
1347 1347 (grep(r"\")
1348 1348 ^ here)
1349 1349 [255]
1350 1350 $ log 'head()'
1351 1351 0
1352 1352 1
1353 1353 2
1354 1354 3
1355 1355 4
1356 1356 5
1357 1357 6
1358 1358 7
1359 1359 9
1360 1360
1361 1361 Test heads
1362 1362
1363 1363 $ log 'heads(6::)'
1364 1364 7
1365 1365
1366 1366 heads() can be computed in subset '9:'
1367 1367
1368 1368 $ hg debugrevspec -s '9: & heads(all())'
1369 1369 * set:
1370 1370 <filteredset
1371 1371 <filteredset
1372 1372 <baseset [9]>,
1373 1373 <spanset+ 0:10>>,
1374 1374 <not
1375 1375 <filteredset
1376 1376 <baseset [9]>, set([0, 1, 2, 3, 4, 5, 6, 8])>>>
1377 1377 9
1378 1378
1379 1379 but should follow the order of the subset
1380 1380
1381 1381 $ log 'heads(all())'
1382 1382 7
1383 1383 9
1384 1384 $ log 'heads(tip:0)'
1385 1385 7
1386 1386 9
1387 1387 $ log 'tip:0 & heads(all())'
1388 1388 9
1389 1389 7
1390 1390 $ log 'tip:0 & heads(0:tip)'
1391 1391 9
1392 1392 7
1393 1393
1394 1394 $ log 'keyword(issue)'
1395 1395 6
1396 1396 $ log 'keyword("test a")'
1397 1397
1398 1398 Test first (=limit) and last
1399 1399
1400 1400 $ log 'limit(head(), 1)'
1401 1401 0
1402 1402 $ log 'limit(author("re:bob|test"), 3, 5)'
1403 1403 5
1404 1404 6
1405 1405 7
1406 1406 $ log 'limit(author("re:bob|test"), offset=6)'
1407 1407 6
1408 1408 $ log 'limit(author("re:bob|test"), offset=10)'
1409 1409 $ log 'limit(all(), 1, -1)'
1410 1410 hg: parse error: negative offset
1411 1411 [255]
1412 1412 $ log 'limit(all(), -1)'
1413 1413 hg: parse error: negative number to select
1414 1414 [255]
1415 1415 $ log 'limit(all(), 0)'
1416 1416
1417 1417 $ log 'last(all(), -1)'
1418 1418 hg: parse error: negative number to select
1419 1419 [255]
1420 1420 $ log 'last(all(), 0)'
1421 1421 $ log 'last(all(), 1)'
1422 1422 9
1423 1423 $ log 'last(all(), 2)'
1424 1424 8
1425 1425 9
1426 1426
1427 1427 Test smartset.slice() by first/last()
1428 1428
1429 1429 (using unoptimized set, filteredset as example)
1430 1430
1431 1431 $ hg debugrevspec --no-show-revs -s '0:7 & branch("re:")'
1432 1432 * set:
1433 1433 <filteredset
1434 1434 <spanset+ 0:8>,
1435 1435 <branch 're:'>>
1436 1436 $ log 'limit(0:7 & branch("re:"), 3, 4)'
1437 1437 4
1438 1438 5
1439 1439 6
1440 1440 $ log 'limit(7:0 & branch("re:"), 3, 4)'
1441 1441 3
1442 1442 2
1443 1443 1
1444 1444 $ log 'last(0:7 & branch("re:"), 2)'
1445 1445 6
1446 1446 7
1447 1447
1448 1448 (using baseset)
1449 1449
1450 1450 $ hg debugrevspec --no-show-revs -s 0+1+2+3+4+5+6+7
1451 1451 * set:
1452 1452 <baseset [0, 1, 2, 3, 4, 5, 6, 7]>
1453 1453 $ hg debugrevspec --no-show-revs -s 0::7
1454 1454 * set:
1455 1455 <baseset+ [0, 1, 2, 3, 4, 5, 6, 7]>
1456 1456 $ log 'limit(0+1+2+3+4+5+6+7, 3, 4)'
1457 1457 4
1458 1458 5
1459 1459 6
1460 1460 $ log 'limit(sort(0::7, rev), 3, 4)'
1461 1461 4
1462 1462 5
1463 1463 6
1464 1464 $ log 'limit(sort(0::7, -rev), 3, 4)'
1465 1465 3
1466 1466 2
1467 1467 1
1468 1468 $ log 'last(sort(0::7, rev), 2)'
1469 1469 6
1470 1470 7
1471 1471 $ hg debugrevspec -s 'limit(sort(0::7, rev), 3, 6)'
1472 1472 * set:
1473 1473 <baseset+ [6, 7]>
1474 1474 6
1475 1475 7
1476 1476 $ hg debugrevspec -s 'limit(sort(0::7, rev), 3, 9)'
1477 1477 * set:
1478 1478 <baseset+ []>
1479 1479 $ hg debugrevspec -s 'limit(sort(0::7, -rev), 3, 6)'
1480 1480 * set:
1481 1481 <baseset- [0, 1]>
1482 1482 1
1483 1483 0
1484 1484 $ hg debugrevspec -s 'limit(sort(0::7, -rev), 3, 9)'
1485 1485 * set:
1486 1486 <baseset- []>
1487 1487 $ hg debugrevspec -s 'limit(0::7, 0)'
1488 1488 * set:
1489 1489 <baseset+ []>
1490 1490
1491 1491 (using spanset)
1492 1492
1493 1493 $ hg debugrevspec --no-show-revs -s 0:7
1494 1494 * set:
1495 1495 <spanset+ 0:8>
1496 1496 $ log 'limit(0:7, 3, 4)'
1497 1497 4
1498 1498 5
1499 1499 6
1500 1500 $ log 'limit(7:0, 3, 4)'
1501 1501 3
1502 1502 2
1503 1503 1
1504 1504 $ log 'limit(0:7, 3, 6)'
1505 1505 6
1506 1506 7
1507 1507 $ log 'limit(7:0, 3, 6)'
1508 1508 1
1509 1509 0
1510 1510 $ log 'last(0:7, 2)'
1511 1511 6
1512 1512 7
1513 1513 $ hg debugrevspec -s 'limit(0:7, 3, 6)'
1514 1514 * set:
1515 1515 <spanset+ 6:8>
1516 1516 6
1517 1517 7
1518 1518 $ hg debugrevspec -s 'limit(0:7, 3, 9)'
1519 1519 * set:
1520 1520 <spanset+ 8:8>
1521 1521 $ hg debugrevspec -s 'limit(7:0, 3, 6)'
1522 1522 * set:
1523 1523 <spanset- 0:2>
1524 1524 1
1525 1525 0
1526 1526 $ hg debugrevspec -s 'limit(7:0, 3, 9)'
1527 1527 * set:
1528 1528 <spanset- 0:0>
1529 1529 $ hg debugrevspec -s 'limit(0:7, 0)'
1530 1530 * set:
1531 1531 <spanset+ 0:0>
1532 1532
1533 1533 Test order of first/last revisions
1534 1534
1535 1535 $ hg debugrevspec -s 'first(4:0, 3) & 3:'
1536 1536 * set:
1537 1537 <filteredset
1538 1538 <spanset- 2:5>,
1539 1539 <spanset+ 3:10>>
1540 1540 4
1541 1541 3
1542 1542
1543 1543 $ hg debugrevspec -s '3: & first(4:0, 3)'
1544 1544 * set:
1545 1545 <filteredset
1546 1546 <spanset+ 3:10>,
1547 1547 <spanset- 2:5>>
1548 1548 3
1549 1549 4
1550 1550
1551 1551 $ hg debugrevspec -s 'last(4:0, 3) & :1'
1552 1552 * set:
1553 1553 <filteredset
1554 1554 <spanset- 0:3>,
1555 1555 <spanset+ 0:2>>
1556 1556 1
1557 1557 0
1558 1558
1559 1559 $ hg debugrevspec -s ':1 & last(4:0, 3)'
1560 1560 * set:
1561 1561 <filteredset
1562 1562 <spanset+ 0:2>,
1563 1563 <spanset+ 0:3>>
1564 1564 0
1565 1565 1
1566 1566
1567 1567 Test scmutil.revsingle() should return the last revision
1568 1568
1569 1569 $ hg debugrevspec -s 'last(0::)'
1570 1570 * set:
1571 1571 <baseset slice=0:1
1572 1572 <generatorsetasc->>
1573 1573 9
1574 1574 $ hg identify -r '0::' --num
1575 1575 9
1576 1576
1577 1577 Test matching
1578 1578
1579 1579 $ log 'matching(6)'
1580 1580 6
1581 1581 $ log 'matching(6:7, "phase parents user date branch summary files description substate")'
1582 1582 6
1583 1583 7
1584 1584
1585 1585 Testing min and max
1586 1586
1587 1587 max: simple
1588 1588
1589 1589 $ log 'max(contains(a))'
1590 1590 5
1591 1591
1592 1592 max: simple on unordered set)
1593 1593
1594 1594 $ log 'max((4+0+2+5+7) and contains(a))'
1595 1595 5
1596 1596
1597 1597 max: no result
1598 1598
1599 1599 $ log 'max(contains(stringthatdoesnotappearanywhere))'
1600 1600
1601 1601 max: no result on unordered set
1602 1602
1603 1603 $ log 'max((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
1604 1604
1605 1605 min: simple
1606 1606
1607 1607 $ log 'min(contains(a))'
1608 1608 0
1609 1609
1610 1610 min: simple on unordered set
1611 1611
1612 1612 $ log 'min((4+0+2+5+7) and contains(a))'
1613 1613 0
1614 1614
1615 1615 min: empty
1616 1616
1617 1617 $ log 'min(contains(stringthatdoesnotappearanywhere))'
1618 1618
1619 1619 min: empty on unordered set
1620 1620
1621 1621 $ log 'min((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
1622 1622
1623 1623
1624 1624 $ log 'merge()'
1625 1625 6
1626 1626 $ log 'branchpoint()'
1627 1627 1
1628 1628 4
1629 1629 $ log 'modifies(b)'
1630 1630 4
1631 1631 $ log 'modifies("path:b")'
1632 1632 4
1633 1633 $ log 'modifies("*")'
1634 1634 4
1635 1635 6
1636 1636 $ log 'modifies("set:modified()")'
1637 1637 4
1638 1638 $ log 'id(5)'
1639 1639 2
1640 1640 $ log 'only(9)'
1641 1641 8
1642 1642 9
1643 1643 $ log 'only(8)'
1644 1644 8
1645 1645 $ log 'only(9, 5)'
1646 1646 2
1647 1647 4
1648 1648 8
1649 1649 9
1650 1650 $ log 'only(7 + 9, 5 + 2)'
1651 1651 4
1652 1652 6
1653 1653 7
1654 1654 8
1655 1655 9
1656 1656
1657 1657 Test empty set input
1658 1658 $ log 'only(p2())'
1659 1659 $ log 'only(p1(), p2())'
1660 1660 0
1661 1661 1
1662 1662 2
1663 1663 4
1664 1664 8
1665 1665 9
1666 1666
1667 1667 Test '%' operator
1668 1668
1669 1669 $ log '9%'
1670 1670 8
1671 1671 9
1672 1672 $ log '9%5'
1673 1673 2
1674 1674 4
1675 1675 8
1676 1676 9
1677 1677 $ log '(7 + 9)%(5 + 2)'
1678 1678 4
1679 1679 6
1680 1680 7
1681 1681 8
1682 1682 9
1683 1683
1684 1684 Test operand of '%' is optimized recursively (issue4670)
1685 1685
1686 1686 $ try --optimize '8:9-8%'
1687 1687 (onlypost
1688 1688 (minus
1689 1689 (range
1690 1690 (symbol '8')
1691 1691 (symbol '9'))
1692 1692 (symbol '8')))
1693 1693 * optimized:
1694 1694 (func
1695 1695 (symbol 'only')
1696 1696 (difference
1697 1697 (range
1698 1698 (symbol '8')
1699 1699 (symbol '9'))
1700 1700 (symbol '8')))
1701 1701 * set:
1702 1702 <baseset+ [8, 9]>
1703 1703 8
1704 1704 9
1705 1705 $ try --optimize '(9)%(5)'
1706 1706 (only
1707 1707 (group
1708 1708 (symbol '9'))
1709 1709 (group
1710 1710 (symbol '5')))
1711 1711 * optimized:
1712 1712 (func
1713 1713 (symbol 'only')
1714 1714 (list
1715 1715 (symbol '9')
1716 1716 (symbol '5')))
1717 1717 * set:
1718 1718 <baseset+ [2, 4, 8, 9]>
1719 1719 2
1720 1720 4
1721 1721 8
1722 1722 9
1723 1723
1724 1724 Test the order of operations
1725 1725
1726 1726 $ log '7 + 9%5 + 2'
1727 1727 7
1728 1728 2
1729 1729 4
1730 1730 8
1731 1731 9
1732 1732
1733 1733 Test explicit numeric revision
1734 1734 $ log 'rev(-2)'
1735 1735 $ log 'rev(-1)'
1736 1736 -1
1737 1737 $ log 'rev(0)'
1738 1738 0
1739 1739 $ log 'rev(9)'
1740 1740 9
1741 1741 $ log 'rev(10)'
1742 1742 $ log 'rev(tip)'
1743 1743 hg: parse error: rev expects a number
1744 1744 [255]
1745 1745
1746 1746 Test hexadecimal revision
1747 1747 $ log 'id(2)'
1748 1748 $ log 'id(23268)'
1749 1749 4
1750 1750 $ log 'id(2785f51eece)'
1751 1751 0
1752 1752 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532c)'
1753 1753 8
1754 1754 $ log 'id(d5d0dcbdc4a)'
1755 1755 $ log 'id(d5d0dcbdc4w)'
1756 1756 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532d)'
1757 1757 $ log 'id(d5d0dcbdc4d9ff5dbb2d336f32f0bb561c1a532q)'
1758 1758 $ log 'id(1.0)'
1759 1759 $ log 'id(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)'
1760 1760
1761 1761 Test null revision
1762 1762 $ log '(null)'
1763 1763 -1
1764 1764 $ log '(null:0)'
1765 1765 -1
1766 1766 0
1767 1767 $ log '(0:null)'
1768 1768 0
1769 1769 -1
1770 1770 $ log 'null::0'
1771 1771 -1
1772 1772 0
1773 1773 $ log 'null:tip - 0:'
1774 1774 -1
1775 1775 $ log 'null: and null::' | head -1
1776 1776 -1
1777 1777 $ log 'null: or 0:' | head -2
1778 1778 -1
1779 1779 0
1780 1780 $ log 'ancestors(null)'
1781 1781 -1
1782 1782 $ log 'reverse(null:)' | tail -2
1783 1783 0
1784 1784 -1
1785 1785 $ log 'first(null:)'
1786 1786 -1
1787 1787 $ log 'min(null:)'
1788 1788 BROKEN: should be '-1'
1789 1789 $ log 'tip:null and all()' | tail -2
1790 1790 1
1791 1791 0
1792 1792
1793 1793 Test working-directory revision
1794 1794 $ hg debugrevspec 'wdir()'
1795 1795 2147483647
1796 1796 $ hg debugrevspec 'wdir()^'
1797 1797 9
1798 1798 $ hg up 7
1799 1799 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1800 1800 $ hg debugrevspec 'wdir()^'
1801 1801 7
1802 1802 $ hg debugrevspec 'wdir()^0'
1803 1803 2147483647
1804 1804 $ hg debugrevspec 'wdir()~3'
1805 1805 5
1806 1806 $ hg debugrevspec 'ancestors(wdir())'
1807 1807 0
1808 1808 1
1809 1809 2
1810 1810 3
1811 1811 4
1812 1812 5
1813 1813 6
1814 1814 7
1815 1815 2147483647
1816 $ hg debugrevspec '0:wdir() & ancestor(wdir())'
1817 2147483647
1818 $ hg debugrevspec '0:wdir() & ancestor(.:wdir())'
1819 4
1820 $ hg debugrevspec '0:wdir() & ancestor(wdir(), wdir())'
1821 2147483647
1822 $ hg debugrevspec '0:wdir() & ancestor(wdir(), tip)'
1823 4
1824 $ hg debugrevspec 'null:wdir() & ancestor(wdir(), null)'
1825 -1
1816 1826 $ hg debugrevspec 'wdir()~0'
1817 1827 2147483647
1818 1828 $ hg debugrevspec 'p1(wdir())'
1819 1829 7
1820 1830 $ hg debugrevspec 'p2(wdir())'
1821 1831 $ hg debugrevspec 'parents(wdir())'
1822 1832 7
1823 1833 $ hg debugrevspec 'wdir()^1'
1824 1834 7
1825 1835 $ hg debugrevspec 'wdir()^2'
1826 1836 $ hg debugrevspec 'wdir()^3'
1827 1837 hg: parse error: ^ expects a number 0, 1, or 2
1828 1838 [255]
1829 1839 For tests consistency
1830 1840 $ hg up 9
1831 1841 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1832 1842 $ hg debugrevspec 'tip or wdir()'
1833 1843 9
1834 1844 2147483647
1835 1845 $ hg debugrevspec '0:tip and wdir()'
1836 1846 $ log '0:wdir()' | tail -3
1837 1847 8
1838 1848 9
1839 1849 2147483647
1840 1850 $ log 'wdir():0' | head -3
1841 1851 2147483647
1842 1852 9
1843 1853 8
1844 1854 $ log 'wdir():wdir()'
1845 1855 2147483647
1846 1856 $ log '(all() + wdir()) & min(. + wdir())'
1847 1857 9
1848 1858 $ log '(all() + wdir()) & max(. + wdir())'
1849 1859 2147483647
1850 1860 $ log 'first(wdir() + .)'
1851 1861 2147483647
1852 1862 $ log 'last(. + wdir())'
1853 1863 2147483647
1854 1864
1855 1865 Test working-directory integer revision and node id
1856 1866 (BUG: '0:wdir()' is still needed to populate wdir revision)
1857 1867
1858 1868 $ hg debugrevspec '0:wdir() & 2147483647'
1859 1869 2147483647
1860 1870 $ hg debugrevspec '0:wdir() & rev(2147483647)'
1861 1871 2147483647
1862 1872 $ hg debugrevspec '0:wdir() & ffffffffffffffffffffffffffffffffffffffff'
1863 1873 2147483647
1864 1874 $ hg debugrevspec '0:wdir() & ffffffffffff'
1865 1875 2147483647
1866 1876 $ hg debugrevspec '0:wdir() & id(ffffffffffffffffffffffffffffffffffffffff)'
1867 1877 2147483647
1868 1878 $ hg debugrevspec '0:wdir() & id(ffffffffffff)'
1869 1879 2147483647
1870 1880
1871 1881 $ cd ..
1872 1882
1873 1883 Test short 'ff...' hash collision
1874 1884 (BUG: '0:wdir()' is still needed to populate wdir revision)
1875 1885
1876 1886 $ hg init wdir-hashcollision
1877 1887 $ cd wdir-hashcollision
1878 1888 $ cat <<EOF >> .hg/hgrc
1879 1889 > [experimental]
1880 1890 > evolution.createmarkers=True
1881 1891 > EOF
1882 1892 $ echo 0 > a
1883 1893 $ hg ci -qAm 0
1884 1894 $ for i in 2463 2961 6726 78127; do
1885 1895 > hg up -q 0
1886 1896 > echo $i > a
1887 1897 > hg ci -qm $i
1888 1898 > done
1889 1899 $ hg up -q null
1890 1900 $ hg log -r '0:wdir()' -T '{rev}:{node} {shortest(node, 3)}\n'
1891 1901 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a b4e
1892 1902 1:fffbae3886c8fbb2114296380d276fd37715d571 fffba
1893 1903 2:fffb6093b00943f91034b9bdad069402c834e572 fffb6
1894 1904 3:fff48a9b9de34a4d64120c29548214c67980ade3 fff4
1895 1905 4:ffff85cff0ff78504fcdc3c0bc10de0c65379249 ffff8
1896 1906 2147483647:ffffffffffffffffffffffffffffffffffffffff fffff
1897 1907 $ hg debugobsolete fffbae3886c8fbb2114296380d276fd37715d571
1898 1908 obsoleted 1 changesets
1899 1909
1900 1910 $ hg debugrevspec '0:wdir() & fff'
1901 1911 abort: 00changelog.i@fff: ambiguous identifier!
1902 1912 [255]
1903 1913 $ hg debugrevspec '0:wdir() & ffff'
1904 1914 abort: 00changelog.i@ffff: ambiguous identifier!
1905 1915 [255]
1906 1916 $ hg debugrevspec '0:wdir() & fffb'
1907 1917 abort: 00changelog.i@fffb: ambiguous identifier!
1908 1918 [255]
1909 1919 BROKEN should be '2' (node lookup uses unfiltered repo)
1910 1920 $ hg debugrevspec '0:wdir() & id(fffb)'
1911 1921 BROKEN should be '2' (node lookup uses unfiltered repo)
1912 1922 $ hg debugrevspec '0:wdir() & ffff8'
1913 1923 4
1914 1924 $ hg debugrevspec '0:wdir() & fffff'
1915 1925 2147483647
1916 1926
1917 1927 $ cd ..
1918 1928
1919 1929 Test branch() with wdir()
1920 1930
1921 1931 $ cd repo
1922 1932
1923 1933 $ log '0:wdir() & branch("literal:Γ©")'
1924 1934 8
1925 1935 9
1926 1936 2147483647
1927 1937 $ log '0:wdir() & branch("re:Γ©")'
1928 1938 8
1929 1939 9
1930 1940 2147483647
1931 1941 $ log '0:wdir() & branch("re:^a")'
1932 1942 0
1933 1943 2
1934 1944 $ log '0:wdir() & branch(8)'
1935 1945 8
1936 1946 9
1937 1947 2147483647
1938 1948
1939 1949 branch(wdir()) returns all revisions belonging to the working branch. The wdir
1940 1950 itself isn't returned unless it is explicitly populated.
1941 1951
1942 1952 $ log 'branch(wdir())'
1943 1953 8
1944 1954 9
1945 1955 $ log '0:wdir() & branch(wdir())'
1946 1956 8
1947 1957 9
1948 1958 2147483647
1949 1959
1950 1960 $ log 'outgoing()'
1951 1961 8
1952 1962 9
1953 1963 $ log 'outgoing("../remote1")'
1954 1964 8
1955 1965 9
1956 1966 $ log 'outgoing("../remote2")'
1957 1967 3
1958 1968 5
1959 1969 6
1960 1970 7
1961 1971 9
1962 1972 $ log 'p1(merge())'
1963 1973 5
1964 1974 $ log 'p2(merge())'
1965 1975 4
1966 1976 $ log 'parents(merge())'
1967 1977 4
1968 1978 5
1969 1979 $ log 'p1(branchpoint())'
1970 1980 0
1971 1981 2
1972 1982 $ log 'p2(branchpoint())'
1973 1983 $ log 'parents(branchpoint())'
1974 1984 0
1975 1985 2
1976 1986 $ log 'removes(a)'
1977 1987 2
1978 1988 6
1979 1989 $ log 'roots(all())'
1980 1990 0
1981 1991 $ log 'reverse(2 or 3 or 4 or 5)'
1982 1992 5
1983 1993 4
1984 1994 3
1985 1995 2
1986 1996 $ log 'reverse(all())'
1987 1997 9
1988 1998 8
1989 1999 7
1990 2000 6
1991 2001 5
1992 2002 4
1993 2003 3
1994 2004 2
1995 2005 1
1996 2006 0
1997 2007 $ log 'reverse(all()) & filelog(b)'
1998 2008 4
1999 2009 1
2000 2010 $ log 'rev(5)'
2001 2011 5
2002 2012 $ log 'sort(limit(reverse(all()), 3))'
2003 2013 7
2004 2014 8
2005 2015 9
2006 2016 $ log 'sort(2 or 3 or 4 or 5, date)'
2007 2017 2
2008 2018 3
2009 2019 5
2010 2020 4
2011 2021 $ log 'tagged()'
2012 2022 6
2013 2023 $ log 'tag()'
2014 2024 6
2015 2025 $ log 'tag(1.0)'
2016 2026 6
2017 2027 $ log 'tag(tip)'
2018 2028 9
2019 2029
2020 2030 Test order of revisions in compound expression
2021 2031 ----------------------------------------------
2022 2032
2023 2033 The general rule is that only the outermost (= leftmost) predicate can
2024 2034 enforce its ordering requirement. The other predicates should take the
2025 2035 ordering defined by it.
2026 2036
2027 2037 'A & B' should follow the order of 'A':
2028 2038
2029 2039 $ log '2:0 & 0::2'
2030 2040 2
2031 2041 1
2032 2042 0
2033 2043
2034 2044 'head()' combines sets in right order:
2035 2045
2036 2046 $ log '2:0 & head()'
2037 2047 2
2038 2048 1
2039 2049 0
2040 2050
2041 2051 'x:y' takes ordering parameter into account:
2042 2052
2043 2053 $ try -p optimized '3:0 & 0:3 & not 2:1'
2044 2054 * optimized:
2045 2055 (difference
2046 2056 (and
2047 2057 (range
2048 2058 (symbol '3')
2049 2059 (symbol '0'))
2050 2060 (range
2051 2061 (symbol '0')
2052 2062 (symbol '3')))
2053 2063 (range
2054 2064 (symbol '2')
2055 2065 (symbol '1')))
2056 2066 * set:
2057 2067 <filteredset
2058 2068 <filteredset
2059 2069 <spanset- 0:4>,
2060 2070 <spanset+ 0:4>>,
2061 2071 <not
2062 2072 <spanset+ 1:3>>>
2063 2073 3
2064 2074 0
2065 2075
2066 2076 'a + b', which is optimized to '_list(a b)', should take the ordering of
2067 2077 the left expression:
2068 2078
2069 2079 $ try --optimize '2:0 & (0 + 1 + 2)'
2070 2080 (and
2071 2081 (range
2072 2082 (symbol '2')
2073 2083 (symbol '0'))
2074 2084 (group
2075 2085 (or
2076 2086 (list
2077 2087 (symbol '0')
2078 2088 (symbol '1')
2079 2089 (symbol '2')))))
2080 2090 * optimized:
2081 2091 (and
2082 2092 (range
2083 2093 (symbol '2')
2084 2094 (symbol '0'))
2085 2095 (func
2086 2096 (symbol '_list')
2087 2097 (string '0\x001\x002')))
2088 2098 * set:
2089 2099 <filteredset
2090 2100 <spanset- 0:3>,
2091 2101 <baseset [0, 1, 2]>>
2092 2102 2
2093 2103 1
2094 2104 0
2095 2105
2096 2106 'A + B' should take the ordering of the left expression:
2097 2107
2098 2108 $ try --optimize '2:0 & (0:1 + 2)'
2099 2109 (and
2100 2110 (range
2101 2111 (symbol '2')
2102 2112 (symbol '0'))
2103 2113 (group
2104 2114 (or
2105 2115 (list
2106 2116 (range
2107 2117 (symbol '0')
2108 2118 (symbol '1'))
2109 2119 (symbol '2')))))
2110 2120 * optimized:
2111 2121 (and
2112 2122 (range
2113 2123 (symbol '2')
2114 2124 (symbol '0'))
2115 2125 (or
2116 2126 (list
2117 2127 (range
2118 2128 (symbol '0')
2119 2129 (symbol '1'))
2120 2130 (symbol '2'))))
2121 2131 * set:
2122 2132 <filteredset
2123 2133 <spanset- 0:3>,
2124 2134 <addset
2125 2135 <spanset+ 0:2>,
2126 2136 <baseset [2]>>>
2127 2137 2
2128 2138 1
2129 2139 0
2130 2140
2131 2141 '_intlist(a b)' should behave like 'a + b':
2132 2142
2133 2143 $ trylist --optimize '2:0 & %ld' 0 1 2
2134 2144 (and
2135 2145 (range
2136 2146 (symbol '2')
2137 2147 (symbol '0'))
2138 2148 (func
2139 2149 (symbol '_intlist')
2140 2150 (string '0\x001\x002')))
2141 2151 * optimized:
2142 2152 (andsmally
2143 2153 (range
2144 2154 (symbol '2')
2145 2155 (symbol '0'))
2146 2156 (func
2147 2157 (symbol '_intlist')
2148 2158 (string '0\x001\x002')))
2149 2159 * set:
2150 2160 <filteredset
2151 2161 <spanset- 0:3>,
2152 2162 <baseset+ [0, 1, 2]>>
2153 2163 2
2154 2164 1
2155 2165 0
2156 2166
2157 2167 $ trylist --optimize '%ld & 2:0' 0 2 1
2158 2168 (and
2159 2169 (func
2160 2170 (symbol '_intlist')
2161 2171 (string '0\x002\x001'))
2162 2172 (range
2163 2173 (symbol '2')
2164 2174 (symbol '0')))
2165 2175 * optimized:
2166 2176 (and
2167 2177 (func
2168 2178 (symbol '_intlist')
2169 2179 (string '0\x002\x001'))
2170 2180 (range
2171 2181 (symbol '2')
2172 2182 (symbol '0')))
2173 2183 * set:
2174 2184 <filteredset
2175 2185 <baseset [0, 2, 1]>,
2176 2186 <spanset- 0:3>>
2177 2187 0
2178 2188 2
2179 2189 1
2180 2190
2181 2191 '_hexlist(a b)' should behave like 'a + b':
2182 2192
2183 2193 $ trylist --optimize --bin '2:0 & %ln' `hg log -T '{node} ' -r0:2`
2184 2194 (and
2185 2195 (range
2186 2196 (symbol '2')
2187 2197 (symbol '0'))
2188 2198 (func
2189 2199 (symbol '_hexlist')
2190 2200 (string '*'))) (glob)
2191 2201 * optimized:
2192 2202 (and
2193 2203 (range
2194 2204 (symbol '2')
2195 2205 (symbol '0'))
2196 2206 (func
2197 2207 (symbol '_hexlist')
2198 2208 (string '*'))) (glob)
2199 2209 * set:
2200 2210 <filteredset
2201 2211 <spanset- 0:3>,
2202 2212 <baseset [0, 1, 2]>>
2203 2213 2
2204 2214 1
2205 2215 0
2206 2216
2207 2217 $ trylist --optimize --bin '%ln & 2:0' `hg log -T '{node} ' -r0+2+1`
2208 2218 (and
2209 2219 (func
2210 2220 (symbol '_hexlist')
2211 2221 (string '*')) (glob)
2212 2222 (range
2213 2223 (symbol '2')
2214 2224 (symbol '0')))
2215 2225 * optimized:
2216 2226 (andsmally
2217 2227 (func
2218 2228 (symbol '_hexlist')
2219 2229 (string '*')) (glob)
2220 2230 (range
2221 2231 (symbol '2')
2222 2232 (symbol '0')))
2223 2233 * set:
2224 2234 <baseset [0, 2, 1]>
2225 2235 0
2226 2236 2
2227 2237 1
2228 2238
2229 2239 '_list' should not go through the slow follow-order path if order doesn't
2230 2240 matter:
2231 2241
2232 2242 $ try -p optimized '2:0 & not (0 + 1)'
2233 2243 * optimized:
2234 2244 (difference
2235 2245 (range
2236 2246 (symbol '2')
2237 2247 (symbol '0'))
2238 2248 (func
2239 2249 (symbol '_list')
2240 2250 (string '0\x001')))
2241 2251 * set:
2242 2252 <filteredset
2243 2253 <spanset- 0:3>,
2244 2254 <not
2245 2255 <baseset [0, 1]>>>
2246 2256 2
2247 2257
2248 2258 $ try -p optimized '2:0 & not (0:2 & (0 + 1))'
2249 2259 * optimized:
2250 2260 (difference
2251 2261 (range
2252 2262 (symbol '2')
2253 2263 (symbol '0'))
2254 2264 (and
2255 2265 (range
2256 2266 (symbol '0')
2257 2267 (symbol '2'))
2258 2268 (func
2259 2269 (symbol '_list')
2260 2270 (string '0\x001'))))
2261 2271 * set:
2262 2272 <filteredset
2263 2273 <spanset- 0:3>,
2264 2274 <not
2265 2275 <baseset [0, 1]>>>
2266 2276 2
2267 2277
2268 2278 because 'present()' does nothing other than suppressing an error, the
2269 2279 ordering requirement should be forwarded to the nested expression
2270 2280
2271 2281 $ try -p optimized 'present(2 + 0 + 1)'
2272 2282 * optimized:
2273 2283 (func
2274 2284 (symbol 'present')
2275 2285 (func
2276 2286 (symbol '_list')
2277 2287 (string '2\x000\x001')))
2278 2288 * set:
2279 2289 <baseset [2, 0, 1]>
2280 2290 2
2281 2291 0
2282 2292 1
2283 2293
2284 2294 $ try --optimize '2:0 & present(0 + 1 + 2)'
2285 2295 (and
2286 2296 (range
2287 2297 (symbol '2')
2288 2298 (symbol '0'))
2289 2299 (func
2290 2300 (symbol 'present')
2291 2301 (or
2292 2302 (list
2293 2303 (symbol '0')
2294 2304 (symbol '1')
2295 2305 (symbol '2')))))
2296 2306 * optimized:
2297 2307 (and
2298 2308 (range
2299 2309 (symbol '2')
2300 2310 (symbol '0'))
2301 2311 (func
2302 2312 (symbol 'present')
2303 2313 (func
2304 2314 (symbol '_list')
2305 2315 (string '0\x001\x002'))))
2306 2316 * set:
2307 2317 <filteredset
2308 2318 <spanset- 0:3>,
2309 2319 <baseset [0, 1, 2]>>
2310 2320 2
2311 2321 1
2312 2322 0
2313 2323
2314 2324 'reverse()' should take effect only if it is the outermost expression:
2315 2325
2316 2326 $ try --optimize '0:2 & reverse(all())'
2317 2327 (and
2318 2328 (range
2319 2329 (symbol '0')
2320 2330 (symbol '2'))
2321 2331 (func
2322 2332 (symbol 'reverse')
2323 2333 (func
2324 2334 (symbol 'all')
2325 2335 None)))
2326 2336 * optimized:
2327 2337 (and
2328 2338 (range
2329 2339 (symbol '0')
2330 2340 (symbol '2'))
2331 2341 (func
2332 2342 (symbol 'reverse')
2333 2343 (func
2334 2344 (symbol 'all')
2335 2345 None)))
2336 2346 * set:
2337 2347 <filteredset
2338 2348 <spanset+ 0:3>,
2339 2349 <spanset+ 0:10>>
2340 2350 0
2341 2351 1
2342 2352 2
2343 2353
2344 2354 'sort()' should take effect only if it is the outermost expression:
2345 2355
2346 2356 $ try --optimize '0:2 & sort(all(), -rev)'
2347 2357 (and
2348 2358 (range
2349 2359 (symbol '0')
2350 2360 (symbol '2'))
2351 2361 (func
2352 2362 (symbol 'sort')
2353 2363 (list
2354 2364 (func
2355 2365 (symbol 'all')
2356 2366 None)
2357 2367 (negate
2358 2368 (symbol 'rev')))))
2359 2369 * optimized:
2360 2370 (and
2361 2371 (range
2362 2372 (symbol '0')
2363 2373 (symbol '2'))
2364 2374 (func
2365 2375 (symbol 'sort')
2366 2376 (list
2367 2377 (func
2368 2378 (symbol 'all')
2369 2379 None)
2370 2380 (string '-rev'))))
2371 2381 * set:
2372 2382 <filteredset
2373 2383 <spanset+ 0:3>,
2374 2384 <spanset+ 0:10>>
2375 2385 0
2376 2386 1
2377 2387 2
2378 2388
2379 2389 invalid argument passed to noop sort():
2380 2390
2381 2391 $ log '0:2 & sort()'
2382 2392 hg: parse error: sort requires one or two arguments
2383 2393 [255]
2384 2394 $ log '0:2 & sort(all(), -invalid)'
2385 2395 hg: parse error: unknown sort key '-invalid'
2386 2396 [255]
2387 2397
2388 2398 for 'A & f(B)', 'B' should not be affected by the order of 'A':
2389 2399
2390 2400 $ try --optimize '2:0 & first(1 + 0 + 2)'
2391 2401 (and
2392 2402 (range
2393 2403 (symbol '2')
2394 2404 (symbol '0'))
2395 2405 (func
2396 2406 (symbol 'first')
2397 2407 (or
2398 2408 (list
2399 2409 (symbol '1')
2400 2410 (symbol '0')
2401 2411 (symbol '2')))))
2402 2412 * optimized:
2403 2413 (and
2404 2414 (range
2405 2415 (symbol '2')
2406 2416 (symbol '0'))
2407 2417 (func
2408 2418 (symbol 'first')
2409 2419 (func
2410 2420 (symbol '_list')
2411 2421 (string '1\x000\x002'))))
2412 2422 * set:
2413 2423 <filteredset
2414 2424 <baseset [1]>,
2415 2425 <spanset- 0:3>>
2416 2426 1
2417 2427
2418 2428 $ try --optimize '2:0 & not last(0 + 2 + 1)'
2419 2429 (and
2420 2430 (range
2421 2431 (symbol '2')
2422 2432 (symbol '0'))
2423 2433 (not
2424 2434 (func
2425 2435 (symbol 'last')
2426 2436 (or
2427 2437 (list
2428 2438 (symbol '0')
2429 2439 (symbol '2')
2430 2440 (symbol '1'))))))
2431 2441 * optimized:
2432 2442 (difference
2433 2443 (range
2434 2444 (symbol '2')
2435 2445 (symbol '0'))
2436 2446 (func
2437 2447 (symbol 'last')
2438 2448 (func
2439 2449 (symbol '_list')
2440 2450 (string '0\x002\x001'))))
2441 2451 * set:
2442 2452 <filteredset
2443 2453 <spanset- 0:3>,
2444 2454 <not
2445 2455 <baseset [1]>>>
2446 2456 2
2447 2457 0
2448 2458
2449 2459 for 'A & (op)(B)', 'B' should not be affected by the order of 'A':
2450 2460
2451 2461 $ try --optimize '2:0 & (1 + 0 + 2):(0 + 2 + 1)'
2452 2462 (and
2453 2463 (range
2454 2464 (symbol '2')
2455 2465 (symbol '0'))
2456 2466 (range
2457 2467 (group
2458 2468 (or
2459 2469 (list
2460 2470 (symbol '1')
2461 2471 (symbol '0')
2462 2472 (symbol '2'))))
2463 2473 (group
2464 2474 (or
2465 2475 (list
2466 2476 (symbol '0')
2467 2477 (symbol '2')
2468 2478 (symbol '1'))))))
2469 2479 * optimized:
2470 2480 (and
2471 2481 (range
2472 2482 (symbol '2')
2473 2483 (symbol '0'))
2474 2484 (range
2475 2485 (func
2476 2486 (symbol '_list')
2477 2487 (string '1\x000\x002'))
2478 2488 (func
2479 2489 (symbol '_list')
2480 2490 (string '0\x002\x001'))))
2481 2491 * set:
2482 2492 <filteredset
2483 2493 <spanset- 0:3>,
2484 2494 <baseset [1]>>
2485 2495 1
2486 2496
2487 2497 'A & B' can be rewritten as 'flipand(B, A)' by weight.
2488 2498
2489 2499 $ try --optimize 'contains("glob:*") & (2 + 0 + 1)'
2490 2500 (and
2491 2501 (func
2492 2502 (symbol 'contains')
2493 2503 (string 'glob:*'))
2494 2504 (group
2495 2505 (or
2496 2506 (list
2497 2507 (symbol '2')
2498 2508 (symbol '0')
2499 2509 (symbol '1')))))
2500 2510 * optimized:
2501 2511 (andsmally
2502 2512 (func
2503 2513 (symbol 'contains')
2504 2514 (string 'glob:*'))
2505 2515 (func
2506 2516 (symbol '_list')
2507 2517 (string '2\x000\x001')))
2508 2518 * set:
2509 2519 <filteredset
2510 2520 <baseset+ [0, 1, 2]>,
2511 2521 <contains 'glob:*'>>
2512 2522 0
2513 2523 1
2514 2524 2
2515 2525
2516 2526 and in this example, 'A & B' is rewritten as 'B & A', but 'A' overrides
2517 2527 the order appropriately:
2518 2528
2519 2529 $ try --optimize 'reverse(contains("glob:*")) & (0 + 2 + 1)'
2520 2530 (and
2521 2531 (func
2522 2532 (symbol 'reverse')
2523 2533 (func
2524 2534 (symbol 'contains')
2525 2535 (string 'glob:*')))
2526 2536 (group
2527 2537 (or
2528 2538 (list
2529 2539 (symbol '0')
2530 2540 (symbol '2')
2531 2541 (symbol '1')))))
2532 2542 * optimized:
2533 2543 (andsmally
2534 2544 (func
2535 2545 (symbol 'reverse')
2536 2546 (func
2537 2547 (symbol 'contains')
2538 2548 (string 'glob:*')))
2539 2549 (func
2540 2550 (symbol '_list')
2541 2551 (string '0\x002\x001')))
2542 2552 * set:
2543 2553 <filteredset
2544 2554 <baseset- [0, 1, 2]>,
2545 2555 <contains 'glob:*'>>
2546 2556 2
2547 2557 1
2548 2558 0
2549 2559
2550 2560 test sort revset
2551 2561 --------------------------------------------
2552 2562
2553 2563 test when adding two unordered revsets
2554 2564
2555 2565 $ log 'sort(keyword(issue) or modifies(b))'
2556 2566 4
2557 2567 6
2558 2568
2559 2569 test when sorting a reversed collection in the same way it is
2560 2570
2561 2571 $ log 'sort(reverse(all()), -rev)'
2562 2572 9
2563 2573 8
2564 2574 7
2565 2575 6
2566 2576 5
2567 2577 4
2568 2578 3
2569 2579 2
2570 2580 1
2571 2581 0
2572 2582
2573 2583 test when sorting a reversed collection
2574 2584
2575 2585 $ log 'sort(reverse(all()), rev)'
2576 2586 0
2577 2587 1
2578 2588 2
2579 2589 3
2580 2590 4
2581 2591 5
2582 2592 6
2583 2593 7
2584 2594 8
2585 2595 9
2586 2596
2587 2597
2588 2598 test sorting two sorted collections in different orders
2589 2599
2590 2600 $ log 'sort(outgoing() or reverse(removes(a)), rev)'
2591 2601 2
2592 2602 6
2593 2603 8
2594 2604 9
2595 2605
2596 2606 test sorting two sorted collections in different orders backwards
2597 2607
2598 2608 $ log 'sort(outgoing() or reverse(removes(a)), -rev)'
2599 2609 9
2600 2610 8
2601 2611 6
2602 2612 2
2603 2613
2604 2614 test empty sort key which is noop
2605 2615
2606 2616 $ log 'sort(0 + 2 + 1, "")'
2607 2617 0
2608 2618 2
2609 2619 1
2610 2620
2611 2621 test invalid sort keys
2612 2622
2613 2623 $ log 'sort(all(), -invalid)'
2614 2624 hg: parse error: unknown sort key '-invalid'
2615 2625 [255]
2616 2626
2617 2627 $ cd ..
2618 2628
2619 2629 test sorting by multiple keys including variable-length strings
2620 2630
2621 2631 $ hg init sorting
2622 2632 $ cd sorting
2623 2633 $ cat <<EOF >> .hg/hgrc
2624 2634 > [ui]
2625 2635 > logtemplate = '{rev} {branch|p5}{desc|p5}{author|p5}{date|hgdate}\n'
2626 2636 > [templatealias]
2627 2637 > p5(s) = pad(s, 5)
2628 2638 > EOF
2629 2639 $ hg branch -qf b12
2630 2640 $ hg ci -m m111 -u u112 -d '111 10800'
2631 2641 $ hg branch -qf b11
2632 2642 $ hg ci -m m12 -u u111 -d '112 7200'
2633 2643 $ hg branch -qf b111
2634 2644 $ hg ci -m m11 -u u12 -d '111 3600'
2635 2645 $ hg branch -qf b112
2636 2646 $ hg ci -m m111 -u u11 -d '120 0'
2637 2647 $ hg branch -qf b111
2638 2648 $ hg ci -m m112 -u u111 -d '110 14400'
2639 2649 created new head
2640 2650
2641 2651 compare revisions (has fast path):
2642 2652
2643 2653 $ hg log -r 'sort(all(), rev)'
2644 2654 0 b12 m111 u112 111 10800
2645 2655 1 b11 m12 u111 112 7200
2646 2656 2 b111 m11 u12 111 3600
2647 2657 3 b112 m111 u11 120 0
2648 2658 4 b111 m112 u111 110 14400
2649 2659
2650 2660 $ hg log -r 'sort(all(), -rev)'
2651 2661 4 b111 m112 u111 110 14400
2652 2662 3 b112 m111 u11 120 0
2653 2663 2 b111 m11 u12 111 3600
2654 2664 1 b11 m12 u111 112 7200
2655 2665 0 b12 m111 u112 111 10800
2656 2666
2657 2667 compare variable-length strings (issue5218):
2658 2668
2659 2669 $ hg log -r 'sort(all(), branch)'
2660 2670 1 b11 m12 u111 112 7200
2661 2671 2 b111 m11 u12 111 3600
2662 2672 4 b111 m112 u111 110 14400
2663 2673 3 b112 m111 u11 120 0
2664 2674 0 b12 m111 u112 111 10800
2665 2675
2666 2676 $ hg log -r 'sort(all(), -branch)'
2667 2677 0 b12 m111 u112 111 10800
2668 2678 3 b112 m111 u11 120 0
2669 2679 2 b111 m11 u12 111 3600
2670 2680 4 b111 m112 u111 110 14400
2671 2681 1 b11 m12 u111 112 7200
2672 2682
2673 2683 $ hg log -r 'sort(all(), desc)'
2674 2684 2 b111 m11 u12 111 3600
2675 2685 0 b12 m111 u112 111 10800
2676 2686 3 b112 m111 u11 120 0
2677 2687 4 b111 m112 u111 110 14400
2678 2688 1 b11 m12 u111 112 7200
2679 2689
2680 2690 $ hg log -r 'sort(all(), -desc)'
2681 2691 1 b11 m12 u111 112 7200
2682 2692 4 b111 m112 u111 110 14400
2683 2693 0 b12 m111 u112 111 10800
2684 2694 3 b112 m111 u11 120 0
2685 2695 2 b111 m11 u12 111 3600
2686 2696
2687 2697 $ hg log -r 'sort(all(), user)'
2688 2698 3 b112 m111 u11 120 0
2689 2699 1 b11 m12 u111 112 7200
2690 2700 4 b111 m112 u111 110 14400
2691 2701 0 b12 m111 u112 111 10800
2692 2702 2 b111 m11 u12 111 3600
2693 2703
2694 2704 $ hg log -r 'sort(all(), -user)'
2695 2705 2 b111 m11 u12 111 3600
2696 2706 0 b12 m111 u112 111 10800
2697 2707 1 b11 m12 u111 112 7200
2698 2708 4 b111 m112 u111 110 14400
2699 2709 3 b112 m111 u11 120 0
2700 2710
2701 2711 compare dates (tz offset should have no effect):
2702 2712
2703 2713 $ hg log -r 'sort(all(), date)'
2704 2714 4 b111 m112 u111 110 14400
2705 2715 0 b12 m111 u112 111 10800
2706 2716 2 b111 m11 u12 111 3600
2707 2717 1 b11 m12 u111 112 7200
2708 2718 3 b112 m111 u11 120 0
2709 2719
2710 2720 $ hg log -r 'sort(all(), -date)'
2711 2721 3 b112 m111 u11 120 0
2712 2722 1 b11 m12 u111 112 7200
2713 2723 0 b12 m111 u112 111 10800
2714 2724 2 b111 m11 u12 111 3600
2715 2725 4 b111 m112 u111 110 14400
2716 2726
2717 2727 be aware that 'sort(x, -k)' is not exactly the same as 'reverse(sort(x, k))'
2718 2728 because '-k' reverses the comparison, not the list itself:
2719 2729
2720 2730 $ hg log -r 'sort(0 + 2, date)'
2721 2731 0 b12 m111 u112 111 10800
2722 2732 2 b111 m11 u12 111 3600
2723 2733
2724 2734 $ hg log -r 'sort(0 + 2, -date)'
2725 2735 0 b12 m111 u112 111 10800
2726 2736 2 b111 m11 u12 111 3600
2727 2737
2728 2738 $ hg log -r 'reverse(sort(0 + 2, date))'
2729 2739 2 b111 m11 u12 111 3600
2730 2740 0 b12 m111 u112 111 10800
2731 2741
2732 2742 sort by multiple keys:
2733 2743
2734 2744 $ hg log -r 'sort(all(), "branch -rev")'
2735 2745 1 b11 m12 u111 112 7200
2736 2746 4 b111 m112 u111 110 14400
2737 2747 2 b111 m11 u12 111 3600
2738 2748 3 b112 m111 u11 120 0
2739 2749 0 b12 m111 u112 111 10800
2740 2750
2741 2751 $ hg log -r 'sort(all(), "-desc -date")'
2742 2752 1 b11 m12 u111 112 7200
2743 2753 4 b111 m112 u111 110 14400
2744 2754 3 b112 m111 u11 120 0
2745 2755 0 b12 m111 u112 111 10800
2746 2756 2 b111 m11 u12 111 3600
2747 2757
2748 2758 $ hg log -r 'sort(all(), "user -branch date rev")'
2749 2759 3 b112 m111 u11 120 0
2750 2760 4 b111 m112 u111 110 14400
2751 2761 1 b11 m12 u111 112 7200
2752 2762 0 b12 m111 u112 111 10800
2753 2763 2 b111 m11 u12 111 3600
2754 2764
2755 2765 toposort prioritises graph branches
2756 2766
2757 2767 $ hg up 2
2758 2768 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
2759 2769 $ touch a
2760 2770 $ hg addremove
2761 2771 adding a
2762 2772 $ hg ci -m 't1' -u 'tu' -d '130 0'
2763 2773 created new head
2764 2774 $ echo 'a' >> a
2765 2775 $ hg ci -m 't2' -u 'tu' -d '130 0'
2766 2776 $ hg book book1
2767 2777 $ hg up 4
2768 2778 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
2769 2779 (leaving bookmark book1)
2770 2780 $ touch a
2771 2781 $ hg addremove
2772 2782 adding a
2773 2783 $ hg ci -m 't3' -u 'tu' -d '130 0'
2774 2784
2775 2785 $ hg log -r 'sort(all(), topo)'
2776 2786 7 b111 t3 tu 130 0
2777 2787 4 b111 m112 u111 110 14400
2778 2788 3 b112 m111 u11 120 0
2779 2789 6 b111 t2 tu 130 0
2780 2790 5 b111 t1 tu 130 0
2781 2791 2 b111 m11 u12 111 3600
2782 2792 1 b11 m12 u111 112 7200
2783 2793 0 b12 m111 u112 111 10800
2784 2794
2785 2795 $ hg log -r 'sort(all(), -topo)'
2786 2796 0 b12 m111 u112 111 10800
2787 2797 1 b11 m12 u111 112 7200
2788 2798 2 b111 m11 u12 111 3600
2789 2799 5 b111 t1 tu 130 0
2790 2800 6 b111 t2 tu 130 0
2791 2801 3 b112 m111 u11 120 0
2792 2802 4 b111 m112 u111 110 14400
2793 2803 7 b111 t3 tu 130 0
2794 2804
2795 2805 $ hg log -r 'sort(all(), topo, topo.firstbranch=book1)'
2796 2806 6 b111 t2 tu 130 0
2797 2807 5 b111 t1 tu 130 0
2798 2808 7 b111 t3 tu 130 0
2799 2809 4 b111 m112 u111 110 14400
2800 2810 3 b112 m111 u11 120 0
2801 2811 2 b111 m11 u12 111 3600
2802 2812 1 b11 m12 u111 112 7200
2803 2813 0 b12 m111 u112 111 10800
2804 2814
2805 2815 topographical sorting can't be combined with other sort keys, and you can't
2806 2816 use the topo.firstbranch option when topo sort is not active:
2807 2817
2808 2818 $ hg log -r 'sort(all(), "topo user")'
2809 2819 hg: parse error: topo sort order cannot be combined with other sort keys
2810 2820 [255]
2811 2821
2812 2822 $ hg log -r 'sort(all(), user, topo.firstbranch=book1)'
2813 2823 hg: parse error: topo.firstbranch can only be used when using the topo sort key
2814 2824 [255]
2815 2825
2816 2826 topo.firstbranch should accept any kind of expressions:
2817 2827
2818 2828 $ hg log -r 'sort(0, topo, topo.firstbranch=(book1))'
2819 2829 0 b12 m111 u112 111 10800
2820 2830
2821 2831 $ cd ..
2822 2832 $ cd repo
2823 2833
2824 2834 test multiline revset with errors
2825 2835
2826 2836 $ echo > multiline-revset
2827 2837 $ echo '. +' >> multiline-revset
2828 2838 $ echo '.^ +' >> multiline-revset
2829 2839 $ hg log -r "`cat multiline-revset`"
2830 2840 hg: parse error at 9: not a prefix: end
2831 2841 ( . + .^ +
2832 2842 ^ here)
2833 2843 [255]
2834 2844 $ hg debugrevspec -v 'revset(first(rev(0)))' -p all
2835 2845 * parsed:
2836 2846 (func
2837 2847 (symbol 'revset')
2838 2848 (func
2839 2849 (symbol 'first')
2840 2850 (func
2841 2851 (symbol 'rev')
2842 2852 (symbol '0'))))
2843 2853 * expanded:
2844 2854 (func
2845 2855 (symbol 'revset')
2846 2856 (func
2847 2857 (symbol 'first')
2848 2858 (func
2849 2859 (symbol 'rev')
2850 2860 (symbol '0'))))
2851 2861 * concatenated:
2852 2862 (func
2853 2863 (symbol 'revset')
2854 2864 (func
2855 2865 (symbol 'first')
2856 2866 (func
2857 2867 (symbol 'rev')
2858 2868 (symbol '0'))))
2859 2869 * analyzed:
2860 2870 (func
2861 2871 (symbol 'first')
2862 2872 (func
2863 2873 (symbol 'rev')
2864 2874 (symbol '0')))
2865 2875 * optimized:
2866 2876 (func
2867 2877 (symbol 'first')
2868 2878 (func
2869 2879 (symbol 'rev')
2870 2880 (symbol '0')))
2871 2881 * set:
2872 2882 <baseset+ [0]>
2873 2883 0
General Comments 0
You need to be logged in to leave comments. Login now