diff options
-rw-r--r-- | Documentation/config.txt | 6 | ||||
-rw-r--r-- | builtin-apply.c | 61 | ||||
-rw-r--r-- | cache.h | 4 | ||||
-rw-r--r-- | diff.c | 155 | ||||
-rwxr-xr-x | t/t4015-diff-whitespace.sh | 11 | ||||
-rwxr-xr-x | t/t4019-diff-wserror.sh | 11 | ||||
-rwxr-xr-x | t/t4124-apply-ws-rule.sh | 91 | ||||
-rw-r--r-- | ws.c | 19 |
8 files changed, 288 insertions, 70 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index 2632c5149..273bbde70 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -401,13 +401,17 @@ core.whitespace:: consider them as errors. You can prefix `-` to disable any of them (e.g. `-trailing-space`): + -* `trailing-space` treats trailing whitespaces at the end of the line +* `blank-at-eol` treats trailing whitespaces at the end of the line as an error (enabled by default). * `space-before-tab` treats a space character that appears immediately before a tab character in the initial indent part of the line as an 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). +* `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 + `blank-at-eof`. * `cr-at-eol` treats a carriage-return at the end of line as part of the line terminator, i.e. with it, `trailing-space` does not trigger if the character before such a carriage-return diff --git a/builtin-apply.c b/builtin-apply.c index 39dc96ae0..82415d93c 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -131,6 +131,7 @@ struct fragment { const char *patch; int size; int rejected; + int linenr; struct fragment *next; }; @@ -1149,23 +1150,29 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc return -1; } -static void check_whitespace(const char *line, int len, unsigned ws_rule) +static void record_ws_error(unsigned result, const char *line, int len, int linenr) { char *err; - unsigned result = ws_check(line + 1, len - 1, ws_rule); + if (!result) return; whitespace_error++; if (squelch_whitespace_errors && squelch_whitespace_errors < whitespace_error) - ; - else { - err = whitespace_error_string(result); - fprintf(stderr, "%s:%d: %s.\n%.*s\n", - patch_input_file, linenr, err, len - 2, line + 1); - free(err); - } + return; + + err = whitespace_error_string(result); + fprintf(stderr, "%s:%d: %s.\n%.*s\n", + patch_input_file, linenr, err, len, line); + free(err); +} + +static void check_whitespace(const char *line, int len, unsigned ws_rule) +{ + unsigned result = ws_check(line + 1, len - 1, ws_rule); + + record_ws_error(result, line + 1, len - 2, linenr); } /* @@ -1281,6 +1288,7 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc int len; fragment = xcalloc(1, sizeof(*fragment)); + fragment->linenr = linenr; len = parse_fragment(line, size, patch, fragment); if (len <= 0) die("corrupt patch at line %d", linenr); @@ -2005,6 +2013,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, int len = linelen(patch, size); int plen, added; int added_blank_line = 0; + int is_blank_context = 0; if (!len) break; @@ -2037,8 +2046,12 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, *new++ = '\n'; add_line_info(&preimage, "\n", 1, LINE_COMMON); add_line_info(&postimage, "\n", 1, LINE_COMMON); + is_blank_context = 1; break; case ' ': + if (plen && (ws_rule & WS_BLANK_AT_EOF) && + ws_blank_line(patch + 1, plen, ws_rule)) + is_blank_context = 1; case '-': memcpy(old, patch + 1, plen); add_line_info(&preimage, old, plen, @@ -2065,7 +2078,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, (first == '+' ? 0 : LINE_COMMON)); new += added; if (first == '+' && - added == 1 && new[-1] == '\n') + (ws_rule & WS_BLANK_AT_EOF) && + ws_blank_line(patch + 1, plen, ws_rule)) added_blank_line = 1; break; case '@': case '\\': @@ -2078,6 +2092,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, } if (added_blank_line) new_blank_lines_at_end++; + else if (is_blank_context) + ; else new_blank_lines_at_end = 0; patch += len; @@ -2159,17 +2175,24 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, } if (applied_pos >= 0) { - if (ws_error_action == correct_ws_error && - new_blank_lines_at_end && - postimage.nr + applied_pos == img->nr) { + if (new_blank_lines_at_end && + preimage.nr + applied_pos == img->nr && + (ws_rule & WS_BLANK_AT_EOF) && + ws_error_action != nowarn_ws_error) { + record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr); + if (ws_error_action == correct_ws_error) { + while (new_blank_lines_at_end--) + remove_last_line(&postimage); + } /* - * If the patch application adds blank lines - * at the end, and if the patch applies at the - * end of the image, remove those added blank - * lines. + * We would want to prevent write_out_results() + * from taking place in apply_patch() that follows + * the callchain led us here, which is: + * apply_patch->check_patch_list->check_patch-> + * apply_data->apply_fragments->apply_one_fragment */ - while (new_blank_lines_at_end--) - remove_last_line(&postimage); + if (ws_error_action == die_on_ws_error) + apply = 0; } /* @@ -967,10 +967,12 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i * whitespace rules. * used by both diff and apply */ -#define WS_TRAILING_SPACE 01 +#define WS_BLANK_AT_EOL 01 #define WS_SPACE_BEFORE_TAB 02 #define WS_INDENT_WITH_NON_TAB 04 #define WS_CR_AT_EOL 010 +#define WS_BLANK_AT_EOF 020 +#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; extern unsigned whitespace_rule(const char *); @@ -532,8 +532,12 @@ static void diff_words_show(struct diff_words_data *diff_words) typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); struct emit_callback { - int nparents, color_diff; + int color_diff; unsigned ws_rule; + int blank_at_eof_in_preimage; + int blank_at_eof_in_postimage; + int lno_in_preimage; + int lno_in_postimage; sane_truncate_fn truncate; const char **label_path; struct diff_words_data *diff_words; @@ -586,6 +590,17 @@ static void emit_line(FILE *file, const char *set, const char *reset, const char fputc('\n', file); } +static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) +{ + if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && + ecbdata->blank_at_eof_in_preimage && + ecbdata->blank_at_eof_in_postimage && + ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage && + ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage)) + return 0; + return ws_blank_line(line + 1, len - 1, ecbdata->ws_rule); +} + static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) { const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); @@ -593,11 +608,13 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons if (!*ws) emit_line(ecbdata->file, set, reset, line, len); + else if (new_blank_line_at_eof(ecbdata, line, len)) + /* Blank line at EOF - paint '+' as well */ + emit_line(ecbdata->file, ws, reset, line, len); else { /* Emit just the prefix, then the rest. */ - emit_line(ecbdata->file, set, reset, line, ecbdata->nparents); - ws_check_emit(line + ecbdata->nparents, - len - ecbdata->nparents, ecbdata->ws_rule, + emit_line(ecbdata->file, set, reset, line, 1); + ws_check_emit(line + 1, len - 1, ecbdata->ws_rule, ecbdata->file, set, reset, ws); } } @@ -620,10 +637,23 @@ static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, u return allot - l; } +static void find_lno(const char *line, struct emit_callback *ecbdata) +{ + const char *p; + ecbdata->lno_in_preimage = 0; + ecbdata->lno_in_postimage = 0; + p = strchr(line, '-'); + if (!p) + return; /* cannot happen */ + ecbdata->lno_in_preimage = strtol(p + 1, NULL, 10); + p = strchr(p, '+'); + if (!p) + return; /* cannot happen */ + ecbdata->lno_in_postimage = strtol(p + 1, NULL, 10); +} + static void fn_out_consume(void *priv, char *line, unsigned long len) { - int i; - int color; struct emit_callback *ecbdata = priv; const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN); @@ -650,14 +680,9 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) len = 1; } - /* This is not really necessary for now because - * this codepath only deals with two-way diffs. - */ - for (i = 0; i < len && line[i] == '@'; i++) - ; - if (2 <= i && i < len && line[i] == ' ') { - ecbdata->nparents = i - 1; + if (line[0] == '@') { len = sane_truncate_line(ecbdata, line, len); + find_lno(line, ecbdata); emit_line(ecbdata->file, diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), reset, line, len); @@ -666,15 +691,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) return; } - if (len < ecbdata->nparents) { + if (len < 1) { emit_line(ecbdata->file, reset, reset, line, len); return; } - color = DIFF_PLAIN; - if (ecbdata->diff_words && ecbdata->nparents != 1) - /* fall back to normal diff */ - free_diff_words_data(ecbdata); if (ecbdata->diff_words) { if (line[0] == '-') { diff_words_append(line, len, @@ -693,20 +714,19 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) emit_line(ecbdata->file, plain, reset, line, len); return; } - for (i = 0; i < ecbdata->nparents && len; i++) { - if (line[i] == '-') - color = DIFF_FILE_OLD; - else if (line[i] == '+') - color = DIFF_FILE_NEW; - } - if (color != DIFF_FILE_NEW) { - emit_line(ecbdata->file, - diff_get_color(ecbdata->color_diff, color), - reset, line, len); - return; + if (line[0] != '+') { + const char *color = + diff_get_color(ecbdata->color_diff, + line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN); + ecbdata->lno_in_preimage++; + if (line[0] == ' ') + ecbdata->lno_in_postimage++; + emit_line(ecbdata->file, color, reset, line, len); + } else { + ecbdata->lno_in_postimage++; + emit_add_line(reset, ecbdata, line, len); } - emit_add_line(reset, ecbdata, line, len); } static char *pprint_rename(const char *a, const char *b) @@ -1211,7 +1231,6 @@ struct checkdiff_t { struct diff_options *o; unsigned ws_rule; unsigned status; - int trailing_blanks_start; }; static int is_conflict_marker(const char *line, unsigned long len) @@ -1255,10 +1274,6 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) if (line[0] == '+') { unsigned bad; data->lineno++; - if (!ws_blank_line(line + 1, len - 1, data->ws_rule)) - data->trailing_blanks_start = 0; - else if (!data->trailing_blanks_start) - data->trailing_blanks_start = data->lineno; if (is_conflict_marker(line + 1, len - 1)) { data->status |= 1; fprintf(data->o->file, @@ -1278,14 +1293,12 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) data->o->file, set, reset, ws); } else if (line[0] == ' ') { data->lineno++; - data->trailing_blanks_start = 0; } else if (line[0] == '@') { char *plus = strchr(line, '+'); if (plus) data->lineno = strtol(plus, NULL, 10) - 1; else die("invalid diff"); - data->trailing_blanks_start = 0; } } @@ -1437,6 +1450,51 @@ static const char *get_textconv(struct diff_filespec *one) return one->driver->textconv; } +static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule) +{ + char *ptr = mf->ptr; + long size = mf->size; + int cnt = 0; + + if (!size) + return cnt; + ptr += size - 1; /* pointing at the very end */ + if (*ptr != '\n') + ; /* incomplete line */ + else + ptr--; /* skip the last LF */ + while (mf->ptr < ptr) { + char *prev_eol; + for (prev_eol = ptr; mf->ptr <= prev_eol; prev_eol--) + if (*prev_eol == '\n') + break; + if (!ws_blank_line(prev_eol + 1, ptr - prev_eol, ws_rule)) + break; + cnt++; + ptr = prev_eol - 1; + } + return cnt; +} + +static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, + struct emit_callback *ecbdata) +{ + int l1, l2, at; + unsigned ws_rule = ecbdata->ws_rule; + l1 = count_trailing_blank(mf1, ws_rule); + l2 = count_trailing_blank(mf2, ws_rule); + if (l2 <= l1) { + ecbdata->blank_at_eof_in_preimage = 0; + ecbdata->blank_at_eof_in_postimage = 0; + return; + } + at = count_lines(mf1->ptr, mf1->size); + ecbdata->blank_at_eof_in_preimage = (at - l1) + 1; + + at = count_lines(mf2->ptr, mf2->size); + ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; +} + static void builtin_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -1562,6 +1620,8 @@ static void builtin_diff(const char *name_a, ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF); ecbdata.found_changesp = &o->found_changes; ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + if (ecbdata.ws_rule & WS_BLANK_AT_EOF) + check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.file = o->file; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; @@ -1704,11 +1764,22 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, &xpp, &xecfg, &ecb); - if ((data.ws_rule & WS_TRAILING_SPACE) && - data.trailing_blanks_start) { - fprintf(o->file, "%s:%d: ends with blank lines.\n", - data.filename, data.trailing_blanks_start); - data.status = 1; /* report errors */ + if (data.ws_rule & WS_BLANK_AT_EOF) { + struct emit_callback ecbdata; + int blank_at_eof; + + ecbdata.ws_rule = data.ws_rule; + check_blank_at_eof(&mf1, &mf2, &ecbdata); + blank_at_eof = ecbdata.blank_at_eof_in_preimage; + + if (blank_at_eof) { + static char *err; + if (!err) + err = whitespace_error_string(WS_BLANK_AT_EOF); + fprintf(o->file, "%s:%d: %s.\n", + data.filename, blank_at_eof, err); + data.status = 1; /* report errors */ + } } } free_and_return: diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 6d13da30d..8dd147d78 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -362,10 +362,17 @@ test_expect_success 'line numbers in --check output are correct' ' ' -test_expect_success 'checkdiff detects trailing blank lines' ' +test_expect_success 'checkdiff detects new trailing blank lines (1)' ' echo "foo();" >x && echo "" >>x && - git diff --check | grep "ends with blank" + git diff --check | grep "new blank line" +' + +test_expect_success 'checkdiff detects new trailing blank lines (2)' ' + { echo a; echo b; echo; echo; } >x && + git add x && + { echo a; echo; echo; echo; echo; } >x && + git diff --check | grep "new blank line" ' test_expect_success 'checkdiff allows new blank lines' ' diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 84a1fe311..3a3663fbc 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -165,7 +165,7 @@ test_expect_success 'trailing empty lines (1)' ' rm -f .gitattributes && test_must_fail git diff --check >output && - grep "ends with blank lines." output && + grep "new blank line at" output && grep "trailing whitespace" output ' @@ -190,4 +190,13 @@ test_expect_success 'do not color trailing cr in context' ' ' +test_expect_success 'color new trailing blank lines' ' + { echo a; echo b; echo; echo; } >x && + git add x && + { echo a; echo; echo; echo; echo c; echo; echo; echo; echo; } >x && + git diff --color x >output && + cnt=$(grep "${blue_grep}" output | wc -l) && + test $cnt = 2 +' + test_done diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index fac2093d7..ca2639759 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -170,4 +170,95 @@ test_expect_success 'trailing whitespace & no newline at the end of file' ' grep "^$" target ' +test_expect_success 'blank at EOF with --whitespace=fix (1)' ' + : these can fail depending on what we did before + git config --unset core.whitespace + rm -f .gitattributes + + { echo a; echo b; echo c; } >one && + git add one && + { echo a; echo b; echo c; } >expect && + { cat expect; echo; } >one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + +test_expect_success 'blank at EOF with --whitespace=fix (2)' ' + { echo a; echo b; echo c; } >one && + git add one && + { echo a; echo c; } >expect && + { cat expect; echo; echo; } >one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + +test_expect_success 'blank at EOF with --whitespace=fix (3)' ' + { echo a; echo b; echo; } >one && + git add one && + { echo a; echo c; echo; } >expect && + { cat expect; echo; echo; } >one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + +test_expect_success 'blank at end of hunk, not at EOF with --whitespace=fix' ' + { echo a; echo b; echo; echo; echo; echo; echo; echo d; } >one && + git add one && + { echo a; echo c; echo; echo; echo; echo; echo; echo; echo d; } >expect && + cp expect one && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=fix patch && + test_cmp expect one +' + +test_expect_success 'blank at EOF with --whitespace=warn' ' + { echo a; echo b; echo c; } >one && + git add one && + echo >>one && + cat one >expect && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=warn patch 2>error && + test_cmp expect one && + grep "new blank line at EOF" error +' + +test_expect_success 'blank at EOF with --whitespace=error' ' + { echo a; echo b; echo c; } >one && + git add one && + cat one >expect && + echo >>one && + git diff -- one >patch && + + git checkout one && + test_must_fail git apply --whitespace=error patch 2>error && + test_cmp expect one && + grep "new blank line at EOF" error +' + +test_expect_success 'blank but not empty at EOF' ' + { echo a; echo b; echo c; } >one && + git add one && + echo " " >>one && + cat one >expect && + git diff -- one >patch && + + git checkout one && + git apply --whitespace=warn patch 2>error && + test_cmp expect one && + grep "new blank line at EOF" error +' + test_done @@ -16,6 +16,8 @@ static struct whitespace_rule { { "space-before-tab", WS_SPACE_BEFORE_TAB, 0 }, { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB, 0 }, { "cr-at-eol", WS_CR_AT_EOL, 1 }, + { "blank-at-eol", WS_BLANK_AT_EOL, 0 }, + { "blank-at-eof", WS_BLANK_AT_EOF, 0 }, }; unsigned parse_whitespace_rule(const char *string) @@ -102,8 +104,17 @@ unsigned whitespace_rule(const char *pathname) char *whitespace_error_string(unsigned ws) { struct strbuf err = STRBUF_INIT; - if (ws & WS_TRAILING_SPACE) + if ((ws & WS_TRAILING_SPACE) == WS_TRAILING_SPACE) strbuf_addstr(&err, "trailing whitespace"); + else { + if (ws & WS_BLANK_AT_EOL) + strbuf_addstr(&err, "trailing whitespace"); + if (ws & WS_BLANK_AT_EOF) { + if (err.len) + strbuf_addstr(&err, ", "); + strbuf_addstr(&err, "new blank line at EOF"); + } + } if (ws & WS_SPACE_BEFORE_TAB) { if (err.len) strbuf_addstr(&err, ", "); @@ -141,11 +152,11 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule, } /* Check for trailing whitespace. */ - if (ws_rule & WS_TRAILING_SPACE) { + if (ws_rule & WS_BLANK_AT_EOL) { for (i = len - 1; i >= 0; i--) { if (isspace(line[i])) { trailing_whitespace = i; - result |= WS_TRAILING_SPACE; + result |= WS_BLANK_AT_EOL; } else break; @@ -261,7 +272,7 @@ int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *erro /* * Strip trailing whitespace */ - if (ws_rule & WS_TRAILING_SPACE) { + if (ws_rule & WS_BLANK_AT_EOL) { if (0 < len && src[len - 1] == '\n') { add_nl_to_tail = 1; len--; |