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