From 448c3ef144b2f528bb6dc50153ac13cc2abbc3c4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 22 Sep 2006 22:48:39 -0700 Subject: [PATCH 1/2] diff.c: second war on whitespace. This adds DIFF_WHITESPACE color class (default = reverse red) to colored diff output to let you catch common whitespace errors. - trailing whitespaces at the end of line - a space followed by a tab in the indent Signed-off-by: Junio C Hamano --- diff.c | 172 +++++++++++++++++++++++++++++++++++++++++++-------------- diff.h | 1 + 2 files changed, 132 insertions(+), 41 deletions(-) diff --git a/diff.c b/diff.c index 443e24861b..2464238ccd 100644 --- a/diff.c +++ b/diff.c @@ -20,12 +20,13 @@ static int diff_use_color_default; static char diff_colors[][COLOR_MAXLEN] = { "\033[m", /* reset */ - "", /* normal */ - "\033[1m", /* bold */ - "\033[36m", /* cyan */ - "\033[31m", /* red */ - "\033[32m", /* green */ - "\033[33m" /* yellow */ + "", /* PLAIN (normal) */ + "\033[1m", /* METAINFO (bold) */ + "\033[36m", /* FRAGINFO (cyan) */ + "\033[31m", /* OLD (red) */ + "\033[32m", /* NEW (green) */ + "\033[33m", /* COMMIT (yellow) */ + "\033[41m", /* WHITESPACE (red background) */ }; static int parse_diff_color_slot(const char *var, int ofs) @@ -42,6 +43,8 @@ static int parse_diff_color_slot(const char *var, int ofs) return DIFF_FILE_NEW; if (!strcasecmp(var+ofs, "commit")) return DIFF_COMMIT; + if (!strcasecmp(var+ofs, "whitespace")) + return DIFF_WHITESPACE; die("bad config variable '%s'", var); } @@ -383,9 +386,89 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) return ""; } +static void emit_line(const char *set, const char *reset, const char *line, int len) +{ + if (len > 0 && line[len-1] == '\n') + len--; + fputs(set, stdout); + fwrite(line, len, 1, stdout); + puts(reset); +} + +static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) +{ + int col0 = ecbdata->nparents; + int last_tab_in_indent = -1; + int last_space_in_indent = -1; + int i; + int tail = len; + int need_highlight_leading_space = 0; + 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(set, reset, line, len); + return; + } + + /* The line is a newly added line. Does it have funny leading + * whitespaces? In indent, SP should never precede a TAB. + */ + for (i = col0; i < len; i++) { + if (line[i] == '\t') { + last_tab_in_indent = i; + if (0 <= last_space_in_indent) + need_highlight_leading_space = 1; + } + else if (line[i] == ' ') + last_space_in_indent = i; + else + break; + } + fputs(set, stdout); + fwrite(line, col0, 1, stdout); + fputs(reset, stdout); + if (((i == len) || line[i] == '\n') && i != col0) { + /* The whole line was indent */ + emit_line(ws, reset, line + col0, len - col0); + return; + } + i = col0; + if (need_highlight_leading_space) { + while (i < last_tab_in_indent) { + if (line[i] == ' ') { + fputs(ws, stdout); + putchar(' '); + fputs(reset, stdout); + } + else + putchar(line[i]); + i++; + } + } + tail = len - 1; + if (line[tail] == '\n' && i < tail) + tail--; + while (i < tail) { + if (!isspace(line[tail])) + break; + tail--; + } + if ((i < tail && line[tail + 1] != '\n')) { + /* This has whitespace between tail+1..len */ + fputs(set, stdout); + fwrite(line + i, tail - i + 1, 1, stdout); + fputs(reset, stdout); + emit_line(ws, reset, line + tail + 1, len - tail - 1); + } + else + emit_line(set, reset, line + i, len - i); +} + static void fn_out_consume(void *priv, char *line, unsigned long len) { int i; + int color; struct emit_callback *ecbdata = priv; const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); @@ -403,45 +486,52 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) ; if (2 <= i && i < len && line[i] == ' ') { ecbdata->nparents = i - 1; - set = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO); + emit_line(diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), + reset, line, len); + return; } - else if (len < ecbdata->nparents) + + if (len < ecbdata->nparents) { set = reset; - else { - int nparents = ecbdata->nparents; - int color = DIFF_PLAIN; - if (ecbdata->diff_words && nparents != 1) - /* fall back to normal diff */ - free_diff_words_data(ecbdata); - if (ecbdata->diff_words) { - if (line[0] == '-') { - diff_words_append(line, len, - &ecbdata->diff_words->minus); - return; - } else if (line[0] == '+') { - diff_words_append(line, len, - &ecbdata->diff_words->plus); - return; - } - if (ecbdata->diff_words->minus.text.size || - ecbdata->diff_words->plus.text.size) - diff_words_show(ecbdata->diff_words); - line++; - len--; - } else - for (i = 0; i < nparents && len; i++) { - if (line[i] == '-') - color = DIFF_FILE_OLD; - else if (line[i] == '+') - color = DIFF_FILE_NEW; - } - set = diff_get_color(ecbdata->color_diff, color); + emit_line(reset, reset, line, len); + return; } - if (len > 0 && line[len-1] == '\n') + + 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, + &ecbdata->diff_words->minus); + return; + } else if (line[0] == '+') { + diff_words_append(line, len, + &ecbdata->diff_words->plus); + return; + } + if (ecbdata->diff_words->minus.text.size || + ecbdata->diff_words->plus.text.size) + diff_words_show(ecbdata->diff_words); + line++; len--; - fputs (set, stdout); - fwrite (line, len, 1, stdout); - puts (reset); + emit_line(set, 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(diff_get_color(ecbdata->color_diff, color), + reset, line, len); + return; + } + emit_add_line(reset, ecbdata, line, len); } static char *pprint_rename(const char *a, const char *b) diff --git a/diff.h b/diff.h index b60a02e627..3435fe7b3f 100644 --- a/diff.h +++ b/diff.h @@ -86,6 +86,7 @@ enum color_diff { DIFF_FILE_OLD = 4, DIFF_FILE_NEW = 5, DIFF_COMMIT = 6, + DIFF_WHITESPACE = 7, }; const char *diff_get_color(int diff_use_color, enum color_diff ix); From d0c25035df4897bb58422b4d64f00b54cf11f07e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 23 Sep 2006 00:37:19 -0700 Subject: [PATCH 2/2] git-apply: second war on whitespace. This makes --whitespace={warn,error,strip} option to also notice the leading whitespace errors in addition to the trailing whitespace errors. Spaces that are followed by a tab in indent are detected as errors, and --whitespace=strip option fixes them. Signed-off-by: Junio C Hamano --- builtin-apply.c | 122 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 97 insertions(+), 25 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 25e90d8d29..de5f855266 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -854,6 +854,49 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc return -1; } +static void check_whitespace(const char *line, int len) +{ + const char *err = "Adds trailing whitespace"; + int seen_space = 0; + int i; + + /* + * We know len is at least two, since we have a '+' and we + * checked that the last character was a '\n' before calling + * this function. That is, an addition of an empty line would + * check the '+' here. Sneaky... + */ + if (isspace(line[len-2])) + goto error; + + /* + * Make sure that there is no space followed by a tab in + * indentation. + */ + err = "Space in indent is followed by a tab"; + for (i = 1; i < len; i++) { + if (line[i] == '\t') { + if (seen_space) + goto error; + } + else if (line[i] == ' ') + seen_space = 1; + else + break; + } + return; + + error: + whitespace_error++; + if (squelch_whitespace_errors && + squelch_whitespace_errors < whitespace_error) + ; + else + fprintf(stderr, "%s.\n%s:%d:%.*s\n", + err, patch_input_file, linenr, len-2, line+1); +} + + /* * Parse a unified diff. Note that this really needs to parse each * fragment separately, since the only way to know the difference @@ -904,25 +947,8 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s trailing = 0; break; case '+': - /* - * We know len is at least two, since we have a '+' and - * we checked that the last character was a '\n' above. - * That is, an addition of an empty line would check - * the '+' here. Sneaky... - */ - if ((new_whitespace != nowarn_whitespace) && - isspace(line[len-2])) { - whitespace_error++; - if (squelch_whitespace_errors && - squelch_whitespace_errors < - whitespace_error) - ; - else { - fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n", - patch_input_file, - linenr, len-2, line+1); - } - } + if (new_whitespace != nowarn_whitespace) + check_whitespace(line, len); added++; newlines--; trailing = 0; @@ -1494,22 +1520,68 @@ static int apply_line(char *output, const char *patch, int plen) { /* plen is number of bytes to be copied from patch, * starting at patch+1 (patch[0] is '+'). Typically - * patch[plen] is '\n'. + * patch[plen] is '\n', unless this is the incomplete + * last line. */ + int i; int add_nl_to_tail = 0; - if ((new_whitespace == strip_whitespace) && - 1 < plen && isspace(patch[plen-1])) { + int fixed = 0; + int last_tab_in_indent = -1; + int last_space_in_indent = -1; + int need_fix_leading_space = 0; + char *buf; + + if ((new_whitespace != strip_whitespace) || !whitespace_error) { + memcpy(output, patch + 1, plen); + return plen; + } + + if (1 < plen && isspace(patch[plen-1])) { if (patch[plen] == '\n') add_nl_to_tail = 1; plen--; while (0 < plen && isspace(patch[plen])) plen--; - applied_after_stripping++; + fixed = 1; } - memcpy(output, patch + 1, plen); + + for (i = 1; i < plen; i++) { + char ch = patch[i]; + if (ch == '\t') { + last_tab_in_indent = i; + if (0 <= last_space_in_indent) + need_fix_leading_space = 1; + } + else if (ch == ' ') + last_space_in_indent = i; + else + break; + } + + buf = output; + if (need_fix_leading_space) { + /* between patch[1..last_tab_in_indent] strip the + * funny spaces, updating them to tab as needed. + */ + for (i = 1; i < last_tab_in_indent; i++, plen--) { + char ch = patch[i]; + if (ch != ' ') + *output++ = ch; + else if ((i % 8) == 0) + *output++ = '\t'; + } + fixed = 1; + i = last_tab_in_indent; + } + else + i = 1; + + memcpy(output, patch + i, plen); if (add_nl_to_tail) output[plen++] = '\n'; - return plen; + if (fixed) + applied_after_stripping++; + return output + plen - buf; } static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, int inaccurate_eof)