mercurial/logcmdutil.py
changeset 45565 c1d0f83d62c4
parent 45564 a717de1cb624
child 45566 24df19a9ab87
--- a/mercurial/logcmdutil.py	Sat Sep 12 16:19:01 2020 +0900
+++ b/mercurial/logcmdutil.py	Sat Sep 12 21:06:16 2020 +0900
@@ -18,6 +18,8 @@
     wdirrev,
 )
 
+from .thirdparty import attr
+
 from . import (
     dagop,
     error,
@@ -45,11 +47,13 @@
 if pycompat.TYPE_CHECKING:
     from typing import (
         Any,
+        Dict,
+        List,
         Optional,
         Tuple,
     )
 
-    for t in (Any, Optional, Tuple):
+    for t in (Any, Dict, List, Optional, Tuple):
         assert t
 
 
@@ -672,7 +676,27 @@
     return changesettemplater(ui, repo, spec, *postargs)
 
 
-def _makematcher(repo, revs, pats, opts):
+@attr.s
+class walkopts(object):
+    """Options to configure a set of revisions and file matcher factory
+    to scan revision/file history
+    """
+
+    # raw command-line parameters, which a matcher will be built from
+    pats = attr.ib()  # type: List[bytes]
+    opts = attr.ib()  # type: Dict[bytes, Any]
+
+
+def parseopts(ui, pats, opts):
+    # type: (Any, List[bytes], Dict[bytes, Any]) -> walkopts
+    """Parse log command options into walkopts
+
+    The returned walkopts will be passed in to getrevs().
+    """
+    return walkopts(pats=pats, opts=opts)
+
+
+def _makematcher(repo, revs, wopts):
     """Build matcher and expanded patterns from log options
 
     If --follow, revs are the revisions to follow from.
@@ -687,11 +711,13 @@
     # scmutil.match(). The difference is input pats are globbed on
     # platforms without shell expansion (windows).
     wctx = repo[None]
-    match, pats = scmutil.matchandpats(wctx, pats, opts)
-    slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
+    match, pats = scmutil.matchandpats(wctx, wopts.pats, wopts.opts)
+    slowpath = match.anypats() or (
+        not match.always() and wopts.opts.get(b'removed')
+    )
     if not slowpath:
-        follow = opts.get(b'follow') or opts.get(b'follow_first')
-        if follow and opts.get(b'rev'):
+        follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first')
+        if follow and wopts.opts.get(b'rev'):
             # There may be the case that a path doesn't exist in some (but
             # not all) of the specified start revisions, but let's consider
             # the path is valid. Missing files will be warned by the matcher.
@@ -800,9 +826,9 @@
 }
 
 
-def _makerevset(repo, pats, slowpath, opts):
+def _makerevset(repo, wopts, slowpath):
     """Return a revset string built from log options and file patterns"""
-    opts = dict(opts)
+    opts = dict(wopts.opts)
     # follow or not follow?
     follow = opts.get(b'follow') or opts.get(b'follow_first')
 
@@ -821,7 +847,7 @@
         # not. Besides, filesets are evaluated against the working
         # directory.
         matchargs = [b'r:', b'd:relpath']
-        for p in pats:
+        for p in wopts.pats:
             matchargs.append(b'p:' + p)
         for p in opts.get(b'include', []):
             matchargs.append(b'i:' + p)
@@ -829,7 +855,7 @@
             matchargs.append(b'x:' + p)
         opts[b'_matchfiles'] = matchargs
     elif not follow:
-        opts[b'_patslog'] = list(pats)
+        opts[b'_patslog'] = list(wopts.pats)
 
     expr = []
     for op, val in sorted(pycompat.iteritems(opts)):
@@ -854,11 +880,11 @@
     return expr
 
 
-def _initialrevs(repo, opts):
+def _initialrevs(repo, wopts):
     """Return the initial set of revisions to be filtered or followed"""
-    follow = opts.get(b'follow') or opts.get(b'follow_first')
-    if opts.get(b'rev'):
-        revs = scmutil.revrange(repo, opts[b'rev'])
+    follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first')
+    if wopts.opts.get(b'rev'):
+        revs = scmutil.revrange(repo, wopts.opts[b'rev'])
     elif follow and repo.dirstate.p1() == nullid:
         revs = smartset.baseset()
     elif follow:
@@ -869,19 +895,21 @@
     return revs
 
 
-def getrevs(repo, pats, opts):
-    # type: (Any, Any, Any) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
+def getrevs(repo, wopts):
+    # type: (Any, walkopts) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
     """Return (revs, differ) where revs is a smartset
 
     differ is a changesetdiffer with pre-configured file matcher.
     """
-    follow = opts.get(b'follow') or opts.get(b'follow_first')
-    followfirst = opts.get(b'follow_first')
-    limit = getlimit(opts)
-    revs = _initialrevs(repo, opts)
+    follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first')
+    followfirst = wopts.opts.get(b'follow_first')
+    limit = getlimit(wopts.opts)
+    revs = _initialrevs(repo, wopts)
     if not revs:
         return smartset.baseset(), None
-    match, pats, slowpath = _makematcher(repo, revs, pats, opts)
+    match, pats, slowpath = _makematcher(repo, revs, wopts)
+    wopts = attr.evolve(wopts, pats=pats)
+
     filematcher = None
     if follow:
         if slowpath or match.always():
@@ -890,14 +918,14 @@
             revs, filematcher = _fileancestors(repo, revs, match, followfirst)
         revs.reverse()
     if filematcher is None:
-        filematcher = _makenofollowfilematcher(repo, pats, opts)
+        filematcher = _makenofollowfilematcher(repo, wopts.pats, wopts.opts)
     if filematcher is None:
 
         def filematcher(ctx):
             return match
 
-    expr = _makerevset(repo, pats, slowpath, opts)
-    if opts.get(b'graph'):
+    expr = _makerevset(repo, wopts, slowpath)
+    if wopts.opts.get(b'graph'):
         if repo.ui.configbool(b'experimental', b'log.topo'):
             if not revs.istopo():
                 revs = dagop.toposort(revs, repo.changelog.parentrevs)