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