git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2 11/12] diff: highlight and error out on incomplete lines

From
Junio C Hamano <gitster@pobox.com>
Date
Nov 5, 2025, 21:30 UTC
Message-ID
<20251105213052.1499224-12-gitster@pobox.com>
In-Reply-To
<20251105213052.1499224-1-gitster@pobox.com>

Teach "git diff" to highlight "\ No newline at end of file" message as a whitespace error when incomplete-line whitespace error class is in effect. Thanks to the previous refactoring of complete rewrite code path, we can do this at a single place.

Unlike whitespace errors in the payload where we need to annotate in line, possibly using colors, the line that has whitespace problems, we have a dedicated line already that can serve as the error message, so paint it as a whitespace error message.

Also teach "git diff --check" to notice incomplete lines as whitespace errors and report when incomplete-line whitespace error class is in effect.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 diff.c                     | 29 ++++++++++++++++--
 t/t4015-diff-whitespace.sh | 63 +++++++++++++++++++++++++++++++++++---
 2 files changed, 86 insertions(+), 6 deletions(-)
diff --git a/diff.c b/diff.c
index 8d03146aaa..965b97f7f0 100644
--- a/diff.c
+++ b/diff.c
@@ -1370,7 +1370,11 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
 		emit_line(o, "", "", line, len);
 		break;
 	case DIFF_SYMBOL_CONTEXT_INCOMPLETE:
-		set = diff_get_color_opt(o, DIFF_CONTEXT);
+		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;
@@ -1666,8 +1670,14 @@ static void emit_context_line(struct emit_callback *ecbdata,
 static void emit_incomplete_line(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, 0);
+			 line, len, flags);
 }
 
 static void emit_hunk_header(struct emit_callback *ecbdata,
@@ -3257,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)
@@ -3295,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);
@@ -3305,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++;
@@ -3327,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;
 }
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index 9de7f73f42..138730cbce 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -43,6 +43,49 @@ do
 	'
 done
 
+test_expect_success "incomplete line in both pre- and post-image context" '
+	(echo foo && echo baz | tr -d "\012") >x &&
+	git add x &&
+	(echo bar && echo baz | tr -d "\012") >x &&
+	git diff x &&
+	git -c core.whitespace=incomplete diff --check x &&
+	git diff -R x &&
+	git -c core.whitespace=incomplete diff -R --check x
+'
+
+test_expect_success "incomplete lines on both pre- and post-image" '
+	# The interpretation taken here is "since you are toucing
+	# the line anyway, you would better fix the incomplete line
+	# while you are at it."  but this is debatable.
+	echo foo | tr -d "\012" >x &&
+	git add x &&
+	echo bar | tr -d "\012" >x &&
+	git diff x &&
+	test_must_fail git -c core.whitespace=incomplete diff --check x &&
+	git diff -R x &&
+	test_must_fail git -c core.whitespace=incomplete diff -R --check x
+'
+
+test_expect_success "fix incomplete line in pre-image" '
+	echo foo | tr -d "\012" >x &&
+	git add x &&
+	echo bar >x &&
+	git diff x &&
+	git -c core.whitespace=incomplete diff --check x &&
+	git diff -R x &&
+	test_must_fail git -c core.whitespace=incomplete diff -R --check x
+'
+
+test_expect_success "new incomplete line in post-image" '
+	echo foo >x &&
+	git add x &&
+	echo bar | tr -d "\012" >x &&
+	git diff x &&
+	test_must_fail git -c core.whitespace=incomplete diff --check x &&
+	git diff -R x &&
+	git -c core.whitespace=incomplete diff -R --check x
+'
+
 test_expect_success "Ray Lehtiniemi's example" '
 	cat <<-\EOF >x &&
 	do {
@@ -1040,7 +1083,8 @@ test_expect_success 'ws-error-highlight test setup' '
 	{
 		echo "0. blank-at-eol " &&
 		echo "1. still-blank-at-eol " &&
-		echo "2. and a new line "
+		echo "2. and a new line " &&
+		printf "3. and more"
 	} >x &&
 	new_hash_x=$(git hash-object x) &&
 	after=$(git rev-parse --short "$new_hash_x") &&
@@ -1050,11 +1094,13 @@ test_expect_success 'ws-error-highlight test setup' '
 	<BOLD>index $before..$after 100644<RESET>
 	<BOLD>--- a/x<RESET>
 	<BOLD>+++ b/x<RESET>
-	<CYAN>@@ -1,2 +1,3 @@<RESET>
+	<CYAN>@@ -1,2 +1,4 @@<RESET>
 	 0. blank-at-eol <RESET>
 	<RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET>
 	<GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>
 	<GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
+	<GREEN>+<RESET><GREEN>3. and more<RESET>
+	<BLUE>\ No newline at end of file<RESET>
 	EOF
 
 	cat >expect.all <<-EOF &&
@@ -1062,11 +1108,13 @@ test_expect_success 'ws-error-highlight test setup' '
 	<BOLD>index $before..$after 100644<RESET>
 	<BOLD>--- a/x<RESET>
 	<BOLD>+++ b/x<RESET>
-	<CYAN>@@ -1,2 +1,3 @@<RESET>
+	<CYAN>@@ -1,2 +1,4 @@<RESET>
 	 <RESET>0. blank-at-eol<RESET><BLUE> <RESET>
 	<RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET>
 	<GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET>
 	<GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET>
+	<GREEN>+<RESET><GREEN>3. and more<RESET>
+	<BLUE>\ No newline at end of file<RESET>
 	EOF
 
 	cat >expect.none <<-EOF
@@ -1074,16 +1122,19 @@ test_expect_success 'ws-error-highlight test setup' '
 	<BOLD>index $before..$after 100644<RESET>
 	<BOLD>--- a/x<RESET>
 	<BOLD>+++ b/x<RESET>
-	<CYAN>@@ -1,2 +1,3 @@<RESET>
+	<CYAN>@@ -1,2 +1,4 @@<RESET>
 	 0. blank-at-eol <RESET>
 	<RED>-1. blank-at-eol <RESET>
 	<GREEN>+1. still-blank-at-eol <RESET>
 	<GREEN>+2. and a new line <RESET>
+	<GREEN>+3. and more<RESET>
+	\ No newline at end of file<RESET>
 	EOF
 
 '
 
 test_expect_success 'test --ws-error-highlight option' '
+	git config core.whitespace blank-at-eol,incomplete-line &&
 
 	git diff --color --ws-error-highlight=default,old >current.raw &&
 	test_decode_color <current.raw >current &&
@@ -1100,6 +1151,7 @@ test_expect_success 'test --ws-error-highlight option' '
 '
 
 test_expect_success 'test diff.wsErrorHighlight config' '
+	git config core.whitespace blank-at-eol,incomplete-line &&
 
 	git -c diff.wsErrorHighlight=default,old diff --color >current.raw &&
 	test_decode_color <current.raw >current &&
@@ -1116,6 +1168,7 @@ test_expect_success 'test diff.wsErrorHighlight config' '
 '
 
 test_expect_success 'option overrides diff.wsErrorHighlight' '
+	git config core.whitespace blank-at-eol,incomplete-line &&
 
 	git -c diff.wsErrorHighlight=none \
 		diff --color --ws-error-highlight=default,old >current.raw &&
@@ -1135,6 +1188,8 @@ test_expect_success 'option overrides diff.wsErrorHighlight' '
 '
 
 test_expect_success 'detect moved code, complete file' '
+	git config core.whitespace blank-at-eol &&
+
 	git reset --hard &&
 	cat <<-\EOF >test.c &&
 	#include<stdio.h>
-- 
2.52.0-rc0-105-gc08128fbb6
Previous: Junio C HamanoNext: Junio C Hamano
Message 40 of 73 in “Incomplete lines”
  1. 00/12 Incomplete linesJunio C Hamano, Nov 4, 2025
  2. 01/12 whitespace: correct bit assignment commentsJunio C Hamano, Nov 4, 2025
  3. 02/12 diff: emit_line_ws_markup() if/else style fixJunio C Hamano, Nov 4, 2025
  4. 03/12 diff: correct suppress_blank_empty hackJunio C Hamano, Nov 4, 2025
  5. 04/12 diff: fix incorrect counting of line numbersJunio C Hamano, Nov 4, 2025
  6. Phillip WoodNov 10, 2025
  7. Junio C HamanoNov 10, 2025
  8. Phillip WoodNov 11, 2025
  9. Junio C HamanoNov 11, 2025
  10. 05/12 diff: refactor output of incomplete lineJunio C Hamano, Nov 4, 2025
  11. 06/12 diff: call emit_callback ecbdata everywhereJunio C Hamano, Nov 4, 2025
  12. 07/12 diff: update the way rewrite diff handles incomplete linesJunio C Hamano, Nov 4, 2025
  13. Phillip WoodNov 10, 2025
  14. Junio C HamanoNov 10, 2025
  15. 08/12 apply: revamp the parsing of incomplete linesJunio C Hamano, Nov 4, 2025
  16. 09/12 whitespace: allocate a few more bitsJunio C Hamano, Nov 4, 2025
  17. 10/12 apply: check and fix incomplete linesJunio C Hamano, Nov 4, 2025
  18. 11/12 diff: highlight and error out on incomplete linesJunio C Hamano, Nov 4, 2025
  19. Phillip WoodNov 10, 2025
  20. Junio C HamanoNov 10, 2025
  21. D. Ben KnobleNov 10, 2025
  22. 12/12 attr: enable incomplete-line whitespace error for this projectJunio C Hamano, Nov 4, 2025
  23. Phillip WoodNov 10, 2025
  24. Junio C HamanoNov 10, 2025
  25. 00/12 Incomplete linesJunio C Hamano, Nov 5, 2025
  26. 01/12 whitespace: correct bit assignment commentsJunio C Hamano, Nov 5, 2025
  27. 02/12 diff: emit_line_ws_markup() if/else style fixJunio C Hamano, Nov 5, 2025
  28. 03/12 diff: correct suppress_blank_empty hackJunio C Hamano, Nov 5, 2025
  29. 04/12 diff: fix incorrect counting of line numbersJunio C Hamano, Nov 5, 2025
  30. 05/12 diff: refactor output of incomplete lineJunio C Hamano, Nov 5, 2025
  31. Patrick SteinhardtNov 10, 2025
  32. Junio C HamanoNov 10, 2025
  33. 06/12 diff: call emit_callback ecbdata everywhereJunio C Hamano, Nov 5, 2025
  34. 07/12 diff: update the way rewrite diff handles incomplete linesJunio C Hamano, Nov 5, 2025
  35. Patrick SteinhardtNov 10, 2025
  36. Junio C HamanoNov 10, 2025
  37. 08/12 apply: revamp the parsing of incomplete linesJunio C Hamano, Nov 5, 2025
  38. 09/12 whitespace: allocate a few more bits and define WS_INCOMPLETE_LINEJunio C Hamano, Nov 5, 2025
  39. 10/12 apply: check and fix incomplete linesJunio C Hamano, Nov 5, 2025
  40. 11/12 diff: highlight and error out on incomplete linesJunio C Hamano, Nov 5, 2025
  41. 12/12 attr: enable incomplete-line whitespace error for this projectJunio C Hamano, Nov 5, 2025
  42. Patrick SteinhardtNov 10, 2025
  43. Phillip WoodNov 10, 2025
  44. 00/12 Incomplete linesJunio C Hamano, Nov 11, 2025
  45. 01/12 whitespace: correct bit assignment commentsJunio C Hamano, Nov 11, 2025
  46. 02/12 diff: emit_line_ws_markup() if/else style fixJunio C Hamano, Nov 11, 2025
  47. 03/12 diff: correct suppress_blank_empty hackJunio C Hamano, Nov 11, 2025
  48. 04/12 diff: fix incorrect counting of line numbersJunio C Hamano, Nov 11, 2025
  49. 05/12 diff: refactor output of incomplete lineJunio C Hamano, Nov 11, 2025
  50. 06/12 diff: call emit_callback ecbdata everywhereJunio C Hamano, Nov 11, 2025
  51. 07/12 diff: update the way rewrite diff handles incomplete linesJunio C Hamano, Nov 11, 2025
  52. 08/12 apply: revamp the parsing of incomplete linesJunio C Hamano, Nov 11, 2025
  53. 09/12 whitespace: allocate a few more bits and define WS_INCOMPLETE_LINEJunio C Hamano, Nov 11, 2025
  54. 10/12 apply: check and fix incomplete linesJunio C Hamano, Nov 11, 2025
  55. 11/12 diff: highlight and error out on incomplete linesJunio C Hamano, Nov 11, 2025
  56. 12/12 attr: enable incomplete-line whitespace error for this projectJunio C Hamano, Nov 11, 2025
  57. Phillip WoodNov 11, 2025
  58. 00/12 Incomplete linesJunio C Hamano, Nov 12, 2025
  59. 01/12 whitespace: correct bit assignment commentsJunio C Hamano, Nov 12, 2025
  60. 02/12 diff: emit_line_ws_markup() if/else style fixJunio C Hamano, Nov 12, 2025
  61. 03/12 diff: correct suppress_blank_empty hackJunio C Hamano, Nov 12, 2025
  62. 04/12 diff: keep track of the type of the last line seenJunio C Hamano, Nov 12, 2025
  63. 05/12 diff: refactor output of incomplete lineJunio C Hamano, Nov 12, 2025
  64. 06/12 diff: call emit_callback ecbdata everywhereJunio C Hamano, Nov 12, 2025
  65. 07/12 diff: update the way rewrite diff handles incomplete linesJunio C Hamano, Nov 12, 2025
  66. 08/12 apply: revamp the parsing of incomplete linesJunio C Hamano, Nov 12, 2025
  67. 09/12 whitespace: allocate a few more bits and define WS_INCOMPLETE_LINEJunio C Hamano, Nov 12, 2025
  68. 10/12 apply: check and fix incomplete linesJunio C Hamano, Nov 12, 2025
  69. 11/12 diff: highlight and error out on incomplete linesJunio C Hamano, Nov 12, 2025
  70. 12/12 attr: enable incomplete-line whitespace error for this projectJunio C Hamano, Nov 12, 2025
  71. Phillip WoodNov 14, 2025
  72. Junio C HamanoNov 14, 2025
  73. Junio C HamanoNov 23, 2025

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.