Skip to content

Commit cf1b786

Browse files
committed
Use gitattributes to define per-path whitespace rule
The `core.whitespace` configuration variable allows you to define what `diff` and `apply` should consider whitespace errors for all paths in the project (See gitlink:git-config[1]). This attribute gives you finer control per path. For example, if you have these in the .gitattributes: frotz whitespace nitfol -whitespace xyzzy whitespace=-trailing all types of whitespace problems known to git are noticed in path 'frotz' (i.e. diff shows them in diff.whitespace color, and apply warns about them), no whitespace problem is noticed in path 'nitfol', and the default types of whitespace problems except "trailing whitespace" are noticed for path 'xyzzy'. A project with mixed Python and C might want to have: *.c whitespace *.py whitespace=-indent-with-non-tab in its toplevel .gitattributes file. Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 91af7ae commit cf1b786

File tree

10 files changed

+233
-74
lines changed

10 files changed

+233
-74
lines changed

Documentation/gitattributes.txt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,37 @@ When left unspecified, the driver itself is used for both
360360
internal merge and the final merge.
361361

362362

363+
Checking whitespace errors
364+
~~~~~~~~~~~~~~~~~~~~~~~~~~
365+
366+
`whitespace`
367+
^^^^^^^^^^^^
368+
369+
The `core.whitespace` configuration variable allows you to define what
370+
`diff` and `apply` should consider whitespace errors for all paths in
371+
the project (See gitlink:git-config[1]). This attribute gives you finer
372+
control per path.
373+
374+
Set::
375+
376+
Notice all types of potential whitespace errors known to git.
377+
378+
Unset::
379+
380+
Do not notice anything as error.
381+
382+
Unspecified::
383+
384+
Use the value of `core.whitespace` configuration variable to
385+
decide what to notice as error.
386+
387+
String::
388+
389+
Specify a comma separate list of common whitespace problems to
390+
notice in the same format as `core.whitespace` configuration
391+
variable.
392+
393+
363394
EXAMPLE
364395
-------
365396

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ LIB_OBJS = \
312312
alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
313313
color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
314314
convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \
315-
transport.o bundle.o walker.o parse-options.o
315+
transport.o bundle.o walker.o parse-options.o ws.o
316316

317317
BUILTIN_OBJS = \
318318
builtin-add.o \

builtin-apply.c

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ struct patch {
144144
unsigned int old_mode, new_mode;
145145
int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */
146146
int rejected;
147+
unsigned ws_rule;
147148
unsigned long deflate_origlen;
148149
int lines_added, lines_deleted;
149150
int score;
@@ -898,7 +899,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
898899
return -1;
899900
}
900901

901-
static void check_whitespace(const char *line, int len)
902+
static void check_whitespace(const char *line, int len, unsigned ws_rule)
902903
{
903904
const char *err = "Adds trailing whitespace";
904905
int seen_space = 0;
@@ -910,14 +911,14 @@ static void check_whitespace(const char *line, int len)
910911
* this function. That is, an addition of an empty line would
911912
* check the '+' here. Sneaky...
912913
*/
913-
if ((whitespace_rule & WS_TRAILING_SPACE) && isspace(line[len-2]))
914+
if ((ws_rule & WS_TRAILING_SPACE) && isspace(line[len-2]))
914915
goto error;
915916

916917
/*
917918
* Make sure that there is no space followed by a tab in
918919
* indentation.
919920
*/
920-
if (whitespace_rule & WS_SPACE_BEFORE_TAB) {
921+
if (ws_rule & WS_SPACE_BEFORE_TAB) {
921922
err = "Space in indent is followed by a tab";
922923
for (i = 1; i < len; i++) {
923924
if (line[i] == '\t') {
@@ -935,7 +936,7 @@ static void check_whitespace(const char *line, int len)
935936
* Make sure that the indentation does not contain more than
936937
* 8 spaces.
937938
*/
938-
if ((whitespace_rule & WS_INDENT_WITH_NON_TAB) &&
939+
if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
939940
(8 < len) && !strncmp("+ ", line, 9)) {
940941
err = "Indent more than 8 places with spaces";
941942
goto error;
@@ -1001,15 +1002,15 @@ static int parse_fragment(char *line, unsigned long size,
10011002
case '-':
10021003
if (apply_in_reverse &&
10031004
ws_error_action != nowarn_ws_error)
1004-
check_whitespace(line, len);
1005+
check_whitespace(line, len, patch->ws_rule);
10051006
deleted++;
10061007
oldlines--;
10071008
trailing = 0;
10081009
break;
10091010
case '+':
10101011
if (!apply_in_reverse &&
10111012
ws_error_action != nowarn_ws_error)
1012-
check_whitespace(line, len);
1013+
check_whitespace(line, len, patch->ws_rule);
10131014
added++;
10141015
newlines--;
10151016
trailing = 0;
@@ -1318,6 +1319,10 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
13181319
if (offset < 0)
13191320
return offset;
13201321

1322+
patch->ws_rule = whitespace_rule(patch->new_name
1323+
? patch->new_name
1324+
: patch->old_name);
1325+
13211326
patchsize = parse_single_patch(buffer + offset + hdrsize,
13221327
size - offset - hdrsize, patch);
13231328

@@ -1568,7 +1573,8 @@ static void remove_last_line(const char **rbuf, int *rsize)
15681573
*rsize = offset + 1;
15691574
}
15701575

1571-
static int apply_line(char *output, const char *patch, int plen)
1576+
static int apply_line(char *output, const char *patch, int plen,
1577+
unsigned ws_rule)
15721578
{
15731579
/*
15741580
* plen is number of bytes to be copied from patch,
@@ -1593,7 +1599,7 @@ static int apply_line(char *output, const char *patch, int plen)
15931599
/*
15941600
* Strip trailing whitespace
15951601
*/
1596-
if ((whitespace_rule & WS_TRAILING_SPACE) &&
1602+
if ((ws_rule & WS_TRAILING_SPACE) &&
15971603
(1 < plen && isspace(patch[plen-1]))) {
15981604
if (patch[plen] == '\n')
15991605
add_nl_to_tail = 1;
@@ -1610,12 +1616,12 @@ static int apply_line(char *output, const char *patch, int plen)
16101616
char ch = patch[i];
16111617
if (ch == '\t') {
16121618
last_tab_in_indent = i;
1613-
if ((whitespace_rule & WS_SPACE_BEFORE_TAB) &&
1619+
if ((ws_rule & WS_SPACE_BEFORE_TAB) &&
16141620
0 <= last_space_in_indent)
16151621
need_fix_leading_space = 1;
16161622
} else if (ch == ' ') {
16171623
last_space_in_indent = i;
1618-
if ((whitespace_rule & WS_INDENT_WITH_NON_TAB) &&
1624+
if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
16191625
last_tab_in_indent < 0 &&
16201626
8 <= i)
16211627
need_fix_leading_space = 1;
@@ -1629,7 +1635,7 @@ static int apply_line(char *output, const char *patch, int plen)
16291635
int consecutive_spaces = 0;
16301636
int last = last_tab_in_indent + 1;
16311637

1632-
if (whitespace_rule & WS_INDENT_WITH_NON_TAB) {
1638+
if (ws_rule & WS_INDENT_WITH_NON_TAB) {
16331639
/* have "last" point at one past the indent */
16341640
if (last_tab_in_indent < last_space_in_indent)
16351641
last = last_space_in_indent + 1;
@@ -1671,7 +1677,7 @@ static int apply_line(char *output, const char *patch, int plen)
16711677
}
16721678

16731679
static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
1674-
int inaccurate_eof)
1680+
int inaccurate_eof, unsigned ws_rule)
16751681
{
16761682
int match_beginning, match_end;
16771683
const char *patch = frag->patch;
@@ -1730,7 +1736,7 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
17301736
case '+':
17311737
if (first != '+' || !no_add) {
17321738
int added = apply_line(new + newsize, patch,
1733-
plen);
1739+
plen, ws_rule);
17341740
newsize += added;
17351741
if (first == '+' &&
17361742
added == 1 && new[newsize-1] == '\n')
@@ -1953,12 +1959,14 @@ static int apply_fragments(struct strbuf *buf, struct patch *patch)
19531959
{
19541960
struct fragment *frag = patch->fragments;
19551961
const char *name = patch->old_name ? patch->old_name : patch->new_name;
1962+
unsigned ws_rule = patch->ws_rule;
1963+
unsigned inaccurate_eof = patch->inaccurate_eof;
19561964

19571965
if (patch->is_binary)
19581966
return apply_binary(buf, patch);
19591967

19601968
while (frag) {
1961-
if (apply_one_fragment(buf, frag, patch->inaccurate_eof)) {
1969+
if (apply_one_fragment(buf, frag, inaccurate_eof, ws_rule)) {
19621970
error("patch failed: %s:%ld", name, frag->oldpos);
19631971
if (!apply_with_reject)
19641972
return -1;

cache.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,8 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i
610610
#define WS_SPACE_BEFORE_TAB 02
611611
#define WS_INDENT_WITH_NON_TAB 04
612612
#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
613-
extern unsigned whitespace_rule;
613+
extern unsigned whitespace_rule_cfg;
614+
extern unsigned whitespace_rule(const char *);
615+
extern unsigned parse_whitespace_rule(const char *);
614616

615617
#endif /* CACHE_H */

config.c

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -246,54 +246,6 @@ static unsigned long get_unit_factor(const char *end)
246246
die("unknown unit: '%s'", end);
247247
}
248248

249-
static struct whitespace_rule {
250-
const char *rule_name;
251-
unsigned rule_bits;
252-
} whitespace_rule_names[] = {
253-
{ "trailing-space", WS_TRAILING_SPACE },
254-
{ "space-before-tab", WS_SPACE_BEFORE_TAB },
255-
{ "indent-with-non-tab", WS_INDENT_WITH_NON_TAB },
256-
};
257-
258-
static unsigned parse_whitespace_rule(const char *string)
259-
{
260-
unsigned rule = WS_DEFAULT_RULE;
261-
262-
while (string) {
263-
int i;
264-
size_t len;
265-
const char *ep;
266-
int negated = 0;
267-
268-
string = string + strspn(string, ", \t\n\r");
269-
ep = strchr(string, ',');
270-
if (!ep)
271-
len = strlen(string);
272-
else
273-
len = ep - string;
274-
275-
if (*string == '-') {
276-
negated = 1;
277-
string++;
278-
len--;
279-
}
280-
if (!len)
281-
break;
282-
for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++) {
283-
if (strncmp(whitespace_rule_names[i].rule_name,
284-
string, len))
285-
continue;
286-
if (negated)
287-
rule &= ~whitespace_rule_names[i].rule_bits;
288-
else
289-
rule |= whitespace_rule_names[i].rule_bits;
290-
break;
291-
}
292-
string = ep;
293-
}
294-
return rule;
295-
}
296-
297249
int git_parse_long(const char *value, long *ret)
298250
{
299251
if (value && *value) {
@@ -480,7 +432,7 @@ int git_default_config(const char *var, const char *value)
480432
}
481433

482434
if (!strcmp(var, "core.whitespace")) {
483-
whitespace_rule = parse_whitespace_rule(value);
435+
whitespace_rule_cfg = parse_whitespace_rule(value);
484436
return 0;
485437
}
486438

diff.c

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ static void diff_words_show(struct diff_words_data *diff_words)
454454
struct emit_callback {
455455
struct xdiff_emit_state xm;
456456
int nparents, color_diff;
457+
unsigned ws_rule;
457458
const char **label_path;
458459
struct diff_words_data *diff_words;
459460
int *found_changesp;
@@ -493,8 +494,8 @@ static void emit_line(const char *set, const char *reset, const char *line, int
493494
}
494495

495496
static void emit_line_with_ws(int nparents,
496-
const char *set, const char *reset, const char *ws,
497-
const char *line, int len)
497+
const char *set, const char *reset, const char *ws,
498+
const char *line, int len, unsigned ws_rule)
498499
{
499500
int col0 = nparents;
500501
int last_tab_in_indent = -1;
@@ -511,7 +512,7 @@ static void emit_line_with_ws(int nparents,
511512
for (i = col0; i < len; i++) {
512513
if (line[i] == '\t') {
513514
last_tab_in_indent = i;
514-
if ((whitespace_rule & WS_SPACE_BEFORE_TAB) &&
515+
if ((ws_rule & WS_SPACE_BEFORE_TAB) &&
515516
0 <= last_space_in_indent)
516517
need_highlight_leading_space = 1;
517518
}
@@ -520,7 +521,7 @@ static void emit_line_with_ws(int nparents,
520521
else
521522
break;
522523
}
523-
if ((whitespace_rule & WS_INDENT_WITH_NON_TAB) &&
524+
if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
524525
0 <= last_space_in_indent &&
525526
last_tab_in_indent < 0 &&
526527
8 <= (i - col0)) {
@@ -551,7 +552,7 @@ static void emit_line_with_ws(int nparents,
551552
tail = len - 1;
552553
if (line[tail] == '\n' && i < tail)
553554
tail--;
554-
if (whitespace_rule & WS_TRAILING_SPACE) {
555+
if (ws_rule & WS_TRAILING_SPACE) {
555556
while (i < tail) {
556557
if (!isspace(line[tail]))
557558
break;
@@ -578,7 +579,7 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons
578579
emit_line(set, reset, line, len);
579580
else
580581
emit_line_with_ws(ecbdata->nparents, set, reset, ws,
581-
line, len);
582+
line, len, ecbdata->ws_rule);
582583
}
583584

584585
static void fn_out_consume(void *priv, char *line, unsigned long len)
@@ -994,6 +995,7 @@ struct checkdiff_t {
994995
struct xdiff_emit_state xm;
995996
const char *filename;
996997
int lineno, color_diff;
998+
unsigned ws_rule;
997999
};
9981000

9991001
static void checkdiff_consume(void *priv, char *line, unsigned long len)
@@ -1029,7 +1031,8 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
10291031
if (white_space_at_end)
10301032
printf("white space at end");
10311033
printf(":%s ", reset);
1032-
emit_line_with_ws(1, set, reset, ws, line, len);
1034+
emit_line_with_ws(1, set, reset, ws, line, len,
1035+
data->ws_rule);
10331036
}
10341037

10351038
data->lineno++;
@@ -1330,6 +1333,7 @@ static void builtin_diff(const char *name_a,
13301333
ecbdata.label_path = lbl;
13311334
ecbdata.color_diff = o->color_diff;
13321335
ecbdata.found_changesp = &o->found_changes;
1336+
ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
13331337
xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
13341338
xecfg.ctxlen = o->context;
13351339
xecfg.flags = XDL_EMIT_FUNCNAMES;
@@ -1423,6 +1427,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
14231427
data.filename = name_b ? name_b : name_a;
14241428
data.lineno = 0;
14251429
data.color_diff = o->color_diff;
1430+
data.ws_rule = whitespace_rule(data.filename);
14261431

14271432
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
14281433
die("unable to read files to diff");

environment.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ int pager_in_use;
3535
int pager_use_color = 1;
3636
char *editor_program;
3737
int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */
38-
unsigned whitespace_rule = WS_DEFAULT_RULE;
38+
unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
3939

4040
/* This is set by setup_git_dir_gently() and/or git_default_config() */
4141
char *git_work_tree_cfg;

0 commit comments

Comments
 (0)