diff options
-rw-r--r-- | Documentation/config.txt | 2 | ||||
-rw-r--r-- | builtin/apply.c | 81 | ||||
-rw-r--r-- | cache.h | 3 | ||||
-rwxr-xr-x | t/t4015-diff-whitespace.sh | 42 | ||||
-rwxr-xr-x | t/t4124-apply-ws-rule.sh | 53 | ||||
-rw-r--r-- | ws.c | 66 |
6 files changed, 162 insertions, 85 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index 8f86050b05..6308026bf6 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -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 diff --git a/builtin/apply.c b/builtin/apply.c index 660cf92538..d56cabf5e2 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -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); @@ -1042,6 +1042,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; @@ -1050,7 +1051,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 */ diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 90f3342373..e92eab09cb 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -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 && diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index fb9ad247bf..8d46df6c3a 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -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 @@ -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 }, @@ -18,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) @@ -56,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; } @@ -82,7 +87,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)) { @@ -125,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); } @@ -163,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; @@ -175,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; } @@ -252,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 @@ -267,7 +286,6 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro int last_tab_in_indent = -1; int last_space_in_indent = -1; int need_fix_leading_space = 0; - char *buf; /* * Strip trailing whitespace @@ -307,7 +325,6 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro break; } - buf = dst; if (need_fix_leading_space) { /* Process indent ourselves */ int consecutive_spaces = 0; @@ -329,28 +346,41 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro char 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; + } else 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; } - 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; } |