diff: add option to show whole functions as context

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>

René Scharfe committed Oct 9, 2011 at 13:36 UTC 14937c2c06e63f93f65e1bc6693b95d4e54053d7
6 files changed +154 -6
Documentation/diff-options.txt
+4
@@ -404,6 +404,10 @@ endif::git-format-patch[]
404 Show the context between diff hunks, up to the specified number
405 of lines, thereby fusing hunks that are close to each other.
406
407 +-W::
408 +--function-context::
409 + Show whole surrounding functions of changes.
410 +
411 ifndef::git-format-patch[]
412 --exit-code::
413 Make the program exit with codes similar to diff(1).
diff.c
+8
@@ -2169,6 +2169,8 @@ static void builtin_diff(const char *name_a,
2169 xecfg.ctxlen = o->context;
2170 xecfg.interhunkctxlen = o->interhunkcontext;
2171 xecfg.flags = XDL_EMIT_FUNCNAMES;
2172 + if (DIFF_OPT_TST(o, FUNCCONTEXT))
2173 + xecfg.flags |= XDL_EMIT_FUNCCONTEXT;
2174 if (pe)
2175 xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
2176 if (!diffopts)
@@ -3530,6 +3532,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
3532 else if (opt_arg(arg, '\0', "inter-hunk-context",
3533 &options->interhunkcontext))
3534 ;
3535 + else if (!strcmp(arg, "-W"))
3536 + DIFF_OPT_SET(options, FUNCCONTEXT);
3537 + else if (!strcmp(arg, "--function-context"))
3538 + DIFF_OPT_SET(options, FUNCCONTEXT);
3539 + else if (!strcmp(arg, "--no-function-context"))
3540 + DIFF_OPT_CLR(options, FUNCCONTEXT);
3541 else if ((argcount = parse_long_opt("output", av, &optarg))) {
3542 options->file = fopen(optarg, "w");
3543 if (!options->file)
diff.h
+1
@@ -79,6 +79,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
79 #define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
80 #define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27)
81 #define DIFF_OPT_DIRSTAT_BY_LINE (1 << 28)
82 +#define DIFF_OPT_FUNCCONTEXT (1 << 29)
83
84 #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag)
85 #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag)
t/t4051-diff-function-context.sh new
+92
@@ -0,0 +1,92 @@
1 +#!/bin/sh
2 +
3 +test_description='diff function context'
4 +
5 +. ./test-lib.sh
6 +. "$TEST_DIRECTORY"/diff-lib.sh
7 +
8 +
9 +cat <<\EOF >hello.c
10 +#include <stdio.h>
11 +
12 +static int a(void)
13 +{
14 + /*
15 + * Dummy.
16 + */
17 +}
18 +
19 +static int hello_world(void)
20 +{
21 + /* Classic. */
22 + printf("Hello world.\n");
23 +
24 + /* Success! */
25 + return 0;
26 +}
27 +static int b(void)
28 +{
29 + /*
30 + * Dummy, too.
31 + */
32 +}
33 +
34 +int main(int argc, char **argv)
35 +{
36 + a();
37 + b();
38 + return hello_world();
39 +}
40 +EOF
41 +
42 +test_expect_success 'setup' '
43 + git add hello.c &&
44 + test_tick &&
45 + git commit -m initial &&
46 +
47 + grep -v Classic <hello.c >hello.c.new &&
48 + mv hello.c.new hello.c
49 +'
50 +
51 +cat <<\EOF >expected
52 +diff --git a/hello.c b/hello.c
53 +--- a/hello.c
54 ++++ b/hello.c
55 +@@ -10,8 +10,7 @@ static int a(void)
56 + static int hello_world(void)
57 + {
58 +- /* Classic. */
59 + printf("Hello world.\n");
60 +
61 + /* Success! */
62 + return 0;
63 + }
64 +EOF
65 +
66 +test_expect_success 'diff -U0 -W' '
67 + git diff -U0 -W >actual &&
68 + compare_diff_patch actual expected
69 +'
70 +
71 +cat <<\EOF >expected
72 +diff --git a/hello.c b/hello.c
73 +--- a/hello.c
74 ++++ b/hello.c
75 +@@ -9,9 +9,8 @@ static int a(void)
76 +
77 + static int hello_world(void)
78 + {
79 +- /* Classic. */
80 + printf("Hello world.\n");
81 +
82 + /* Success! */
83 + return 0;
84 + }
85 +EOF
86 +
87 +test_expect_success 'diff -W' '
88 + git diff -W >actual &&
89 + compare_diff_patch actual expected
90 +'
91 +
92 +test_done
xdiff/xdiff.h
+1
@@ -43,6 +43,7 @@ extern "C" {
43
44 #define XDL_EMIT_FUNCNAMES (1 << 0)
45 #define XDL_EMIT_COMMON (1 << 1)
46 +#define XDL_EMIT_FUNCCONTEXT (1 << 2)
47
48 #define XDL_MMB_READONLY (1 << 0)
49
xdiff/xemit.c
+48 -6
@@ -105,22 +105,27 @@ struct func_line {
105 char buf[80];
106 };
107
108 -static void get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
108 +static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
109 struct func_line *func_line, long start, long limit)
110 {
111 find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff;
112 - long l, size = sizeof(func_line->buf);
113 - char *buf = func_line->buf;
112 + long l, size, step = (start > limit) ? -1 : 1;
113 + char *buf, dummy[1];
114
115 - for (l = start; l > limit && 0 <= l; l--) {
115 + buf = func_line ? func_line->buf : dummy;
116 + size = func_line ? sizeof(func_line->buf) : sizeof(dummy);
117 +
118 + for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) {
119 const char *rec;
120 long reclen = xdl_get_rec(&xe->xdf1, l, &rec);
121 long len = ff(rec, reclen, buf, size, xecfg->find_func_priv);
122 if (len >= 0) {
120 - func_line->len = len;
121 - break;
123 + if (func_line)
124 + func_line->len = len;
125 + return l;
126 }
127 }
128 + return -1;
129 }
130
131 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,
144 s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
145 s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
146
147 + if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
148 + long fs1 = get_func_line(xe, xecfg, NULL, xch->i1, -1);
149 + if (fs1 < 0)
150 + fs1 = 0;
151 + if (fs1 < s1) {
152 + s2 -= s1 - fs1;
153 + s1 = fs1;
154 + }
155 + }
156 +
157 + again:
158 lctx = xecfg->ctxlen;
159 lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1));
160 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,
162 e1 = xche->i1 + xche->chg1 + lctx;
163 e2 = xche->i2 + xche->chg2 + lctx;
164
165 + if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
166 + long fe1 = get_func_line(xe, xecfg, NULL,
167 + xche->i1 + xche->chg1,
168 + xe->xdf1.nrec);
169 + if (fe1 < 0)
170 + fe1 = xe->xdf1.nrec;
171 + if (fe1 > e1) {
172 + e2 += fe1 - e1;
173 + e1 = fe1;
174 + }
175 +
176 + /*
177 + * Overlap with next change? Then include it
178 + * in the current hunk and start over to find
179 + * its new end.
180 + */
181 + if (xche->next) {
182 + long l = xche->next->i1;
183 + if (l <= e1 ||
184 + get_func_line(xe, xecfg, NULL, l, e1) < 0) {
185 + xche = xche->next;
186 + goto again;
187 + }
188 + }
189 + }
190 +
191 /*
192 * Emit current hunk header.
193 */