# HG changeset patch # User Patrick Mezard # Date 1330272735 -3600 # Node ID af3e67354beba48667df4e45dd22cd20a32b4648 # Parent 352053e6cd8ef8283c797a853758d6da20df6080 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. diff -r 352053e6cd8e -r af3e67354beb hgext/graphlog.py --- 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) diff -r 352053e6cd8e -r af3e67354beb tests/test-glog.t --- a/tests/test-glog.t Sun Feb 26 17:10:57 2012 +0100 +++ b/tests/test-glog.t Sun Feb 26 17:12:15 2012 +0100 @@ -90,7 +90,7 @@ > def uisetup(ui): > def printrevset(orig, ui, repo, *pats, **opts): > if opts.get('print_revset'): - > expr = graphlog.revset(repo, pats, opts) + > expr = graphlog.revset(repo, pats, opts)[0] > tree = revset.parse(expr)[0] > ui.write(tree, "\n") > return 0 @@ -1655,3 +1655,99 @@ abort: can only follow copies/renames for explicit filenames abort: can only follow copies/renames for explicit filenames abort: can only follow copies/renames for explicit filenames + +Test --patch and --stat with --follow and --follow-first + + $ hg up -q 3 + $ hg log -G --git --patch b + o changeset: 1:216d4c92cf98 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: copy a b + | + | diff --git a/a b/b + | copy from a + | copy to b + | + + $ hg log -G --git --stat b + o changeset: 1:216d4c92cf98 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: copy a b + | + | a | 0 + | 1 files changed, 0 insertions(+), 0 deletions(-) + | + + $ hg log -G --git --patch --follow b + o changeset: 1:216d4c92cf98 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: copy a b + | + | diff --git a/a b/b + | copy from a + | copy to b + | + o changeset: 0:f8035bb17114 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: add a + + diff --git a/a b/a + new file mode 100644 + --- /dev/null + +++ b/a + @@ -0,0 +1,1 @@ + +a + + + $ hg log -G --git --stat --follow b + o changeset: 1:216d4c92cf98 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: copy a b + | + | a | 0 + | 1 files changed, 0 insertions(+), 0 deletions(-) + | + o changeset: 0:f8035bb17114 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: add a + + a | 1 + + 1 files changed, 1 insertions(+), 0 deletions(-) + + + $ hg up -q 6 + $ hg log -G --git --patch --follow-first e + @ changeset: 6:fc281d8ff18d + |\ tag: tip + | | parent: 5:99b31f1c2782 + | | parent: 4:17d952250a9d + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: merge 5 and 4 + | | + | | diff --git a/e b/e + | | --- a/e + | | +++ b/e + | | @@ -1,1 +1,1 @@ + | | -ee + | | +merge + | | + o | changeset: 5:99b31f1c2782 + | | parent: 3:5918b8d165d1 + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: add another e + | | + | | diff --git a/e b/e + | | new file mode 100644 + | | --- /dev/null + | | +++ b/e + | | @@ -0,0 +1,1 @@ + | | +ee + | |