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