diff: add option to show whole functions as context
authorRené Scharfe <rene.scharfe@lsrfire.ath.cx>
Sun, 9 Oct 2011 11:36:57 +0000 (13:36 +0200)
committerJunio C Hamano <gitster@pobox.com>
Mon, 10 Oct 2011 19:05:07 +0000 (12:05 -0700)
Add the option -W/--function-context to git diff.  It is similar to
the same option of git grep and expands the context of change hunks
so that the whole surrounding function is shown.  This "natural"
context can allow changes to be understood better.

Note: GNU patch doesn't like diffs generated with the new option;
it seems to expect context lines to be the same before and after
changes.  git apply doesn't complain.

This implementation has the same shortcoming as the one in grep,
namely that there is no way to explicitly find the end of a
function.  That means that a few lines of extra context are shown,
right up to the next recognized function begins.  It's already
useful in its current form, though.

The function get_func_line() in xdiff/xemit.c is extended to work
forward as well as backward to find post-context as well as
pre-context.  It returns the position of the first found matching
line.  The func_line parameter is made optional, as we don't need
it for -W.

The enhanced function is then used in xdl_emit_diff() to extend
the context as needed.  If the added context overlaps with the
next change, it is merged into the current hunk.

Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/diff-options.txt
diff.c
diff.h
t/t4051-diff-function-context.sh [new file with mode: 0644]
xdiff/xdiff.h
xdiff/xemit.c

index b620b3afeca14166840fb76b4cefe1e4038fb47e..1ce4ae3b17a7bca7082e499bf7edbc80359c2e79 100644 (file)
@@ -404,6 +404,10 @@ endif::git-format-patch[]
        Show the context between diff hunks, up to the specified number
        of lines, thereby fusing hunks that are close to each other.
 
+-W::
+--function-context::
+       Show whole surrounding functions of changes.
+
 ifndef::git-format-patch[]
 --exit-code::
        Make the program exit with codes similar to diff(1).
diff --git a/diff.c b/diff.c
index fcc0078074c364d0a4c2bd75a6d390e517eb7f87..42c4891eb3e7163786d51d01f7219b2421e09dd3 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -2169,6 +2169,8 @@ static void builtin_diff(const char *name_a,
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
+               if (DIFF_OPT_TST(o, FUNCCONTEXT))
+                       xecfg.flags |= XDL_EMIT_FUNCCONTEXT;
                if (pe)
                        xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
                if (!diffopts)
@@ -3530,6 +3532,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (opt_arg(arg, '\0', "inter-hunk-context",
                         &options->interhunkcontext))
                ;
+       else if (!strcmp(arg, "-W"))
+               DIFF_OPT_SET(options, FUNCCONTEXT);
+       else if (!strcmp(arg, "--function-context"))
+               DIFF_OPT_SET(options, FUNCCONTEXT);
+       else if (!strcmp(arg, "--no-function-context"))
+               DIFF_OPT_CLR(options, FUNCCONTEXT);
        else if ((argcount = parse_long_opt("output", av, &optarg))) {
                options->file = fopen(optarg, "w");
                if (!options->file)
diff --git a/diff.h b/diff.h
index 8c66b59517305546d3e2f66fca652284468e1da1..0c51724493f76461b67d6ef8f5556852819ad809 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -79,6 +79,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
 #define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
 #define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27)
 #define DIFF_OPT_DIRSTAT_BY_LINE     (1 << 28)
+#define DIFF_OPT_FUNCCONTEXT         (1 << 29)
 
 #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
 #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)
diff --git a/t/t4051-diff-function-context.sh b/t/t4051-diff-function-context.sh
new file mode 100644 (file)
index 0000000..001d678
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='diff function context'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
+
+
+cat <<\EOF >hello.c
+#include <stdio.h>
+
+static int a(void)
+{
+       /*
+        * Dummy.
+        */
+}
+
+static int hello_world(void)
+{
+       /* Classic. */
+       printf("Hello world.\n");
+
+       /* Success! */
+       return 0;
+}
+static int b(void)
+{
+       /*
+        * Dummy, too.
+        */
+}
+
+int main(int argc, char **argv)
+{
+       a();
+       b();
+       return hello_world();
+}
+EOF
+
+test_expect_success 'setup' '
+       git add hello.c &&
+       test_tick &&
+       git commit -m initial &&
+
+       grep -v Classic <hello.c >hello.c.new &&
+       mv hello.c.new hello.c
+'
+
+cat <<\EOF >expected
+diff --git a/hello.c b/hello.c
+--- a/hello.c
++++ b/hello.c
+@@ -10,8 +10,7 @@ static int a(void)
+ static int hello_world(void)
+ {
+-      /* Classic. */
+       printf("Hello world.\n");
+       /* Success! */
+       return 0;
+ }
+EOF
+
+test_expect_success 'diff -U0 -W' '
+       git diff -U0 -W >actual &&
+       compare_diff_patch actual expected
+'
+
+cat <<\EOF >expected
+diff --git a/hello.c b/hello.c
+--- a/hello.c
++++ b/hello.c
+@@ -9,9 +9,8 @@ static int a(void)
+ static int hello_world(void)
+ {
+-      /* Classic. */
+       printf("Hello world.\n");
+       /* Success! */
+       return 0;
+ }
+EOF
+
+test_expect_success 'diff -W' '
+       git diff -W >actual &&
+       compare_diff_patch actual expected
+'
+
+test_done
index 4beb10c678702a34b14914f8b292dd65b5c273cd..00d36c3ac7a7642831d2ffc49647caf77a4d066c 100644 (file)
@@ -43,6 +43,7 @@ extern "C" {
 
 #define XDL_EMIT_FUNCNAMES (1 << 0)
 #define XDL_EMIT_COMMON (1 << 1)
+#define XDL_EMIT_FUNCCONTEXT (1 << 2)
 
 #define XDL_MMB_READONLY (1 << 0)
 
index 64eb17a46387e0dc5e19b5c4901433c4c6fe4fab..2e669c3e2570332ee50a72e06a77ad4a356ecfd6 100644 (file)
@@ -105,22 +105,27 @@ struct func_line {
        char buf[80];
 };
 
-static void get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
+static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
                          struct func_line *func_line, long start, long limit)
 {
        find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff;
-       long l, size = sizeof(func_line->buf);
-       char *buf = func_line->buf;
+       long l, size, step = (start > limit) ? -1 : 1;
+       char *buf, dummy[1];
 
-       for (l = start; l > limit && 0 <= l; l--) {
+       buf = func_line ? func_line->buf : dummy;
+       size = func_line ? sizeof(func_line->buf) : sizeof(dummy);
+
+       for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) {
                const char *rec;
                long reclen = xdl_get_rec(&xe->xdf1, l, &rec);
                long len = ff(rec, reclen, buf, size, xecfg->find_func_priv);
                if (len >= 0) {
-                       func_line->len = len;
-                       break;
+                       if (func_line)
+                               func_line->len = len;
+                       return l;
                }
        }
+       return -1;
 }
 
 int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
@@ -139,6 +144,17 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
                s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
 
+               if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
+                       long fs1 = get_func_line(xe, xecfg, NULL, xch->i1, -1);
+                       if (fs1 < 0)
+                               fs1 = 0;
+                       if (fs1 < s1) {
+                               s2 -= s1 - fs1;
+                               s1 = fs1;
+                       }
+               }
+
+ again:
                lctx = xecfg->ctxlen;
                lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1));
                lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2));
@@ -146,6 +162,32 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                e1 = xche->i1 + xche->chg1 + lctx;
                e2 = xche->i2 + xche->chg2 + lctx;
 
+               if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
+                       long fe1 = get_func_line(xe, xecfg, NULL,
+                                                xche->i1 + xche->chg1,
+                                                xe->xdf1.nrec);
+                       if (fe1 < 0)
+                               fe1 = xe->xdf1.nrec;
+                       if (fe1 > e1) {
+                               e2 += fe1 - e1;
+                               e1 = fe1;
+                       }
+
+                       /*
+                        * Overlap with next change?  Then include it
+                        * in the current hunk and start over to find
+                        * its new end.
+                        */
+                       if (xche->next) {
+                               long l = xche->next->i1;
+                               if (l <= e1 ||
+                                   get_func_line(xe, xecfg, NULL, l, e1) < 0) {
+                                       xche = xche->next;
+                                       goto again;
+                               }
+                       }
+               }
+
                /*
                 * Emit current hunk header.
                 */