From ef2035c5e55f852905b012dfb2dd242b4f77da70 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Sep 2009 01:41:47 -0700 Subject: apply --whitespace=fix: fix handling of blank lines at the eof b94f2ed (builtin-apply.c: make it more line oriented, 2008-01-26) broke the logic used to detect if a hunk adds blank lines at the end of the file. With the new code after that commit: - img holds the contents of the file that the hunk is being applied to; - preimage has the lines the hunk expects to be in img; and - postimage has the lines the hunk wants to update the part in img that corresponds to preimage with. and we need to compare if the last line of preimage (not postimage) matches the last line of img to see if the hunk applies at the end of the file. Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 7a1ff04..5b5bde4 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2069,7 +2069,7 @@ 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) { + preimage.nr + applied_pos == img->nr) { /* * If the patch application adds blank lines * at the end, and if the patch applies at the diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index f83322e..6898722 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -148,4 +148,33 @@ do done done + +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_done -- cgit v0.10.2-6-g49f6 From efa574438fca4ed328d2e47ecdb6363c50a905ec Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 14:08:20 -0700 Subject: apply --whitespace=fix: detect new blank lines at eof correctly The command tries to strip blank lines at the end of the file added by a patch. It is done by first detecting if a hunk in patch has additional blank lines at the end of itself, and if so checking if such a hunk applies at the end of file. This patch addresses a bug in the logic to implement the former (the previous one addressed a bug in the latter). If the original ends with blank lines, often the patch hunk ends like this: @@ -l,5 +m,7 @@$ _context$ _context$ -deleted$ +$ +$ +$ _$ _$ where _ stands for SP and $ shows a end-of-line. This example patch adds three trailing blank lines, but the code fails to notice it, because it only pays attention to added blank lines at the very end of the hunk. In this example, the three added blank lines do not appear textually at the end in the patch, even though you can see that they are indeed added at the end, if you rearrange the diff like this: @@ -l,5 +m,7 @@$ _context$ _context$ -deleted$ _$ _$ +$ +$ +$ The fix is not to reset the number of (candidate) added blank lines at the end when the loop sees a context line that is empty. Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 5b5bde4..c5e4048 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1913,6 +1913,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; @@ -1945,8 +1946,11 @@ 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 && patch[1] == '\n') + is_blank_context = 1; case '-': memcpy(old, patch + 1, plen); add_line_info(&preimage, old, plen, @@ -1986,6 +1990,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; diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index 6898722..fedc8b9 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -177,4 +177,28 @@ test_expect_success 'blank at EOF with --whitespace=fix (2)' ' 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_done -- cgit v0.10.2-6-g49f6 From 92a1747eeab6bc497db748e79ca4f029b1c1dc10 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 22:26:33 -0700 Subject: apply.c: split check_whitespace() into two This splits the logic to record the presence of whitespace errors out of the check_whitespace() function, which checks and then records. The new function, record_ws_error(), can be used by the blank-at-eof check that does not use ws_check() logic to report its findings in the same output format. Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index c5e4048..80ddf55 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1055,23 +1055,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); } /* -- cgit v0.10.2-6-g49f6 From 77b15bbd88e2f48de093ff0e60de6dbc11e3329e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 16:02:32 -0700 Subject: apply --whitespace=warn/error: diagnose blank at EOF "git apply" strips new blank lines at EOF under --whitespace=fix option, but neigher --whitespace=warn nor --whitespace=error paid any attention to these errors. Introduce a new whitespace error class, blank-at-eof, to make the whitespace error handling more consistent. The patch adds a new "linenr" field to the struct fragment in order to record which line the hunk started in the input file, but this is needed solely for reporting purposes. The detection of this class of whitespace errors cannot be done while parsing a patch like we do for all the other classes of whitespace errors. It instead has to wait until we find where to apply the hunk, but at that point, we do not have an access to the original line number in the input file anymore, hence the new field. Depending on your point of view, this may be a bugfix that makes warn and error in line with fix. Or you could call it a new feature. The line between them is somewhat fuzzy in this case. Strictly speaking, triggering more errors than before is a change in behaviour that is not backward compatible, even though the reason for the change is because the code was not checking for an error that it should have. People who do not want added blank lines at EOF to trigger an error can disable the new error class. Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index 113d9d1..871384e 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -389,6 +389,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). +* `blank-at-eof` treats blank lines added at the end of file as an error + (enabled by default). * `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 80ddf55..37d3bc0 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -126,6 +126,7 @@ struct fragment { const char *patch; int size; int rejected; + int linenr; struct fragment *next; }; @@ -1193,6 +1194,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); @@ -2079,17 +2081,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 && - preimage.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; } /* diff --git a/cache.h b/cache.h index 099a32e..7152fea 100644 --- a/cache.h +++ b/cache.h @@ -845,7 +845,8 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i #define WS_SPACE_BEFORE_TAB 02 #define WS_INDENT_WITH_NON_TAB 04 #define WS_CR_AT_EOL 010 -#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB) +#define WS_BLANK_AT_EOF 020 +#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|WS_BLANK_AT_EOF) extern unsigned whitespace_rule_cfg; extern unsigned whitespace_rule(const char *); extern unsigned parse_whitespace_rule(const char *); diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index fedc8b9..3a77a9a 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -201,4 +201,30 @@ test_expect_success 'blank at end of hunk, not at EOF with --whitespace=fix' ' 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_done diff --git a/ws.c b/ws.c index 7a7ff13..d56636b 100644 --- a/ws.c +++ b/ws.c @@ -15,6 +15,7 @@ static struct whitespace_rule { { "space-before-tab", WS_SPACE_BEFORE_TAB }, { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB }, { "cr-at-eol", WS_CR_AT_EOL }, + { "blank-at-eof", WS_BLANK_AT_EOF }, }; unsigned parse_whitespace_rule(const char *string) @@ -113,6 +114,11 @@ char *whitespace_error_string(unsigned ws) strbuf_addstr(&err, ", "); strbuf_addstr(&err, "indent with spaces"); } + if (ws & WS_BLANK_AT_EOF) { + if (err.len) + strbuf_addstr(&err, ", "); + strbuf_addstr(&err, "new blank line at EOF"); + } return strbuf_detach(&err, NULL); } -- cgit v0.10.2-6-g49f6 From 94ea026b358f9fd5395d9344a80f2d5a50e93e1f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Sep 2009 02:25:57 -0700 Subject: apply --whitespace: warn blank but not necessarily empty lines at EOF The whitespace error of adding blank lines at the end of file should trigger if you added a non-empty line at the end, if the contents of the line is full of whitespaces. Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 37d3bc0..6662cc4 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1957,7 +1957,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, is_blank_context = 1; break; case ' ': - if (plen && patch[1] == '\n') + 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); @@ -1985,7 +1986,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 '\\': diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index 3a77a9a..778d45b 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -227,4 +227,17 @@ test_expect_success 'blank at EOF with --whitespace=error' ' 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 -- cgit v0.10.2-6-g49f6 From b8d9c1a66b99ad3ca8069add010dafdd1bc6cab8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 23:59:25 -0700 Subject: diff.c: the builtin_diff() deals with only two-file comparison The combined diff is implemented in combine_diff() and fn_out_consume() codepath never has to deal with anything but two-file comparision. Drop nparents from the emit_callback structure and simplify the code. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 6fea3c0..1eddd59 100644 --- a/diff.c +++ b/diff.c @@ -489,7 +489,7 @@ typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); struct emit_callback { struct xdiff_emit_state xm; - int nparents, color_diff; + int color_diff; unsigned ws_rule; sane_truncate_fn truncate; const char **label_path; @@ -549,9 +549,8 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons emit_line(ecbdata->file, set, 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); } } @@ -576,7 +575,6 @@ static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, u 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); @@ -598,13 +596,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; } - /* 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); emit_line(ecbdata->file, diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), @@ -614,15 +606,12 @@ 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, @@ -641,13 +630,10 @@ 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 (line[0] == '-') + color = DIFF_FILE_OLD; + else if (line[0] == '+') + color = DIFF_FILE_NEW; if (color != DIFF_FILE_NEW) { emit_line(ecbdata->file, diff_get_color(ecbdata->color_diff, color), -- cgit v0.10.2-6-g49f6 From 5b5061efd88e1d113a4484369dfab654b43364de Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 22:30:27 -0700 Subject: diff --whitespace=warn/error: obey blank-at-eof The "diff --check" code used to conflate trailing-space whitespace error class with this, but now we have a proper separate error class, we should check it under blank-at-eof, not trailing-space. The whitespace error is not about _having_ blank lines at end, but about adding _new_ blank lines. To keep the message consistent with what is given by "git apply", call whitespace_error_string() to generate it, instead of using a hardcoded custom message. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 1eddd59..a693d18 100644 --- a/diff.c +++ b/diff.c @@ -1650,10 +1650,14 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, ecb.priv = &data; xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); - if ((data.ws_rule & WS_TRAILING_SPACE) && + if ((data.ws_rule & WS_BLANK_AT_EOF) && data.trailing_blanks_start) { - fprintf(o->file, "%s:%d: ends with blank lines.\n", - data.filename, data.trailing_blanks_start); + static char *err; + + if (!err) + err = whitespace_error_string(WS_BLANK_AT_EOF); + fprintf(o->file, "%s:%d: %s\n", + data.filename, data.trailing_blanks_start, err); data.status = 1; /* report errors */ } } diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index b1cbd36..a5d4461 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -335,10 +335,10 @@ 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 allows new blank lines' ' diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 84a1fe3..1517fff 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 ' -- cgit v0.10.2-6-g49f6 From 467babf8d059caee9587567452fc8b46505b4e67 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 3 Sep 2009 23:39:43 -0700 Subject: diff --whitespace=warn/error: fix blank-at-eof check The "diff --check" logic used to share the same issue as the one fixed for "git apply" earlier in this series, in that a patch that adds new blank lines at end could appear as @@ -l,5 +m,7 @@$ _context$ _context$ -deleted$ +$ +$ +$ _$ _$ where _ stands for SP and $ shows a end-of-line. Instead of looking at each line in the patch in the callback, simply count the blank lines from the end in two versions, and notice the presence of new ones. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index a693d18..c19c476 100644 --- a/diff.c +++ b/diff.c @@ -1149,7 +1149,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) @@ -1193,10 +1192,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, @@ -1216,14 +1211,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 +1430,44 @@ static const struct funcname_pattern_entry *diff_funcname_pattern(struct diff_fi return NULL; } +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 int adds_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, unsigned ws_rule) +{ + int l1, l2, at; + l1 = count_trailing_blank(mf1, ws_rule); + l2 = count_trailing_blank(mf2, ws_rule); + if (l2 <= l1) + return 0; + /* starting where? */ + at = count_lines(mf1->ptr, mf1->size); + return (at - l1) + 1; /* the line number counts from 1 */ +} + static void builtin_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -1650,15 +1681,16 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, ecb.priv = &data; xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); - if ((data.ws_rule & WS_BLANK_AT_EOF) && - data.trailing_blanks_start) { - static char *err; - - if (!err) - err = whitespace_error_string(WS_BLANK_AT_EOF); - fprintf(o->file, "%s:%d: %s\n", - data.filename, data.trailing_blanks_start, err); - data.status = 1; /* report errors */ + if (data.ws_rule & WS_BLANK_AT_EOF) { + int blank_at_eof = adds_blank_at_eof(&mf1, &mf2, data.ws_rule); + 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 a5d4461..e0b481d 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -341,6 +341,13 @@ test_expect_success 'checkdiff detects new trailing blank lines (1)' ' 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' ' git checkout x && mv x y && -- cgit v0.10.2-6-g49f6 From 690ed8436326484fe7e3f4deac4cffd780c7d630 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Sep 2009 00:41:15 -0700 Subject: diff --color: color blank-at-eof Since the coloring logic processed the patch output one line at a time, we couldn't easily color code the new blank lines at the end of file. Reuse the adds_blank_at_eof() function to find where the runs of such blank lines start, keep track of the line number in the preimage while processing the patch output one line at a time, and paint the new blank lines that appear after that line to implement this. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index c19c476..2b285b8 100644 --- a/diff.c +++ b/diff.c @@ -491,6 +491,8 @@ struct emit_callback { struct xdiff_emit_state xm; int color_diff; unsigned ws_rule; + int blank_at_eof; + int lno_in_preimage; sane_truncate_fn truncate; const char **label_path; struct diff_words_data *diff_words; @@ -547,6 +549,12 @@ 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 ((ecbdata->ws_rule & WS_BLANK_AT_EOF) && + ecbdata->blank_at_eof && + (ecbdata->blank_at_eof <= ecbdata->lno_in_preimage) && + ws_blank_line(line + 1, len - 1, ecbdata->ws_rule)) + /* Blank line at EOF */ + emit_line(ecbdata->file, ws, reset, line, len); else { /* Emit just the prefix, then the rest. */ emit_line(ecbdata->file, set, reset, line, 1); @@ -573,9 +581,16 @@ static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, u return allot - l; } +static int find_preimage_lno(const char *line) +{ + char *p = strchr(line, '-'); + if (!p) + return 0; /* should not happen */ + return strtol(p+1, NULL, 10); +} + static void fn_out_consume(void *priv, char *line, unsigned long len) { - 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); @@ -598,6 +613,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) if (line[0] == '@') { len = sane_truncate_line(ecbdata, line, len); + ecbdata->lno_in_preimage = find_preimage_lno(line); emit_line(ecbdata->file, diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), reset, line, len); @@ -611,7 +627,6 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) return; } - color = DIFF_PLAIN; if (ecbdata->diff_words) { if (line[0] == '-') { diff_words_append(line, len, @@ -630,14 +645,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) emit_line(ecbdata->file, plain, reset, line, len); return; } - if (line[0] == '-') - color = DIFF_FILE_OLD; - else if (line[0] == '+') - color = DIFF_FILE_NEW; - if (color != DIFF_FILE_NEW) { - emit_line(ecbdata->file, - diff_get_color(ecbdata->color_diff, color), - reset, line, len); + + if (line[0] != '+') { + const char *color = + diff_get_color(ecbdata->color_diff, + line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN); + ecbdata->lno_in_preimage++; + emit_line(ecbdata->file, color, reset, line, len); return; } emit_add_line(reset, ecbdata, line, len); @@ -1557,6 +1571,9 @@ 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) + ecbdata.blank_at_eof = + adds_blank_at_eof(&mf1, &mf2, ecbdata.ws_rule); ecbdata.file = o->file; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 1517fff..1e75f1a 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -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; } >x && + git diff --color x >output && + cnt=$(grep "${blue_grep}" output | wc -l) && + test $cnt = 2 +' + test_done -- cgit v0.10.2-6-g49f6 From aeb84b05ae448596c336807631d9633492b3049a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 5 Sep 2009 22:21:17 -0700 Subject: core.whitespace: split trailing-space into blank-at-{eol,eof} People who configured trailing-space depended on it to catch both extra white space at the end of line, and extra blank lines at the end of file. Earlier attempt to introduce only blank-at-eof gave them an escape hatch to keep the old behaviour, but it is a regression until they explicitly specify the new error class. This introduces a blank-at-eol that only catches extra white space at the end of line, and makes the traditional trailing-space a convenient synonym to catch both blank-at-eol and blank-at-eof. This way, people who used trailing-space continue to catch both classes of errors. Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index 871384e..2a2f7fc 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -382,7 +382,7 @@ 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 @@ -391,6 +391,8 @@ core.whitespace:: 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/cache.h b/cache.h index 7152fea..ee12e74 100644 --- a/cache.h +++ b/cache.h @@ -841,12 +841,13 @@ 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_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|WS_BLANK_AT_EOF) +#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 *); extern unsigned parse_whitespace_rule(const char *); diff --git a/ws.c b/ws.c index d56636b..cd03bc0 100644 --- a/ws.c +++ b/ws.c @@ -15,6 +15,7 @@ static struct whitespace_rule { { "space-before-tab", WS_SPACE_BEFORE_TAB }, { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB }, { "cr-at-eol", WS_CR_AT_EOL }, + { "blank-at-eol", WS_BLANK_AT_EOL }, { "blank-at-eof", WS_BLANK_AT_EOF }, }; @@ -101,9 +102,19 @@ unsigned whitespace_rule(const char *pathname) char *whitespace_error_string(unsigned ws) { struct strbuf err; + strbuf_init(&err, 0); - 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, ", "); @@ -114,11 +125,6 @@ char *whitespace_error_string(unsigned ws) strbuf_addstr(&err, ", "); strbuf_addstr(&err, "indent with spaces"); } - if (ws & WS_BLANK_AT_EOF) { - if (err.len) - strbuf_addstr(&err, ", "); - strbuf_addstr(&err, "new blank line at EOF"); - } return strbuf_detach(&err, NULL); } @@ -146,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; @@ -266,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) && (2 <= len && isspace(src[len-2]))) { if (src[len - 1] == '\n') { add_nl_to_tail = 1; -- cgit v0.10.2-6-g49f6 From d68fe26f3e03b230ac9bbbcf002a9acdc4bebde9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 22:05:57 -0700 Subject: diff --whitespace: fix blank lines at end The earlier logic tried to colour any and all blank lines that were added beyond the last blank line in the original, but this was very wrong. If you added 96 blank lines, a non-blank line, and then 3 blank lines at the end, only the last 3 lines should trigger the error, not the earlier 96 blank lines. We need to also make sure that the lines are after the last non-blank line in the postimage as well before deciding to paint them. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 2b285b8..63a3bfc 100644 --- a/diff.c +++ b/diff.c @@ -491,8 +491,10 @@ struct emit_callback { struct xdiff_emit_state xm; int color_diff; unsigned ws_rule; - int blank_at_eof; + 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; @@ -542,6 +544,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); @@ -549,11 +562,8 @@ 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 ((ecbdata->ws_rule & WS_BLANK_AT_EOF) && - ecbdata->blank_at_eof && - (ecbdata->blank_at_eof <= ecbdata->lno_in_preimage) && - ws_blank_line(line + 1, len - 1, ecbdata->ws_rule)) - /* Blank line at EOF */ + 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. */ @@ -581,12 +591,19 @@ static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, u return allot - l; } -static int find_preimage_lno(const char *line) +static void find_lno(const char *line, struct emit_callback *ecbdata) { - char *p = strchr(line, '-'); + const char *p; + ecbdata->lno_in_preimage = 0; + ecbdata->lno_in_postimage = 0; + p = strchr(line, '-'); if (!p) - return 0; /* should not happen */ - return strtol(p+1, NULL, 10); + 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) @@ -613,7 +630,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) if (line[0] == '@') { len = sane_truncate_line(ecbdata, line, len); - ecbdata->lno_in_preimage = find_preimage_lno(line); + find_lno(line, ecbdata); emit_line(ecbdata->file, diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), reset, line, len); @@ -651,10 +668,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) 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); - return; + } 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) @@ -1470,16 +1490,23 @@ static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule) return cnt; } -static int adds_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, unsigned ws_rule) +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) - return 0; - /* starting where? */ + 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); - return (at - l1) + 1; /* the line number counts from 1 */ + 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, @@ -1572,8 +1599,7 @@ static void builtin_diff(const char *name_a, 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) - ecbdata.blank_at_eof = - adds_blank_at_eof(&mf1, &mf2, ecbdata.ws_rule); + check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.file = o->file; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; @@ -1699,7 +1725,13 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); if (data.ws_rule & WS_BLANK_AT_EOF) { - int blank_at_eof = adds_blank_at_eof(&mf1, &mf2, data.ws_rule); + 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) diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 1e75f1a..3a3663f 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -193,7 +193,7 @@ 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; } >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 -- cgit v0.10.2-6-g49f6 From 6957eb9a39cc765862e125edeef0dd70f359cff1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 18:44:01 -0700 Subject: diff.c: shuffling code around Move function, type, and structure definitions for fill_mmfile(), count_trailing_blank(), check_blank_at_eof(), emit_line(), new_blank_line_at_eof(), emit_add_line(), sane_truncate_fn, and emit_callback up in the file, so that they can be refactored into helper functions and reused by codepath for emitting rewrite patches. This only moves the lines around to make the next two patches easier to read. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 63a3bfc..7548966 100644 --- a/diff.c +++ b/diff.c @@ -241,6 +241,23 @@ static struct diff_tempfile { char tmp_path[PATH_MAX]; } diff_temp[2]; +typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); + +struct emit_callback { + struct xdiff_emit_state xm; + 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; + int *found_changesp; + FILE *file; +}; + static int count_lines(const char *data, int size) { int count, ch, completely_empty = 1, nl_just_seen = 0; @@ -301,6 +318,114 @@ static void copy_file_with_prefix(FILE *file, fprintf(file, "%s\n\\ No newline at end of file\n", reset); } +static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) +{ + if (!DIFF_FILE_VALID(one)) { + mf->ptr = (char *)""; /* does not matter */ + mf->size = 0; + return 0; + } + else if (diff_populate_filespec(one, 0)) + return -1; + mf->ptr = one->data; + mf->size = one->size; + return 0; +} + +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 emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) +{ + int has_trailing_newline, has_trailing_carriage_return; + + has_trailing_newline = (len > 0 && line[len-1] == '\n'); + if (has_trailing_newline) + len--; + has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); + if (has_trailing_carriage_return) + len--; + + fputs(set, file); + fwrite(line, len, 1, file); + fputs(reset, file); + if (has_trailing_carriage_return) + fputc('\r', file); + if (has_trailing_newline) + 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); + const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); + + 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, 1); + ws_check_emit(line + 1, len - 1, ecbdata->ws_rule, + ecbdata->file, set, reset, ws); + } +} + static void emit_rewrite_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -345,20 +470,6 @@ static void emit_rewrite_diff(const char *name_a, copy_file_with_prefix(o->file, '+', two->data, two->size, new, reset); } -static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) -{ - if (!DIFF_FILE_VALID(one)) { - mf->ptr = (char *)""; /* does not matter */ - mf->size = 0; - return 0; - } - else if (diff_populate_filespec(one, 0)) - return -1; - mf->ptr = one->data; - mf->size = one->size; - return 0; -} - struct diff_words_buffer { mmfile_t text; long alloc; @@ -485,23 +596,6 @@ 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 { - struct xdiff_emit_state xm; - 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; - int *found_changesp; - FILE *file; -}; - static void free_diff_words_data(struct emit_callback *ecbdata) { if (ecbdata->diff_words) { @@ -524,55 +618,6 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) return ""; } -static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) -{ - int has_trailing_newline, has_trailing_carriage_return; - - has_trailing_newline = (len > 0 && line[len-1] == '\n'); - if (has_trailing_newline) - len--; - has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); - if (has_trailing_carriage_return) - len--; - - fputs(set, file); - fwrite(line, len, 1, file); - fputs(reset, file); - if (has_trailing_carriage_return) - fputc('\r', file); - if (has_trailing_newline) - 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); - const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); - - 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, 1); - ws_check_emit(line + 1, len - 1, ecbdata->ws_rule, - ecbdata->file, set, reset, ws); - } -} - static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len) { const char *cp; @@ -1464,51 +1509,6 @@ static const struct funcname_pattern_entry *diff_funcname_pattern(struct diff_fi return NULL; } -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, -- cgit v0.10.2-6-g49f6 From 250f79930d84af54dce15320914ea911d58dd8ca Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 18:44:01 -0700 Subject: diff.c: split emit_line() from the first char and the rest of the line A new helper function emit_line_0() takes the first line of diff output (typically "-", " ", or "+") separately from the remainder of the line. No other functional changes. This change will make it easier to reuse the logic when emitting the rewrite diff, as we do not want to copy a line only to add "+"/"-"/" " immediately before its first character when we produce rewrite diff output. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 7548966..e4aaebf 100644 --- a/diff.c +++ b/diff.c @@ -377,18 +377,31 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2, ecbdata->blank_at_eof_in_postimage = (at - l2) + 1; } -static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) +static void emit_line_0(FILE *file, const char *set, const char *reset, + int first, const char *line, int len) { int has_trailing_newline, has_trailing_carriage_return; + int nofirst; - has_trailing_newline = (len > 0 && line[len-1] == '\n'); - if (has_trailing_newline) - len--; - has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); - if (has_trailing_carriage_return) - len--; + if (len == 0) { + has_trailing_newline = (first == '\n'); + has_trailing_carriage_return = (!has_trailing_newline && + (first == '\r')); + nofirst = has_trailing_newline || has_trailing_carriage_return; + } else { + has_trailing_newline = (len > 0 && line[len-1] == '\n'); + if (has_trailing_newline) + len--; + has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); + if (has_trailing_carriage_return) + len--; + nofirst = 0; + } fputs(set, file); + + if (!nofirst) + fputc(first, file); fwrite(line, len, 1, file); fputs(reset, file); if (has_trailing_carriage_return) @@ -397,6 +410,12 @@ static void emit_line(FILE *file, const char *set, const char *reset, const char fputc('\n', file); } +static void emit_line(FILE *file, const char *set, const char *reset, + const char *line, int len) +{ + emit_line_0(file, set, reset, line[0], line+1, len-1); +} + static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len) { if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) && -- cgit v0.10.2-6-g49f6 From 018cff70462eb59779c96a383531c4440fb35b9c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 18:44:01 -0700 Subject: diff.c: emit_add_line() takes only the rest of the line As the first character on the line that is fed to this function is always "+", it is pointless to send that along with the rest of the line. This change will make it easier to reuse the logic when emitting the rewrite diff, as we do not want to copy a line only to add "+"/"-"/" " immediately before its first character when we produce rewrite diff output. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index e4aaebf..07cf043 100644 --- a/diff.c +++ b/diff.c @@ -424,23 +424,25 @@ static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line 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); + return ws_blank_line(line, len, ecbdata->ws_rule); } -static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) +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); const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); if (!*ws) - emit_line(ecbdata->file, set, reset, line, len); + emit_line_0(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); + emit_line_0(ecbdata->file, ws, reset, '+', line, len); else { /* Emit just the prefix, then the rest. */ - emit_line(ecbdata->file, set, reset, line, 1); - ws_check_emit(line + 1, len - 1, ecbdata->ws_rule, + emit_line_0(ecbdata->file, set, reset, '+', "", 0); + ws_check_emit(line, len, ecbdata->ws_rule, ecbdata->file, set, reset, ws); } } @@ -737,7 +739,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) 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 + 1, len - 1); } } -- cgit v0.10.2-6-g49f6 From 7f7ee2ff2dfbb8435a4b46750f573ef0f7d0b853 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Sep 2009 18:44:01 -0700 Subject: diff -B: colour whitespace errors We used to send the old and new contents more or less straight out to the output with only the original "old is red, new is green" colouring. Now all the necessary support routines have been prepared, call them with a line of data at a time from the output code and have them check and color whitespace errors in exactly the same way as they are called from the low level diff callback routines. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 07cf043..8414478 100644 --- a/diff.c +++ b/diff.c @@ -296,28 +296,6 @@ static void print_line_count(FILE *file, int count) } } -static void copy_file_with_prefix(FILE *file, - int prefix, const char *data, int size, - const char *set, const char *reset) -{ - int ch, nl_just_seen = 1; - while (0 < size--) { - ch = *data++; - if (nl_just_seen) { - fputs(set, file); - putc(prefix, file); - } - if (ch == '\n') { - nl_just_seen = 1; - fputs(reset, file); - } else - nl_just_seen = 0; - putc(ch, file); - } - if (!nl_just_seen) - fprintf(file, "%s\n\\ No newline at end of file\n", reset); -} - static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) { if (!DIFF_FILE_VALID(one)) { @@ -447,6 +425,38 @@ static void emit_add_line(const char *reset, } } +static void emit_rewrite_lines(struct emit_callback *ecb, + int prefix, const char *data, int size) +{ + const char *endp = NULL; + static const char *nneof = " No newline at end of file\n"; + const char *old = diff_get_color(ecb->color_diff, DIFF_FILE_OLD); + const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET); + + while (0 < size) { + int len; + + endp = memchr(data, '\n', size); + len = endp ? (endp - data + 1) : size; + if (prefix != '+') { + ecb->lno_in_preimage++; + emit_line_0(ecb->file, old, reset, '-', + data, len); + } else { + ecb->lno_in_postimage++; + emit_add_line(reset, ecb, data, len); + } + size -= len; + data += len; + } + if (!endp) { + const char *plain = diff_get_color(ecb->color_diff, + DIFF_PLAIN); + emit_line_0(ecb->file, plain, reset, '\\', + nneof, strlen(nneof)); + } +} + static void emit_rewrite_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -458,10 +468,23 @@ static void emit_rewrite_diff(const char *name_a, const char *name_a_tab, *name_b_tab; const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO); const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO); - const char *old = diff_get_color(color_diff, DIFF_FILE_OLD); - const char *new = diff_get_color(color_diff, DIFF_FILE_NEW); const char *reset = diff_get_color(color_diff, DIFF_RESET); static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT; + struct emit_callback ecbdata; + + memset(&ecbdata, 0, sizeof(ecbdata)); + ecbdata.color_diff = color_diff; + ecbdata.found_changesp = &o->found_changes; + ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); + ecbdata.file = o->file; + if (ecbdata.ws_rule & WS_BLANK_AT_EOF) { + mmfile_t mf1, mf2; + fill_mmfile(&mf1, one); + fill_mmfile(&mf2, two); + check_blank_at_eof(&mf1, &mf2, &ecbdata); + } + ecbdata.lno_in_preimage = 1; + ecbdata.lno_in_postimage = 1; name_a += (*name_a == '/'); name_b += (*name_b == '/'); @@ -486,9 +509,9 @@ static void emit_rewrite_diff(const char *name_a, print_line_count(o->file, lc_b); fprintf(o->file, " @@%s\n", reset); if (lc_a) - copy_file_with_prefix(o->file, '-', one->data, one->size, old, reset); + emit_rewrite_lines(&ecbdata, '-', one->data, one->size); if (lc_b) - copy_file_with_prefix(o->file, '+', two->data, two->size, new, reset); + emit_rewrite_lines(&ecbdata, '+', two->data, two->size); } struct diff_words_buffer { -- cgit v0.10.2-6-g49f6