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