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