diff: correct suppress_blank_empty hack

The suppress-blank-empty feature abused the CONTEXT_INCOMPLETE symbol that was meant to be used only for "\ No newline at the end of file" code path. The intent of the feature was to turn a context line we receive from xdiff machinery (which always uses ' ' for context lines, even an empty one) and spit it out as a truly empty line. Perform such a conversion very locally at where a line from xdiff that begins with ' ' is handled for output; there are many checks before the control reaches such place that checks the first letter of the diff output line to see if it is a context line, and having to check for '\n' and treat it as a special case is error prone. In order to catch similar hacks in the future, make sure the code path that is meant for "\ No newline" case checks the first byte is indeed a backslash. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Nov 12, 2025 at 14:02 UTC fc7abcd9d5460b381701ef43b7f6dafa73962950
1 file changed +11 -16
diff.c
+11 -16
@@ -1321,6 +1321,11 @@ static void emit_line_ws_markup(struct diff_options *o,
1321 const char *ws = NULL;
1322 int sign = o->output_indicators[sign_index];
1323
1324 + if (diff_suppress_blank_empty &&
1325 + sign_index == OUTPUT_INDICATOR_CONTEXT &&
1326 + len == 1 && line[0] == '\n')
1327 + sign = 0;
1328 +
1329 if (o->ws_error_highlight & ws_rule) {
1330 ws = diff_get_color_opt(o, DIFF_WHITESPACE);
1331 if (!*ws)
@@ -1498,15 +1503,9 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
1503 case DIFF_SYMBOL_WORDS:
1504 context = diff_get_color_opt(o, DIFF_CONTEXT);
1505 reset = diff_get_color_opt(o, DIFF_RESET);
1501 - /*
1502 - * Skip the prefix character, if any. With
1503 - * diff_suppress_blank_empty, there may be
1504 - * none.
1505 - */
1506 - if (line[0] != '\n') {
1507 - line++;
1508 - len--;
1509 - }
1506 +
1507 + /* Skip the prefix character */
1508 + line++; len--;
1509 emit_line(o, context, reset, line, len);
1510 break;
1511 case DIFF_SYMBOL_FILEPAIR_PLUS:
@@ -2375,12 +2374,6 @@ static int fn_out_consume(void *priv, char *line, unsigned long len)
2374 ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
2375 }
2376
2378 - if (diff_suppress_blank_empty
2379 - && len == 2 && line[0] == ' ' && line[1] == '\n') {
2380 - line[0] = '\n';
2381 - len = 1;
2382 - }
2383 -
2377 if (line[0] == '@') {
2378 if (ecbdata->diff_words)
2379 diff_words_flush(ecbdata);
@@ -2431,12 +2424,14 @@ static int fn_out_consume(void *priv, char *line, unsigned long len)
2424 ecbdata->lno_in_preimage++;
2425 emit_context_line(ecbdata, line + 1, len - 1);
2426 break;
2434 - default:
2427 + case '\\':
2428 /* incomplete line at the end */
2429 ecbdata->lno_in_preimage++;
2430 emit_diff_symbol(o, DIFF_SYMBOL_CONTEXT_INCOMPLETE,
2431 line, len, 0);
2432 break;
2433 + default:
2434 + BUG("fn_out_consume: unknown line '%s'", line);
2435 }
2436 return 0;
2437 }