mercurial/revset.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43089 c59eb1560c44
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    91 #           ^^^^^^
    91 #           ^^^^^^
    92 #      For most revsets, 'define' means using the order this subset provides
    92 #      For most revsets, 'define' means using the order this subset provides
    93 #
    93 #
    94 # There are a few revsets that always redefine the order if 'define' is
    94 # There are a few revsets that always redefine the order if 'define' is
    95 # specified: 'sort(X)', 'reverse(X)', 'x:y'.
    95 # specified: 'sort(X)', 'reverse(X)', 'x:y'.
    96 anyorder = 'any'  # don't care the order, could be even random-shuffled
    96 anyorder = b'any'  # don't care the order, could be even random-shuffled
    97 defineorder = 'define'  # ALWAYS redefine, or ALWAYS follow the current order
    97 defineorder = b'define'  # ALWAYS redefine, or ALWAYS follow the current order
    98 followorder = 'follow'  # MUST follow the current order
    98 followorder = b'follow'  # MUST follow the current order
    99 
    99 
   100 # helpers
   100 # helpers
   101 
   101 
   102 
   102 
   103 def getset(repo, subset, x, order=defineorder):
   103 def getset(repo, subset, x, order=defineorder):
   104     if not x:
   104     if not x:
   105         raise error.ParseError(_("missing argument"))
   105         raise error.ParseError(_(b"missing argument"))
   106     return methods[x[0]](repo, subset, *x[1:], order=order)
   106     return methods[x[0]](repo, subset, *x[1:], order=order)
   107 
   107 
   108 
   108 
   109 def _getrevsource(repo, r):
   109 def _getrevsource(repo, r):
   110     extra = repo[r].extra()
   110     extra = repo[r].extra()
   111     for label in ('source', 'transplant_source', 'rebase_source'):
   111     for label in (b'source', b'transplant_source', b'rebase_source'):
   112         if label in extra:
   112         if label in extra:
   113             try:
   113             try:
   114                 return repo[extra[label]].rev()
   114                 return repo[extra[label]].rev()
   115             except error.RepoLookupError:
   115             except error.RepoLookupError:
   116                 pass
   116                 pass
   124 # operator methods
   124 # operator methods
   125 
   125 
   126 
   126 
   127 def stringset(repo, subset, x, order):
   127 def stringset(repo, subset, x, order):
   128     if not x:
   128     if not x:
   129         raise error.ParseError(_("empty string is not a valid revision"))
   129         raise error.ParseError(_(b"empty string is not a valid revision"))
   130     x = scmutil.intrev(scmutil.revsymbol(repo, x))
   130     x = scmutil.intrev(scmutil.revsymbol(repo, x))
   131     if x in subset or x in _virtualrevs and isinstance(subset, fullreposet):
   131     if x in subset or x in _virtualrevs and isinstance(subset, fullreposet):
   132         return baseset([x])
   132         return baseset([x])
   133     return baseset()
   133     return baseset()
   134 
   134 
   244 def notset(repo, subset, x, order):
   244 def notset(repo, subset, x, order):
   245     return subset - getset(repo, subset, x, anyorder)
   245     return subset - getset(repo, subset, x, anyorder)
   246 
   246 
   247 
   247 
   248 def relationset(repo, subset, x, y, order):
   248 def relationset(repo, subset, x, y, order):
   249     raise error.ParseError(_("can't use a relation in this context"))
   249     raise error.ParseError(_(b"can't use a relation in this context"))
   250 
   250 
   251 
   251 
   252 def _splitrange(a, b):
   252 def _splitrange(a, b):
   253     """Split range with bounds a and b into two ranges at 0 and return two
   253     """Split range with bounds a and b into two ranges at 0 and return two
   254     tuples of numbers for use as startdepth and stopdepth arguments of
   254     tuples of numbers for use as startdepth and stopdepth arguments of
   283 def generationsrel(repo, subset, x, rel, z, order):
   283 def generationsrel(repo, subset, x, rel, z, order):
   284     # TODO: rewrite tests, and drop startdepth argument from ancestors() and
   284     # TODO: rewrite tests, and drop startdepth argument from ancestors() and
   285     # descendants() predicates
   285     # descendants() predicates
   286     a, b = getintrange(
   286     a, b = getintrange(
   287         z,
   287         z,
   288         _('relation subscript must be an integer or a range'),
   288         _(b'relation subscript must be an integer or a range'),
   289         _('relation subscript bounds must be integers'),
   289         _(b'relation subscript bounds must be integers'),
   290         deffirst=-(dagop.maxlogdepth - 1),
   290         deffirst=-(dagop.maxlogdepth - 1),
   291         deflast=+(dagop.maxlogdepth - 1),
   291         deflast=+(dagop.maxlogdepth - 1),
   292     )
   292     )
   293     (ancstart, ancstop), (descstart, descstop) = _splitrange(a, b)
   293     (ancstart, ancstop), (descstart, descstop) = _splitrange(a, b)
   294 
   294 
   321     relnames = [r for r in subscriptrelations.keys() if len(r) > 1]
   321     relnames = [r for r in subscriptrelations.keys() if len(r) > 1]
   322     raise error.UnknownIdentifier(rel, relnames)
   322     raise error.UnknownIdentifier(rel, relnames)
   323 
   323 
   324 
   324 
   325 def subscriptset(repo, subset, x, y, order):
   325 def subscriptset(repo, subset, x, y, order):
   326     raise error.ParseError(_("can't use a subscript in this context"))
   326     raise error.ParseError(_(b"can't use a subscript in this context"))
   327 
   327 
   328 
   328 
   329 def listset(repo, subset, *xs, **opts):
   329 def listset(repo, subset, *xs, **opts):
   330     raise error.ParseError(
   330     raise error.ParseError(
   331         _("can't use a list in this context"),
   331         _(b"can't use a list in this context"),
   332         hint=_('see \'hg help "revsets.x or y"\''),
   332         hint=_(b'see \'hg help "revsets.x or y"\''),
   333     )
   333     )
   334 
   334 
   335 
   335 
   336 def keyvaluepair(repo, subset, k, v, order):
   336 def keyvaluepair(repo, subset, k, v, order):
   337     raise error.ParseError(_("can't use a key-value pair in this context"))
   337     raise error.ParseError(_(b"can't use a key-value pair in this context"))
   338 
   338 
   339 
   339 
   340 def func(repo, subset, a, b, order):
   340 def func(repo, subset, a, b, order):
   341     f = getsymbol(a)
   341     f = getsymbol(a)
   342     if f in symbols:
   342     if f in symbols:
   367 safesymbols = set()
   367 safesymbols = set()
   368 
   368 
   369 predicate = registrar.revsetpredicate()
   369 predicate = registrar.revsetpredicate()
   370 
   370 
   371 
   371 
   372 @predicate('_destupdate')
   372 @predicate(b'_destupdate')
   373 def _destupdate(repo, subset, x):
   373 def _destupdate(repo, subset, x):
   374     # experimental revset for update destination
   374     # experimental revset for update destination
   375     args = getargsdict(x, 'limit', 'clean')
   375     args = getargsdict(x, b'limit', b'clean')
   376     return subset & baseset(
   376     return subset & baseset(
   377         [destutil.destupdate(repo, **pycompat.strkwargs(args))[0]]
   377         [destutil.destupdate(repo, **pycompat.strkwargs(args))[0]]
   378     )
   378     )
   379 
   379 
   380 
   380 
   381 @predicate('_destmerge')
   381 @predicate(b'_destmerge')
   382 def _destmerge(repo, subset, x):
   382 def _destmerge(repo, subset, x):
   383     # experimental revset for merge destination
   383     # experimental revset for merge destination
   384     sourceset = None
   384     sourceset = None
   385     if x is not None:
   385     if x is not None:
   386         sourceset = getset(repo, fullreposet(repo), x)
   386         sourceset = getset(repo, fullreposet(repo), x)
   387     return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
   387     return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
   388 
   388 
   389 
   389 
   390 @predicate('adds(pattern)', safe=True, weight=30)
   390 @predicate(b'adds(pattern)', safe=True, weight=30)
   391 def adds(repo, subset, x):
   391 def adds(repo, subset, x):
   392     """Changesets that add a file matching pattern.
   392     """Changesets that add a file matching pattern.
   393 
   393 
   394     The pattern without explicit kind like ``glob:`` is expected to be
   394     The pattern without explicit kind like ``glob:`` is expected to be
   395     relative to the current directory and match against a file or a
   395     relative to the current directory and match against a file or a
   396     directory.
   396     directory.
   397     """
   397     """
   398     # i18n: "adds" is a keyword
   398     # i18n: "adds" is a keyword
   399     pat = getstring(x, _("adds requires a pattern"))
   399     pat = getstring(x, _(b"adds requires a pattern"))
   400     return checkstatus(repo, subset, pat, 1)
   400     return checkstatus(repo, subset, pat, 1)
   401 
   401 
   402 
   402 
   403 @predicate('ancestor(*changeset)', safe=True, weight=0.5)
   403 @predicate(b'ancestor(*changeset)', safe=True, weight=0.5)
   404 def ancestor(repo, subset, x):
   404 def ancestor(repo, subset, x):
   405     """A greatest common ancestor of the changesets.
   405     """A greatest common ancestor of the changesets.
   406 
   406 
   407     Accepts 0 or more changesets.
   407     Accepts 0 or more changesets.
   408     Will return empty list when passed no args.
   408     Will return empty list when passed no args.
   430         return baseset()
   430         return baseset()
   431     s = dagop.revancestors(repo, heads, followfirst, startdepth, stopdepth)
   431     s = dagop.revancestors(repo, heads, followfirst, startdepth, stopdepth)
   432     return subset & s
   432     return subset & s
   433 
   433 
   434 
   434 
   435 @predicate('ancestors(set[, depth])', safe=True)
   435 @predicate(b'ancestors(set[, depth])', safe=True)
   436 def ancestors(repo, subset, x):
   436 def ancestors(repo, subset, x):
   437     """Changesets that are ancestors of changesets in set, including the
   437     """Changesets that are ancestors of changesets in set, including the
   438     given changesets themselves.
   438     given changesets themselves.
   439 
   439 
   440     If depth is specified, the result only includes changesets up to
   440     If depth is specified, the result only includes changesets up to
   441     the specified generation.
   441     the specified generation.
   442     """
   442     """
   443     # startdepth is for internal use only until we can decide the UI
   443     # startdepth is for internal use only until we can decide the UI
   444     args = getargsdict(x, 'ancestors', 'set depth startdepth')
   444     args = getargsdict(x, b'ancestors', b'set depth startdepth')
   445     if 'set' not in args:
   445     if b'set' not in args:
   446         # i18n: "ancestors" is a keyword
   446         # i18n: "ancestors" is a keyword
   447         raise error.ParseError(_('ancestors takes at least 1 argument'))
   447         raise error.ParseError(_(b'ancestors takes at least 1 argument'))
   448     startdepth = stopdepth = None
   448     startdepth = stopdepth = None
   449     if 'startdepth' in args:
   449     if b'startdepth' in args:
   450         n = getinteger(
   450         n = getinteger(
   451             args['startdepth'], "ancestors expects an integer startdepth"
   451             args[b'startdepth'], b"ancestors expects an integer startdepth"
   452         )
   452         )
   453         if n < 0:
   453         if n < 0:
   454             raise error.ParseError("negative startdepth")
   454             raise error.ParseError(b"negative startdepth")
   455         startdepth = n
   455         startdepth = n
   456     if 'depth' in args:
   456     if b'depth' in args:
   457         # i18n: "ancestors" is a keyword
   457         # i18n: "ancestors" is a keyword
   458         n = getinteger(args['depth'], _("ancestors expects an integer depth"))
   458         n = getinteger(args[b'depth'], _(b"ancestors expects an integer depth"))
   459         if n < 0:
   459         if n < 0:
   460             raise error.ParseError(_("negative depth"))
   460             raise error.ParseError(_(b"negative depth"))
   461         stopdepth = n + 1
   461         stopdepth = n + 1
   462     return _ancestors(
   462     return _ancestors(
   463         repo, subset, args['set'], startdepth=startdepth, stopdepth=stopdepth
   463         repo, subset, args[b'set'], startdepth=startdepth, stopdepth=stopdepth
   464     )
   464     )
   465 
   465 
   466 
   466 
   467 @predicate('_firstancestors', safe=True)
   467 @predicate(b'_firstancestors', safe=True)
   468 def _firstancestors(repo, subset, x):
   468 def _firstancestors(repo, subset, x):
   469     # ``_firstancestors(set)``
   469     # ``_firstancestors(set)``
   470     # Like ``ancestors(set)`` but follows only the first parents.
   470     # Like ``ancestors(set)`` but follows only the first parents.
   471     return _ancestors(repo, subset, x, followfirst=True)
   471     return _ancestors(repo, subset, x, followfirst=True)
   472 
   472 
   481             c = repo[r].children()
   481             c = repo[r].children()
   482             if len(c) == 0:
   482             if len(c) == 0:
   483                 break
   483                 break
   484             if len(c) > 1:
   484             if len(c) > 1:
   485                 raise error.RepoLookupError(
   485                 raise error.RepoLookupError(
   486                     _("revision in set has more than one child")
   486                     _(b"revision in set has more than one child")
   487                 )
   487                 )
   488             r = c[0].rev()
   488             r = c[0].rev()
   489         else:
   489         else:
   490             cs.add(r)
   490             cs.add(r)
   491     return subset & cs
   491     return subset & cs
   494 def ancestorspec(repo, subset, x, n, order):
   494 def ancestorspec(repo, subset, x, n, order):
   495     """``set~n``
   495     """``set~n``
   496     Changesets that are the Nth ancestor (first parents only) of a changeset
   496     Changesets that are the Nth ancestor (first parents only) of a changeset
   497     in set.
   497     in set.
   498     """
   498     """
   499     n = getinteger(n, _("~ expects a number"))
   499     n = getinteger(n, _(b"~ expects a number"))
   500     if n < 0:
   500     if n < 0:
   501         # children lookup
   501         # children lookup
   502         return _childrenspec(repo, subset, x, -n, order)
   502         return _childrenspec(repo, subset, x, -n, order)
   503     ps = set()
   503     ps = set()
   504     cl = repo.changelog
   504     cl = repo.changelog
   510                 r = repo[r].p1().rev()
   510                 r = repo[r].p1().rev()
   511         ps.add(r)
   511         ps.add(r)
   512     return subset & ps
   512     return subset & ps
   513 
   513 
   514 
   514 
   515 @predicate('author(string)', safe=True, weight=10)
   515 @predicate(b'author(string)', safe=True, weight=10)
   516 def author(repo, subset, x):
   516 def author(repo, subset, x):
   517     """Alias for ``user(string)``.
   517     """Alias for ``user(string)``.
   518     """
   518     """
   519     # i18n: "author" is a keyword
   519     # i18n: "author" is a keyword
   520     n = getstring(x, _("author requires a string"))
   520     n = getstring(x, _(b"author requires a string"))
   521     kind, pattern, matcher = _substringmatcher(n, casesensitive=False)
   521     kind, pattern, matcher = _substringmatcher(n, casesensitive=False)
   522     return subset.filter(
   522     return subset.filter(
   523         lambda x: matcher(repo[x].user()), condrepr=('<user %r>', n)
   523         lambda x: matcher(repo[x].user()), condrepr=(b'<user %r>', n)
   524     )
   524     )
   525 
   525 
   526 
   526 
   527 @predicate('bisect(string)', safe=True)
   527 @predicate(b'bisect(string)', safe=True)
   528 def bisect(repo, subset, x):
   528 def bisect(repo, subset, x):
   529     """Changesets marked in the specified bisect status:
   529     """Changesets marked in the specified bisect status:
   530 
   530 
   531     - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
   531     - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
   532     - ``goods``, ``bads``      : csets topologically good/bad
   532     - ``goods``, ``bads``      : csets topologically good/bad
   535     - ``untested``           : csets whose fate is yet unknown
   535     - ``untested``           : csets whose fate is yet unknown
   536     - ``ignored``            : csets ignored due to DAG topology
   536     - ``ignored``            : csets ignored due to DAG topology
   537     - ``current``            : the cset currently being bisected
   537     - ``current``            : the cset currently being bisected
   538     """
   538     """
   539     # i18n: "bisect" is a keyword
   539     # i18n: "bisect" is a keyword
   540     status = getstring(x, _("bisect requires a string")).lower()
   540     status = getstring(x, _(b"bisect requires a string")).lower()
   541     state = set(hbisect.get(repo, status))
   541     state = set(hbisect.get(repo, status))
   542     return subset & state
   542     return subset & state
   543 
   543 
   544 
   544 
   545 # Backward-compatibility
   545 # Backward-compatibility
   546 # - no help entry so that we do not advertise it any more
   546 # - no help entry so that we do not advertise it any more
   547 @predicate('bisected', safe=True)
   547 @predicate(b'bisected', safe=True)
   548 def bisected(repo, subset, x):
   548 def bisected(repo, subset, x):
   549     return bisect(repo, subset, x)
   549     return bisect(repo, subset, x)
   550 
   550 
   551 
   551 
   552 @predicate('bookmark([name])', safe=True)
   552 @predicate(b'bookmark([name])', safe=True)
   553 def bookmark(repo, subset, x):
   553 def bookmark(repo, subset, x):
   554     """The named bookmark or all bookmarks.
   554     """The named bookmark or all bookmarks.
   555 
   555 
   556     Pattern matching is supported for `name`. See :hg:`help revisions.patterns`.
   556     Pattern matching is supported for `name`. See :hg:`help revisions.patterns`.
   557     """
   557     """
   558     # i18n: "bookmark" is a keyword
   558     # i18n: "bookmark" is a keyword
   559     args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
   559     args = getargs(x, 0, 1, _(b'bookmark takes one or no arguments'))
   560     if args:
   560     if args:
   561         bm = getstring(
   561         bm = getstring(
   562             args[0],
   562             args[0],
   563             # i18n: "bookmark" is a keyword
   563             # i18n: "bookmark" is a keyword
   564             _('the argument to bookmark must be a string'),
   564             _(b'the argument to bookmark must be a string'),
   565         )
   565         )
   566         kind, pattern, matcher = stringutil.stringmatcher(bm)
   566         kind, pattern, matcher = stringutil.stringmatcher(bm)
   567         bms = set()
   567         bms = set()
   568         if kind == 'literal':
   568         if kind == b'literal':
   569             if bm == pattern:
   569             if bm == pattern:
   570                 pattern = repo._bookmarks.expandname(pattern)
   570                 pattern = repo._bookmarks.expandname(pattern)
   571             bmrev = repo._bookmarks.get(pattern, None)
   571             bmrev = repo._bookmarks.get(pattern, None)
   572             if not bmrev:
   572             if not bmrev:
   573                 raise error.RepoLookupError(
   573                 raise error.RepoLookupError(
   574                     _("bookmark '%s' does not exist") % pattern
   574                     _(b"bookmark '%s' does not exist") % pattern
   575                 )
   575                 )
   576             bms.add(repo[bmrev].rev())
   576             bms.add(repo[bmrev].rev())
   577         else:
   577         else:
   578             matchrevs = set()
   578             matchrevs = set()
   579             for name, bmrev in repo._bookmarks.iteritems():
   579             for name, bmrev in repo._bookmarks.iteritems():
   585         bms = {repo[r].rev() for r in repo._bookmarks.values()}
   585         bms = {repo[r].rev() for r in repo._bookmarks.values()}
   586     bms -= {node.nullrev}
   586     bms -= {node.nullrev}
   587     return subset & bms
   587     return subset & bms
   588 
   588 
   589 
   589 
   590 @predicate('branch(string or set)', safe=True, weight=10)
   590 @predicate(b'branch(string or set)', safe=True, weight=10)
   591 def branch(repo, subset, x):
   591 def branch(repo, subset, x):
   592     """
   592     """
   593     All changesets belonging to the given branch or the branches of the given
   593     All changesets belonging to the given branch or the branches of the given
   594     changesets.
   594     changesets.
   595 
   595 
   603             return getbi(r)[0]
   603             return getbi(r)[0]
   604         except error.WdirUnsupported:
   604         except error.WdirUnsupported:
   605             return repo[r].branch()
   605             return repo[r].branch()
   606 
   606 
   607     try:
   607     try:
   608         b = getstring(x, '')
   608         b = getstring(x, b'')
   609     except error.ParseError:
   609     except error.ParseError:
   610         # not a string, but another revspec, e.g. tip()
   610         # not a string, but another revspec, e.g. tip()
   611         pass
   611         pass
   612     else:
   612     else:
   613         kind, pattern, matcher = stringutil.stringmatcher(b)
   613         kind, pattern, matcher = stringutil.stringmatcher(b)
   614         if kind == 'literal':
   614         if kind == b'literal':
   615             # note: falls through to the revspec case if no branch with
   615             # note: falls through to the revspec case if no branch with
   616             # this name exists and pattern kind is not specified explicitly
   616             # this name exists and pattern kind is not specified explicitly
   617             if repo.branchmap().hasbranch(pattern):
   617             if repo.branchmap().hasbranch(pattern):
   618                 return subset.filter(
   618                 return subset.filter(
   619                     lambda r: matcher(getbranch(r)), condrepr=('<branch %r>', b)
   619                     lambda r: matcher(getbranch(r)),
       
   620                     condrepr=(b'<branch %r>', b),
   620                 )
   621                 )
   621             if b.startswith('literal:'):
   622             if b.startswith(b'literal:'):
   622                 raise error.RepoLookupError(
   623                 raise error.RepoLookupError(
   623                     _("branch '%s' does not exist") % pattern
   624                     _(b"branch '%s' does not exist") % pattern
   624                 )
   625                 )
   625         else:
   626         else:
   626             return subset.filter(
   627             return subset.filter(
   627                 lambda r: matcher(getbranch(r)), condrepr=('<branch %r>', b)
   628                 lambda r: matcher(getbranch(r)), condrepr=(b'<branch %r>', b)
   628             )
   629             )
   629 
   630 
   630     s = getset(repo, fullreposet(repo), x)
   631     s = getset(repo, fullreposet(repo), x)
   631     b = set()
   632     b = set()
   632     for r in s:
   633     for r in s:
   633         b.add(getbranch(r))
   634         b.add(getbranch(r))
   634     c = s.__contains__
   635     c = s.__contains__
   635     return subset.filter(
   636     return subset.filter(
   636         lambda r: c(r) or getbranch(r) in b,
   637         lambda r: c(r) or getbranch(r) in b,
   637         condrepr=lambda: '<branch %r>' % _sortedb(b),
   638         condrepr=lambda: b'<branch %r>' % _sortedb(b),
   638     )
   639     )
   639 
   640 
   640 
   641 
   641 @predicate('phasedivergent()', safe=True)
   642 @predicate(b'phasedivergent()', safe=True)
   642 def phasedivergent(repo, subset, x):
   643 def phasedivergent(repo, subset, x):
   643     """Mutable changesets marked as successors of public changesets.
   644     """Mutable changesets marked as successors of public changesets.
   644 
   645 
   645     Only non-public and non-obsolete changesets can be `phasedivergent`.
   646     Only non-public and non-obsolete changesets can be `phasedivergent`.
   646     (EXPERIMENTAL)
   647     (EXPERIMENTAL)
   647     """
   648     """
   648     # i18n: "phasedivergent" is a keyword
   649     # i18n: "phasedivergent" is a keyword
   649     getargs(x, 0, 0, _("phasedivergent takes no arguments"))
   650     getargs(x, 0, 0, _(b"phasedivergent takes no arguments"))
   650     phasedivergent = obsmod.getrevs(repo, 'phasedivergent')
   651     phasedivergent = obsmod.getrevs(repo, b'phasedivergent')
   651     return subset & phasedivergent
   652     return subset & phasedivergent
   652 
   653 
   653 
   654 
   654 @predicate('bundle()', safe=True)
   655 @predicate(b'bundle()', safe=True)
   655 def bundle(repo, subset, x):
   656 def bundle(repo, subset, x):
   656     """Changesets in the bundle.
   657     """Changesets in the bundle.
   657 
   658 
   658     Bundle must be specified by the -R option."""
   659     Bundle must be specified by the -R option."""
   659 
   660 
   660     try:
   661     try:
   661         bundlerevs = repo.changelog.bundlerevs
   662         bundlerevs = repo.changelog.bundlerevs
   662     except AttributeError:
   663     except AttributeError:
   663         raise error.Abort(_("no bundle provided - specify with -R"))
   664         raise error.Abort(_(b"no bundle provided - specify with -R"))
   664     return subset & bundlerevs
   665     return subset & bundlerevs
   665 
   666 
   666 
   667 
   667 def checkstatus(repo, subset, pat, field):
   668 def checkstatus(repo, subset, pat, field):
   668     """Helper for status-related revsets (adds, removes, modifies).
   669     """Helper for status-related revsets (adds, removes, modifies).
   669     The field parameter says which kind is desired:
   670     The field parameter says which kind is desired:
   670     0: modified
   671     0: modified
   671     1: added
   672     1: added
   672     2: removed
   673     2: removed
   673     """
   674     """
   674     hasset = matchmod.patkind(pat) == 'set'
   675     hasset = matchmod.patkind(pat) == b'set'
   675 
   676 
   676     mcache = [None]
   677     mcache = [None]
   677 
   678 
   678     def matches(x):
   679     def matches(x):
   679         c = repo[x]
   680         c = repo[x]
   699         else:
   700         else:
   700             for f in files:
   701             for f in files:
   701                 if m(f):
   702                 if m(f):
   702                     return True
   703                     return True
   703 
   704 
   704     return subset.filter(matches, condrepr=('<status[%r] %r>', field, pat))
   705     return subset.filter(matches, condrepr=(b'<status[%r] %r>', field, pat))
   705 
   706 
   706 
   707 
   707 def _children(repo, subset, parentset):
   708 def _children(repo, subset, parentset):
   708     if not parentset:
   709     if not parentset:
   709         return baseset()
   710         return baseset()
   720         if p2 != nullrev and p2 in parentset:
   721         if p2 != nullrev and p2 in parentset:
   721             cs.add(r)
   722             cs.add(r)
   722     return baseset(cs)
   723     return baseset(cs)
   723 
   724 
   724 
   725 
   725 @predicate('children(set)', safe=True)
   726 @predicate(b'children(set)', safe=True)
   726 def children(repo, subset, x):
   727 def children(repo, subset, x):
   727     """Child changesets of changesets in set.
   728     """Child changesets of changesets in set.
   728     """
   729     """
   729     s = getset(repo, fullreposet(repo), x)
   730     s = getset(repo, fullreposet(repo), x)
   730     cs = _children(repo, subset, s)
   731     cs = _children(repo, subset, s)
   731     return subset & cs
   732     return subset & cs
   732 
   733 
   733 
   734 
   734 @predicate('closed()', safe=True, weight=10)
   735 @predicate(b'closed()', safe=True, weight=10)
   735 def closed(repo, subset, x):
   736 def closed(repo, subset, x):
   736     """Changeset is closed.
   737     """Changeset is closed.
   737     """
   738     """
   738     # i18n: "closed" is a keyword
   739     # i18n: "closed" is a keyword
   739     getargs(x, 0, 0, _("closed takes no arguments"))
   740     getargs(x, 0, 0, _(b"closed takes no arguments"))
   740     return subset.filter(
   741     return subset.filter(
   741         lambda r: repo[r].closesbranch(), condrepr='<branch closed>'
   742         lambda r: repo[r].closesbranch(), condrepr=b'<branch closed>'
   742     )
   743     )
   743 
   744 
   744 
   745 
   745 # for internal use
   746 # for internal use
   746 @predicate('_commonancestorheads(set)', safe=True)
   747 @predicate(b'_commonancestorheads(set)', safe=True)
   747 def _commonancestorheads(repo, subset, x):
   748 def _commonancestorheads(repo, subset, x):
   748     # This is an internal method is for quickly calculating "heads(::x and
   749     # This is an internal method is for quickly calculating "heads(::x and
   749     # ::y)"
   750     # ::y)"
   750 
   751 
   751     # These greatest common ancestors are the same ones that the consensus bid
   752     # These greatest common ancestors are the same ones that the consensus bid
   754 
   755 
   755     ancs = repo.changelog._commonancestorsheads(*list(startrevs))
   756     ancs = repo.changelog._commonancestorsheads(*list(startrevs))
   756     return subset & baseset(ancs)
   757     return subset & baseset(ancs)
   757 
   758 
   758 
   759 
   759 @predicate('commonancestors(set)', safe=True)
   760 @predicate(b'commonancestors(set)', safe=True)
   760 def commonancestors(repo, subset, x):
   761 def commonancestors(repo, subset, x):
   761     """Changesets that are ancestors of every changeset in set.
   762     """Changesets that are ancestors of every changeset in set.
   762     """
   763     """
   763     startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
   764     startrevs = getset(repo, fullreposet(repo), x, order=anyorder)
   764     if not startrevs:
   765     if not startrevs:
   766     for r in startrevs:
   767     for r in startrevs:
   767         subset &= dagop.revancestors(repo, baseset([r]))
   768         subset &= dagop.revancestors(repo, baseset([r]))
   768     return subset
   769     return subset
   769 
   770 
   770 
   771 
   771 @predicate('contains(pattern)', weight=100)
   772 @predicate(b'contains(pattern)', weight=100)
   772 def contains(repo, subset, x):
   773 def contains(repo, subset, x):
   773     """The revision's manifest contains a file matching pattern (but might not
   774     """The revision's manifest contains a file matching pattern (but might not
   774     modify it). See :hg:`help patterns` for information about file patterns.
   775     modify it). See :hg:`help patterns` for information about file patterns.
   775 
   776 
   776     The pattern without explicit kind like ``glob:`` is expected to be
   777     The pattern without explicit kind like ``glob:`` is expected to be
   777     relative to the current directory and match against a file exactly
   778     relative to the current directory and match against a file exactly
   778     for efficiency.
   779     for efficiency.
   779     """
   780     """
   780     # i18n: "contains" is a keyword
   781     # i18n: "contains" is a keyword
   781     pat = getstring(x, _("contains requires a pattern"))
   782     pat = getstring(x, _(b"contains requires a pattern"))
   782 
   783 
   783     def matches(x):
   784     def matches(x):
   784         if not matchmod.patkind(pat):
   785         if not matchmod.patkind(pat):
   785             pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
   786             pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
   786             if pats in repo[x]:
   787             if pats in repo[x]:
   791             for f in c.manifest():
   792             for f in c.manifest():
   792                 if m(f):
   793                 if m(f):
   793                     return True
   794                     return True
   794         return False
   795         return False
   795 
   796 
   796     return subset.filter(matches, condrepr=('<contains %r>', pat))
   797     return subset.filter(matches, condrepr=(b'<contains %r>', pat))
   797 
   798 
   798 
   799 
   799 @predicate('converted([id])', safe=True)
   800 @predicate(b'converted([id])', safe=True)
   800 def converted(repo, subset, x):
   801 def converted(repo, subset, x):
   801     """Changesets converted from the given identifier in the old repository if
   802     """Changesets converted from the given identifier in the old repository if
   802     present, or all converted changesets if no identifier is specified.
   803     present, or all converted changesets if no identifier is specified.
   803     """
   804     """
   804 
   805 
   805     # There is exactly no chance of resolving the revision, so do a simple
   806     # There is exactly no chance of resolving the revision, so do a simple
   806     # string compare and hope for the best
   807     # string compare and hope for the best
   807 
   808 
   808     rev = None
   809     rev = None
   809     # i18n: "converted" is a keyword
   810     # i18n: "converted" is a keyword
   810     l = getargs(x, 0, 1, _('converted takes one or no arguments'))
   811     l = getargs(x, 0, 1, _(b'converted takes one or no arguments'))
   811     if l:
   812     if l:
   812         # i18n: "converted" is a keyword
   813         # i18n: "converted" is a keyword
   813         rev = getstring(l[0], _('converted requires a revision'))
   814         rev = getstring(l[0], _(b'converted requires a revision'))
   814 
   815 
   815     def _matchvalue(r):
   816     def _matchvalue(r):
   816         source = repo[r].extra().get('convert_revision', None)
   817         source = repo[r].extra().get(b'convert_revision', None)
   817         return source is not None and (rev is None or source.startswith(rev))
   818         return source is not None and (rev is None or source.startswith(rev))
   818 
   819 
   819     return subset.filter(
   820     return subset.filter(
   820         lambda r: _matchvalue(r), condrepr=('<converted %r>', rev)
   821         lambda r: _matchvalue(r), condrepr=(b'<converted %r>', rev)
   821     )
   822     )
   822 
   823 
   823 
   824 
   824 @predicate('date(interval)', safe=True, weight=10)
   825 @predicate(b'date(interval)', safe=True, weight=10)
   825 def date(repo, subset, x):
   826 def date(repo, subset, x):
   826     """Changesets within the interval, see :hg:`help dates`.
   827     """Changesets within the interval, see :hg:`help dates`.
   827     """
   828     """
   828     # i18n: "date" is a keyword
   829     # i18n: "date" is a keyword
   829     ds = getstring(x, _("date requires a string"))
   830     ds = getstring(x, _(b"date requires a string"))
   830     dm = dateutil.matchdate(ds)
   831     dm = dateutil.matchdate(ds)
   831     return subset.filter(
   832     return subset.filter(
   832         lambda x: dm(repo[x].date()[0]), condrepr=('<date %r>', ds)
   833         lambda x: dm(repo[x].date()[0]), condrepr=(b'<date %r>', ds)
   833     )
   834     )
   834 
   835 
   835 
   836 
   836 @predicate('desc(string)', safe=True, weight=10)
   837 @predicate(b'desc(string)', safe=True, weight=10)
   837 def desc(repo, subset, x):
   838 def desc(repo, subset, x):
   838     """Search commit message for string. The match is case-insensitive.
   839     """Search commit message for string. The match is case-insensitive.
   839 
   840 
   840     Pattern matching is supported for `string`. See
   841     Pattern matching is supported for `string`. See
   841     :hg:`help revisions.patterns`.
   842     :hg:`help revisions.patterns`.
   842     """
   843     """
   843     # i18n: "desc" is a keyword
   844     # i18n: "desc" is a keyword
   844     ds = getstring(x, _("desc requires a string"))
   845     ds = getstring(x, _(b"desc requires a string"))
   845 
   846 
   846     kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
   847     kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
   847 
   848 
   848     return subset.filter(
   849     return subset.filter(
   849         lambda r: matcher(repo[r].description()), condrepr=('<desc %r>', ds)
   850         lambda r: matcher(repo[r].description()), condrepr=(b'<desc %r>', ds)
   850     )
   851     )
   851 
   852 
   852 
   853 
   853 def _descendants(
   854 def _descendants(
   854     repo, subset, x, followfirst=False, startdepth=None, stopdepth=None
   855     repo, subset, x, followfirst=False, startdepth=None, stopdepth=None
   858         return baseset()
   859         return baseset()
   859     s = dagop.revdescendants(repo, roots, followfirst, startdepth, stopdepth)
   860     s = dagop.revdescendants(repo, roots, followfirst, startdepth, stopdepth)
   860     return subset & s
   861     return subset & s
   861 
   862 
   862 
   863 
   863 @predicate('descendants(set[, depth])', safe=True)
   864 @predicate(b'descendants(set[, depth])', safe=True)
   864 def descendants(repo, subset, x):
   865 def descendants(repo, subset, x):
   865     """Changesets which are descendants of changesets in set, including the
   866     """Changesets which are descendants of changesets in set, including the
   866     given changesets themselves.
   867     given changesets themselves.
   867 
   868 
   868     If depth is specified, the result only includes changesets up to
   869     If depth is specified, the result only includes changesets up to
   869     the specified generation.
   870     the specified generation.
   870     """
   871     """
   871     # startdepth is for internal use only until we can decide the UI
   872     # startdepth is for internal use only until we can decide the UI
   872     args = getargsdict(x, 'descendants', 'set depth startdepth')
   873     args = getargsdict(x, b'descendants', b'set depth startdepth')
   873     if 'set' not in args:
   874     if b'set' not in args:
   874         # i18n: "descendants" is a keyword
   875         # i18n: "descendants" is a keyword
   875         raise error.ParseError(_('descendants takes at least 1 argument'))
   876         raise error.ParseError(_(b'descendants takes at least 1 argument'))
   876     startdepth = stopdepth = None
   877     startdepth = stopdepth = None
   877     if 'startdepth' in args:
   878     if b'startdepth' in args:
   878         n = getinteger(
   879         n = getinteger(
   879             args['startdepth'], "descendants expects an integer startdepth"
   880             args[b'startdepth'], b"descendants expects an integer startdepth"
   880         )
   881         )
   881         if n < 0:
   882         if n < 0:
   882             raise error.ParseError("negative startdepth")
   883             raise error.ParseError(b"negative startdepth")
   883         startdepth = n
   884         startdepth = n
   884     if 'depth' in args:
   885     if b'depth' in args:
   885         # i18n: "descendants" is a keyword
   886         # i18n: "descendants" is a keyword
   886         n = getinteger(args['depth'], _("descendants expects an integer depth"))
   887         n = getinteger(
       
   888             args[b'depth'], _(b"descendants expects an integer depth")
       
   889         )
   887         if n < 0:
   890         if n < 0:
   888             raise error.ParseError(_("negative depth"))
   891             raise error.ParseError(_(b"negative depth"))
   889         stopdepth = n + 1
   892         stopdepth = n + 1
   890     return _descendants(
   893     return _descendants(
   891         repo, subset, args['set'], startdepth=startdepth, stopdepth=stopdepth
   894         repo, subset, args[b'set'], startdepth=startdepth, stopdepth=stopdepth
   892     )
   895     )
   893 
   896 
   894 
   897 
   895 @predicate('_firstdescendants', safe=True)
   898 @predicate(b'_firstdescendants', safe=True)
   896 def _firstdescendants(repo, subset, x):
   899 def _firstdescendants(repo, subset, x):
   897     # ``_firstdescendants(set)``
   900     # ``_firstdescendants(set)``
   898     # Like ``descendants(set)`` but follows only the first parents.
   901     # Like ``descendants(set)`` but follows only the first parents.
   899     return _descendants(repo, subset, x, followfirst=True)
   902     return _descendants(repo, subset, x, followfirst=True)
   900 
   903 
   901 
   904 
   902 @predicate('destination([set])', safe=True, weight=10)
   905 @predicate(b'destination([set])', safe=True, weight=10)
   903 def destination(repo, subset, x):
   906 def destination(repo, subset, x):
   904     """Changesets that were created by a graft, transplant or rebase operation,
   907     """Changesets that were created by a graft, transplant or rebase operation,
   905     with the given revisions specified as the source.  Omitting the optional set
   908     with the given revisions specified as the source.  Omitting the optional set
   906     is the same as passing all().
   909     is the same as passing all().
   907     """
   910     """
   941             r = src
   944             r = src
   942             src = _getrevsource(repo, r)
   945             src = _getrevsource(repo, r)
   943 
   946 
   944     return subset.filter(
   947     return subset.filter(
   945         dests.__contains__,
   948         dests.__contains__,
   946         condrepr=lambda: '<destination %r>' % _sortedb(dests),
   949         condrepr=lambda: b'<destination %r>' % _sortedb(dests),
   947     )
   950     )
   948 
   951 
   949 
   952 
   950 @predicate('contentdivergent()', safe=True)
   953 @predicate(b'contentdivergent()', safe=True)
   951 def contentdivergent(repo, subset, x):
   954 def contentdivergent(repo, subset, x):
   952     """
   955     """
   953     Final successors of changesets with an alternative set of final
   956     Final successors of changesets with an alternative set of final
   954     successors. (EXPERIMENTAL)
   957     successors. (EXPERIMENTAL)
   955     """
   958     """
   956     # i18n: "contentdivergent" is a keyword
   959     # i18n: "contentdivergent" is a keyword
   957     getargs(x, 0, 0, _("contentdivergent takes no arguments"))
   960     getargs(x, 0, 0, _(b"contentdivergent takes no arguments"))
   958     contentdivergent = obsmod.getrevs(repo, 'contentdivergent')
   961     contentdivergent = obsmod.getrevs(repo, b'contentdivergent')
   959     return subset & contentdivergent
   962     return subset & contentdivergent
   960 
   963 
   961 
   964 
   962 @predicate('expectsize(set[, size])', safe=True, takeorder=True)
   965 @predicate(b'expectsize(set[, size])', safe=True, takeorder=True)
   963 def expectsize(repo, subset, x, order):
   966 def expectsize(repo, subset, x, order):
   964     """Return the given revset if size matches the revset size.
   967     """Return the given revset if size matches the revset size.
   965     Abort if the revset doesn't expect given size.
   968     Abort if the revset doesn't expect given size.
   966     size can either be an integer range or an integer.
   969     size can either be an integer range or an integer.
   967 
   970 
   968     For example, ``expectsize(0:1, 3:5)`` will abort as revset size is 2 and
   971     For example, ``expectsize(0:1, 3:5)`` will abort as revset size is 2 and
   969     2 is not between 3 and 5 inclusive."""
   972     2 is not between 3 and 5 inclusive."""
   970 
   973 
   971     args = getargsdict(x, 'expectsize', 'set size')
   974     args = getargsdict(x, b'expectsize', b'set size')
   972     minsize = 0
   975     minsize = 0
   973     maxsize = len(repo) + 1
   976     maxsize = len(repo) + 1
   974     err = ''
   977     err = b''
   975     if 'size' not in args or 'set' not in args:
   978     if b'size' not in args or b'set' not in args:
   976         raise error.ParseError(_('invalid set of arguments'))
   979         raise error.ParseError(_(b'invalid set of arguments'))
   977     minsize, maxsize = getintrange(
   980     minsize, maxsize = getintrange(
   978         args['size'],
   981         args[b'size'],
   979         _('expectsize requires a size range' ' or a positive integer'),
   982         _(b'expectsize requires a size range' b' or a positive integer'),
   980         _('size range bounds must be integers'),
   983         _(b'size range bounds must be integers'),
   981         minsize,
   984         minsize,
   982         maxsize,
   985         maxsize,
   983     )
   986     )
   984     if minsize < 0 or maxsize < 0:
   987     if minsize < 0 or maxsize < 0:
   985         raise error.ParseError(_('negative size'))
   988         raise error.ParseError(_(b'negative size'))
   986     rev = getset(repo, fullreposet(repo), args['set'], order=order)
   989     rev = getset(repo, fullreposet(repo), args[b'set'], order=order)
   987     if minsize != maxsize and (len(rev) < minsize or len(rev) > maxsize):
   990     if minsize != maxsize and (len(rev) < minsize or len(rev) > maxsize):
   988         err = _(
   991         err = _(
   989             'revset size mismatch.' ' expected between %d and %d, got %d'
   992             b'revset size mismatch.' b' expected between %d and %d, got %d'
   990         ) % (minsize, maxsize, len(rev))
   993         ) % (minsize, maxsize, len(rev))
   991     elif minsize == maxsize and len(rev) != minsize:
   994     elif minsize == maxsize and len(rev) != minsize:
   992         err = _('revset size mismatch.' ' expected %d, got %d') % (
   995         err = _(b'revset size mismatch.' b' expected %d, got %d') % (
   993             minsize,
   996             minsize,
   994             len(rev),
   997             len(rev),
   995         )
   998         )
   996     if err:
   999     if err:
   997         raise error.RepoLookupError(err)
  1000         raise error.RepoLookupError(err)
   999         return subset & rev
  1002         return subset & rev
  1000     else:
  1003     else:
  1001         return rev & subset
  1004         return rev & subset
  1002 
  1005 
  1003 
  1006 
  1004 @predicate('extdata(source)', safe=False, weight=100)
  1007 @predicate(b'extdata(source)', safe=False, weight=100)
  1005 def extdata(repo, subset, x):
  1008 def extdata(repo, subset, x):
  1006     """Changesets in the specified extdata source. (EXPERIMENTAL)"""
  1009     """Changesets in the specified extdata source. (EXPERIMENTAL)"""
  1007     # i18n: "extdata" is a keyword
  1010     # i18n: "extdata" is a keyword
  1008     args = getargsdict(x, 'extdata', 'source')
  1011     args = getargsdict(x, b'extdata', b'source')
  1009     source = getstring(
  1012     source = getstring(
  1010         args.get('source'),
  1013         args.get(b'source'),
  1011         # i18n: "extdata" is a keyword
  1014         # i18n: "extdata" is a keyword
  1012         _('extdata takes at least 1 string argument'),
  1015         _(b'extdata takes at least 1 string argument'),
  1013     )
  1016     )
  1014     data = scmutil.extdatasource(repo, source)
  1017     data = scmutil.extdatasource(repo, source)
  1015     return subset & baseset(data)
  1018     return subset & baseset(data)
  1016 
  1019 
  1017 
  1020 
  1018 @predicate('extinct()', safe=True)
  1021 @predicate(b'extinct()', safe=True)
  1019 def extinct(repo, subset, x):
  1022 def extinct(repo, subset, x):
  1020     """Obsolete changesets with obsolete descendants only.
  1023     """Obsolete changesets with obsolete descendants only.
  1021     """
  1024     """
  1022     # i18n: "extinct" is a keyword
  1025     # i18n: "extinct" is a keyword
  1023     getargs(x, 0, 0, _("extinct takes no arguments"))
  1026     getargs(x, 0, 0, _(b"extinct takes no arguments"))
  1024     extincts = obsmod.getrevs(repo, 'extinct')
  1027     extincts = obsmod.getrevs(repo, b'extinct')
  1025     return subset & extincts
  1028     return subset & extincts
  1026 
  1029 
  1027 
  1030 
  1028 @predicate('extra(label, [value])', safe=True)
  1031 @predicate(b'extra(label, [value])', safe=True)
  1029 def extra(repo, subset, x):
  1032 def extra(repo, subset, x):
  1030     """Changesets with the given label in the extra metadata, with the given
  1033     """Changesets with the given label in the extra metadata, with the given
  1031     optional value.
  1034     optional value.
  1032 
  1035 
  1033     Pattern matching is supported for `value`. See
  1036     Pattern matching is supported for `value`. See
  1034     :hg:`help revisions.patterns`.
  1037     :hg:`help revisions.patterns`.
  1035     """
  1038     """
  1036     args = getargsdict(x, 'extra', 'label value')
  1039     args = getargsdict(x, b'extra', b'label value')
  1037     if 'label' not in args:
  1040     if b'label' not in args:
  1038         # i18n: "extra" is a keyword
  1041         # i18n: "extra" is a keyword
  1039         raise error.ParseError(_('extra takes at least 1 argument'))
  1042         raise error.ParseError(_(b'extra takes at least 1 argument'))
  1040     # i18n: "extra" is a keyword
  1043     # i18n: "extra" is a keyword
  1041     label = getstring(
  1044     label = getstring(
  1042         args['label'], _('first argument to extra must be ' 'a string')
  1045         args[b'label'], _(b'first argument to extra must be ' b'a string')
  1043     )
  1046     )
  1044     value = None
  1047     value = None
  1045 
  1048 
  1046     if 'value' in args:
  1049     if b'value' in args:
  1047         # i18n: "extra" is a keyword
  1050         # i18n: "extra" is a keyword
  1048         value = getstring(
  1051         value = getstring(
  1049             args['value'], _('second argument to extra must be ' 'a string')
  1052             args[b'value'], _(b'second argument to extra must be ' b'a string')
  1050         )
  1053         )
  1051         kind, value, matcher = stringutil.stringmatcher(value)
  1054         kind, value, matcher = stringutil.stringmatcher(value)
  1052 
  1055 
  1053     def _matchvalue(r):
  1056     def _matchvalue(r):
  1054         extra = repo[r].extra()
  1057         extra = repo[r].extra()
  1055         return label in extra and (value is None or matcher(extra[label]))
  1058         return label in extra and (value is None or matcher(extra[label]))
  1056 
  1059 
  1057     return subset.filter(
  1060     return subset.filter(
  1058         lambda r: _matchvalue(r), condrepr=('<extra[%r] %r>', label, value)
  1061         lambda r: _matchvalue(r), condrepr=(b'<extra[%r] %r>', label, value)
  1059     )
  1062     )
  1060 
  1063 
  1061 
  1064 
  1062 @predicate('filelog(pattern)', safe=True)
  1065 @predicate(b'filelog(pattern)', safe=True)
  1063 def filelog(repo, subset, x):
  1066 def filelog(repo, subset, x):
  1064     """Changesets connected to the specified filelog.
  1067     """Changesets connected to the specified filelog.
  1065 
  1068 
  1066     For performance reasons, visits only revisions mentioned in the file-level
  1069     For performance reasons, visits only revisions mentioned in the file-level
  1067     filelog, rather than filtering through all changesets (much faster, but
  1070     filelog, rather than filtering through all changesets (much faster, but
  1072     relative to the current directory and match against a file exactly
  1075     relative to the current directory and match against a file exactly
  1073     for efficiency.
  1076     for efficiency.
  1074     """
  1077     """
  1075 
  1078 
  1076     # i18n: "filelog" is a keyword
  1079     # i18n: "filelog" is a keyword
  1077     pat = getstring(x, _("filelog requires a pattern"))
  1080     pat = getstring(x, _(b"filelog requires a pattern"))
  1078     s = set()
  1081     s = set()
  1079     cl = repo.changelog
  1082     cl = repo.changelog
  1080 
  1083 
  1081     if not matchmod.patkind(pat):
  1084     if not matchmod.patkind(pat):
  1082         f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
  1085         f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
  1121                             continue
  1124                             continue
  1122 
  1125 
  1123     return subset & s
  1126     return subset & s
  1124 
  1127 
  1125 
  1128 
  1126 @predicate('first(set, [n])', safe=True, takeorder=True, weight=0)
  1129 @predicate(b'first(set, [n])', safe=True, takeorder=True, weight=0)
  1127 def first(repo, subset, x, order):
  1130 def first(repo, subset, x, order):
  1128     """An alias for limit().
  1131     """An alias for limit().
  1129     """
  1132     """
  1130     return limit(repo, subset, x, order)
  1133     return limit(repo, subset, x, order)
  1131 
  1134 
  1132 
  1135 
  1133 def _follow(repo, subset, x, name, followfirst=False):
  1136 def _follow(repo, subset, x, name, followfirst=False):
  1134     args = getargsdict(x, name, 'file startrev')
  1137     args = getargsdict(x, name, b'file startrev')
  1135     revs = None
  1138     revs = None
  1136     if 'startrev' in args:
  1139     if b'startrev' in args:
  1137         revs = getset(repo, fullreposet(repo), args['startrev'])
  1140         revs = getset(repo, fullreposet(repo), args[b'startrev'])
  1138     if 'file' in args:
  1141     if b'file' in args:
  1139         x = getstring(args['file'], _("%s expected a pattern") % name)
  1142         x = getstring(args[b'file'], _(b"%s expected a pattern") % name)
  1140         if revs is None:
  1143         if revs is None:
  1141             revs = [None]
  1144             revs = [None]
  1142         fctxs = []
  1145         fctxs = []
  1143         for r in revs:
  1146         for r in revs:
  1144             ctx = mctx = repo[r]
  1147             ctx = mctx = repo[r]
  1145             if r is None:
  1148             if r is None:
  1146                 ctx = repo['.']
  1149                 ctx = repo[b'.']
  1147             m = matchmod.match(
  1150             m = matchmod.match(
  1148                 repo.root, repo.getcwd(), [x], ctx=mctx, default='path'
  1151                 repo.root, repo.getcwd(), [x], ctx=mctx, default=b'path'
  1149             )
  1152             )
  1150             fctxs.extend(ctx[f].introfilectx() for f in ctx.manifest().walk(m))
  1153             fctxs.extend(ctx[f].introfilectx() for f in ctx.manifest().walk(m))
  1151         s = dagop.filerevancestors(fctxs, followfirst)
  1154         s = dagop.filerevancestors(fctxs, followfirst)
  1152     else:
  1155     else:
  1153         if revs is None:
  1156         if revs is None:
  1154             revs = baseset([repo['.'].rev()])
  1157             revs = baseset([repo[b'.'].rev()])
  1155         s = dagop.revancestors(repo, revs, followfirst)
  1158         s = dagop.revancestors(repo, revs, followfirst)
  1156 
  1159 
  1157     return subset & s
  1160     return subset & s
  1158 
  1161 
  1159 
  1162 
  1160 @predicate('follow([file[, startrev]])', safe=True)
  1163 @predicate(b'follow([file[, startrev]])', safe=True)
  1161 def follow(repo, subset, x):
  1164 def follow(repo, subset, x):
  1162     """
  1165     """
  1163     An alias for ``::.`` (ancestors of the working directory's first parent).
  1166     An alias for ``::.`` (ancestors of the working directory's first parent).
  1164     If file pattern is specified, the histories of files matching given
  1167     If file pattern is specified, the histories of files matching given
  1165     pattern in the revision given by startrev are followed, including copies.
  1168     pattern in the revision given by startrev are followed, including copies.
  1166     """
  1169     """
  1167     return _follow(repo, subset, x, 'follow')
  1170     return _follow(repo, subset, x, b'follow')
  1168 
  1171 
  1169 
  1172 
  1170 @predicate('_followfirst', safe=True)
  1173 @predicate(b'_followfirst', safe=True)
  1171 def _followfirst(repo, subset, x):
  1174 def _followfirst(repo, subset, x):
  1172     # ``followfirst([file[, startrev]])``
  1175     # ``followfirst([file[, startrev]])``
  1173     # Like ``follow([file[, startrev]])`` but follows only the first parent
  1176     # Like ``follow([file[, startrev]])`` but follows only the first parent
  1174     # of every revisions or files revisions.
  1177     # of every revisions or files revisions.
  1175     return _follow(repo, subset, x, '_followfirst', followfirst=True)
  1178     return _follow(repo, subset, x, b'_followfirst', followfirst=True)
  1176 
  1179 
  1177 
  1180 
  1178 @predicate(
  1181 @predicate(
  1179     'followlines(file, fromline:toline[, startrev=., descend=False])', safe=True
  1182     b'followlines(file, fromline:toline[, startrev=., descend=False])',
       
  1183     safe=True,
  1180 )
  1184 )
  1181 def followlines(repo, subset, x):
  1185 def followlines(repo, subset, x):
  1182     """Changesets modifying `file` in line range ('fromline', 'toline').
  1186     """Changesets modifying `file` in line range ('fromline', 'toline').
  1183 
  1187 
  1184     Line range corresponds to 'file' content at 'startrev' and should hence be
  1188     Line range corresponds to 'file' content at 'startrev' and should hence be
  1187 
  1191 
  1188     By default, ancestors of 'startrev' are returned. If 'descend' is True,
  1192     By default, ancestors of 'startrev' are returned. If 'descend' is True,
  1189     descendants of 'startrev' are returned though renames are (currently) not
  1193     descendants of 'startrev' are returned though renames are (currently) not
  1190     followed in this direction.
  1194     followed in this direction.
  1191     """
  1195     """
  1192     args = getargsdict(x, 'followlines', 'file *lines startrev descend')
  1196     args = getargsdict(x, b'followlines', b'file *lines startrev descend')
  1193     if len(args['lines']) != 1:
  1197     if len(args[b'lines']) != 1:
  1194         raise error.ParseError(_("followlines requires a line range"))
  1198         raise error.ParseError(_(b"followlines requires a line range"))
  1195 
  1199 
  1196     rev = '.'
  1200     rev = b'.'
  1197     if 'startrev' in args:
  1201     if b'startrev' in args:
  1198         revs = getset(repo, fullreposet(repo), args['startrev'])
  1202         revs = getset(repo, fullreposet(repo), args[b'startrev'])
  1199         if len(revs) != 1:
  1203         if len(revs) != 1:
  1200             raise error.ParseError(
  1204             raise error.ParseError(
  1201                 # i18n: "followlines" is a keyword
  1205                 # i18n: "followlines" is a keyword
  1202                 _("followlines expects exactly one revision")
  1206                 _(b"followlines expects exactly one revision")
  1203             )
  1207             )
  1204         rev = revs.last()
  1208         rev = revs.last()
  1205 
  1209 
  1206     pat = getstring(args['file'], _("followlines requires a pattern"))
  1210     pat = getstring(args[b'file'], _(b"followlines requires a pattern"))
  1207     # i18n: "followlines" is a keyword
  1211     # i18n: "followlines" is a keyword
  1208     msg = _("followlines expects exactly one file")
  1212     msg = _(b"followlines expects exactly one file")
  1209     fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
  1213     fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
  1210     fromline, toline = util.processlinerange(
  1214     fromline, toline = util.processlinerange(
  1211         *getintrange(
  1215         *getintrange(
  1212             args['lines'][0],
  1216             args[b'lines'][0],
  1213             # i18n: "followlines" is a keyword
  1217             # i18n: "followlines" is a keyword
  1214             _("followlines expects a line number or a range"),
  1218             _(b"followlines expects a line number or a range"),
  1215             _("line range bounds must be integers"),
  1219             _(b"line range bounds must be integers"),
  1216         )
  1220         )
  1217     )
  1221     )
  1218 
  1222 
  1219     fctx = repo[rev].filectx(fname)
  1223     fctx = repo[rev].filectx(fname)
  1220     descend = False
  1224     descend = False
  1221     if 'descend' in args:
  1225     if b'descend' in args:
  1222         descend = getboolean(
  1226         descend = getboolean(
  1223             args['descend'],
  1227             args[b'descend'],
  1224             # i18n: "descend" is a keyword
  1228             # i18n: "descend" is a keyword
  1225             _("descend argument must be a boolean"),
  1229             _(b"descend argument must be a boolean"),
  1226         )
  1230         )
  1227     if descend:
  1231     if descend:
  1228         rs = generatorset(
  1232         rs = generatorset(
  1229             (
  1233             (
  1230                 c.rev()
  1234                 c.rev()
  1245             iterasc=False,
  1249             iterasc=False,
  1246         )
  1250         )
  1247     return subset & rs
  1251     return subset & rs
  1248 
  1252 
  1249 
  1253 
  1250 @predicate('all()', safe=True)
  1254 @predicate(b'all()', safe=True)
  1251 def getall(repo, subset, x):
  1255 def getall(repo, subset, x):
  1252     """All changesets, the same as ``0:tip``.
  1256     """All changesets, the same as ``0:tip``.
  1253     """
  1257     """
  1254     # i18n: "all" is a keyword
  1258     # i18n: "all" is a keyword
  1255     getargs(x, 0, 0, _("all takes no arguments"))
  1259     getargs(x, 0, 0, _(b"all takes no arguments"))
  1256     return subset & spanset(repo)  # drop "null" if any
  1260     return subset & spanset(repo)  # drop "null" if any
  1257 
  1261 
  1258 
  1262 
  1259 @predicate('grep(regex)', weight=10)
  1263 @predicate(b'grep(regex)', weight=10)
  1260 def grep(repo, subset, x):
  1264 def grep(repo, subset, x):
  1261     """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
  1265     """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
  1262     to ensure special escape characters are handled correctly. Unlike
  1266     to ensure special escape characters are handled correctly. Unlike
  1263     ``keyword(string)``, the match is case-sensitive.
  1267     ``keyword(string)``, the match is case-sensitive.
  1264     """
  1268     """
  1265     try:
  1269     try:
  1266         # i18n: "grep" is a keyword
  1270         # i18n: "grep" is a keyword
  1267         gr = re.compile(getstring(x, _("grep requires a string")))
  1271         gr = re.compile(getstring(x, _(b"grep requires a string")))
  1268     except re.error as e:
  1272     except re.error as e:
  1269         raise error.ParseError(
  1273         raise error.ParseError(
  1270             _('invalid match pattern: %s') % stringutil.forcebytestr(e)
  1274             _(b'invalid match pattern: %s') % stringutil.forcebytestr(e)
  1271         )
  1275         )
  1272 
  1276 
  1273     def matches(x):
  1277     def matches(x):
  1274         c = repo[x]
  1278         c = repo[x]
  1275         for e in c.files() + [c.user(), c.description()]:
  1279         for e in c.files() + [c.user(), c.description()]:
  1276             if gr.search(e):
  1280             if gr.search(e):
  1277                 return True
  1281                 return True
  1278         return False
  1282         return False
  1279 
  1283 
  1280     return subset.filter(matches, condrepr=('<grep %r>', gr.pattern))
  1284     return subset.filter(matches, condrepr=(b'<grep %r>', gr.pattern))
  1281 
  1285 
  1282 
  1286 
  1283 @predicate('_matchfiles', safe=True)
  1287 @predicate(b'_matchfiles', safe=True)
  1284 def _matchfiles(repo, subset, x):
  1288 def _matchfiles(repo, subset, x):
  1285     # _matchfiles takes a revset list of prefixed arguments:
  1289     # _matchfiles takes a revset list of prefixed arguments:
  1286     #
  1290     #
  1287     #   [p:foo, i:bar, x:baz]
  1291     #   [p:foo, i:bar, x:baz]
  1288     #
  1292     #
  1292     # a revision identifier, or the empty string to reference the
  1296     # a revision identifier, or the empty string to reference the
  1293     # working directory, from which the match object is
  1297     # working directory, from which the match object is
  1294     # initialized. Use 'd:' to set the default matching mode, default
  1298     # initialized. Use 'd:' to set the default matching mode, default
  1295     # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
  1299     # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
  1296 
  1300 
  1297     l = getargs(x, 1, -1, "_matchfiles requires at least one argument")
  1301     l = getargs(x, 1, -1, b"_matchfiles requires at least one argument")
  1298     pats, inc, exc = [], [], []
  1302     pats, inc, exc = [], [], []
  1299     rev, default = None, None
  1303     rev, default = None, None
  1300     for arg in l:
  1304     for arg in l:
  1301         s = getstring(arg, "_matchfiles requires string arguments")
  1305         s = getstring(arg, b"_matchfiles requires string arguments")
  1302         prefix, value = s[:2], s[2:]
  1306         prefix, value = s[:2], s[2:]
  1303         if prefix == 'p:':
  1307         if prefix == b'p:':
  1304             pats.append(value)
  1308             pats.append(value)
  1305         elif prefix == 'i:':
  1309         elif prefix == b'i:':
  1306             inc.append(value)
  1310             inc.append(value)
  1307         elif prefix == 'x:':
  1311         elif prefix == b'x:':
  1308             exc.append(value)
  1312             exc.append(value)
  1309         elif prefix == 'r:':
  1313         elif prefix == b'r:':
  1310             if rev is not None:
  1314             if rev is not None:
  1311                 raise error.ParseError(
  1315                 raise error.ParseError(
  1312                     '_matchfiles expected at most one ' 'revision'
  1316                     b'_matchfiles expected at most one ' b'revision'
  1313                 )
  1317                 )
  1314             if value == '':  # empty means working directory
  1318             if value == b'':  # empty means working directory
  1315                 rev = node.wdirrev
  1319                 rev = node.wdirrev
  1316             else:
  1320             else:
  1317                 rev = value
  1321                 rev = value
  1318         elif prefix == 'd:':
  1322         elif prefix == b'd:':
  1319             if default is not None:
  1323             if default is not None:
  1320                 raise error.ParseError(
  1324                 raise error.ParseError(
  1321                     '_matchfiles expected at most one ' 'default mode'
  1325                     b'_matchfiles expected at most one ' b'default mode'
  1322                 )
  1326                 )
  1323             default = value
  1327             default = value
  1324         else:
  1328         else:
  1325             raise error.ParseError('invalid _matchfiles prefix: %s' % prefix)
  1329             raise error.ParseError(b'invalid _matchfiles prefix: %s' % prefix)
  1326     if not default:
  1330     if not default:
  1327         default = 'glob'
  1331         default = b'glob'
  1328     hasset = any(matchmod.patkind(p) == 'set' for p in pats + inc + exc)
  1332     hasset = any(matchmod.patkind(p) == b'set' for p in pats + inc + exc)
  1329 
  1333 
  1330     mcache = [None]
  1334     mcache = [None]
  1331 
  1335 
  1332     # This directly read the changelog data as creating changectx for all
  1336     # This directly read the changelog data as creating changectx for all
  1333     # revisions is quite expensive.
  1337     # revisions is quite expensive.
  1359         return False
  1363         return False
  1360 
  1364 
  1361     return subset.filter(
  1365     return subset.filter(
  1362         matches,
  1366         matches,
  1363         condrepr=(
  1367         condrepr=(
  1364             '<matchfiles patterns=%r, include=%r '
  1368             b'<matchfiles patterns=%r, include=%r '
  1365             'exclude=%r, default=%r, rev=%r>',
  1369             b'exclude=%r, default=%r, rev=%r>',
  1366             pats,
  1370             pats,
  1367             inc,
  1371             inc,
  1368             exc,
  1372             exc,
  1369             default,
  1373             default,
  1370             rev,
  1374             rev,
  1371         ),
  1375         ),
  1372     )
  1376     )
  1373 
  1377 
  1374 
  1378 
  1375 @predicate('file(pattern)', safe=True, weight=10)
  1379 @predicate(b'file(pattern)', safe=True, weight=10)
  1376 def hasfile(repo, subset, x):
  1380 def hasfile(repo, subset, x):
  1377     """Changesets affecting files matched by pattern.
  1381     """Changesets affecting files matched by pattern.
  1378 
  1382 
  1379     For a faster but less accurate result, consider using ``filelog()``
  1383     For a faster but less accurate result, consider using ``filelog()``
  1380     instead.
  1384     instead.
  1381 
  1385 
  1382     This predicate uses ``glob:`` as the default kind of pattern.
  1386     This predicate uses ``glob:`` as the default kind of pattern.
  1383     """
  1387     """
  1384     # i18n: "file" is a keyword
  1388     # i18n: "file" is a keyword
  1385     pat = getstring(x, _("file requires a pattern"))
  1389     pat = getstring(x, _(b"file requires a pattern"))
  1386     return _matchfiles(repo, subset, ('string', 'p:' + pat))
  1390     return _matchfiles(repo, subset, (b'string', b'p:' + pat))
  1387 
  1391 
  1388 
  1392 
  1389 @predicate('head()', safe=True)
  1393 @predicate(b'head()', safe=True)
  1390 def head(repo, subset, x):
  1394 def head(repo, subset, x):
  1391     """Changeset is a named branch head.
  1395     """Changeset is a named branch head.
  1392     """
  1396     """
  1393     # i18n: "head" is a keyword
  1397     # i18n: "head" is a keyword
  1394     getargs(x, 0, 0, _("head takes no arguments"))
  1398     getargs(x, 0, 0, _(b"head takes no arguments"))
  1395     hs = set()
  1399     hs = set()
  1396     cl = repo.changelog
  1400     cl = repo.changelog
  1397     for ls in repo.branchmap().iterheads():
  1401     for ls in repo.branchmap().iterheads():
  1398         hs.update(cl.rev(h) for h in ls)
  1402         hs.update(cl.rev(h) for h in ls)
  1399     return subset & baseset(hs)
  1403     return subset & baseset(hs)
  1400 
  1404 
  1401 
  1405 
  1402 @predicate('heads(set)', safe=True, takeorder=True)
  1406 @predicate(b'heads(set)', safe=True, takeorder=True)
  1403 def heads(repo, subset, x, order):
  1407 def heads(repo, subset, x, order):
  1404     """Members of set with no children in set.
  1408     """Members of set with no children in set.
  1405     """
  1409     """
  1406     # argument set should never define order
  1410     # argument set should never define order
  1407     if order == defineorder:
  1411     if order == defineorder:
  1419         heads.add(node.wdirrev)
  1423         heads.add(node.wdirrev)
  1420     heads = baseset(heads)
  1424     heads = baseset(heads)
  1421     return subset & heads
  1425     return subset & heads
  1422 
  1426 
  1423 
  1427 
  1424 @predicate('hidden()', safe=True)
  1428 @predicate(b'hidden()', safe=True)
  1425 def hidden(repo, subset, x):
  1429 def hidden(repo, subset, x):
  1426     """Hidden changesets.
  1430     """Hidden changesets.
  1427     """
  1431     """
  1428     # i18n: "hidden" is a keyword
  1432     # i18n: "hidden" is a keyword
  1429     getargs(x, 0, 0, _("hidden takes no arguments"))
  1433     getargs(x, 0, 0, _(b"hidden takes no arguments"))
  1430     hiddenrevs = repoview.filterrevs(repo, 'visible')
  1434     hiddenrevs = repoview.filterrevs(repo, b'visible')
  1431     return subset & hiddenrevs
  1435     return subset & hiddenrevs
  1432 
  1436 
  1433 
  1437 
  1434 @predicate('keyword(string)', safe=True, weight=10)
  1438 @predicate(b'keyword(string)', safe=True, weight=10)
  1435 def keyword(repo, subset, x):
  1439 def keyword(repo, subset, x):
  1436     """Search commit message, user name, and names of changed files for
  1440     """Search commit message, user name, and names of changed files for
  1437     string. The match is case-insensitive.
  1441     string. The match is case-insensitive.
  1438 
  1442 
  1439     For a regular expression or case sensitive search of these fields, use
  1443     For a regular expression or case sensitive search of these fields, use
  1440     ``grep(regex)``.
  1444     ``grep(regex)``.
  1441     """
  1445     """
  1442     # i18n: "keyword" is a keyword
  1446     # i18n: "keyword" is a keyword
  1443     kw = encoding.lower(getstring(x, _("keyword requires a string")))
  1447     kw = encoding.lower(getstring(x, _(b"keyword requires a string")))
  1444 
  1448 
  1445     def matches(r):
  1449     def matches(r):
  1446         c = repo[r]
  1450         c = repo[r]
  1447         return any(
  1451         return any(
  1448             kw in encoding.lower(t)
  1452             kw in encoding.lower(t)
  1449             for t in c.files() + [c.user(), c.description()]
  1453             for t in c.files() + [c.user(), c.description()]
  1450         )
  1454         )
  1451 
  1455 
  1452     return subset.filter(matches, condrepr=('<keyword %r>', kw))
  1456     return subset.filter(matches, condrepr=(b'<keyword %r>', kw))
  1453 
  1457 
  1454 
  1458 
  1455 @predicate('limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
  1459 @predicate(b'limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
  1456 def limit(repo, subset, x, order):
  1460 def limit(repo, subset, x, order):
  1457     """First n members of set, defaulting to 1, starting from offset.
  1461     """First n members of set, defaulting to 1, starting from offset.
  1458     """
  1462     """
  1459     args = getargsdict(x, 'limit', 'set n offset')
  1463     args = getargsdict(x, b'limit', b'set n offset')
  1460     if 'set' not in args:
  1464     if b'set' not in args:
  1461         # i18n: "limit" is a keyword
  1465         # i18n: "limit" is a keyword
  1462         raise error.ParseError(_("limit requires one to three arguments"))
  1466         raise error.ParseError(_(b"limit requires one to three arguments"))
  1463     # i18n: "limit" is a keyword
  1467     # i18n: "limit" is a keyword
  1464     lim = getinteger(args.get('n'), _("limit expects a number"), default=1)
  1468     lim = getinteger(args.get(b'n'), _(b"limit expects a number"), default=1)
  1465     if lim < 0:
  1469     if lim < 0:
  1466         raise error.ParseError(_("negative number to select"))
  1470         raise error.ParseError(_(b"negative number to select"))
  1467     # i18n: "limit" is a keyword
  1471     # i18n: "limit" is a keyword
  1468     ofs = getinteger(args.get('offset'), _("limit expects a number"), default=0)
  1472     ofs = getinteger(
       
  1473         args.get(b'offset'), _(b"limit expects a number"), default=0
       
  1474     )
  1469     if ofs < 0:
  1475     if ofs < 0:
  1470         raise error.ParseError(_("negative offset"))
  1476         raise error.ParseError(_(b"negative offset"))
  1471     os = getset(repo, fullreposet(repo), args['set'])
  1477     os = getset(repo, fullreposet(repo), args[b'set'])
  1472     ls = os.slice(ofs, ofs + lim)
  1478     ls = os.slice(ofs, ofs + lim)
  1473     if order == followorder and lim > 1:
  1479     if order == followorder and lim > 1:
  1474         return subset & ls
  1480         return subset & ls
  1475     return ls & subset
  1481     return ls & subset
  1476 
  1482 
  1477 
  1483 
  1478 @predicate('last(set, [n])', safe=True, takeorder=True)
  1484 @predicate(b'last(set, [n])', safe=True, takeorder=True)
  1479 def last(repo, subset, x, order):
  1485 def last(repo, subset, x, order):
  1480     """Last n members of set, defaulting to 1.
  1486     """Last n members of set, defaulting to 1.
  1481     """
  1487     """
  1482     # i18n: "last" is a keyword
  1488     # i18n: "last" is a keyword
  1483     l = getargs(x, 1, 2, _("last requires one or two arguments"))
  1489     l = getargs(x, 1, 2, _(b"last requires one or two arguments"))
  1484     lim = 1
  1490     lim = 1
  1485     if len(l) == 2:
  1491     if len(l) == 2:
  1486         # i18n: "last" is a keyword
  1492         # i18n: "last" is a keyword
  1487         lim = getinteger(l[1], _("last expects a number"))
  1493         lim = getinteger(l[1], _(b"last expects a number"))
  1488     if lim < 0:
  1494     if lim < 0:
  1489         raise error.ParseError(_("negative number to select"))
  1495         raise error.ParseError(_(b"negative number to select"))
  1490     os = getset(repo, fullreposet(repo), l[0])
  1496     os = getset(repo, fullreposet(repo), l[0])
  1491     os.reverse()
  1497     os.reverse()
  1492     ls = os.slice(0, lim)
  1498     ls = os.slice(0, lim)
  1493     if order == followorder and lim > 1:
  1499     if order == followorder and lim > 1:
  1494         return subset & ls
  1500         return subset & ls
  1495     ls.reverse()
  1501     ls.reverse()
  1496     return ls & subset
  1502     return ls & subset
  1497 
  1503 
  1498 
  1504 
  1499 @predicate('max(set)', safe=True)
  1505 @predicate(b'max(set)', safe=True)
  1500 def maxrev(repo, subset, x):
  1506 def maxrev(repo, subset, x):
  1501     """Changeset with highest revision number in set.
  1507     """Changeset with highest revision number in set.
  1502     """
  1508     """
  1503     os = getset(repo, fullreposet(repo), x)
  1509     os = getset(repo, fullreposet(repo), x)
  1504     try:
  1510     try:
  1505         m = os.max()
  1511         m = os.max()
  1506         if m in subset:
  1512         if m in subset:
  1507             return baseset([m], datarepr=('<max %r, %r>', subset, os))
  1513             return baseset([m], datarepr=(b'<max %r, %r>', subset, os))
  1508     except ValueError:
  1514     except ValueError:
  1509         # os.max() throws a ValueError when the collection is empty.
  1515         # os.max() throws a ValueError when the collection is empty.
  1510         # Same as python's max().
  1516         # Same as python's max().
  1511         pass
  1517         pass
  1512     return baseset(datarepr=('<max %r, %r>', subset, os))
  1518     return baseset(datarepr=(b'<max %r, %r>', subset, os))
  1513 
  1519 
  1514 
  1520 
  1515 @predicate('merge()', safe=True)
  1521 @predicate(b'merge()', safe=True)
  1516 def merge(repo, subset, x):
  1522 def merge(repo, subset, x):
  1517     """Changeset is a merge changeset.
  1523     """Changeset is a merge changeset.
  1518     """
  1524     """
  1519     # i18n: "merge" is a keyword
  1525     # i18n: "merge" is a keyword
  1520     getargs(x, 0, 0, _("merge takes no arguments"))
  1526     getargs(x, 0, 0, _(b"merge takes no arguments"))
  1521     cl = repo.changelog
  1527     cl = repo.changelog
  1522     nullrev = node.nullrev
  1528     nullrev = node.nullrev
  1523 
  1529 
  1524     def ismerge(r):
  1530     def ismerge(r):
  1525         try:
  1531         try:
  1526             return cl.parentrevs(r)[1] != nullrev
  1532             return cl.parentrevs(r)[1] != nullrev
  1527         except error.WdirUnsupported:
  1533         except error.WdirUnsupported:
  1528             return bool(repo[r].p2())
  1534             return bool(repo[r].p2())
  1529 
  1535 
  1530     return subset.filter(ismerge, condrepr='<merge>')
  1536     return subset.filter(ismerge, condrepr=b'<merge>')
  1531 
  1537 
  1532 
  1538 
  1533 @predicate('branchpoint()', safe=True)
  1539 @predicate(b'branchpoint()', safe=True)
  1534 def branchpoint(repo, subset, x):
  1540 def branchpoint(repo, subset, x):
  1535     """Changesets with more than one child.
  1541     """Changesets with more than one child.
  1536     """
  1542     """
  1537     # i18n: "branchpoint" is a keyword
  1543     # i18n: "branchpoint" is a keyword
  1538     getargs(x, 0, 0, _("branchpoint takes no arguments"))
  1544     getargs(x, 0, 0, _(b"branchpoint takes no arguments"))
  1539     cl = repo.changelog
  1545     cl = repo.changelog
  1540     if not subset:
  1546     if not subset:
  1541         return baseset()
  1547         return baseset()
  1542     # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
  1548     # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
  1543     # (and if it is not, it should.)
  1549     # (and if it is not, it should.)
  1546     for r in cl.revs(start=baserev + 1):
  1552     for r in cl.revs(start=baserev + 1):
  1547         for p in cl.parentrevs(r):
  1553         for p in cl.parentrevs(r):
  1548             if p >= baserev:
  1554             if p >= baserev:
  1549                 parentscount[p - baserev] += 1
  1555                 parentscount[p - baserev] += 1
  1550     return subset.filter(
  1556     return subset.filter(
  1551         lambda r: parentscount[r - baserev] > 1, condrepr='<branchpoint>'
  1557         lambda r: parentscount[r - baserev] > 1, condrepr=b'<branchpoint>'
  1552     )
  1558     )
  1553 
  1559 
  1554 
  1560 
  1555 @predicate('min(set)', safe=True)
  1561 @predicate(b'min(set)', safe=True)
  1556 def minrev(repo, subset, x):
  1562 def minrev(repo, subset, x):
  1557     """Changeset with lowest revision number in set.
  1563     """Changeset with lowest revision number in set.
  1558     """
  1564     """
  1559     os = getset(repo, fullreposet(repo), x)
  1565     os = getset(repo, fullreposet(repo), x)
  1560     try:
  1566     try:
  1561         m = os.min()
  1567         m = os.min()
  1562         if m in subset:
  1568         if m in subset:
  1563             return baseset([m], datarepr=('<min %r, %r>', subset, os))
  1569             return baseset([m], datarepr=(b'<min %r, %r>', subset, os))
  1564     except ValueError:
  1570     except ValueError:
  1565         # os.min() throws a ValueError when the collection is empty.
  1571         # os.min() throws a ValueError when the collection is empty.
  1566         # Same as python's min().
  1572         # Same as python's min().
  1567         pass
  1573         pass
  1568     return baseset(datarepr=('<min %r, %r>', subset, os))
  1574     return baseset(datarepr=(b'<min %r, %r>', subset, os))
  1569 
  1575 
  1570 
  1576 
  1571 @predicate('modifies(pattern)', safe=True, weight=30)
  1577 @predicate(b'modifies(pattern)', safe=True, weight=30)
  1572 def modifies(repo, subset, x):
  1578 def modifies(repo, subset, x):
  1573     """Changesets modifying files matched by pattern.
  1579     """Changesets modifying files matched by pattern.
  1574 
  1580 
  1575     The pattern without explicit kind like ``glob:`` is expected to be
  1581     The pattern without explicit kind like ``glob:`` is expected to be
  1576     relative to the current directory and match against a file or a
  1582     relative to the current directory and match against a file or a
  1577     directory.
  1583     directory.
  1578     """
  1584     """
  1579     # i18n: "modifies" is a keyword
  1585     # i18n: "modifies" is a keyword
  1580     pat = getstring(x, _("modifies requires a pattern"))
  1586     pat = getstring(x, _(b"modifies requires a pattern"))
  1581     return checkstatus(repo, subset, pat, 0)
  1587     return checkstatus(repo, subset, pat, 0)
  1582 
  1588 
  1583 
  1589 
  1584 @predicate('named(namespace)')
  1590 @predicate(b'named(namespace)')
  1585 def named(repo, subset, x):
  1591 def named(repo, subset, x):
  1586     """The changesets in a given namespace.
  1592     """The changesets in a given namespace.
  1587 
  1593 
  1588     Pattern matching is supported for `namespace`. See
  1594     Pattern matching is supported for `namespace`. See
  1589     :hg:`help revisions.patterns`.
  1595     :hg:`help revisions.patterns`.
  1590     """
  1596     """
  1591     # i18n: "named" is a keyword
  1597     # i18n: "named" is a keyword
  1592     args = getargs(x, 1, 1, _('named requires a namespace argument'))
  1598     args = getargs(x, 1, 1, _(b'named requires a namespace argument'))
  1593 
  1599 
  1594     ns = getstring(
  1600     ns = getstring(
  1595         args[0],
  1601         args[0],
  1596         # i18n: "named" is a keyword
  1602         # i18n: "named" is a keyword
  1597         _('the argument to named must be a string'),
  1603         _(b'the argument to named must be a string'),
  1598     )
  1604     )
  1599     kind, pattern, matcher = stringutil.stringmatcher(ns)
  1605     kind, pattern, matcher = stringutil.stringmatcher(ns)
  1600     namespaces = set()
  1606     namespaces = set()
  1601     if kind == 'literal':
  1607     if kind == b'literal':
  1602         if pattern not in repo.names:
  1608         if pattern not in repo.names:
  1603             raise error.RepoLookupError(_("namespace '%s' does not exist") % ns)
  1609             raise error.RepoLookupError(
       
  1610                 _(b"namespace '%s' does not exist") % ns
       
  1611             )
  1604         namespaces.add(repo.names[pattern])
  1612         namespaces.add(repo.names[pattern])
  1605     else:
  1613     else:
  1606         for name, ns in repo.names.iteritems():
  1614         for name, ns in repo.names.iteritems():
  1607             if matcher(name):
  1615             if matcher(name):
  1608                 namespaces.add(ns)
  1616                 namespaces.add(ns)
  1615 
  1623 
  1616     names -= {node.nullrev}
  1624     names -= {node.nullrev}
  1617     return subset & names
  1625     return subset & names
  1618 
  1626 
  1619 
  1627 
  1620 @predicate('id(string)', safe=True)
  1628 @predicate(b'id(string)', safe=True)
  1621 def node_(repo, subset, x):
  1629 def node_(repo, subset, x):
  1622     """Revision non-ambiguously specified by the given hex string prefix.
  1630     """Revision non-ambiguously specified by the given hex string prefix.
  1623     """
  1631     """
  1624     # i18n: "id" is a keyword
  1632     # i18n: "id" is a keyword
  1625     l = getargs(x, 1, 1, _("id requires one argument"))
  1633     l = getargs(x, 1, 1, _(b"id requires one argument"))
  1626     # i18n: "id" is a keyword
  1634     # i18n: "id" is a keyword
  1627     n = getstring(l[0], _("id requires a string"))
  1635     n = getstring(l[0], _(b"id requires a string"))
  1628     if len(n) == 40:
  1636     if len(n) == 40:
  1629         try:
  1637         try:
  1630             rn = repo.changelog.rev(node.bin(n))
  1638             rn = repo.changelog.rev(node.bin(n))
  1631         except error.WdirUnsupported:
  1639         except error.WdirUnsupported:
  1632             rn = node.wdirrev
  1640             rn = node.wdirrev
  1647         return baseset()
  1655         return baseset()
  1648     result = baseset([rn])
  1656     result = baseset([rn])
  1649     return result & subset
  1657     return result & subset
  1650 
  1658 
  1651 
  1659 
  1652 @predicate('none()', safe=True)
  1660 @predicate(b'none()', safe=True)
  1653 def none(repo, subset, x):
  1661 def none(repo, subset, x):
  1654     """No changesets.
  1662     """No changesets.
  1655     """
  1663     """
  1656     # i18n: "none" is a keyword
  1664     # i18n: "none" is a keyword
  1657     getargs(x, 0, 0, _("none takes no arguments"))
  1665     getargs(x, 0, 0, _(b"none takes no arguments"))
  1658     return baseset()
  1666     return baseset()
  1659 
  1667 
  1660 
  1668 
  1661 @predicate('obsolete()', safe=True)
  1669 @predicate(b'obsolete()', safe=True)
  1662 def obsolete(repo, subset, x):
  1670 def obsolete(repo, subset, x):
  1663     """Mutable changeset with a newer version."""
  1671     """Mutable changeset with a newer version."""
  1664     # i18n: "obsolete" is a keyword
  1672     # i18n: "obsolete" is a keyword
  1665     getargs(x, 0, 0, _("obsolete takes no arguments"))
  1673     getargs(x, 0, 0, _(b"obsolete takes no arguments"))
  1666     obsoletes = obsmod.getrevs(repo, 'obsolete')
  1674     obsoletes = obsmod.getrevs(repo, b'obsolete')
  1667     return subset & obsoletes
  1675     return subset & obsoletes
  1668 
  1676 
  1669 
  1677 
  1670 @predicate('only(set, [set])', safe=True)
  1678 @predicate(b'only(set, [set])', safe=True)
  1671 def only(repo, subset, x):
  1679 def only(repo, subset, x):
  1672     """Changesets that are ancestors of the first set that are not ancestors
  1680     """Changesets that are ancestors of the first set that are not ancestors
  1673     of any other head in the repo. If a second set is specified, the result
  1681     of any other head in the repo. If a second set is specified, the result
  1674     is ancestors of the first set that are not ancestors of the second set
  1682     is ancestors of the first set that are not ancestors of the second set
  1675     (i.e. ::<set1> - ::<set2>).
  1683     (i.e. ::<set1> - ::<set2>).
  1676     """
  1684     """
  1677     cl = repo.changelog
  1685     cl = repo.changelog
  1678     # i18n: "only" is a keyword
  1686     # i18n: "only" is a keyword
  1679     args = getargs(x, 1, 2, _('only takes one or two arguments'))
  1687     args = getargs(x, 1, 2, _(b'only takes one or two arguments'))
  1680     include = getset(repo, fullreposet(repo), args[0])
  1688     include = getset(repo, fullreposet(repo), args[0])
  1681     if len(args) == 1:
  1689     if len(args) == 1:
  1682         if not include:
  1690         if not include:
  1683             return baseset()
  1691             return baseset()
  1684 
  1692 
  1695     # XXX we should turn this into a baseset instead of a set, smartset may do
  1703     # XXX we should turn this into a baseset instead of a set, smartset may do
  1696     # some optimizations from the fact this is a baseset.
  1704     # some optimizations from the fact this is a baseset.
  1697     return subset & results
  1705     return subset & results
  1698 
  1706 
  1699 
  1707 
  1700 @predicate('origin([set])', safe=True)
  1708 @predicate(b'origin([set])', safe=True)
  1701 def origin(repo, subset, x):
  1709 def origin(repo, subset, x):
  1702     """
  1710     """
  1703     Changesets that were specified as a source for the grafts, transplants or
  1711     Changesets that were specified as a source for the grafts, transplants or
  1704     rebases that created the given revisions.  Omitting the optional set is the
  1712     rebases that created the given revisions.  Omitting the optional set is the
  1705     same as passing all().  If a changeset created by these operations is itself
  1713     same as passing all().  If a changeset created by these operations is itself
  1728     # XXX we should turn this into a baseset instead of a set, smartset may do
  1736     # XXX we should turn this into a baseset instead of a set, smartset may do
  1729     # some optimizations from the fact this is a baseset.
  1737     # some optimizations from the fact this is a baseset.
  1730     return subset & o
  1738     return subset & o
  1731 
  1739 
  1732 
  1740 
  1733 @predicate('outgoing([path])', safe=False, weight=10)
  1741 @predicate(b'outgoing([path])', safe=False, weight=10)
  1734 def outgoing(repo, subset, x):
  1742 def outgoing(repo, subset, x):
  1735     """Changesets not found in the specified destination repository, or the
  1743     """Changesets not found in the specified destination repository, or the
  1736     default push location.
  1744     default push location.
  1737     """
  1745     """
  1738     # Avoid cycles.
  1746     # Avoid cycles.
  1740         discovery,
  1748         discovery,
  1741         hg,
  1749         hg,
  1742     )
  1750     )
  1743 
  1751 
  1744     # i18n: "outgoing" is a keyword
  1752     # i18n: "outgoing" is a keyword
  1745     l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
  1753     l = getargs(x, 0, 1, _(b"outgoing takes one or no arguments"))
  1746     # i18n: "outgoing" is a keyword
  1754     # i18n: "outgoing" is a keyword
  1747     dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
  1755     dest = (
       
  1756         l and getstring(l[0], _(b"outgoing requires a repository path")) or b''
       
  1757     )
  1748     if not dest:
  1758     if not dest:
  1749         # ui.paths.getpath() explicitly tests for None, not just a boolean
  1759         # ui.paths.getpath() explicitly tests for None, not just a boolean
  1750         dest = None
  1760         dest = None
  1751     path = repo.ui.paths.getpath(dest, default=('default-push', 'default'))
  1761     path = repo.ui.paths.getpath(dest, default=(b'default-push', b'default'))
  1752     if not path:
  1762     if not path:
  1753         raise error.Abort(
  1763         raise error.Abort(
  1754             _('default repository not configured!'),
  1764             _(b'default repository not configured!'),
  1755             hint=_("see 'hg help config.paths'"),
  1765             hint=_(b"see 'hg help config.paths'"),
  1756         )
  1766         )
  1757     dest = path.pushloc or path.loc
  1767     dest = path.pushloc or path.loc
  1758     branches = path.branch, []
  1768     branches = path.branch, []
  1759 
  1769 
  1760     revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
  1770     revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
  1767     cl = repo.changelog
  1777     cl = repo.changelog
  1768     o = {cl.rev(r) for r in outgoing.missing}
  1778     o = {cl.rev(r) for r in outgoing.missing}
  1769     return subset & o
  1779     return subset & o
  1770 
  1780 
  1771 
  1781 
  1772 @predicate('p1([set])', safe=True)
  1782 @predicate(b'p1([set])', safe=True)
  1773 def p1(repo, subset, x):
  1783 def p1(repo, subset, x):
  1774     """First parent of changesets in set, or the working directory.
  1784     """First parent of changesets in set, or the working directory.
  1775     """
  1785     """
  1776     if x is None:
  1786     if x is None:
  1777         p = repo[x].p1().rev()
  1787         p = repo[x].p1().rev()
  1790     # XXX we should turn this into a baseset instead of a set, smartset may do
  1800     # XXX we should turn this into a baseset instead of a set, smartset may do
  1791     # some optimizations from the fact this is a baseset.
  1801     # some optimizations from the fact this is a baseset.
  1792     return subset & ps
  1802     return subset & ps
  1793 
  1803 
  1794 
  1804 
  1795 @predicate('p2([set])', safe=True)
  1805 @predicate(b'p2([set])', safe=True)
  1796 def p2(repo, subset, x):
  1806 def p2(repo, subset, x):
  1797     """Second parent of changesets in set, or the working directory.
  1807     """Second parent of changesets in set, or the working directory.
  1798     """
  1808     """
  1799     if x is None:
  1809     if x is None:
  1800         ps = repo[x].parents()
  1810         ps = repo[x].parents()
  1823 
  1833 
  1824 def parentpost(repo, subset, x, order):
  1834 def parentpost(repo, subset, x, order):
  1825     return p1(repo, subset, x)
  1835     return p1(repo, subset, x)
  1826 
  1836 
  1827 
  1837 
  1828 @predicate('parents([set])', safe=True)
  1838 @predicate(b'parents([set])', safe=True)
  1829 def parents(repo, subset, x):
  1839 def parents(repo, subset, x):
  1830     """
  1840     """
  1831     The set of all parents for all changesets in set, or the working directory.
  1841     The set of all parents for all changesets in set, or the working directory.
  1832     """
  1842     """
  1833     if x is None:
  1843     if x is None:
  1849 def _phase(repo, subset, *targets):
  1859 def _phase(repo, subset, *targets):
  1850     """helper to select all rev in <targets> phases"""
  1860     """helper to select all rev in <targets> phases"""
  1851     return repo._phasecache.getrevset(repo, targets, subset)
  1861     return repo._phasecache.getrevset(repo, targets, subset)
  1852 
  1862 
  1853 
  1863 
  1854 @predicate('_phase(idx)', safe=True)
  1864 @predicate(b'_phase(idx)', safe=True)
  1855 def phase(repo, subset, x):
  1865 def phase(repo, subset, x):
  1856     l = getargs(x, 1, 1, "_phase requires one argument")
  1866     l = getargs(x, 1, 1, b"_phase requires one argument")
  1857     target = getinteger(l[0], "_phase expects a number")
  1867     target = getinteger(l[0], b"_phase expects a number")
  1858     return _phase(repo, subset, target)
  1868     return _phase(repo, subset, target)
  1859 
  1869 
  1860 
  1870 
  1861 @predicate('draft()', safe=True)
  1871 @predicate(b'draft()', safe=True)
  1862 def draft(repo, subset, x):
  1872 def draft(repo, subset, x):
  1863     """Changeset in draft phase."""
  1873     """Changeset in draft phase."""
  1864     # i18n: "draft" is a keyword
  1874     # i18n: "draft" is a keyword
  1865     getargs(x, 0, 0, _("draft takes no arguments"))
  1875     getargs(x, 0, 0, _(b"draft takes no arguments"))
  1866     target = phases.draft
  1876     target = phases.draft
  1867     return _phase(repo, subset, target)
  1877     return _phase(repo, subset, target)
  1868 
  1878 
  1869 
  1879 
  1870 @predicate('secret()', safe=True)
  1880 @predicate(b'secret()', safe=True)
  1871 def secret(repo, subset, x):
  1881 def secret(repo, subset, x):
  1872     """Changeset in secret phase."""
  1882     """Changeset in secret phase."""
  1873     # i18n: "secret" is a keyword
  1883     # i18n: "secret" is a keyword
  1874     getargs(x, 0, 0, _("secret takes no arguments"))
  1884     getargs(x, 0, 0, _(b"secret takes no arguments"))
  1875     target = phases.secret
  1885     target = phases.secret
  1876     return _phase(repo, subset, target)
  1886     return _phase(repo, subset, target)
  1877 
  1887 
  1878 
  1888 
  1879 @predicate('stack([revs])', safe=True)
  1889 @predicate(b'stack([revs])', safe=True)
  1880 def stack(repo, subset, x):
  1890 def stack(repo, subset, x):
  1881     """Experimental revset for the stack of changesets or working directory
  1891     """Experimental revset for the stack of changesets or working directory
  1882     parent. (EXPERIMENTAL)
  1892     parent. (EXPERIMENTAL)
  1883     """
  1893     """
  1884     if x is None:
  1894     if x is None:
  1901     try:
  1911     try:
  1902         n = int(n[1])
  1912         n = int(n[1])
  1903         if n not in (0, 1, 2):
  1913         if n not in (0, 1, 2):
  1904             raise ValueError
  1914             raise ValueError
  1905     except (TypeError, ValueError):
  1915     except (TypeError, ValueError):
  1906         raise error.ParseError(_("^ expects a number 0, 1, or 2"))
  1916         raise error.ParseError(_(b"^ expects a number 0, 1, or 2"))
  1907     ps = set()
  1917     ps = set()
  1908     cl = repo.changelog
  1918     cl = repo.changelog
  1909     for r in getset(repo, fullreposet(repo), x):
  1919     for r in getset(repo, fullreposet(repo), x):
  1910         if n == 0:
  1920         if n == 0:
  1911             ps.add(r)
  1921             ps.add(r)
  1924                 if len(parents) == 2:
  1934                 if len(parents) == 2:
  1925                     ps.add(parents[1].rev())
  1935                     ps.add(parents[1].rev())
  1926     return subset & ps
  1936     return subset & ps
  1927 
  1937 
  1928 
  1938 
  1929 @predicate('present(set)', safe=True, takeorder=True)
  1939 @predicate(b'present(set)', safe=True, takeorder=True)
  1930 def present(repo, subset, x, order):
  1940 def present(repo, subset, x, order):
  1931     """An empty set, if any revision in set isn't found; otherwise,
  1941     """An empty set, if any revision in set isn't found; otherwise,
  1932     all revisions in set.
  1942     all revisions in set.
  1933 
  1943 
  1934     If any of specified revisions is not present in the local repository,
  1944     If any of specified revisions is not present in the local repository,
  1940     except error.RepoLookupError:
  1950     except error.RepoLookupError:
  1941         return baseset()
  1951         return baseset()
  1942 
  1952 
  1943 
  1953 
  1944 # for internal use
  1954 # for internal use
  1945 @predicate('_notpublic', safe=True)
  1955 @predicate(b'_notpublic', safe=True)
  1946 def _notpublic(repo, subset, x):
  1956 def _notpublic(repo, subset, x):
  1947     getargs(x, 0, 0, "_notpublic takes no arguments")
  1957     getargs(x, 0, 0, b"_notpublic takes no arguments")
  1948     return _phase(repo, subset, phases.draft, phases.secret)
  1958     return _phase(repo, subset, phases.draft, phases.secret)
  1949 
  1959 
  1950 
  1960 
  1951 # for internal use
  1961 # for internal use
  1952 @predicate('_phaseandancestors(phasename, set)', safe=True)
  1962 @predicate(b'_phaseandancestors(phasename, set)', safe=True)
  1953 def _phaseandancestors(repo, subset, x):
  1963 def _phaseandancestors(repo, subset, x):
  1954     # equivalent to (phasename() & ancestors(set)) but more efficient
  1964     # equivalent to (phasename() & ancestors(set)) but more efficient
  1955     # phasename could be one of 'draft', 'secret', or '_notpublic'
  1965     # phasename could be one of 'draft', 'secret', or '_notpublic'
  1956     args = getargs(x, 2, 2, "_phaseandancestors requires two arguments")
  1966     args = getargs(x, 2, 2, b"_phaseandancestors requires two arguments")
  1957     phasename = getsymbol(args[0])
  1967     phasename = getsymbol(args[0])
  1958     s = getset(repo, fullreposet(repo), args[1])
  1968     s = getset(repo, fullreposet(repo), args[1])
  1959 
  1969 
  1960     draft = phases.draft
  1970     draft = phases.draft
  1961     secret = phases.secret
  1971     secret = phases.secret
  1962     phasenamemap = {
  1972     phasenamemap = {
  1963         '_notpublic': draft,
  1973         b'_notpublic': draft,
  1964         'draft': draft,  # follow secret's ancestors
  1974         b'draft': draft,  # follow secret's ancestors
  1965         'secret': secret,
  1975         b'secret': secret,
  1966     }
  1976     }
  1967     if phasename not in phasenamemap:
  1977     if phasename not in phasenamemap:
  1968         raise error.ParseError('%r is not a valid phasename' % phasename)
  1978         raise error.ParseError(b'%r is not a valid phasename' % phasename)
  1969 
  1979 
  1970     minimalphase = phasenamemap[phasename]
  1980     minimalphase = phasenamemap[phasename]
  1971     getphase = repo._phasecache.phase
  1981     getphase = repo._phasecache.phase
  1972 
  1982 
  1973     def cutfunc(rev):
  1983     def cutfunc(rev):
  1974         return getphase(repo, rev) < minimalphase
  1984         return getphase(repo, rev) < minimalphase
  1975 
  1985 
  1976     revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
  1986     revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
  1977 
  1987 
  1978     if phasename == 'draft':  # need to remove secret changesets
  1988     if phasename == b'draft':  # need to remove secret changesets
  1979         revs = revs.filter(lambda r: getphase(repo, r) == draft)
  1989         revs = revs.filter(lambda r: getphase(repo, r) == draft)
  1980     return subset & revs
  1990     return subset & revs
  1981 
  1991 
  1982 
  1992 
  1983 @predicate('public()', safe=True)
  1993 @predicate(b'public()', safe=True)
  1984 def public(repo, subset, x):
  1994 def public(repo, subset, x):
  1985     """Changeset in public phase."""
  1995     """Changeset in public phase."""
  1986     # i18n: "public" is a keyword
  1996     # i18n: "public" is a keyword
  1987     getargs(x, 0, 0, _("public takes no arguments"))
  1997     getargs(x, 0, 0, _(b"public takes no arguments"))
  1988     return _phase(repo, subset, phases.public)
  1998     return _phase(repo, subset, phases.public)
  1989 
  1999 
  1990 
  2000 
  1991 @predicate('remote([id [,path]])', safe=False)
  2001 @predicate(b'remote([id [,path]])', safe=False)
  1992 def remote(repo, subset, x):
  2002 def remote(repo, subset, x):
  1993     """Local revision that corresponds to the given identifier in a
  2003     """Local revision that corresponds to the given identifier in a
  1994     remote repository, if present. Here, the '.' identifier is a
  2004     remote repository, if present. Here, the '.' identifier is a
  1995     synonym for the current local branch.
  2005     synonym for the current local branch.
  1996     """
  2006     """
  1997 
  2007 
  1998     from . import hg  # avoid start-up nasties
  2008     from . import hg  # avoid start-up nasties
  1999 
  2009 
  2000     # i18n: "remote" is a keyword
  2010     # i18n: "remote" is a keyword
  2001     l = getargs(x, 0, 2, _("remote takes zero, one, or two arguments"))
  2011     l = getargs(x, 0, 2, _(b"remote takes zero, one, or two arguments"))
  2002 
  2012 
  2003     q = '.'
  2013     q = b'.'
  2004     if len(l) > 0:
  2014     if len(l) > 0:
  2005         # i18n: "remote" is a keyword
  2015         # i18n: "remote" is a keyword
  2006         q = getstring(l[0], _("remote requires a string id"))
  2016         q = getstring(l[0], _(b"remote requires a string id"))
  2007     if q == '.':
  2017     if q == b'.':
  2008         q = repo['.'].branch()
  2018         q = repo[b'.'].branch()
  2009 
  2019 
  2010     dest = ''
  2020     dest = b''
  2011     if len(l) > 1:
  2021     if len(l) > 1:
  2012         # i18n: "remote" is a keyword
  2022         # i18n: "remote" is a keyword
  2013         dest = getstring(l[1], _("remote requires a repository path"))
  2023         dest = getstring(l[1], _(b"remote requires a repository path"))
  2014     dest = repo.ui.expandpath(dest or 'default')
  2024     dest = repo.ui.expandpath(dest or b'default')
  2015     dest, branches = hg.parseurl(dest)
  2025     dest, branches = hg.parseurl(dest)
  2016     revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
  2026     revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
  2017     if revs:
  2027     if revs:
  2018         revs = [repo.lookup(rev) for rev in revs]
  2028         revs = [repo.lookup(rev) for rev in revs]
  2019     other = hg.peer(repo, {}, dest)
  2029     other = hg.peer(repo, {}, dest)
  2023         if r in subset:
  2033         if r in subset:
  2024             return baseset([r])
  2034             return baseset([r])
  2025     return baseset()
  2035     return baseset()
  2026 
  2036 
  2027 
  2037 
  2028 @predicate('removes(pattern)', safe=True, weight=30)
  2038 @predicate(b'removes(pattern)', safe=True, weight=30)
  2029 def removes(repo, subset, x):
  2039 def removes(repo, subset, x):
  2030     """Changesets which remove files matching pattern.
  2040     """Changesets which remove files matching pattern.
  2031 
  2041 
  2032     The pattern without explicit kind like ``glob:`` is expected to be
  2042     The pattern without explicit kind like ``glob:`` is expected to be
  2033     relative to the current directory and match against a file or a
  2043     relative to the current directory and match against a file or a
  2034     directory.
  2044     directory.
  2035     """
  2045     """
  2036     # i18n: "removes" is a keyword
  2046     # i18n: "removes" is a keyword
  2037     pat = getstring(x, _("removes requires a pattern"))
  2047     pat = getstring(x, _(b"removes requires a pattern"))
  2038     return checkstatus(repo, subset, pat, 2)
  2048     return checkstatus(repo, subset, pat, 2)
  2039 
  2049 
  2040 
  2050 
  2041 @predicate('rev(number)', safe=True)
  2051 @predicate(b'rev(number)', safe=True)
  2042 def rev(repo, subset, x):
  2052 def rev(repo, subset, x):
  2043     """Revision with the given numeric identifier.
  2053     """Revision with the given numeric identifier.
  2044     """
  2054     """
  2045     # i18n: "rev" is a keyword
  2055     # i18n: "rev" is a keyword
  2046     l = getargs(x, 1, 1, _("rev requires one argument"))
  2056     l = getargs(x, 1, 1, _(b"rev requires one argument"))
  2047     try:
  2057     try:
  2048         # i18n: "rev" is a keyword
  2058         # i18n: "rev" is a keyword
  2049         l = int(getstring(l[0], _("rev requires a number")))
  2059         l = int(getstring(l[0], _(b"rev requires a number")))
  2050     except (TypeError, ValueError):
  2060     except (TypeError, ValueError):
  2051         # i18n: "rev" is a keyword
  2061         # i18n: "rev" is a keyword
  2052         raise error.ParseError(_("rev expects a number"))
  2062         raise error.ParseError(_(b"rev expects a number"))
  2053     if l not in repo.changelog and l not in _virtualrevs:
  2063     if l not in repo.changelog and l not in _virtualrevs:
  2054         return baseset()
  2064         return baseset()
  2055     return subset & baseset([l])
  2065     return subset & baseset([l])
  2056 
  2066 
  2057 
  2067 
  2058 @predicate('_rev(number)', safe=True)
  2068 @predicate(b'_rev(number)', safe=True)
  2059 def _rev(repo, subset, x):
  2069 def _rev(repo, subset, x):
  2060     # internal version of "rev(x)" that raise error if "x" is invalid
  2070     # internal version of "rev(x)" that raise error if "x" is invalid
  2061     # i18n: "rev" is a keyword
  2071     # i18n: "rev" is a keyword
  2062     l = getargs(x, 1, 1, _("rev requires one argument"))
  2072     l = getargs(x, 1, 1, _(b"rev requires one argument"))
  2063     try:
  2073     try:
  2064         # i18n: "rev" is a keyword
  2074         # i18n: "rev" is a keyword
  2065         l = int(getstring(l[0], _("rev requires a number")))
  2075         l = int(getstring(l[0], _(b"rev requires a number")))
  2066     except (TypeError, ValueError):
  2076     except (TypeError, ValueError):
  2067         # i18n: "rev" is a keyword
  2077         # i18n: "rev" is a keyword
  2068         raise error.ParseError(_("rev expects a number"))
  2078         raise error.ParseError(_(b"rev expects a number"))
  2069     repo.changelog.node(l)  # check that the rev exists
  2079     repo.changelog.node(l)  # check that the rev exists
  2070     return subset & baseset([l])
  2080     return subset & baseset([l])
  2071 
  2081 
  2072 
  2082 
  2073 @predicate('revset(set)', safe=True, takeorder=True)
  2083 @predicate(b'revset(set)', safe=True, takeorder=True)
  2074 def revsetpredicate(repo, subset, x, order):
  2084 def revsetpredicate(repo, subset, x, order):
  2075     """Strictly interpret the content as a revset.
  2085     """Strictly interpret the content as a revset.
  2076 
  2086 
  2077     The content of this special predicate will be strictly interpreted as a
  2087     The content of this special predicate will be strictly interpreted as a
  2078     revset. For example, ``revset(id(0))`` will be interpreted as "id(0)"
  2088     revset. For example, ``revset(id(0))`` will be interpreted as "id(0)"
  2079     without possible ambiguity with a "id(0)" bookmark or tag.
  2089     without possible ambiguity with a "id(0)" bookmark or tag.
  2080     """
  2090     """
  2081     return getset(repo, subset, x, order)
  2091     return getset(repo, subset, x, order)
  2082 
  2092 
  2083 
  2093 
  2084 @predicate('matching(revision [, field])', safe=True)
  2094 @predicate(b'matching(revision [, field])', safe=True)
  2085 def matching(repo, subset, x):
  2095 def matching(repo, subset, x):
  2086     """Changesets in which a given set of fields match the set of fields in the
  2096     """Changesets in which a given set of fields match the set of fields in the
  2087     selected revision or set.
  2097     selected revision or set.
  2088 
  2098 
  2089     To match more than one field pass the list of fields to match separated
  2099     To match more than one field pass the list of fields to match separated
  2105 
  2115 
  2106     ``metadata`` is the default field which is used when no fields are
  2116     ``metadata`` is the default field which is used when no fields are
  2107     specified. You can match more than one field at a time.
  2117     specified. You can match more than one field at a time.
  2108     """
  2118     """
  2109     # i18n: "matching" is a keyword
  2119     # i18n: "matching" is a keyword
  2110     l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
  2120     l = getargs(x, 1, 2, _(b"matching takes 1 or 2 arguments"))
  2111 
  2121 
  2112     revs = getset(repo, fullreposet(repo), l[0])
  2122     revs = getset(repo, fullreposet(repo), l[0])
  2113 
  2123 
  2114     fieldlist = ['metadata']
  2124     fieldlist = [b'metadata']
  2115     if len(l) > 1:
  2125     if len(l) > 1:
  2116         fieldlist = getstring(
  2126         fieldlist = getstring(
  2117             l[1],
  2127             l[1],
  2118             # i18n: "matching" is a keyword
  2128             # i18n: "matching" is a keyword
  2119             _("matching requires a string " "as its second argument"),
  2129             _(b"matching requires a string " b"as its second argument"),
  2120         ).split()
  2130         ).split()
  2121 
  2131 
  2122     # Make sure that there are no repeated fields,
  2132     # Make sure that there are no repeated fields,
  2123     # expand the 'special' 'metadata' field type
  2133     # expand the 'special' 'metadata' field type
  2124     # and check the 'files' whenever we check the 'diff'
  2134     # and check the 'files' whenever we check the 'diff'
  2125     fields = []
  2135     fields = []
  2126     for field in fieldlist:
  2136     for field in fieldlist:
  2127         if field == 'metadata':
  2137         if field == b'metadata':
  2128             fields += ['user', 'description', 'date']
  2138             fields += [b'user', b'description', b'date']
  2129         elif field == 'diff':
  2139         elif field == b'diff':
  2130             # a revision matching the diff must also match the files
  2140             # a revision matching the diff must also match the files
  2131             # since matching the diff is very costly, make sure to
  2141             # since matching the diff is very costly, make sure to
  2132             # also match the files first
  2142             # also match the files first
  2133             fields += ['files', 'diff']
  2143             fields += [b'files', b'diff']
  2134         else:
  2144         else:
  2135             if field == 'author':
  2145             if field == b'author':
  2136                 field = 'user'
  2146                 field = b'user'
  2137             fields.append(field)
  2147             fields.append(field)
  2138     fields = set(fields)
  2148     fields = set(fields)
  2139     if 'summary' in fields and 'description' in fields:
  2149     if b'summary' in fields and b'description' in fields:
  2140         # If a revision matches its description it also matches its summary
  2150         # If a revision matches its description it also matches its summary
  2141         fields.discard('summary')
  2151         fields.discard(b'summary')
  2142 
  2152 
  2143     # We may want to match more than one field
  2153     # We may want to match more than one field
  2144     # Not all fields take the same amount of time to be matched
  2154     # Not all fields take the same amount of time to be matched
  2145     # Sort the selected fields in order of increasing matching cost
  2155     # Sort the selected fields in order of increasing matching cost
  2146     fieldorder = [
  2156     fieldorder = [
  2147         'phase',
  2157         b'phase',
  2148         'parents',
  2158         b'parents',
  2149         'user',
  2159         b'user',
  2150         'date',
  2160         b'date',
  2151         'branch',
  2161         b'branch',
  2152         'summary',
  2162         b'summary',
  2153         'files',
  2163         b'files',
  2154         'description',
  2164         b'description',
  2155         'substate',
  2165         b'substate',
  2156         'diff',
  2166         b'diff',
  2157     ]
  2167     ]
  2158 
  2168 
  2159     def fieldkeyfunc(f):
  2169     def fieldkeyfunc(f):
  2160         try:
  2170         try:
  2161             return fieldorder.index(f)
  2171             return fieldorder.index(f)
  2168 
  2178 
  2169     # Each field will be matched with its own "getfield" function
  2179     # Each field will be matched with its own "getfield" function
  2170     # which will be added to the getfieldfuncs array of functions
  2180     # which will be added to the getfieldfuncs array of functions
  2171     getfieldfuncs = []
  2181     getfieldfuncs = []
  2172     _funcs = {
  2182     _funcs = {
  2173         'user': lambda r: repo[r].user(),
  2183         b'user': lambda r: repo[r].user(),
  2174         'branch': lambda r: repo[r].branch(),
  2184         b'branch': lambda r: repo[r].branch(),
  2175         'date': lambda r: repo[r].date(),
  2185         b'date': lambda r: repo[r].date(),
  2176         'description': lambda r: repo[r].description(),
  2186         b'description': lambda r: repo[r].description(),
  2177         'files': lambda r: repo[r].files(),
  2187         b'files': lambda r: repo[r].files(),
  2178         'parents': lambda r: repo[r].parents(),
  2188         b'parents': lambda r: repo[r].parents(),
  2179         'phase': lambda r: repo[r].phase(),
  2189         b'phase': lambda r: repo[r].phase(),
  2180         'substate': lambda r: repo[r].substate,
  2190         b'substate': lambda r: repo[r].substate,
  2181         'summary': lambda r: repo[r].description().splitlines()[0],
  2191         b'summary': lambda r: repo[r].description().splitlines()[0],
  2182         'diff': lambda r: list(
  2192         b'diff': lambda r: list(
  2183             repo[r].diff(opts=diffutil.diffallopts(repo.ui, {'git': True}))
  2193             repo[r].diff(opts=diffutil.diffallopts(repo.ui, {b'git': True}))
  2184         ),
  2194         ),
  2185     }
  2195     }
  2186     for info in fields:
  2196     for info in fields:
  2187         getfield = _funcs.get(info, None)
  2197         getfield = _funcs.get(info, None)
  2188         if getfield is None:
  2198         if getfield is None:
  2189             raise error.ParseError(
  2199             raise error.ParseError(
  2190                 # i18n: "matching" is a keyword
  2200                 # i18n: "matching" is a keyword
  2191                 _("unexpected field name passed to matching: %s")
  2201                 _(b"unexpected field name passed to matching: %s")
  2192                 % info
  2202                 % info
  2193             )
  2203             )
  2194         getfieldfuncs.append(getfield)
  2204         getfieldfuncs.append(getfield)
  2195     # convert the getfield array of functions into a "getinfo" function
  2205     # convert the getfield array of functions into a "getinfo" function
  2196     # which returns an array of field values (or a single value if there
  2206     # which returns an array of field values (or a single value if there
  2206                     match = False
  2216                     match = False
  2207             if match:
  2217             if match:
  2208                 return True
  2218                 return True
  2209         return False
  2219         return False
  2210 
  2220 
  2211     return subset.filter(matches, condrepr=('<matching%r %r>', fields, revs))
  2221     return subset.filter(matches, condrepr=(b'<matching%r %r>', fields, revs))
  2212 
  2222 
  2213 
  2223 
  2214 @predicate('reverse(set)', safe=True, takeorder=True, weight=0)
  2224 @predicate(b'reverse(set)', safe=True, takeorder=True, weight=0)
  2215 def reverse(repo, subset, x, order):
  2225 def reverse(repo, subset, x, order):
  2216     """Reverse order of set.
  2226     """Reverse order of set.
  2217     """
  2227     """
  2218     l = getset(repo, subset, x, order)
  2228     l = getset(repo, subset, x, order)
  2219     if order == defineorder:
  2229     if order == defineorder:
  2220         l.reverse()
  2230         l.reverse()
  2221     return l
  2231     return l
  2222 
  2232 
  2223 
  2233 
  2224 @predicate('roots(set)', safe=True)
  2234 @predicate(b'roots(set)', safe=True)
  2225 def roots(repo, subset, x):
  2235 def roots(repo, subset, x):
  2226     """Changesets in set with no parent changeset in set.
  2236     """Changesets in set with no parent changeset in set.
  2227     """
  2237     """
  2228     s = getset(repo, fullreposet(repo), x)
  2238     s = getset(repo, fullreposet(repo), x)
  2229     parents = repo.changelog.parentrevs
  2239     parents = repo.changelog.parentrevs
  2232         for p in parents(r):
  2242         for p in parents(r):
  2233             if 0 <= p and p in s:
  2243             if 0 <= p and p in s:
  2234                 return False
  2244                 return False
  2235         return True
  2245         return True
  2236 
  2246 
  2237     return subset & s.filter(filter, condrepr='<roots>')
  2247     return subset & s.filter(filter, condrepr=b'<roots>')
  2238 
  2248 
  2239 
  2249 
  2240 _sortkeyfuncs = {
  2250 _sortkeyfuncs = {
  2241     'rev': lambda c: c.rev(),
  2251     b'rev': lambda c: c.rev(),
  2242     'branch': lambda c: c.branch(),
  2252     b'branch': lambda c: c.branch(),
  2243     'desc': lambda c: c.description(),
  2253     b'desc': lambda c: c.description(),
  2244     'user': lambda c: c.user(),
  2254     b'user': lambda c: c.user(),
  2245     'author': lambda c: c.user(),
  2255     b'author': lambda c: c.user(),
  2246     'date': lambda c: c.date()[0],
  2256     b'date': lambda c: c.date()[0],
  2247 }
  2257 }
  2248 
  2258 
  2249 
  2259 
  2250 def _getsortargs(x):
  2260 def _getsortargs(x):
  2251     """Parse sort options into (set, [(key, reverse)], opts)"""
  2261     """Parse sort options into (set, [(key, reverse)], opts)"""
  2252     args = getargsdict(x, 'sort', 'set keys topo.firstbranch')
  2262     args = getargsdict(x, b'sort', b'set keys topo.firstbranch')
  2253     if 'set' not in args:
  2263     if b'set' not in args:
  2254         # i18n: "sort" is a keyword
  2264         # i18n: "sort" is a keyword
  2255         raise error.ParseError(_('sort requires one or two arguments'))
  2265         raise error.ParseError(_(b'sort requires one or two arguments'))
  2256     keys = "rev"
  2266     keys = b"rev"
  2257     if 'keys' in args:
  2267     if b'keys' in args:
  2258         # i18n: "sort" is a keyword
  2268         # i18n: "sort" is a keyword
  2259         keys = getstring(args['keys'], _("sort spec must be a string"))
  2269         keys = getstring(args[b'keys'], _(b"sort spec must be a string"))
  2260 
  2270 
  2261     keyflags = []
  2271     keyflags = []
  2262     for k in keys.split():
  2272     for k in keys.split():
  2263         fk = k
  2273         fk = k
  2264         reverse = k.startswith('-')
  2274         reverse = k.startswith(b'-')
  2265         if reverse:
  2275         if reverse:
  2266             k = k[1:]
  2276             k = k[1:]
  2267         if k not in _sortkeyfuncs and k != 'topo':
  2277         if k not in _sortkeyfuncs and k != b'topo':
  2268             raise error.ParseError(
  2278             raise error.ParseError(
  2269                 _("unknown sort key %r") % pycompat.bytestr(fk)
  2279                 _(b"unknown sort key %r") % pycompat.bytestr(fk)
  2270             )
  2280             )
  2271         keyflags.append((k, reverse))
  2281         keyflags.append((k, reverse))
  2272 
  2282 
  2273     if len(keyflags) > 1 and any(k == 'topo' for k, reverse in keyflags):
  2283     if len(keyflags) > 1 and any(k == b'topo' for k, reverse in keyflags):
  2274         # i18n: "topo" is a keyword
  2284         # i18n: "topo" is a keyword
  2275         raise error.ParseError(
  2285         raise error.ParseError(
  2276             _('topo sort order cannot be combined ' 'with other sort keys')
  2286             _(b'topo sort order cannot be combined ' b'with other sort keys')
  2277         )
  2287         )
  2278 
  2288 
  2279     opts = {}
  2289     opts = {}
  2280     if 'topo.firstbranch' in args:
  2290     if b'topo.firstbranch' in args:
  2281         if any(k == 'topo' for k, reverse in keyflags):
  2291         if any(k == b'topo' for k, reverse in keyflags):
  2282             opts['topo.firstbranch'] = args['topo.firstbranch']
  2292             opts[b'topo.firstbranch'] = args[b'topo.firstbranch']
  2283         else:
  2293         else:
  2284             # i18n: "topo" and "topo.firstbranch" are keywords
  2294             # i18n: "topo" and "topo.firstbranch" are keywords
  2285             raise error.ParseError(
  2295             raise error.ParseError(
  2286                 _(
  2296                 _(
  2287                     'topo.firstbranch can only be used '
  2297                     b'topo.firstbranch can only be used '
  2288                     'when using the topo sort key'
  2298                     b'when using the topo sort key'
  2289                 )
  2299                 )
  2290             )
  2300             )
  2291 
  2301 
  2292     return args['set'], keyflags, opts
  2302     return args[b'set'], keyflags, opts
  2293 
  2303 
  2294 
  2304 
  2295 @predicate(
  2305 @predicate(
  2296     'sort(set[, [-]key... [, ...]])', safe=True, takeorder=True, weight=10
  2306     b'sort(set[, [-]key... [, ...]])', safe=True, takeorder=True, weight=10
  2297 )
  2307 )
  2298 def sort(repo, subset, x, order):
  2308 def sort(repo, subset, x, order):
  2299     """Sort set by keys. The default sort order is ascending, specify a key
  2309     """Sort set by keys. The default sort order is ascending, specify a key
  2300     as ``-key`` to sort in descending order.
  2310     as ``-key`` to sort in descending order.
  2301 
  2311 
  2316     s, keyflags, opts = _getsortargs(x)
  2326     s, keyflags, opts = _getsortargs(x)
  2317     revs = getset(repo, subset, s, order)
  2327     revs = getset(repo, subset, s, order)
  2318 
  2328 
  2319     if not keyflags or order != defineorder:
  2329     if not keyflags or order != defineorder:
  2320         return revs
  2330         return revs
  2321     if len(keyflags) == 1 and keyflags[0][0] == "rev":
  2331     if len(keyflags) == 1 and keyflags[0][0] == b"rev":
  2322         revs.sort(reverse=keyflags[0][1])
  2332         revs.sort(reverse=keyflags[0][1])
  2323         return revs
  2333         return revs
  2324     elif keyflags[0][0] == "topo":
  2334     elif keyflags[0][0] == b"topo":
  2325         firstbranch = ()
  2335         firstbranch = ()
  2326         if 'topo.firstbranch' in opts:
  2336         if b'topo.firstbranch' in opts:
  2327             firstbranch = getset(repo, subset, opts['topo.firstbranch'])
  2337             firstbranch = getset(repo, subset, opts[b'topo.firstbranch'])
  2328         revs = baseset(
  2338         revs = baseset(
  2329             dagop.toposort(revs, repo.changelog.parentrevs, firstbranch),
  2339             dagop.toposort(revs, repo.changelog.parentrevs, firstbranch),
  2330             istopo=True,
  2340             istopo=True,
  2331         )
  2341         )
  2332         if keyflags[0][1]:
  2342         if keyflags[0][1]:
  2338     for k, reverse in reversed(keyflags):
  2348     for k, reverse in reversed(keyflags):
  2339         ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
  2349         ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
  2340     return baseset([c.rev() for c in ctxs])
  2350     return baseset([c.rev() for c in ctxs])
  2341 
  2351 
  2342 
  2352 
  2343 @predicate('subrepo([pattern])')
  2353 @predicate(b'subrepo([pattern])')
  2344 def subrepo(repo, subset, x):
  2354 def subrepo(repo, subset, x):
  2345     """Changesets that add, modify or remove the given subrepo.  If no subrepo
  2355     """Changesets that add, modify or remove the given subrepo.  If no subrepo
  2346     pattern is named, any subrepo changes are returned.
  2356     pattern is named, any subrepo changes are returned.
  2347     """
  2357     """
  2348     # i18n: "subrepo" is a keyword
  2358     # i18n: "subrepo" is a keyword
  2349     args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
  2359     args = getargs(x, 0, 1, _(b'subrepo takes at most one argument'))
  2350     pat = None
  2360     pat = None
  2351     if len(args) != 0:
  2361     if len(args) != 0:
  2352         pat = getstring(args[0], _("subrepo requires a pattern"))
  2362         pat = getstring(args[0], _(b"subrepo requires a pattern"))
  2353 
  2363 
  2354     m = matchmod.exact(['.hgsubstate'])
  2364     m = matchmod.exact([b'.hgsubstate'])
  2355 
  2365 
  2356     def submatches(names):
  2366     def submatches(names):
  2357         k, p, m = stringutil.stringmatcher(pat)
  2367         k, p, m = stringutil.stringmatcher(pat)
  2358         for name in names:
  2368         for name in names:
  2359             if m(name):
  2369             if m(name):
  2380         if s.removed:
  2390         if s.removed:
  2381             return any(submatches(c.p1().substate.keys()))
  2391             return any(submatches(c.p1().substate.keys()))
  2382 
  2392 
  2383         return False
  2393         return False
  2384 
  2394 
  2385     return subset.filter(matches, condrepr=('<subrepo %r>', pat))
  2395     return subset.filter(matches, condrepr=(b'<subrepo %r>', pat))
  2386 
  2396 
  2387 
  2397 
  2388 def _mapbynodefunc(repo, s, f):
  2398 def _mapbynodefunc(repo, s, f):
  2389     """(repo, smartset, [node] -> [node]) -> smartset
  2399     """(repo, smartset, [node] -> [node]) -> smartset
  2390 
  2400 
  2398     nodemap = cl.nodemap
  2408     nodemap = cl.nodemap
  2399     result = set(torev(n) for n in f(tonode(r) for r in s) if n in nodemap)
  2409     result = set(torev(n) for n in f(tonode(r) for r in s) if n in nodemap)
  2400     return smartset.baseset(result - repo.changelog.filteredrevs)
  2410     return smartset.baseset(result - repo.changelog.filteredrevs)
  2401 
  2411 
  2402 
  2412 
  2403 @predicate('successors(set)', safe=True)
  2413 @predicate(b'successors(set)', safe=True)
  2404 def successors(repo, subset, x):
  2414 def successors(repo, subset, x):
  2405     """All successors for set, including the given set themselves"""
  2415     """All successors for set, including the given set themselves"""
  2406     s = getset(repo, fullreposet(repo), x)
  2416     s = getset(repo, fullreposet(repo), x)
  2407     f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
  2417     f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
  2408     d = _mapbynodefunc(repo, s, f)
  2418     d = _mapbynodefunc(repo, s, f)
  2411 
  2421 
  2412 def _substringmatcher(pattern, casesensitive=True):
  2422 def _substringmatcher(pattern, casesensitive=True):
  2413     kind, pattern, matcher = stringutil.stringmatcher(
  2423     kind, pattern, matcher = stringutil.stringmatcher(
  2414         pattern, casesensitive=casesensitive
  2424         pattern, casesensitive=casesensitive
  2415     )
  2425     )
  2416     if kind == 'literal':
  2426     if kind == b'literal':
  2417         if not casesensitive:
  2427         if not casesensitive:
  2418             pattern = encoding.lower(pattern)
  2428             pattern = encoding.lower(pattern)
  2419             matcher = lambda s: pattern in encoding.lower(s)
  2429             matcher = lambda s: pattern in encoding.lower(s)
  2420         else:
  2430         else:
  2421             matcher = lambda s: pattern in s
  2431             matcher = lambda s: pattern in s
  2422     return kind, pattern, matcher
  2432     return kind, pattern, matcher
  2423 
  2433 
  2424 
  2434 
  2425 @predicate('tag([name])', safe=True)
  2435 @predicate(b'tag([name])', safe=True)
  2426 def tag(repo, subset, x):
  2436 def tag(repo, subset, x):
  2427     """The specified tag by name, or all tagged revisions if no name is given.
  2437     """The specified tag by name, or all tagged revisions if no name is given.
  2428 
  2438 
  2429     Pattern matching is supported for `name`. See
  2439     Pattern matching is supported for `name`. See
  2430     :hg:`help revisions.patterns`.
  2440     :hg:`help revisions.patterns`.
  2431     """
  2441     """
  2432     # i18n: "tag" is a keyword
  2442     # i18n: "tag" is a keyword
  2433     args = getargs(x, 0, 1, _("tag takes one or no arguments"))
  2443     args = getargs(x, 0, 1, _(b"tag takes one or no arguments"))
  2434     cl = repo.changelog
  2444     cl = repo.changelog
  2435     if args:
  2445     if args:
  2436         pattern = getstring(
  2446         pattern = getstring(
  2437             args[0],
  2447             args[0],
  2438             # i18n: "tag" is a keyword
  2448             # i18n: "tag" is a keyword
  2439             _('the argument to tag must be a string'),
  2449             _(b'the argument to tag must be a string'),
  2440         )
  2450         )
  2441         kind, pattern, matcher = stringutil.stringmatcher(pattern)
  2451         kind, pattern, matcher = stringutil.stringmatcher(pattern)
  2442         if kind == 'literal':
  2452         if kind == b'literal':
  2443             # avoid resolving all tags
  2453             # avoid resolving all tags
  2444             tn = repo._tagscache.tags.get(pattern, None)
  2454             tn = repo._tagscache.tags.get(pattern, None)
  2445             if tn is None:
  2455             if tn is None:
  2446                 raise error.RepoLookupError(
  2456                 raise error.RepoLookupError(
  2447                     _("tag '%s' does not exist") % pattern
  2457                     _(b"tag '%s' does not exist") % pattern
  2448                 )
  2458                 )
  2449             s = {repo[tn].rev()}
  2459             s = {repo[tn].rev()}
  2450         else:
  2460         else:
  2451             s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
  2461             s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
  2452     else:
  2462     else:
  2453         s = {cl.rev(n) for t, n in repo.tagslist() if t != 'tip'}
  2463         s = {cl.rev(n) for t, n in repo.tagslist() if t != b'tip'}
  2454     return subset & s
  2464     return subset & s
  2455 
  2465 
  2456 
  2466 
  2457 @predicate('tagged', safe=True)
  2467 @predicate(b'tagged', safe=True)
  2458 def tagged(repo, subset, x):
  2468 def tagged(repo, subset, x):
  2459     return tag(repo, subset, x)
  2469     return tag(repo, subset, x)
  2460 
  2470 
  2461 
  2471 
  2462 @predicate('orphan()', safe=True)
  2472 @predicate(b'orphan()', safe=True)
  2463 def orphan(repo, subset, x):
  2473 def orphan(repo, subset, x):
  2464     """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)
  2474     """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)
  2465     """
  2475     """
  2466     # i18n: "orphan" is a keyword
  2476     # i18n: "orphan" is a keyword
  2467     getargs(x, 0, 0, _("orphan takes no arguments"))
  2477     getargs(x, 0, 0, _(b"orphan takes no arguments"))
  2468     orphan = obsmod.getrevs(repo, 'orphan')
  2478     orphan = obsmod.getrevs(repo, b'orphan')
  2469     return subset & orphan
  2479     return subset & orphan
  2470 
  2480 
  2471 
  2481 
  2472 @predicate('user(string)', safe=True, weight=10)
  2482 @predicate(b'user(string)', safe=True, weight=10)
  2473 def user(repo, subset, x):
  2483 def user(repo, subset, x):
  2474     """User name contains string. The match is case-insensitive.
  2484     """User name contains string. The match is case-insensitive.
  2475 
  2485 
  2476     Pattern matching is supported for `string`. See
  2486     Pattern matching is supported for `string`. See
  2477     :hg:`help revisions.patterns`.
  2487     :hg:`help revisions.patterns`.
  2478     """
  2488     """
  2479     return author(repo, subset, x)
  2489     return author(repo, subset, x)
  2480 
  2490 
  2481 
  2491 
  2482 @predicate('wdir()', safe=True, weight=0)
  2492 @predicate(b'wdir()', safe=True, weight=0)
  2483 def wdir(repo, subset, x):
  2493 def wdir(repo, subset, x):
  2484     """Working directory. (EXPERIMENTAL)"""
  2494     """Working directory. (EXPERIMENTAL)"""
  2485     # i18n: "wdir" is a keyword
  2495     # i18n: "wdir" is a keyword
  2486     getargs(x, 0, 0, _("wdir takes no arguments"))
  2496     getargs(x, 0, 0, _(b"wdir takes no arguments"))
  2487     if node.wdirrev in subset or isinstance(subset, fullreposet):
  2497     if node.wdirrev in subset or isinstance(subset, fullreposet):
  2488         return baseset([node.wdirrev])
  2498         return baseset([node.wdirrev])
  2489     return baseset()
  2499     return baseset()
  2490 
  2500 
  2491 
  2501 
  2492 def _orderedlist(repo, subset, x):
  2502 def _orderedlist(repo, subset, x):
  2493     s = getstring(x, "internal error")
  2503     s = getstring(x, b"internal error")
  2494     if not s:
  2504     if not s:
  2495         return baseset()
  2505         return baseset()
  2496     # remove duplicates here. it's difficult for caller to deduplicate sets
  2506     # remove duplicates here. it's difficult for caller to deduplicate sets
  2497     # because different symbols can point to the same rev.
  2507     # because different symbols can point to the same rev.
  2498     cl = repo.changelog
  2508     cl = repo.changelog
  2499     ls = []
  2509     ls = []
  2500     seen = set()
  2510     seen = set()
  2501     for t in s.split('\0'):
  2511     for t in s.split(b'\0'):
  2502         try:
  2512         try:
  2503             # fast path for integer revision
  2513             # fast path for integer revision
  2504             r = int(t)
  2514             r = int(t)
  2505             if ('%d' % r) != t or r not in cl:
  2515             if (b'%d' % r) != t or r not in cl:
  2506                 raise ValueError
  2516                 raise ValueError
  2507             revs = [r]
  2517             revs = [r]
  2508         except ValueError:
  2518         except ValueError:
  2509             revs = stringset(repo, subset, t, defineorder)
  2519             revs = stringset(repo, subset, t, defineorder)
  2510 
  2520 
  2520             seen.add(r)
  2530             seen.add(r)
  2521     return baseset(ls)
  2531     return baseset(ls)
  2522 
  2532 
  2523 
  2533 
  2524 # for internal use
  2534 # for internal use
  2525 @predicate('_list', safe=True, takeorder=True)
  2535 @predicate(b'_list', safe=True, takeorder=True)
  2526 def _list(repo, subset, x, order):
  2536 def _list(repo, subset, x, order):
  2527     if order == followorder:
  2537     if order == followorder:
  2528         # slow path to take the subset order
  2538         # slow path to take the subset order
  2529         return subset & _orderedlist(repo, fullreposet(repo), x)
  2539         return subset & _orderedlist(repo, fullreposet(repo), x)
  2530     else:
  2540     else:
  2531         return _orderedlist(repo, subset, x)
  2541         return _orderedlist(repo, subset, x)
  2532 
  2542 
  2533 
  2543 
  2534 def _orderedintlist(repo, subset, x):
  2544 def _orderedintlist(repo, subset, x):
  2535     s = getstring(x, "internal error")
  2545     s = getstring(x, b"internal error")
  2536     if not s:
  2546     if not s:
  2537         return baseset()
  2547         return baseset()
  2538     ls = [int(r) for r in s.split('\0')]
  2548     ls = [int(r) for r in s.split(b'\0')]
  2539     s = subset
  2549     s = subset
  2540     return baseset([r for r in ls if r in s])
  2550     return baseset([r for r in ls if r in s])
  2541 
  2551 
  2542 
  2552 
  2543 # for internal use
  2553 # for internal use
  2544 @predicate('_intlist', safe=True, takeorder=True, weight=0)
  2554 @predicate(b'_intlist', safe=True, takeorder=True, weight=0)
  2545 def _intlist(repo, subset, x, order):
  2555 def _intlist(repo, subset, x, order):
  2546     if order == followorder:
  2556     if order == followorder:
  2547         # slow path to take the subset order
  2557         # slow path to take the subset order
  2548         return subset & _orderedintlist(repo, fullreposet(repo), x)
  2558         return subset & _orderedintlist(repo, fullreposet(repo), x)
  2549     else:
  2559     else:
  2550         return _orderedintlist(repo, subset, x)
  2560         return _orderedintlist(repo, subset, x)
  2551 
  2561 
  2552 
  2562 
  2553 def _orderedhexlist(repo, subset, x):
  2563 def _orderedhexlist(repo, subset, x):
  2554     s = getstring(x, "internal error")
  2564     s = getstring(x, b"internal error")
  2555     if not s:
  2565     if not s:
  2556         return baseset()
  2566         return baseset()
  2557     cl = repo.changelog
  2567     cl = repo.changelog
  2558     ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
  2568     ls = [cl.rev(node.bin(r)) for r in s.split(b'\0')]
  2559     s = subset
  2569     s = subset
  2560     return baseset([r for r in ls if r in s])
  2570     return baseset([r for r in ls if r in s])
  2561 
  2571 
  2562 
  2572 
  2563 # for internal use
  2573 # for internal use
  2564 @predicate('_hexlist', safe=True, takeorder=True)
  2574 @predicate(b'_hexlist', safe=True, takeorder=True)
  2565 def _hexlist(repo, subset, x, order):
  2575 def _hexlist(repo, subset, x, order):
  2566     if order == followorder:
  2576     if order == followorder:
  2567         # slow path to take the subset order
  2577         # slow path to take the subset order
  2568         return subset & _orderedhexlist(repo, fullreposet(repo), x)
  2578         return subset & _orderedhexlist(repo, fullreposet(repo), x)
  2569     else:
  2579     else:
  2570         return _orderedhexlist(repo, subset, x)
  2580         return _orderedhexlist(repo, subset, x)
  2571 
  2581 
  2572 
  2582 
  2573 methods = {
  2583 methods = {
  2574     "range": rangeset,
  2584     b"range": rangeset,
  2575     "rangeall": rangeall,
  2585     b"rangeall": rangeall,
  2576     "rangepre": rangepre,
  2586     b"rangepre": rangepre,
  2577     "rangepost": rangepost,
  2587     b"rangepost": rangepost,
  2578     "dagrange": dagrange,
  2588     b"dagrange": dagrange,
  2579     "string": stringset,
  2589     b"string": stringset,
  2580     "symbol": stringset,
  2590     b"symbol": stringset,
  2581     "and": andset,
  2591     b"and": andset,
  2582     "andsmally": andsmallyset,
  2592     b"andsmally": andsmallyset,
  2583     "or": orset,
  2593     b"or": orset,
  2584     "not": notset,
  2594     b"not": notset,
  2585     "difference": differenceset,
  2595     b"difference": differenceset,
  2586     "relation": relationset,
  2596     b"relation": relationset,
  2587     "relsubscript": relsubscriptset,
  2597     b"relsubscript": relsubscriptset,
  2588     "subscript": subscriptset,
  2598     b"subscript": subscriptset,
  2589     "list": listset,
  2599     b"list": listset,
  2590     "keyvalue": keyvaluepair,
  2600     b"keyvalue": keyvaluepair,
  2591     "func": func,
  2601     b"func": func,
  2592     "ancestor": ancestorspec,
  2602     b"ancestor": ancestorspec,
  2593     "parent": parentspec,
  2603     b"parent": parentspec,
  2594     "parentpost": parentpost,
  2604     b"parentpost": parentpost,
  2595     "smartset": rawsmartset,
  2605     b"smartset": rawsmartset,
  2596 }
  2606 }
  2597 
  2607 
  2598 subscriptrelations = {
  2608 subscriptrelations = {
  2599     "g": generationsrel,
  2609     b"g": generationsrel,
  2600     "generations": generationsrel,
  2610     b"generations": generationsrel,
  2601 }
  2611 }
  2602 
  2612 
  2603 
  2613 
  2604 def lookupfn(repo):
  2614 def lookupfn(repo):
  2605     return lambda symbol: scmutil.isrevsymbol(repo, symbol)
  2615     return lambda symbol: scmutil.isrevsymbol(repo, symbol)
  2625         def mfunc(repo, subset=None):
  2635         def mfunc(repo, subset=None):
  2626             return baseset()
  2636             return baseset()
  2627 
  2637 
  2628         return mfunc
  2638         return mfunc
  2629     if not all(specs):
  2639     if not all(specs):
  2630         raise error.ParseError(_("empty query"))
  2640         raise error.ParseError(_(b"empty query"))
  2631     if len(specs) == 1:
  2641     if len(specs) == 1:
  2632         tree = revsetlang.parse(specs[0], lookup)
  2642         tree = revsetlang.parse(specs[0], lookup)
  2633     else:
  2643     else:
  2634         tree = (
  2644         tree = (
  2635             'or',
  2645             b'or',
  2636             ('list',) + tuple(revsetlang.parse(s, lookup) for s in specs),
  2646             (b'list',) + tuple(revsetlang.parse(s, lookup) for s in specs),
  2637         )
  2647         )
  2638 
  2648 
  2639     aliases = []
  2649     aliases = []
  2640     warn = None
  2650     warn = None
  2641     if ui:
  2651     if ui:
  2642         aliases.extend(ui.configitems('revsetalias'))
  2652         aliases.extend(ui.configitems(b'revsetalias'))
  2643         warn = ui.warn
  2653         warn = ui.warn
  2644     if localalias:
  2654     if localalias:
  2645         aliases.extend(localalias.items())
  2655         aliases.extend(localalias.items())
  2646     if aliases:
  2656     if aliases:
  2647         tree = revsetlang.expandaliases(tree, aliases, warn=warn)
  2657         tree = revsetlang.expandaliases(tree, aliases, warn=warn)