mercurial/logcmdutil.py
changeset 45565 c1d0f83d62c4
parent 45564 a717de1cb624
child 45566 24df19a9ab87
equal deleted inserted replaced
45564:a717de1cb624 45565:c1d0f83d62c4
    15 from .node import (
    15 from .node import (
    16     nullid,
    16     nullid,
    17     wdirid,
    17     wdirid,
    18     wdirrev,
    18     wdirrev,
    19 )
    19 )
       
    20 
       
    21 from .thirdparty import attr
    20 
    22 
    21 from . import (
    23 from . import (
    22     dagop,
    24     dagop,
    23     error,
    25     error,
    24     formatter,
    26     formatter,
    43 
    45 
    44 
    46 
    45 if pycompat.TYPE_CHECKING:
    47 if pycompat.TYPE_CHECKING:
    46     from typing import (
    48     from typing import (
    47         Any,
    49         Any,
       
    50         Dict,
       
    51         List,
    48         Optional,
    52         Optional,
    49         Tuple,
    53         Tuple,
    50     )
    54     )
    51 
    55 
    52     for t in (Any, Optional, Tuple):
    56     for t in (Any, Dict, List, Optional, Tuple):
    53         assert t
    57         assert t
    54 
    58 
    55 
    59 
    56 def getlimit(opts):
    60 def getlimit(opts):
    57     """get the log limit according to option -l/--limit"""
    61     """get the log limit according to option -l/--limit"""
   670         return changesetprinter(ui, repo, *postargs)
   674         return changesetprinter(ui, repo, *postargs)
   671 
   675 
   672     return changesettemplater(ui, repo, spec, *postargs)
   676     return changesettemplater(ui, repo, spec, *postargs)
   673 
   677 
   674 
   678 
   675 def _makematcher(repo, revs, pats, opts):
   679 @attr.s
       
   680 class walkopts(object):
       
   681     """Options to configure a set of revisions and file matcher factory
       
   682     to scan revision/file history
       
   683     """
       
   684 
       
   685     # raw command-line parameters, which a matcher will be built from
       
   686     pats = attr.ib()  # type: List[bytes]
       
   687     opts = attr.ib()  # type: Dict[bytes, Any]
       
   688 
       
   689 
       
   690 def parseopts(ui, pats, opts):
       
   691     # type: (Any, List[bytes], Dict[bytes, Any]) -> walkopts
       
   692     """Parse log command options into walkopts
       
   693 
       
   694     The returned walkopts will be passed in to getrevs().
       
   695     """
       
   696     return walkopts(pats=pats, opts=opts)
       
   697 
       
   698 
       
   699 def _makematcher(repo, revs, wopts):
   676     """Build matcher and expanded patterns from log options
   700     """Build matcher and expanded patterns from log options
   677 
   701 
   678     If --follow, revs are the revisions to follow from.
   702     If --follow, revs are the revisions to follow from.
   679 
   703 
   680     Returns (match, pats, slowpath) where
   704     Returns (match, pats, slowpath) where
   685     # pats/include/exclude are passed to match.match() directly in
   709     # pats/include/exclude are passed to match.match() directly in
   686     # _matchfiles() revset but walkchangerevs() builds its matcher with
   710     # _matchfiles() revset but walkchangerevs() builds its matcher with
   687     # scmutil.match(). The difference is input pats are globbed on
   711     # scmutil.match(). The difference is input pats are globbed on
   688     # platforms without shell expansion (windows).
   712     # platforms without shell expansion (windows).
   689     wctx = repo[None]
   713     wctx = repo[None]
   690     match, pats = scmutil.matchandpats(wctx, pats, opts)
   714     match, pats = scmutil.matchandpats(wctx, wopts.pats, wopts.opts)
   691     slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
   715     slowpath = match.anypats() or (
       
   716         not match.always() and wopts.opts.get(b'removed')
       
   717     )
   692     if not slowpath:
   718     if not slowpath:
   693         follow = opts.get(b'follow') or opts.get(b'follow_first')
   719         follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first')
   694         if follow and opts.get(b'rev'):
   720         if follow and wopts.opts.get(b'rev'):
   695             # There may be the case that a path doesn't exist in some (but
   721             # There may be the case that a path doesn't exist in some (but
   696             # not all) of the specified start revisions, but let's consider
   722             # not all) of the specified start revisions, but let's consider
   697             # the path is valid. Missing files will be warned by the matcher.
   723             # the path is valid. Missing files will be warned by the matcher.
   698             startctxs = [repo[r] for r in revs]
   724             startctxs = [repo[r] for r in revs]
   699             for f in match.files():
   725             for f in match.files():
   798     b'prune': (b'ancestors(%s)', b'not %lr'),
   824     b'prune': (b'ancestors(%s)', b'not %lr'),
   799     b'user': (b'user(%s)', b'%lr'),
   825     b'user': (b'user(%s)', b'%lr'),
   800 }
   826 }
   801 
   827 
   802 
   828 
   803 def _makerevset(repo, pats, slowpath, opts):
   829 def _makerevset(repo, wopts, slowpath):
   804     """Return a revset string built from log options and file patterns"""
   830     """Return a revset string built from log options and file patterns"""
   805     opts = dict(opts)
   831     opts = dict(wopts.opts)
   806     # follow or not follow?
   832     # follow or not follow?
   807     follow = opts.get(b'follow') or opts.get(b'follow_first')
   833     follow = opts.get(b'follow') or opts.get(b'follow_first')
   808 
   834 
   809     # branch and only_branch are really aliases and must be handled at
   835     # branch and only_branch are really aliases and must be handled at
   810     # the same time
   836     # the same time
   819         # level. For instance "-I a -X b" matches a revision touching
   845         # level. For instance "-I a -X b" matches a revision touching
   820         # "a" and "b" while "file(a) and not file(b)" does
   846         # "a" and "b" while "file(a) and not file(b)" does
   821         # not. Besides, filesets are evaluated against the working
   847         # not. Besides, filesets are evaluated against the working
   822         # directory.
   848         # directory.
   823         matchargs = [b'r:', b'd:relpath']
   849         matchargs = [b'r:', b'd:relpath']
   824         for p in pats:
   850         for p in wopts.pats:
   825             matchargs.append(b'p:' + p)
   851             matchargs.append(b'p:' + p)
   826         for p in opts.get(b'include', []):
   852         for p in opts.get(b'include', []):
   827             matchargs.append(b'i:' + p)
   853             matchargs.append(b'i:' + p)
   828         for p in opts.get(b'exclude', []):
   854         for p in opts.get(b'exclude', []):
   829             matchargs.append(b'x:' + p)
   855             matchargs.append(b'x:' + p)
   830         opts[b'_matchfiles'] = matchargs
   856         opts[b'_matchfiles'] = matchargs
   831     elif not follow:
   857     elif not follow:
   832         opts[b'_patslog'] = list(pats)
   858         opts[b'_patslog'] = list(wopts.pats)
   833 
   859 
   834     expr = []
   860     expr = []
   835     for op, val in sorted(pycompat.iteritems(opts)):
   861     for op, val in sorted(pycompat.iteritems(opts)):
   836         if not val:
   862         if not val:
   837             continue
   863             continue
   852     else:
   878     else:
   853         expr = None
   879         expr = None
   854     return expr
   880     return expr
   855 
   881 
   856 
   882 
   857 def _initialrevs(repo, opts):
   883 def _initialrevs(repo, wopts):
   858     """Return the initial set of revisions to be filtered or followed"""
   884     """Return the initial set of revisions to be filtered or followed"""
   859     follow = opts.get(b'follow') or opts.get(b'follow_first')
   885     follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first')
   860     if opts.get(b'rev'):
   886     if wopts.opts.get(b'rev'):
   861         revs = scmutil.revrange(repo, opts[b'rev'])
   887         revs = scmutil.revrange(repo, wopts.opts[b'rev'])
   862     elif follow and repo.dirstate.p1() == nullid:
   888     elif follow and repo.dirstate.p1() == nullid:
   863         revs = smartset.baseset()
   889         revs = smartset.baseset()
   864     elif follow:
   890     elif follow:
   865         revs = repo.revs(b'.')
   891         revs = repo.revs(b'.')
   866     else:
   892     else:
   867         revs = smartset.spanset(repo)
   893         revs = smartset.spanset(repo)
   868         revs.reverse()
   894         revs.reverse()
   869     return revs
   895     return revs
   870 
   896 
   871 
   897 
   872 def getrevs(repo, pats, opts):
   898 def getrevs(repo, wopts):
   873     # type: (Any, Any, Any) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
   899     # type: (Any, walkopts) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
   874     """Return (revs, differ) where revs is a smartset
   900     """Return (revs, differ) where revs is a smartset
   875 
   901 
   876     differ is a changesetdiffer with pre-configured file matcher.
   902     differ is a changesetdiffer with pre-configured file matcher.
   877     """
   903     """
   878     follow = opts.get(b'follow') or opts.get(b'follow_first')
   904     follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first')
   879     followfirst = opts.get(b'follow_first')
   905     followfirst = wopts.opts.get(b'follow_first')
   880     limit = getlimit(opts)
   906     limit = getlimit(wopts.opts)
   881     revs = _initialrevs(repo, opts)
   907     revs = _initialrevs(repo, wopts)
   882     if not revs:
   908     if not revs:
   883         return smartset.baseset(), None
   909         return smartset.baseset(), None
   884     match, pats, slowpath = _makematcher(repo, revs, pats, opts)
   910     match, pats, slowpath = _makematcher(repo, revs, wopts)
       
   911     wopts = attr.evolve(wopts, pats=pats)
       
   912 
   885     filematcher = None
   913     filematcher = None
   886     if follow:
   914     if follow:
   887         if slowpath or match.always():
   915         if slowpath or match.always():
   888             revs = dagop.revancestors(repo, revs, followfirst=followfirst)
   916             revs = dagop.revancestors(repo, revs, followfirst=followfirst)
   889         else:
   917         else:
   890             revs, filematcher = _fileancestors(repo, revs, match, followfirst)
   918             revs, filematcher = _fileancestors(repo, revs, match, followfirst)
   891         revs.reverse()
   919         revs.reverse()
   892     if filematcher is None:
   920     if filematcher is None:
   893         filematcher = _makenofollowfilematcher(repo, pats, opts)
   921         filematcher = _makenofollowfilematcher(repo, wopts.pats, wopts.opts)
   894     if filematcher is None:
   922     if filematcher is None:
   895 
   923 
   896         def filematcher(ctx):
   924         def filematcher(ctx):
   897             return match
   925             return match
   898 
   926 
   899     expr = _makerevset(repo, pats, slowpath, opts)
   927     expr = _makerevset(repo, wopts, slowpath)
   900     if opts.get(b'graph'):
   928     if wopts.opts.get(b'graph'):
   901         if repo.ui.configbool(b'experimental', b'log.topo'):
   929         if repo.ui.configbool(b'experimental', b'log.topo'):
   902             if not revs.istopo():
   930             if not revs.istopo():
   903                 revs = dagop.toposort(revs, repo.changelog.parentrevs)
   931                 revs = dagop.toposort(revs, repo.changelog.parentrevs)
   904                 # TODO: try to iterate the set lazily
   932                 # TODO: try to iterate the set lazily
   905                 revs = revset.baseset(list(revs), istopo=True)
   933                 revs = revset.baseset(list(revs), istopo=True)