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