Skip to content

Commit f258475

Browse files
committed
Per-path attribute based hunk header selection.
This makes"diff -p" hunk headers customizable via gitattributes mechanism. It is based on Johannes's earlier patch that allowed to define a single regexp to be used for everything. The mechanism to arrive at the regexp that is used to define hunk header is the same as other use of gitattributes. You assign an attribute, funcname (because "diff -p" typically uses the name of the function the patch is about as the hunk header), a simple string value. This can be one of the names of built-in pattern (currently, "java" is defined) or a custom pattern name, to be looked up from the configuration file. (in .gitattributes) *.java funcname=java *.perl funcname=perl (in .git/config) [funcname] java = ... # ugly and complicated regexp to override the built-in one. perl = ... # another ugly and complicated regexp to define a new one. Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 30b2501 commit f258475

File tree

7 files changed

+256
-15
lines changed

7 files changed

+256
-15
lines changed

diff.c

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1101,22 +1101,26 @@ static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
11011101
static void setup_diff_attr_check(struct git_attr_check *check)
11021102
{
11031103
static struct git_attr *attr_diff;
1104+
static struct git_attr *attr_diff_func_name;
11041105

11051106
if (!attr_diff) {
11061107
attr_diff = git_attr("diff", 4);
1108+
attr_diff_func_name = git_attr("funcname", 8);
11071109
}
11081110
check[0].attr = attr_diff;
1111+
check[1].attr = attr_diff_func_name;
11091112
}
11101113

11111114
static void diff_filespec_check_attr(struct diff_filespec *one)
11121115
{
1113-
struct git_attr_check attr_diff_check[1];
1116+
struct git_attr_check attr_diff_check[2];
11141117

11151118
if (one->checked_attr)
11161119
return;
11171120

11181121
setup_diff_attr_check(attr_diff_check);
11191122
one->is_binary = 0;
1123+
one->hunk_header_ident = NULL;
11201124

11211125
if (!git_checkattr(one->path, ARRAY_SIZE(attr_diff_check), attr_diff_check)) {
11221126
const char *value;
@@ -1127,6 +1131,13 @@ static void diff_filespec_check_attr(struct diff_filespec *one)
11271131
;
11281132
else if (ATTR_FALSE(value))
11291133
one->is_binary = 1;
1134+
1135+
/* hunk header ident */
1136+
value = attr_diff_check[1].value;
1137+
if (ATTR_TRUE(value) || ATTR_FALSE(value) || ATTR_UNSET(value))
1138+
;
1139+
else
1140+
one->hunk_header_ident = value;
11301141
}
11311142

11321143
if (!one->data && DIFF_FILE_VALID(one))
@@ -1143,6 +1154,82 @@ int diff_filespec_is_binary(struct diff_filespec *one)
11431154
return one->is_binary;
11441155
}
11451156

1157+
static struct hunk_header_regexp {
1158+
char *name;
1159+
char *regexp;
1160+
struct hunk_header_regexp *next;
1161+
} *hunk_header_regexp_list, **hunk_header_regexp_tail;
1162+
1163+
static int hunk_header_config(const char *var, const char *value)
1164+
{
1165+
static const char funcname[] = "funcname.";
1166+
struct hunk_header_regexp *hh;
1167+
1168+
if (prefixcmp(var, funcname))
1169+
return 0;
1170+
var += strlen(funcname);
1171+
for (hh = hunk_header_regexp_list; hh; hh = hh->next)
1172+
if (!strcmp(var, hh->name)) {
1173+
free(hh->regexp);
1174+
hh->regexp = xstrdup(value);
1175+
return 0;
1176+
}
1177+
hh = xcalloc(1, sizeof(*hh));
1178+
hh->name = xstrdup(var);
1179+
hh->regexp = xstrdup(value);
1180+
hh->next = NULL;
1181+
*hunk_header_regexp_tail = hh;
1182+
return 0;
1183+
}
1184+
1185+
static const char *hunk_header_regexp(const char *ident)
1186+
{
1187+
struct hunk_header_regexp *hh;
1188+
1189+
if (!hunk_header_regexp_tail) {
1190+
hunk_header_regexp_tail = &hunk_header_regexp_list;
1191+
git_config(hunk_header_config);
1192+
}
1193+
for (hh = hunk_header_regexp_list; hh; hh = hh->next)
1194+
if (!strcmp(ident, hh->name))
1195+
return hh->regexp;
1196+
return NULL;
1197+
}
1198+
1199+
static const char *diff_hunk_header_regexp(struct diff_filespec *one)
1200+
{
1201+
const char *ident, *regexp;
1202+
1203+
diff_filespec_check_attr(one);
1204+
ident = one->hunk_header_ident;
1205+
1206+
if (!ident)
1207+
/*
1208+
* If the config file has "funcname.default" defined, that
1209+
* regexp is used; otherwise NULL is returned and xemit uses
1210+
* the built-in default.
1211+
*/
1212+
return hunk_header_regexp("default");
1213+
1214+
/* Look up custom "funcname.$ident" regexp from config. */
1215+
regexp = hunk_header_regexp(ident);
1216+
if (regexp)
1217+
return regexp;
1218+
1219+
/*
1220+
* And define built-in fallback patterns here. Note that
1221+
* these can be overriden by the user's config settings.
1222+
*/
1223+
if (!strcmp(ident, "java"))
1224+
return "!^[ ]*\\(catch\\|do\\|for\\|if\\|instanceof\\|"
1225+
"new\\|return\\|switch\\|throw\\|while\\)\n"
1226+
"^[ ]*\\(\\([ ]*"
1227+
"[A-Za-z_][A-Za-z_0-9]*\\)\\{2,\\}"
1228+
"[ ]*([^;]*$\\)";
1229+
1230+
return NULL;
1231+
}
1232+
11461233
static void builtin_diff(const char *name_a,
11471234
const char *name_b,
11481235
struct diff_filespec *one,
@@ -1217,6 +1304,11 @@ static void builtin_diff(const char *name_a,
12171304
xdemitconf_t xecfg;
12181305
xdemitcb_t ecb;
12191306
struct emit_callback ecbdata;
1307+
const char *hunk_header_regexp;
1308+
1309+
hunk_header_regexp = diff_hunk_header_regexp(one);
1310+
if (!hunk_header_regexp)
1311+
hunk_header_regexp = diff_hunk_header_regexp(two);
12201312

12211313
memset(&xecfg, 0, sizeof(xecfg));
12221314
memset(&ecbdata, 0, sizeof(ecbdata));
@@ -1226,6 +1318,8 @@ static void builtin_diff(const char *name_a,
12261318
xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
12271319
xecfg.ctxlen = o->context;
12281320
xecfg.flags = XDL_EMIT_FUNCNAMES;
1321+
if (hunk_header_regexp)
1322+
xdiff_set_find_func(&xecfg, hunk_header_regexp);
12291323
if (!diffopts)
12301324
;
12311325
else if (!prefixcmp(diffopts, "--unified="))

diffcore.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ struct diff_filespec {
2727
char *path;
2828
void *data;
2929
void *cnt_data;
30+
const void *hunk_header_ident;
3031
unsigned long size;
3132
int xfrm_flags; /* for use by the xfrm */
3233
unsigned short mode; /* file mode */

t/t4018-diff-funcname.sh

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/bin/sh
2+
#
3+
# Copyright (c) 2007 Johannes E. Schindelin
4+
#
5+
6+
test_description='Test custom diff function name patterns'
7+
8+
. ./test-lib.sh
9+
10+
LF='
11+
'
12+
13+
cat > Beer.java << EOF
14+
public class Beer
15+
{
16+
int special;
17+
public static void main(String args[])
18+
{
19+
String s=" ";
20+
for(int x = 99; x > 0; x--)
21+
{
22+
System.out.print(x + " bottles of beer on the wall "
23+
+ x + " bottles of beer\n"
24+
+ "Take one down, pass it around, " + (x - 1)
25+
+ " bottles of beer on the wall.\n");
26+
}
27+
System.out.print("Go to the store, buy some more,\n"
28+
+ "99 bottles of beer on the wall.\n");
29+
}
30+
}
31+
EOF
32+
33+
sed 's/beer\\/beer,\\/' < Beer.java > Beer-correct.java
34+
35+
test_expect_success 'default behaviour' '
36+
git diff Beer.java Beer-correct.java |
37+
grep "^@@.*@@ public class Beer"
38+
'
39+
40+
test_expect_success 'preset java pattern' '
41+
echo "*.java funcname=java" >.gitattributes &&
42+
git diff Beer.java Beer-correct.java |
43+
grep "^@@.*@@ public static void main("
44+
'
45+
46+
git config funcname.java '!static
47+
!String
48+
[^ ].*s.*'
49+
50+
test_expect_success 'custom pattern' '
51+
git diff Beer.java Beer-correct.java |
52+
grep "^@@.*@@ int special;$"
53+
'
54+
55+
test_expect_success 'last regexp must not be negated' '
56+
git config diff.functionnameregexp "!static" &&
57+
! git diff Beer.java Beer-correct.java
58+
'
59+
60+
test_done

xdiff-interface.c

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,74 @@ int buffer_is_binary(const char *ptr, unsigned long size)
129129
size = FIRST_FEW_BYTES;
130130
return !!memchr(ptr, 0, size);
131131
}
132+
133+
struct ff_regs {
134+
int nr;
135+
struct ff_reg {
136+
regex_t re;
137+
int negate;
138+
} *array;
139+
};
140+
141+
static long ff_regexp(const char *line, long len,
142+
char *buffer, long buffer_size, void *priv)
143+
{
144+
char *line_buffer = xstrndup(line, len); /* make NUL terminated */
145+
struct ff_regs *regs = priv;
146+
regmatch_t pmatch[2];
147+
int result = 0, i;
148+
149+
for (i = 0; i < regs->nr; i++) {
150+
struct ff_reg *reg = regs->array + i;
151+
if (reg->negate ^ !!regexec(&reg->re,
152+
line_buffer, 2, pmatch, 0)) {
153+
free(line_buffer);
154+
return -1;
155+
}
156+
}
157+
i = pmatch[1].rm_so >= 0 ? 1 : 0;
158+
line += pmatch[i].rm_so;
159+
result = pmatch[i].rm_eo - pmatch[i].rm_so;
160+
if (result > buffer_size)
161+
result = buffer_size;
162+
else
163+
while (result > 0 && (isspace(line[result - 1]) ||
164+
line[result - 1] == '\n'))
165+
result--;
166+
memcpy(buffer, line, result);
167+
free(line_buffer);
168+
return result;
169+
}
170+
171+
void xdiff_set_find_func(xdemitconf_t *xecfg, const char *value)
172+
{
173+
int i;
174+
struct ff_regs *regs;
175+
176+
xecfg->find_func = ff_regexp;
177+
regs = xecfg->find_func_priv = xmalloc(sizeof(struct ff_regs));
178+
for (i = 0, regs->nr = 1; value[i]; i++)
179+
if (value[i] == '\n')
180+
regs->nr++;
181+
regs->array = xmalloc(regs->nr * sizeof(struct ff_reg));
182+
for (i = 0; i < regs->nr; i++) {
183+
struct ff_reg *reg = regs->array + i;
184+
const char *ep = strchr(value, '\n'), *expression;
185+
char *buffer = NULL;
186+
187+
reg->negate = (*value == '!');
188+
if (reg->negate && i == regs->nr - 1)
189+
die("Last expression must not be negated: %s", value);
190+
if (*value == '!')
191+
value++;
192+
if (ep)
193+
expression = buffer = xstrndup(value, ep - value);
194+
else
195+
expression = value;
196+
if (regcomp(&reg->re, expression, 0))
197+
die("Invalid regexp to look for hunk header: %s", expression);
198+
if (buffer)
199+
free(buffer);
200+
value = ep + 1;
201+
}
202+
}

xdiff-interface.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ int parse_hunk_header(char *line, int len,
2020
int read_mmfile(mmfile_t *ptr, const char *filename);
2121
int buffer_is_binary(const char *ptr, unsigned long size);
2222

23+
extern void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line);
24+
2325
#endif

xdiff/xdiff.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,13 @@ typedef struct s_xdemitcb {
7373
int (*outf)(void *, mmbuffer_t *, int);
7474
} xdemitcb_t;
7575

76+
typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv);
77+
7678
typedef struct s_xdemitconf {
7779
long ctxlen;
7880
unsigned long flags;
81+
find_func_t find_func;
82+
void *find_func_priv;
7983
} xdemitconf_t;
8084

8185
typedef struct s_bdiffparam {

xdiff/xemit.c

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,24 @@ static xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) {
6969
}
7070

7171

72-
static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll) {
72+
static long def_ff(const char *rec, long len, char *buf, long sz, void *priv)
73+
{
74+
if (len > 0 &&
75+
(isalpha((unsigned char)*rec) || /* identifier? */
76+
*rec == '_' || /* also identifier? */
77+
*rec == '$')) { /* identifiers from VMS and other esoterico */
78+
if (len > sz)
79+
len = sz;
80+
while (0 < len && isspace((unsigned char)rec[len - 1]))
81+
len--;
82+
memcpy(buf, rec, len);
83+
return len;
84+
}
85+
return -1;
86+
}
87+
88+
static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll,
89+
find_func_t ff, void *ff_priv) {
7390

7491
/*
7592
* Be quite stupid about this for now. Find a line in the old file
@@ -80,22 +97,12 @@ static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll) {
8097
const char *rec;
8198
long len;
8299

83-
*ll = 0;
84100
while (i-- > 0) {
85101
len = xdl_get_rec(xf, i, &rec);
86-
if (len > 0 &&
87-
(isalpha((unsigned char)*rec) || /* identifier? */
88-
*rec == '_' || /* also identifier? */
89-
*rec == '$')) { /* mysterious GNU diff's invention */
90-
if (len > sz)
91-
len = sz;
92-
while (0 < len && isspace((unsigned char)rec[len - 1]))
93-
len--;
94-
memcpy(buf, rec, len);
95-
*ll = len;
102+
if ((*ll = ff(rec, len, buf, sz, ff_priv)) >= 0)
96103
return;
97-
}
98104
}
105+
*ll = 0;
99106
}
100107

101108

@@ -120,6 +127,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
120127
xdchange_t *xch, *xche;
121128
char funcbuf[80];
122129
long funclen = 0;
130+
find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff;
123131

124132
if (xecfg->flags & XDL_EMIT_COMMON)
125133
return xdl_emit_common(xe, xscr, ecb, xecfg);
@@ -143,7 +151,8 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
143151

144152
if (xecfg->flags & XDL_EMIT_FUNCNAMES) {
145153
xdl_find_func(&xe->xdf1, s1, funcbuf,
146-
sizeof(funcbuf), &funclen);
154+
sizeof(funcbuf), &funclen,
155+
ff, xecfg->find_func_priv);
147156
}
148157
if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2,
149158
funcbuf, funclen, ecb) < 0)

0 commit comments

Comments
 (0)