log: build follow-log filematcher at once
authorYuya Nishihara <yuya@tcha.org>
Thu, 04 Jan 2018 14:20:58 +0900
changeset 35690 3e394e0558d7
parent 35689 5fe6f946f111
child 35691 735f47b41521
log: build follow-log filematcher at once We no longer need to replay copy tracing to build filematcher as we can walk (rev, fctxs) pairs.
mercurial/cmdutil.py
tests/test-glog.t
--- a/mercurial/cmdutil.py	Thu Jan 04 15:20:46 2018 +0900
+++ b/mercurial/cmdutil.py	Thu Jan 04 14:20:58 2018 +0900
@@ -2388,35 +2388,21 @@
     for r in revs:
         ctx = repo[r]
         fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
-    return dagop.filerevancestors(fctxs, followfirst=followfirst)
-
-def _makefollowlogfilematcher(repo, files, followfirst):
+
     # When displaying a revision with --patch --follow FILE, we have
     # to know which file of the revision must be diffed. With
     # --follow, we want the names of the ancestors of FILE in the
-    # revision, stored in "fcache". "fcache" is populated by
-    # reproducing the graph traversal already done by --follow revset
-    # and relating revs to file names (which is not "correct" but
-    # good enough).
+    # revision, stored in "fcache". "fcache" is populated as a side effect
+    # of the graph traversal.
     fcache = {}
-    fcacheready = [False]
-    pctx = repo['.']
-
-    def populate():
-        for fn in files:
-            fctx = pctx[fn]
-            fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
-            for c in fctx.ancestors(followfirst=followfirst):
-                fcache.setdefault(c.rev(), set()).add(c.path())
-
     def filematcher(rev):
-        if not fcacheready[0]:
-            # Lazy initialization
-            fcacheready[0] = True
-            populate()
         return scmutil.matchfiles(repo, fcache.get(rev, []))
 
-    return filematcher
+    def revgen():
+        for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
+            fcache[rev] = [c.path() for c in cs]
+            yield rev
+    return smartset.generatorset(revgen(), iterasc=False), filematcher
 
 def _makenofollowlogfilematcher(repo, pats, opts):
     '''hook for extensions to override the filematcher for non-follow cases'''
@@ -2435,12 +2421,7 @@
 }
 
 def _makelogrevset(repo, match, pats, slowpath, opts):
-    """Return (expr, filematcher) where expr is a revset string built
-    from log options and file patterns or None. If --stat or --patch
-    are not passed filematcher is None. Otherwise it is a callable
-    taking a revision number and returning a match objects filtering
-    the files to be detailed when displaying the revision.
-    """
+    """Return a revset string built from log options and file patterns"""
     opts = dict(opts)
     # follow or not follow?
     follow = opts.get('follow') or opts.get('follow_first')
@@ -2470,21 +2451,6 @@
     elif not follow:
         opts['_patslog'] = list(pats)
 
-    filematcher = None
-    if opts.get('patch') or opts.get('stat'):
-        # When following files, track renames via a special matcher.
-        # If we're forced to take the slowpath it means we're following
-        # at least one pattern/directory, so don't bother with rename tracking.
-        if follow and not match.always() and not slowpath:
-            # _makefollowlogfilematcher expects its files argument to be
-            # relative to the repo root, so use match.files(), not pats.
-            filematcher = _makefollowlogfilematcher(repo, match.files(),
-                                                    opts.get('follow_first'))
-        else:
-            filematcher = _makenofollowlogfilematcher(repo, pats, opts)
-            if filematcher is None:
-                filematcher = lambda rev: match
-
     expr = []
     for op, val in sorted(opts.iteritems()):
         if not val:
@@ -2505,7 +2471,7 @@
         expr = '(' + ' and '.join(expr) + ')'
     else:
         expr = None
-    return expr, filematcher
+    return expr
 
 def _logrevs(repo, opts):
     """Return the initial set of revisions to be filtered or followed"""
@@ -2524,9 +2490,8 @@
 def getlogrevs(repo, pats, opts):
     """Return (revs, filematcher) where revs is a smartset
 
-    If --stat or --patch is not passed, filematcher is None. Otherwise it
-    is a callable taking a revision number and returning a match objects
-    filtering the files to be detailed when displaying the revision.
+    filematcher is a callable taking a revision number and returning a match
+    objects filtering the files to be detailed when displaying the revision.
     """
     follow = opts.get('follow') or opts.get('follow_first')
     followfirst = opts.get('follow_first')
@@ -2535,13 +2500,20 @@
     if not revs:
         return smartset.baseset(), None
     match, pats, slowpath = _makelogmatcher(repo, revs, pats, opts)
+    filematcher = None
     if follow:
         if slowpath or match.always():
             revs = dagop.revancestors(repo, revs, followfirst=followfirst)
         else:
-            revs = _fileancestors(repo, revs, match, followfirst)
+            revs, filematcher = _fileancestors(repo, revs, match, followfirst)
         revs.reverse()
-    expr, filematcher = _makelogrevset(repo, match, pats, slowpath, opts)
+    if filematcher is None:
+        filematcher = _makenofollowlogfilematcher(repo, pats, opts)
+    if filematcher is None:
+        def filematcher(rev):
+            return match
+
+    expr = _makelogrevset(repo, match, pats, slowpath, opts)
     if opts.get('graph') and opts.get('rev'):
         # User-specified revs might be unsorted, but don't sort before
         # _makelogrevset because it might depend on the order of revs
--- a/tests/test-glog.t	Thu Jan 04 15:20:46 2018 +0900
+++ b/tests/test-glog.t	Thu Jan 04 14:20:58 2018 +0900
@@ -96,7 +96,7 @@
   >     if not revs:
   >         return None
   >     match, pats, slowpath = cmdutil._makelogmatcher(repo, revs, pats, opts)
-  >     return cmdutil._makelogrevset(repo, match, pats, slowpath, opts)[0]
+  >     return cmdutil._makelogrevset(repo, match, pats, slowpath, opts)
   > 
   > def uisetup(ui):
   >     def printrevset(orig, repo, pats, opts):