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