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