Skip to content

Commit f4b05a4

Browse files
j6tgitster
authored andcommitted
Make the tab width used for whitespace checks configurable
A new whitespace "rule" is added that sets the tab width to use for whitespace checks and fix-ups and replaces the hard-coded constant 8. Since the setting is part of the rules, it can be set per file using .gitattributes. The new configuration is backwards compatible because older git versions simply ignore unknown whitespace rules. Signed-off-by: Johannes Sixt <j6t@kdbg.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent dee40e5 commit f4b05a4

File tree

7 files changed

+163
-21
lines changed

7 files changed

+163
-21
lines changed

Documentation/config.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,9 @@ core.whitespace::
513513
part of the line terminator, i.e. with it, `trailing-space`
514514
does not trigger if the character before such a carriage-return
515515
is not a whitespace (not enabled by default).
516+
* `tabwidth=<n>` tells how many character positions a tab occupies; this
517+
is relevant for `indent-with-non-tab` and when git fixes `tab-in-indent`
518+
errors. The default tab width is 8. Allowed values are 1 to 63.
516519

517520
core.fsyncobjectfiles::
518521
This boolean will enable 'fsync()' when writing object files.

Documentation/gitattributes.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -723,20 +723,22 @@ control per path.
723723
Set::
724724

725725
Notice all types of potential whitespace errors known to git.
726+
The tab width is taken from the value of the `core.whitespace`
727+
configuration variable.
726728

727729
Unset::
728730

729731
Do not notice anything as error.
730732

731733
Unspecified::
732734

733-
Use the value of `core.whitespace` configuration variable to
735+
Use the value of the `core.whitespace` configuration variable to
734736
decide what to notice as error.
735737

736738
String::
737739

738740
Specify a comma separate list of common whitespace problems to
739-
notice in the same format as `core.whitespace` configuration
741+
notice in the same format as the `core.whitespace` configuration
740742
variable.
741743

742744

cache.h

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,15 +1087,17 @@ void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char *
10871087
/*
10881088
* whitespace rules.
10891089
* used by both diff and apply
1090+
* last two digits are tab width
10901091
*/
1091-
#define WS_BLANK_AT_EOL 01
1092-
#define WS_SPACE_BEFORE_TAB 02
1093-
#define WS_INDENT_WITH_NON_TAB 04
1094-
#define WS_CR_AT_EOL 010
1095-
#define WS_BLANK_AT_EOF 020
1096-
#define WS_TAB_IN_INDENT 040
1092+
#define WS_BLANK_AT_EOL 0100
1093+
#define WS_SPACE_BEFORE_TAB 0200
1094+
#define WS_INDENT_WITH_NON_TAB 0400
1095+
#define WS_CR_AT_EOL 01000
1096+
#define WS_BLANK_AT_EOF 02000
1097+
#define WS_TAB_IN_INDENT 04000
10971098
#define WS_TRAILING_SPACE (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)
1098-
#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
1099+
#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8)
1100+
#define WS_TAB_WIDTH_MASK 077
10991101
extern unsigned whitespace_rule_cfg;
11001102
extern unsigned whitespace_rule(const char *);
11011103
extern unsigned parse_whitespace_rule(const char *);
@@ -1104,6 +1106,7 @@ extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *str
11041106
extern char *whitespace_error_string(unsigned ws);
11051107
extern void ws_fix_copy(struct strbuf *, const char *, int, unsigned, int *);
11061108
extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
1109+
#define ws_tab_width(rule) ((rule) & WS_TAB_WIDTH_MASK)
11071110

11081111
/* ls-files */
11091112
int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);

t/t4015-diff-whitespace.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,13 @@ test_expect_success 'check spaces as indentation (indent-with-non-tab: on)' '
344344
345345
'
346346

347+
test_expect_success 'ditto, but tabwidth=9' '
348+
349+
git config core.whitespace "indent-with-non-tab,tabwidth=9" &&
350+
git diff --check
351+
352+
'
353+
347354
test_expect_success 'check tabs and spaces as indentation (indent-with-non-tab: on)' '
348355
349356
git config core.whitespace "indent-with-non-tab" &&
@@ -352,6 +359,20 @@ test_expect_success 'check tabs and spaces as indentation (indent-with-non-tab:
352359
353360
'
354361

362+
test_expect_success 'ditto, but tabwidth=10' '
363+
364+
git config core.whitespace "indent-with-non-tab,tabwidth=10" &&
365+
test_must_fail git diff --check
366+
367+
'
368+
369+
test_expect_success 'ditto, but tabwidth=20' '
370+
371+
git config core.whitespace "indent-with-non-tab,tabwidth=20" &&
372+
git diff --check
373+
374+
'
375+
355376
test_expect_success 'check tabs as indentation (tab-in-indent: off)' '
356377
357378
git config core.whitespace "-tab-in-indent" &&
@@ -376,6 +397,13 @@ test_expect_success 'check tabs and spaces as indentation (tab-in-indent: on)' '
376397
377398
'
378399

400+
test_expect_success 'ditto, but tabwidth=1 (must be irrelevant)' '
401+
402+
git config core.whitespace "tab-in-indent,tabwidth=1" &&
403+
test_must_fail git diff --check
404+
405+
'
406+
379407
test_expect_success 'check tab-in-indent and indent-with-non-tab conflict' '
380408
381409
git config core.whitespace "tab-in-indent,indent-with-non-tab" &&

t/t4019-diff-wserror.sh

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,65 @@ test_expect_success default '
5151
5252
'
5353

54+
test_expect_success 'default (attribute)' '
55+
56+
test_might_fail git config --unset core.whitespace &&
57+
echo "F whitespace" >.gitattributes &&
58+
prepare_output &&
59+
60+
grep Eight error >/dev/null &&
61+
grep HT error >/dev/null &&
62+
grep With error >/dev/null &&
63+
grep Return error >/dev/null &&
64+
grep No normal >/dev/null
65+
66+
'
67+
68+
test_expect_success 'default, tabwidth=10 (attribute)' '
69+
70+
git config core.whitespace "tabwidth=10" &&
71+
echo "F whitespace" >.gitattributes &&
72+
prepare_output &&
73+
74+
grep Eight normal >/dev/null &&
75+
grep HT error >/dev/null &&
76+
grep With error >/dev/null &&
77+
grep Return error >/dev/null &&
78+
grep No normal >/dev/null
79+
80+
'
81+
82+
test_expect_success 'no check (attribute)' '
83+
84+
test_might_fail git config --unset core.whitespace &&
85+
echo "F -whitespace" >.gitattributes &&
86+
prepare_output &&
87+
88+
grep Eight normal >/dev/null &&
89+
grep HT normal >/dev/null &&
90+
grep With normal >/dev/null &&
91+
grep Return normal >/dev/null &&
92+
grep No normal >/dev/null
93+
94+
'
95+
96+
test_expect_success 'no check, tabwidth=10 (attribute), must be irrelevant' '
97+
98+
git config core.whitespace "tabwidth=10" &&
99+
echo "F -whitespace" >.gitattributes &&
100+
prepare_output &&
101+
102+
grep Eight normal >/dev/null &&
103+
grep HT normal >/dev/null &&
104+
grep With normal >/dev/null &&
105+
grep Return normal >/dev/null &&
106+
grep No normal >/dev/null
107+
108+
'
109+
54110
test_expect_success 'without -trail' '
55111
112+
rm -f .gitattributes &&
56113
git config core.whitespace -trail &&
57114
prepare_output &&
58115
@@ -134,6 +191,34 @@ test_expect_success 'with indent-non-tab only (attribute)' '
134191
135192
'
136193

194+
test_expect_success 'with indent-non-tab only, tabwidth=10' '
195+
196+
rm -f .gitattributes &&
197+
git config core.whitespace indent,tabwidth=10,-trailing,-space &&
198+
prepare_output &&
199+
200+
grep Eight normal >/dev/null &&
201+
grep HT normal >/dev/null &&
202+
grep With normal >/dev/null &&
203+
grep Return normal >/dev/null &&
204+
grep No normal >/dev/null
205+
206+
'
207+
208+
test_expect_success 'with indent-non-tab only, tabwidth=10 (attribute)' '
209+
210+
test_might_fail git config --unset core.whitespace &&
211+
echo "F whitespace=indent,-trailing,-space,tabwidth=10" >.gitattributes &&
212+
prepare_output &&
213+
214+
grep Eight normal >/dev/null &&
215+
grep HT normal >/dev/null &&
216+
grep With normal >/dev/null &&
217+
grep Return normal >/dev/null &&
218+
grep No normal >/dev/null
219+
220+
'
221+
137222
test_expect_success 'with cr-at-eol' '
138223
139224
rm -f .gitattributes &&

t/t4124-apply-ws-rule.sh

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ prepare_test_file () {
1010
# X RULE
1111
# ! trailing-space
1212
# @ space-before-tab
13-
# # indent-with-non-tab
13+
# # indent-with-non-tab (default tab width 8)
14+
# = indent-with-non-tab,tabwidth=16
1415
# % tab-in-indent
1516
sed -e "s/_/ /g" -e "s/>/ /" <<-\EOF
1617
An_SP in an ordinary line>and a HT.
@@ -25,8 +26,8 @@ prepare_test_file () {
2526
________>_Eight SP, a HT and a SP (@#%).
2627
_______________Fifteen SP (#).
2728
_______________>Fifteen SP and a HT (@#%).
28-
________________Sixteen SP (#).
29-
________________>Sixteen SP and a HT (@#%).
29+
________________Sixteen SP (#=).
30+
________________>Sixteen SP and a HT (@#%=).
3031
_____a__Five SP, a non WS, two SP.
3132
A line with a (!) trailing SP_
3233
A line with a (!) trailing HT>
@@ -139,8 +140,8 @@ test_expect_success 'spaces inserted by tab-in-indent' '
139140
_________________Eight SP, a HT and a SP (@#%).
140141
_______________Fifteen SP (#).
141142
________________Fifteen SP and a HT (@#%).
142-
________________Sixteen SP (#).
143-
________________________Sixteen SP and a HT (@#%).
143+
________________Sixteen SP (#=).
144+
________________________Sixteen SP and a HT (@#%=).
144145
_____a__Five SP, a non WS, two SP.
145146
A line with a (!) trailing SP_
146147
A line with a (!) trailing HT>
@@ -157,7 +158,7 @@ do
157158
case "$s" in '') ts='@' ;; *) ts= ;; esac
158159
for i in - ''
159160
do
160-
case "$i" in '') ti='#' ;; *) ti= ;; esac
161+
case "$i" in '') ti='#' ti16='=';; *) ti= ti16= ;; esac
161162
for h in - ''
162163
do
163164
[ -z "$h$i" ] && continue
@@ -170,12 +171,22 @@ do
170171
test_fix "$tt$ts$ti$th"
171172
'
172173

174+
test_expect_success "rule=$rule,tabwidth=16" '
175+
git config core.whitespace "$rule,tabwidth=16" &&
176+
test_fix "$tt$ts$ti16$th"
177+
'
178+
173179
test_expect_success "rule=$rule (attributes)" '
174180
git config --unset core.whitespace &&
175181
echo "target whitespace=$rule" >.gitattributes &&
176182
test_fix "$tt$ts$ti$th"
177183
'
178184

185+
test_expect_success "rule=$rule,tabwidth=16 (attributes)" '
186+
echo "target whitespace=$rule,tabwidth=16" >.gitattributes &&
187+
test_fix "$tt$ts$ti16$th"
188+
'
189+
179190
done
180191
done
181192
done

ws.c

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ unsigned parse_whitespace_rule(const char *string)
5656
rule |= whitespace_rule_names[i].rule_bits;
5757
break;
5858
}
59+
if (strncmp(string, "tabwidth=", 9) == 0) {
60+
unsigned tabwidth = atoi(string + 9);
61+
if (0 < tabwidth && tabwidth < 0100) {
62+
rule &= ~WS_TAB_WIDTH_MASK;
63+
rule |= tabwidth;
64+
}
65+
else
66+
warning("tabwidth %.*s out of range",
67+
(int)(len - 9), string + 9);
68+
}
5969
string = ep;
6070
}
6171

@@ -84,7 +94,7 @@ unsigned whitespace_rule(const char *pathname)
8494
value = attr_whitespace_rule.value;
8595
if (ATTR_TRUE(value)) {
8696
/* true (whitespace) */
87-
unsigned all_rule = 0;
97+
unsigned all_rule = ws_tab_width(whitespace_rule_cfg);
8898
int i;
8999
for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++)
90100
if (!whitespace_rule_names[i].loosens_error &&
@@ -93,7 +103,7 @@ unsigned whitespace_rule(const char *pathname)
93103
return all_rule;
94104
} else if (ATTR_FALSE(value)) {
95105
/* false (-whitespace) */
96-
return 0;
106+
return ws_tab_width(whitespace_rule_cfg);
97107
} else if (ATTR_UNSET(value)) {
98108
/* reset to default (!whitespace) */
99109
return whitespace_rule_cfg;
@@ -206,7 +216,7 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,
206216
}
207217

208218
/* Check for indent using non-tab. */
209-
if ((ws_rule & WS_INDENT_WITH_NON_TAB) && i - written >= 8) {
219+
if ((ws_rule & WS_INDENT_WITH_NON_TAB) && i - written >= ws_tab_width(ws_rule)) {
210220
result |= WS_INDENT_WITH_NON_TAB;
211221
if (stream) {
212222
fputs(ws, stream);
@@ -320,7 +330,7 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,
320330
} else if (ch == ' ') {
321331
last_space_in_indent = i;
322332
if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
323-
8 <= i - last_tab_in_indent)
333+
ws_tab_width(ws_rule) <= i - last_tab_in_indent)
324334
need_fix_leading_space = 1;
325335
} else
326336
break;
@@ -350,7 +360,7 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,
350360
strbuf_addch(dst, ch);
351361
} else {
352362
consecutive_spaces++;
353-
if (consecutive_spaces == 8) {
363+
if (consecutive_spaces == ws_tab_width(ws_rule)) {
354364
strbuf_addch(dst, '\t');
355365
consecutive_spaces = 0;
356366
}
@@ -369,7 +379,7 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,
369379
if (src[i] == '\t')
370380
do {
371381
strbuf_addch(dst, ' ');
372-
} while ((dst->len - start) % 8);
382+
} while ((dst->len - start) % ws_tab_width(ws_rule));
373383
else
374384
strbuf_addch(dst, src[i]);
375385
}

0 commit comments

Comments
 (0)