Merge branch 'jc/whitespace-incomplete-line'

Both "git apply" and "git diff" learn a new whitespace error class,
"incomplete-line".

* jc/whitespace-incomplete-line:
  attr: enable incomplete-line whitespace error for this project
  diff: highlight and error out on incomplete lines
  apply: check and fix incomplete lines
  whitespace: allocate a few more bits and define WS_INCOMPLETE_LINE
  apply: revamp the parsing of incomplete lines
  diff: update the way rewrite diff handles incomplete lines
  diff: call emit_callback ecbdata everywhere
  diff: refactor output of incomplete line
  diff: keep track of the type of the last line seen
  diff: correct suppress_blank_empty hack
  diff: emit_line_ws_markup() if/else style fix
  whitespace: correct bit assignment comments
This commit is contained in:
Junio C Hamano
2025-11-30 18:31:40 -08:00
9 changed files with 451 additions and 89 deletions

147
diff.c
View File

@@ -601,6 +601,7 @@ struct emit_callback {
int blank_at_eof_in_postimage;
int lno_in_preimage;
int lno_in_postimage;
int last_line_kind;
const char **label_path;
struct diff_words_data *diff_words;
struct diff_options *opt;
@@ -796,21 +797,23 @@ enum diff_symbol {
DIFF_SYMBOL_CONTEXT_INCOMPLETE,
DIFF_SYMBOL_PLUS,
DIFF_SYMBOL_MINUS,
DIFF_SYMBOL_NO_LF_EOF,
DIFF_SYMBOL_CONTEXT_FRAGINFO,
DIFF_SYMBOL_CONTEXT_MARKER,
DIFF_SYMBOL_SEPARATOR
};
/*
* Flags for content lines:
* 0..12 are whitespace rules
* 13-15 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT
* 16 is marking if the line is blank at EOF
* 0..15 are whitespace rules (see ws.h)
* 16..18 are WSEH_NEW | WSEH_CONTEXT | WSEH_OLD
* 19 is marking if the line is blank at EOF
* 20..22 are used for color-moved.
*/
#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF (1<<16)
#define DIFF_SYMBOL_MOVED_LINE (1<<17)
#define DIFF_SYMBOL_MOVED_LINE_ALT (1<<18)
#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING (1<<19)
#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF (1<<19)
#define DIFF_SYMBOL_MOVED_LINE (1<<20)
#define DIFF_SYMBOL_MOVED_LINE_ALT (1<<21)
#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING (1<<22)
#define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK)
/*
@@ -1318,20 +1321,25 @@ static void emit_line_ws_markup(struct diff_options *o,
const char *ws = NULL;
int sign = o->output_indicators[sign_index];
if (diff_suppress_blank_empty &&
sign_index == OUTPUT_INDICATOR_CONTEXT &&
len == 1 && line[0] == '\n')
sign = 0;
if (o->ws_error_highlight & ws_rule) {
ws = diff_get_color_opt(o, DIFF_WHITESPACE);
if (!*ws)
ws = NULL;
}
if (!ws && !set_sign)
if (!ws && !set_sign) {
emit_line_0(o, set, NULL, 0, reset, sign, line, len);
else if (!ws) {
} else if (!ws) {
emit_line_0(o, set_sign, set, !!set_sign, reset, sign, line, len);
} else if (blank_at_eof)
} else if (blank_at_eof) {
/* Blank line at EOF - paint '+' as well */
emit_line_0(o, ws, NULL, 0, reset, sign, line, len);
else {
} else {
/* Emit just the prefix, then the rest. */
emit_line_0(o, set_sign ? set_sign : set, NULL, !!set_sign, reset,
sign, "", 0);
@@ -1343,7 +1351,6 @@ static void emit_line_ws_markup(struct diff_options *o,
static void emit_diff_symbol_from_struct(struct diff_options *o,
struct emitted_diff_symbol *eds)
{
static const char *nneof = " No newline at end of file\n";
const char *context, *reset, *set, *set_sign, *meta, *fraginfo;
enum diff_symbol s = eds->s;
@@ -1355,13 +1362,6 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
return;
switch (s) {
case DIFF_SYMBOL_NO_LF_EOF:
context = diff_get_color_opt(o, DIFF_CONTEXT);
reset = diff_get_color_opt(o, DIFF_RESET);
putc('\n', o->file);
emit_line_0(o, context, NULL, 0, reset, '\\',
nneof, strlen(nneof));
break;
case DIFF_SYMBOL_SUBMODULE_HEADER:
case DIFF_SYMBOL_SUBMODULE_ERROR:
case DIFF_SYMBOL_SUBMODULE_PIPETHROUGH:
@@ -1373,6 +1373,14 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
emit_line(o, "", "", line, len);
break;
case DIFF_SYMBOL_CONTEXT_INCOMPLETE:
if ((flags & WS_INCOMPLETE_LINE) &&
(flags & o->ws_error_highlight))
set = diff_get_color_opt(o, DIFF_WHITESPACE);
else
set = diff_get_color_opt(o, DIFF_CONTEXT);
reset = diff_get_color_opt(o, DIFF_RESET);
emit_line(o, set, reset, line, len);
break;
case DIFF_SYMBOL_CONTEXT_MARKER:
context = diff_get_color_opt(o, DIFF_CONTEXT);
reset = diff_get_color_opt(o, DIFF_RESET);
@@ -1498,15 +1506,9 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
case DIFF_SYMBOL_WORDS:
context = diff_get_color_opt(o, DIFF_CONTEXT);
reset = diff_get_color_opt(o, DIFF_RESET);
/*
* Skip the prefix character, if any. With
* diff_suppress_blank_empty, there may be
* none.
*/
if (line[0] != '\n') {
line++;
len--;
}
/* Skip the prefix character */
line++; len--;
emit_line(o, context, reset, line, len);
break;
case DIFF_SYMBOL_FILEPAIR_PLUS:
@@ -1668,6 +1670,19 @@ static void emit_context_line(struct emit_callback *ecbdata,
emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_CONTEXT, line, len, flags);
}
static void emit_incomplete_line_marker(struct emit_callback *ecbdata,
const char *line, int len)
{
int last_line_kind = ecbdata->last_line_kind;
unsigned flags = (last_line_kind == '+'
? WSEH_NEW
: last_line_kind == '-'
? WSEH_OLD
: WSEH_CONTEXT) | ecbdata->ws_rule;
emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_CONTEXT_INCOMPLETE,
line, len, flags);
}
static void emit_hunk_header(struct emit_callback *ecbdata,
const char *line, int len)
{
@@ -1769,28 +1784,44 @@ static void add_line_count(struct strbuf *out, int count)
}
}
static void emit_rewrite_lines(struct emit_callback *ecb,
static void emit_rewrite_lines(struct emit_callback *ecbdata,
int prefix, const char *data, int size)
{
const char *endp = NULL;
while (0 < size) {
int len;
int len, plen;
char *pdata = NULL;
endp = memchr(data, '\n', size);
len = endp ? (endp - data + 1) : size;
if (prefix != '+') {
ecb->lno_in_preimage++;
emit_del_line(ecb, data, len);
if (endp) {
len = endp - data + 1;
plen = len;
} else {
ecb->lno_in_postimage++;
emit_add_line(ecb, data, len);
len = size;
plen = len + 1;
pdata = xmalloc(plen + 2);
memcpy(pdata, data, len);
pdata[len] = '\n';
pdata[len + 1] = '\0';
}
if (prefix != '+') {
ecbdata->lno_in_preimage++;
emit_del_line(ecbdata, pdata ? pdata : data, plen);
} else {
ecbdata->lno_in_postimage++;
emit_add_line(ecbdata, pdata ? pdata : data, plen);
}
free(pdata);
size -= len;
data += len;
}
if (!endp)
emit_diff_symbol(ecb->opt, DIFF_SYMBOL_NO_LF_EOF, NULL, 0, 0);
if (!endp) {
static const char nneof[] = "\\ No newline at end of file\n";
ecbdata->last_line_kind = prefix;
emit_incomplete_line_marker(ecbdata, nneof, sizeof(nneof) - 1);
}
}
static void emit_rewrite_diff(const char *name_a,
@@ -2375,12 +2406,6 @@ static int fn_out_consume(void *priv, char *line, unsigned long len)
ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
}
if (diff_suppress_blank_empty
&& len == 2 && line[0] == ' ' && line[1] == '\n') {
line[0] = '\n';
len = 1;
}
if (line[0] == '@') {
if (ecbdata->diff_words)
diff_words_flush(ecbdata);
@@ -2431,13 +2456,24 @@ static int fn_out_consume(void *priv, char *line, unsigned long len)
ecbdata->lno_in_preimage++;
emit_context_line(ecbdata, line + 1, len - 1);
break;
default:
case '\\':
/* incomplete line at the end */
switch (ecbdata->last_line_kind) {
case '+':
case '-':
case ' ':
break;
default:
BUG("fn_out_consume: '\\No newline' after unknown line (%c)",
ecbdata->last_line_kind);
}
ecbdata->lno_in_preimage++;
emit_diff_symbol(o, DIFF_SYMBOL_CONTEXT_INCOMPLETE,
line, len, 0);
emit_incomplete_line_marker(ecbdata, line, len);
break;
default:
BUG("fn_out_consume: unknown line '%s'", line);
}
ecbdata->last_line_kind = line[0];
return 0;
}
@@ -3231,6 +3267,7 @@ struct checkdiff_t {
struct diff_options *o;
unsigned ws_rule;
unsigned status;
int last_line_kind;
};
static int is_conflict_marker(const char *line, int marker_size, unsigned long len)
@@ -3269,6 +3306,7 @@ static void checkdiff_consume_hunk(void *priv,
static int checkdiff_consume(void *priv, char *line, unsigned long len)
{
struct checkdiff_t *data = priv;
int last_line_kind;
int marker_size = data->conflict_marker_size;
const char *ws = diff_get_color(data->o->use_color, DIFF_WHITESPACE);
const char *reset = diff_get_color(data->o->use_color, DIFF_RESET);
@@ -3279,6 +3317,8 @@ static int checkdiff_consume(void *priv, char *line, unsigned long len)
assert(data->o);
line_prefix = diff_line_prefix(data->o);
last_line_kind = data->last_line_kind;
data->last_line_kind = line[0];
if (line[0] == '+') {
unsigned bad;
data->lineno++;
@@ -3301,6 +3341,17 @@ static int checkdiff_consume(void *priv, char *line, unsigned long len)
data->o->file, set, reset, ws);
} else if (line[0] == ' ') {
data->lineno++;
} else if (line[0] == '\\') {
/* no newline at the end of the line */
if ((data->ws_rule & WS_INCOMPLETE_LINE) &&
(last_line_kind == '+')) {
unsigned bad = WS_INCOMPLETE_LINE;
data->status |= bad;
err = whitespace_error_string(bad);
fprintf(data->o->file, "%s%s:%d: %s.\n",
line_prefix, data->filename, data->lineno, err);
free(err);
}
}
return 0;
}