Add tab-in-indent whitespace rule
cover Add tab-in-indent whitespace rule
This patch set introduces a new whitespace error/fix class for projects and languages with a coding style where no tab character is used to indent the lines.
To implement this feature, we need to rework ws_fix_copy() to append to a strbuf rather than write into a fixed-size output buffer, because tabs may be expanded into multiple spaces when tab-in-indent is applied. We also have to change the "catch all errors known to git" to exclude tab-in-indent, which directly conflicts with the existing indent-with-non-tab rule.
Junio C Hamano (1):
whitespace: we cannot "catch all errors known to git" anymore
Chris Webb (5):
whitespace: add tab-in-indent error class
whitespace: tests for git-diff --check with tab-in-indent error class
whitespace: replumb ws_fix_copy to take a strbuf *dst instead of char *dst
whitespace: add tab-in-indent support for --whitespace=fix
whitespace: tests for git-apply --whitespace=fix with tab-in-indent
Documentation/config.txt | 2 + builtin/apply.c | 81 +++++++++++++++++++++----------------------- cache.h | 3 +- t/t4015-diff-whitespace.sh | 42 +++++++++++++++++++++++ t/t4124-apply-ws-rule.sh | 53 ++++++++++++++++------------- ws.c | 73 ++++++++++++++++++++++++++++----------- 6 files changed, 166 insertions(+), 88 deletions(-)
PATCH 1/6 whitespace: we cannot "catch all errors known to git" anymore
From: Junio C Hamano <[email protected]>
Traditionally, "*.txt whitespace" in .gitattributes file has been an instruction to catch _all_ classes of whitespace errors known to git.
This has to change, however, in order to introduce "tab-in-indent" which is inherently incompatible with "indent-with-non-tab". As we do not want to break configuration of existing users, add a mechanism to allow marking selected rules to be excluded from "all rules known to git".
--- ws.c | 6 ++++-- 1 files changed, 4 insertions(+), 2 deletions(-)
ws.c +4 -2
@@ -10,7 +10,8 @@static struct whitespace_rule {const char *rule_name;unsigned rule_bits; -unsigned loosens_error; +unsigned loosens_error:1 , + exclude_default:1 ; } whitespace_rule_names[] = { {"trailing-space" ,WS_TRAILING_SPACE ,0 }, {"space-before-tab" ,WS_SPACE_BEFORE_TAB ,0 },
@@ -82,7 +83,8 @@ unsigned whitespace_rule(const char *pathname)unsigned all_rule =0 ;int i;for (i =0 ; i <ARRAY_SIZE (whitespace_rule_names); i++) -if (!whitespace_rule_names[i].loosens_error) +if (!whitespace_rule_names[i].loosens_error && + !whitespace_rule_names[i].exclude_default) all_rule |= whitespace_rule_names[i].rule_bits;return all_rule; }else if (ATTR_FALSE (value)) {
Signature · 1 line
1.7.0.3
PATCH 2/6 whitespace: add tab-in-indent error class
In reply to Chris WebbSome projects and languages use coding style where no tab character is used to indent the lines.
This only adds support and documentation for "apply --whitespace=warn" and "diff --check"; later patches add "apply --whitespace=fix" and tests.
--- Documentation/config.txt | 2 ++ cache.h | 1 + ws.c | 27 ++++++++++++++++++++++----- 3 files changed, 25 insertions(+), 5 deletions(-)
Documentation/config.txt +2 -0
@@ -481,6 +481,8 @@ core.whitespace:: error (enabled by default). * `indent-with-non-tab` treats a line that is indented with 8 or more space characters as an error (not enabled by default). +* `tab-in-indent` treats a tab character in the initial indent part of + the line as an error (not enabled by default). * `blank-at-eof` treats blank lines added at the end of file as an error (enabled by default). * `trailing-space` is a short-hand to cover both `blank-at-eol` and
cache.h +1 -0
@@ -1040,6 +1040,7 @@ void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char *#define WS_INDENT_WITH_NON_TAB 04#define WS_CR_AT_EOL 010#define WS_BLANK_AT_EOF 020 +#define WS_TAB_IN_INDENT 040#define WS_TRAILING_SPACE (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)extern unsigned whitespace_rule_cfg;
ws.c +22 -5
@@ -19,6 +19,7 @@ static struct whitespace_rule { { "cr-at-eol", WS_CR_AT_EOL, 1 }, { "blank-at-eol", WS_BLANK_AT_EOL, 0 }, { "blank-at-eof", WS_BLANK_AT_EOF, 0 }, + { "tab-in-indent", WS_TAB_IN_INDENT, 0, 1 }, };unsigned parse_whitespace_rule (const char *string)
@@ -57,6 +58,9 @@ unsigned parse_whitespace_rule(const char *string) } string = ep; } + +if (rule &WS_TAB_IN_INDENT && rule &WS_INDENT_WITH_NON_TAB ) +die ("cannot enforce both tab-in-indent and indent-with-non-tab" );return rule; }
@@ -127,6 +131,11 @@ char *whitespace_error_string(unsigned ws)strbuf_addstr (&err,", " );strbuf_addstr (&err,"indent with spaces" ); } +if (ws &WS_TAB_IN_INDENT ) { +if (err.len) +strbuf_addstr (&err,", " ); +strbuf_addstr (&err,"tab in indent" ); + }return strbuf_detach (&err,NULL ); }
@@ -165,7 +174,7 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule, } } -/* Check for space before tab in initial indent. */ +/* Check indentation */ for (i =0 ; i < len; i++) {if (line[i] ==' ' )continue ;
@@ -177,11 +186,19 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,fputs (ws, stream);fwrite (line + written, i - written,1 , stream);fputs (reset, stream); +fwrite (line + i,1 ,1 , stream); } - }else if (stream) -fwrite (line + written, i - written,1 , stream); -if (stream) -fwrite (line + i,1 ,1 , stream); + }else if (ws_rule &WS_TAB_IN_INDENT ) { + result |=WS_TAB_IN_INDENT ; +if (stream) { +fwrite (line + written, i - written,1 , stream); +fputs (ws, stream); +fwrite (line + i,1 ,1 , stream); +fputs (reset, stream); + } + }else if (stream) { +fwrite (line + written, i - written +1 ,1 , stream); + } written = i +1 ; }
Signature · 1 line
1.7.0.3
Re: [PATCH 2/6] whitespace: add tab-in-indent error class
Hello!
ws.c unsigned parse_whitespace_rule(const char *string) · line 63
Quoted 2 lines
ws.c +2
-0+if (rule &WS_TAB_IN_INDENT && rule &WS_INDENT_WITH_NON_TAB ) +die ("cannot enforce both tab-in-indent and indent-with-non-tab" );
Well, it would forbid any indented lines, which could possible make sense for some file formats.
Have a nice fortnight
Signature · 3 lines
Martin `MJ' Mares <[email protected]> http://mj.ucw.cz/ Faculty of Math and Physics, Charles University, Prague, Czech Rep., Earth "This is an object-oriented system. If we change anything, the users object."
PATCH 3/6 whitespace: tests for git-diff --check with tab-in-indent error class
In reply to Chris Webb--- t/t4015-diff-whitespace.sh | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 42 insertions(+), 0 deletions(-)
t/t4015-diff-whitespace.sh +42 -0
@@ -352,6 +352,48 @@ test_expect_success 'check tabs and spaces as indentation (indent-with-non-tab: ' +test_expect_success 'check tabs as indentation (tab-in-indent: off)' ' + + git config core.whitespace "-tab-in-indent" + echo " foo ();" > x && + git diff --check + +' + +test_expect_success 'check tabs as indentation (tab-in-indent: on)' ' + + git config core.whitespace "tab-in-indent" && + echo " foo ();" > x && + test_must_fail git diff --check + +' + +test_expect_success 'check tabs and spaces as indentation (tab-in-indent: on)' ' + + git config core.whitespace "tab-in-indent" && + echo " foo ();" > x && + test_must_fail git diff --check + +' + +test_expect_success 'check tab-in-indent and indent-with-non-tab conflict' ' + + git config core.whitespace "tab-in-indent,indent-with-non-tab" + echo "foo ();" > x && + test_must_fail git diff --check + +' + +test_expect_success 'check tab-in-indent excluded from wildcard whitespace attribute' ' + + git config --unset core.whitespace + echo "x whitespace" > .gitattributes && + echo " foo ();" > x && + git diff --check && + rm -f .gitattributes + +' + test_expect_success 'line numbers in --check output are correct' ' echo "" > x &&
Signature · 1 line
1.7.0.3
PATCH 4/6 whitespace: replumb ws_fix_copy to take a strbuf *dst instead of char *dst
In reply to Chris WebbTo implement --whitespace=fix for tab-in-indent, we have to allow for the possibility that whitespace can increase in size when it is fixed, expanding tabs to to multiple spaces in the initial indent.
--- builtin/apply.c | 81 ++++++++++++++++++++++++++---------------------------- cache.h | 2 +- ws.c | 19 +++++------- 3 files changed, 48 insertions(+), 54 deletions(-)
builtin/apply.c +39 -42
@@ -1854,6 +1854,8 @@ static int match_fragment(struct image *img, {int i;char *fixed_buf, *buf, *orig, *target; +struct strbuf fixed; +size_t fixed_len;int preimage_limit;if (preimage->nr + try_lno <= img->nr) {
@@ -1977,12 +1979,12 @@ static int match_fragment(struct image *img,* use the whitespace from the preimage. */ extra_chars = preimage_end - preimage_eof; - fixed_buf =xmalloc (imgoff + extra_chars); -memcpy (fixed_buf, img->buf + try, imgoff); -memcpy (fixed_buf + imgoff, preimage_eof, extra_chars); - imgoff += extra_chars; +strbuf_init (&fixed, imgoff + extra_chars); +strbuf_add (&fixed, img->buf + try, imgoff); +strbuf_add (&fixed, preimage_eof, extra_chars); + fixed_buf =strbuf_detach (&fixed, &fixed_len);update_pre_post_images (preimage, postimage, - fixed_buf, imgoff, postlen); + fixed_buf, fixed_len, postlen);return 1 ; }
@@ -1999,27 +2001,22 @@ static int match_fragment(struct image *img,* but in this loop we will only handle the part of the * preimage that falls within the file. */ - fixed_buf =xmalloc (preimage->len +1 ); - buf = fixed_buf; +strbuf_init (&fixed, preimage->len +1 ); orig = preimage->buf; target = img->buf + try;for (i =0 ; i < preimage_limit; i++) { -size_t fixlen;/* length after fixing the preimage */ size_t oldlen = preimage->line[i].len;size_t tgtlen = img->line[try_lno + i].len; -size_t tgtfixlen;/* length after fixing the target line */ -char tgtfixbuf[1024 ], *tgtfix; +size_t fixstart = fixed.len; +struct strbuf tgtfix;int match;/* Try fixing the line in the preimage */ - fixlen =ws_fix_copy (buf, orig, oldlen, ws_rule,NULL ); +ws_fix_copy (&fixed, orig, oldlen, ws_rule,NULL );/* Try fixing the line in the target */ -if (sizeof (tgtfixbuf) > tgtlen) - tgtfix = tgtfixbuf; -else - tgtfix =xmalloc (tgtlen); - tgtfixlen =ws_fix_copy (tgtfix, target, tgtlen, ws_rule,NULL ); +strbuf_init (&tgtfix, tgtlen); +ws_fix_copy (&tgtfix, target, tgtlen, ws_rule,NULL );/* * If they match, either the preimage was based on
@@ -2031,15 +2028,15 @@ static int match_fragment(struct image *img,* so we might as well take the fix together with their * real change. */ - match = (tgtfixlen == fixlen && !memcmp (tgtfix, buf, fixlen)); + match = (tgtfix.len == fixed.len - fixstart && + !memcmp (tgtfix.buf, fixed.buf + fixstart, + fixed.len - fixstart)); -if (tgtfix != tgtfixbuf) -free (tgtfix); +strbuf_release (&tgtfix);if (!match) goto unmatch_exit; orig += oldlen; - buf += fixlen; target += tgtlen; }
@@ -2051,19 +2048,18 @@ static int match_fragment(struct image *img,* false). */ for ( ; i < preimage->nr; i++) { -size_t fixlen;/* length after fixing the preimage */ +size_t fixstart = fixed.len;/* start of the fixed preimage */ size_t oldlen = preimage->line[i].len;int j;/* Try fixing the line in the preimage */ - fixlen =ws_fix_copy (buf, orig, oldlen, ws_rule,NULL ); +ws_fix_copy (&fixed, orig, oldlen, ws_rule,NULL ); -for (j =0 ; j < fixlen; j++) -if (!isspace (buf[j])) +for (j = fixstart; j < fixed.len; j++) +if (!isspace (fixed.buf[j])) goto unmatch_exit; orig += oldlen; - buf += fixlen; }/*
@@ -2071,12 +2067,13 @@ static int match_fragment(struct image *img,* has whitespace breakages unfixed, and fixing them makes the * hunk match. Update the context lines in the postimage. */ + fixed_buf =strbuf_detach (&fixed, &fixed_len);update_pre_post_images (preimage, postimage, - fixed_buf, buf - fixed_buf,0 ); + fixed_buf, fixed_len,0 );return 1 ; unmatch_exit: -free (fixed_buf); +strbuf_release (&fixed);return 0 ; }
@@ -2244,7 +2241,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,int match_beginning, match_end;const char *patch = frag->patch;int size = frag->size; -char *old, *new, *oldlines, *newlines; +char *old, *oldlines; +struct strbuf newlines;int new_blank_lines_at_end =0 ;unsigned long leading, trailing;int pos, applied_pos;
@@ -2254,16 +2252,16 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,memset (&preimage,0 ,sizeof (preimage));memset (&postimage,0 ,sizeof (postimage)); oldlines =xmalloc (size); - newlines =xmalloc (size); +strbuf_init (&newlines, size); old = oldlines; - new = newlines;while (size >0 ) {char first;int len =linelen (patch, size); -int plen, added; +int plen;int added_blank_line =0 ;int is_blank_context =0 ; +size_t start;if (!len)break ;
@@ -2293,7 +2291,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,/* ... followed by '\No newline'; nothing */ break ; *old++ ='\n' ; - *new++ ='\n' ; +strbuf_addch (&newlines,'\n' );add_line_info (&preimage,"\n" ,1 ,LINE_COMMON );add_line_info (&postimage,"\n" ,1 ,LINE_COMMON ); is_blank_context =1 ;
@@ -2315,18 +2313,17 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,if (first =='+' && no_add)break ; + start = newlines.len;if (first !='+' || !whitespace_error || ws_error_action != correct_ws_error) { -memcpy (new, patch +1 , plen); - added = plen; +strbuf_add (&newlines, patch +1 , plen); }else { - added =ws_fix_copy (new, patch +1 , plen, ws_rule, &applied_after_fixing_ws); +ws_fix_copy (&newlines, patch +1 , plen, ws_rule, &applied_after_fixing_ws); } -add_line_info (&postimage, new, added, +add_line_info (&postimage, newlines.buf + start, newlines.len - start, (first =='+' ?0 :LINE_COMMON )); - new += added;if (first =='+' && (ws_rule &WS_BLANK_AT_EOF ) &&ws_blank_line (patch +1 , plen, ws_rule))
@@ -2351,9 +2348,9 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, }if (inaccurate_eof && old > oldlines && old[-1 ] =='\n' && - new > newlines && new[-1 ] =='\n' ) { + newlines.len >0 && newlines.buf[newlines.len -1 ] =='\n' ) { old--; - new--; +strbuf_setlen (&newlines, newlines.len -1 ); } leading = frag->leading;
@@ -2385,8 +2382,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, pos = frag->newpos ? (frag->newpos -1 ) :0 ; preimage.buf = oldlines; preimage.len = old - oldlines; - postimage.buf = newlines; - postimage.len = new - newlines; + postimage.buf = newlines.buf; + postimage.len = newlines.len; preimage.line = preimage.line_allocated; postimage.line = postimage.line_allocated;
@@ -2462,7 +2459,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, }free (oldlines); -free (newlines); +strbuf_release (&newlines);free (preimage.line_allocated);free (postimage.line_allocated);
cache.h +1 -1
@@ -1049,7 +1049,7 @@ extern unsigned parse_whitespace_rule(const char *);extern unsigned ws_check (const char *line,int len,unsigned ws_rule);extern void ws_check_emit (const char *line,int len,unsigned ws_rule,FILE *stream,const char *set,const char *reset,const char *ws);extern char *whitespace_error_string (unsigned ws); -extern int ws_fix_copy (char *,const char *,int ,unsigned ,int *); +extern void ws_fix_copy (struct strbuf *,const char *,int ,unsigned ,int *);extern int ws_blank_line (const char *line,int len,unsigned ws_rule);/* ls-files */
ws.c +8 -11
@@ -271,8 +271,8 @@ int ws_blank_line(const char *line, int len, unsigned ws_rule)return 1 ; } -/* Copy the line to the buffer while fixing whitespaces */ -int ws_fix_copy (char *dst,const char *src,int len,unsigned ws_rule,int *error_count) +/* Copy the line onto the end of the strbuf while fixing whitespaces */ +void ws_fix_copy (struct strbuf *dst,const char *src,int len,unsigned ws_rule,int *error_count) {/* * len is number of bytes to be copied from src, starting
@@ -286,7 +286,6 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erroint last_tab_in_indent =-1 ;int last_space_in_indent =-1 ;int need_fix_leading_space =0 ; -char *buf;/* * Strip trailing whitespace
@@ -326,7 +325,6 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *errobreak ; } - buf = dst;if (need_fix_leading_space) {/* Process indent ourselves */ int consecutive_spaces =0 ;
@@ -348,28 +346,27 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *errochar ch = src[i];if (ch !=' ' ) { consecutive_spaces =0 ; - *dst++ = ch; +strbuf_addch (dst, ch); }else { consecutive_spaces++;if (consecutive_spaces ==8 ) { - *dst++ ='\t' ; +strbuf_addch (dst,'\t' ); consecutive_spaces =0 ; } } }while (0 < consecutive_spaces--) - *dst++ =' ' ; +strbuf_addch (dst,' ' ); len -= last; src += last; fixed =1 ; } -memcpy (dst, src, len); +strbuf_add (dst, src, len);if (add_cr_to_tail) - dst[len++] ='\r' ; +strbuf_addch (dst,'\r' );if (add_nl_to_tail) - dst[len++] ='\n' ; +strbuf_addch (dst,'\n' );if (fixed && error_count) (*error_count)++; -return dst + len - buf; }
Signature · 1 line
1.7.0.3
PATCH 5/6 whitespace: add tab-in-indent support for --whitespace=fix
In reply to Chris WebbIf tab-in-indent is set, --whitespace=fix will ensure that any stray tabs in the initial indent are expanded to the correct number of space characters.
--- ws.c | 21 ++++++++++++++++++--- 1 files changed, 18 insertions(+), 3 deletions(-)
ws.c +18 -3
@@ -313,8 +313,9 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,char ch = src[i];if (ch =='\t' ) { last_tab_in_indent = i; -if ((ws_rule &WS_SPACE_BEFORE_TAB ) && -0 <= last_space_in_indent) +if ((ws_rule &WS_TAB_IN_INDENT ) || + ((ws_rule &WS_SPACE_BEFORE_TAB ) && +0 <= last_space_in_indent)) need_fix_leading_space =1 ; }else if (ch ==' ' ) { last_space_in_indent = i;
@@ -325,7 +326,21 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule,break ; } -if (need_fix_leading_space) { +if ((ws_rule &WS_TAB_IN_INDENT ) && last_tab_in_indent >=0 ) { +/* Expand tabs into spaces */ +int last = last_tab_in_indent +1 ; +for (i =0 ; i < last; i++) { +if (src[i] =='\t' ) +do { +strbuf_addch (dst,' ' ); + }while (dst->len %8 ); +else +strbuf_addch (dst, src[i]); + } + len -= last; + src += last; + fixed =1 ; + }else if (need_fix_leading_space) {/* Process indent ourselves */ int consecutive_spaces =0 ;int last = last_tab_in_indent +1 ;
Signature · 1 line
1.7.0.3
PATCH 6/6 whitespace: tests for git-apply --whitespace=fix with tab-in-indent
In reply to Chris Webb--- t/t4124-apply-ws-rule.sh | 53 +++++++++++++++++++++++++-------------------- 1 files changed, 29 insertions(+), 24 deletions(-)
t/t4124-apply-ws-rule.sh +29 -24
@@ -11,21 +11,22 @@ prepare_test_file () { # ! trailing-space # @ space-before-tab # # indent-with-non-tab + # % tab-in-indent sed -e "s/_/ /g" -e "s/>/ /" <<-\EOF An_SP in an ordinary line>and a HT. - >A HT. - _>A SP and a HT (@). - _>_A SP, a HT and a SP (@). + >A HT (%). + _>A SP and a HT (@%). + _>_A SP, a HT and a SP (@%). _______Seven SP. ________Eight SP (#). - _______>Seven SP and a HT (@). - ________>Eight SP and a HT (@#). - _______>_Seven SP, a HT and a SP (@). - ________>_Eight SP, a HT and a SP (@#). + _______>Seven SP and a HT (@%). + ________>Eight SP and a HT (@#%). + _______>_Seven SP, a HT and a SP (@%). + ________>_Eight SP, a HT and a SP (@#%). _______________Fifteen SP (#). - _______________>Fifteen SP and a HT (@#). + _______________>Fifteen SP and a HT (@#%). ________________Sixteen SP (#). - ________________>Sixteen SP and a HT (@#). + ________________>Sixteen SP and a HT (@#%). _____a__Five SP, a non WS, two SP. A line with a (!) trailing SP_ A line with a (!) trailing HT>
@@ -39,7 +40,6 @@ apply_patch () { } test_fix () { - # fix should not barf apply_patch --whitespace=fix || return 1
@@ -130,20 +130,25 @@ do for i in - '' do case "$i" in '') ti='#' ;; *) ti= ;; esac - rule=${t}trailing,${s}space,${i}indent - - rm -f .gitattributes - test_expect_success "rule=$rule" ' - git config core.whitespace "$rule" && - test_fix "$tt$ts$ti" - ' - - test_expect_success "rule=$rule (attributes)" ' - git config --unset core.whitespace && - echo "target whitespace=$rule" >.gitattributes && - test_fix "$tt$ts$ti" - ' - + for h in - '' + do + [ -z "$h$i" ] && continue + case "$h" in '') th='%' ;; *) th= ;; esac + rule=${t}trailing,${s}space,${i}indent,${h}tab + + rm -f .gitattributes + test_expect_success "rule=$rule" ' + git config core.whitespace "$rule" && + test_fix "$tt$ts$ti$th" + ' + + test_expect_success "rule=$rule (attributes)" ' + git config --unset core.whitespace && + echo "target whitespace=$rule" >.gitattributes && + test_fix "$tt$ts$ti$th" + ' + + done done done done
Signature · 1 line
1.7.0.3