diff hgext/graphlog.py @ 16186:af3e67354beb

graphlog: apply file filters --patch/--stat output When passing --patch/--stat, file filters have to be applied to generate the correct diff or stat output: - Without --follow, the static match object can be reused - With --follow, the files displayed at revision X are the ancestors of selected files at parent revision. To do this, we reproduce the ancestry calculations done by --follow, lazily. test-glog.t changes show that --patch output is not satisfying because renames are reported as copies. This can probably be fixed by: - Without --follow: compute files to display, look for renames sources and extend the matcher to include them. - With --follow: detect .path() transitions between parent/child filectx, filter them using the linked changectx .removed() field and extend fcache with them.
author Patrick Mezard <patrick@mezard.eu>
date Sun, 26 Feb 2012 17:12:15 +0100
parents 6863caf01daa
children 16ec050490fc
line wrap: on
line diff
--- a/hgext/graphlog.py	Sun Feb 26 17:10:57 2012 +0100
+++ b/hgext/graphlog.py	Sun Feb 26 17:12:15 2012 +0100
@@ -242,8 +242,40 @@
             raise util.Abort(_("-G/--graph option is incompatible with --%s")
                              % op.replace("_", "-"))
 
+def makefilematcher(repo, pats, 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 linkrevs to file names (which is not "correct" but
+    # good enough).
+    fcache = {}
+    fcacheready = [False]
+    pctx = repo['.']
+    wctx = repo[None]
+
+    def populate():
+        for fn in pats:
+            for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
+                for c in i:
+                    fcache.setdefault(c.linkrev(), set()).add(c.path())
+
+    def filematcher(rev):
+        if not fcacheready[0]:
+            # Lazy initialization
+            fcacheready[0] = True
+            populate()
+        return scmutil.match(wctx, fcache.get(rev, []), default='path')
+
+    return filematcher
+
 def revset(repo, pats, opts):
-    """Return revset str built of revisions, log options and file patterns.
+    """Return (expr, filematcher) where expr is a revset string built
+    of revisions, log options and file patterns. If --stat or --patch
+    are not passed filematcher is None. Otherwise it a a callable
+    taking a revision number and returning a match objects filtering
+    the files to be detailed when displaying the revision.
     """
     opt2revset = {
         'follow':           ('follow()', None),
@@ -329,6 +361,13 @@
         else:
             opts['_patslog'] = list(pats)
 
+    filematcher = None
+    if opts.get('patch') or opts.get('stat'):
+        if follow:
+            filematcher = makefilematcher(repo, pats, followfirst)
+        else:
+            filematcher = lambda rev: match
+
     revset = []
     for op, val in opts.iteritems():
         if not val:
@@ -349,9 +388,10 @@
         revset = '(' + ' and '.join(revset) + ')'
     else:
         revset = 'all()'
-    return revset
+    return revset, filematcher
 
-def generate(ui, dag, displayer, showparents, edgefn, getrenamed=None):
+def generate(ui, dag, displayer, showparents, edgefn, getrenamed=None,
+             filematcher=None):
     seen, state = [], asciistate()
     for rev, type, ctx, parents in dag:
         char = ctx.node() in showparents and '@' or 'o'
@@ -362,7 +402,10 @@
                 rename = getrenamed(fn, ctx.rev())
                 if rename:
                     copies.append((fn, rename[0]))
-        displayer.show(ctx, copies=copies)
+        revmatchfn = None
+        if filematcher is not None:
+            revmatchfn = filematcher(ctx.rev())
+        displayer.show(ctx, copies=copies, matchfn=revmatchfn)
         lines = displayer.hunk.pop(rev).split('\n')[:-1]
         displayer.flush(rev)
         edges = edgefn(type, char, lines, seen, rev, parents)
@@ -389,7 +432,8 @@
 
     check_unsupported_flags(pats, opts)
 
-    revs = sorted(scmutil.revrange(repo, [revset(repo, pats, opts)]), reverse=1)
+    expr, filematcher = revset(repo, pats, opts)
+    revs = sorted(scmutil.revrange(repo, [expr]), reverse=1)
     limit = cmdutil.loglimit(opts)
     if limit is not None:
         revs = revs[:limit]
@@ -403,7 +447,8 @@
         getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
     displayer = show_changeset(ui, repo, opts, buffered=True)
     showparents = [ctx.node() for ctx in repo[None].parents()]
-    generate(ui, revdag, displayer, showparents, asciiedges, getrenamed)
+    generate(ui, revdag, displayer, showparents, asciiedges, getrenamed,
+             filematcher)
 
 def graphrevs(repo, nodes, opts):
     limit = cmdutil.loglimit(opts)