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

[PATCH 2/2] diff: fix --exit-code with external diff

From
René Scharfe <l.s.r@web.de>
Date
May 5, 2024, 10:20 UTC
Message-ID
<e2e4a4e9-55db-403c-902d-fd8af3aea05c@web.de>
In-Reply-To
<82561c70-ec33-41bf-b036-52310ffc1926@web.de>

You can ask the diff machinery to let the exit code indicate whether there are changes, e.g. with --exit-code. It as two ways to calculate that bit: The quick one assumes blobs with different hashes have different content, and the more elaborate way actually compares the contents, possibly applying transformations like ignoring whitespace.

Always use the slower path by setting the flag diff_from_contents, because any of the files could have an external diff driver set via an attribute, which might consider binary differences irrelevant, like e.g. diff -b.

And don't stop walking that path in diff_flush() just because output_format is unset. Instead run diff_flush_patch() with output redirected to /dev/null to get the exit status, like we already do for DIFF_FORMAT_NO_OUTPUT. That's consistent with the comments in diff.h:

   /* Same as output_format = 0 but we know that -s flag was given
    * and we should not give default value to output_format.
    */
   #define DIFF_FORMAT_NO_OUTPUT	0x0800

An unset output_format is given e.g. by repo_index_has_changes(). We could set it right there, but there may be other places, so simply let diff_flush() handle it for all of them consistently.

Finally, capture the output of the external diff and only register a change by setting found_changes if the program wrote anything.

Reported-by: German Lashevich <german.lashevich@gmail.com>
Signed-off-by: René Scharfe <l.s.r@web.de>
---
 diff.c                   | 33 ++++++++++++++++++++++++++++++---
 t/t4020-diff-external.sh |  8 ++++++++
 2 files changed, 38 insertions(+), 3 deletions(-)
diff --git a/diff.c b/diff.c
index ded9ac70df..cd3c8a3978 100644
--- a/diff.c
+++ b/diff.c
@@ -40,6 +40,7 @@
 #include "setup.h"
 #include "strmap.h"
 #include "ws.h"
+#include "write-or-die.h"

 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -4404,8 +4405,33 @@ static void run_external_diff(const char *pgm,
 	diff_free_filespec_data(one);
 	diff_free_filespec_data(two);
 	cmd.use_shell = 1;
-	if (run_command(&cmd))
-		die(_("external diff died, stopping at %s"), name);
+	if (o->flags.diff_from_contents) {
+		int got_output = 0;
+		cmd.out = -1;
+		if (start_command(&cmd))
+			die(_("external diff died, stopping at %s"), name);
+		for (;;) {
+			char buffer[8192];
+			ssize_t len = xread(cmd.out, buffer, sizeof(buffer));
+			if (!len)
+				break;
+			if (len < 0)
+				die(_("unable to read from external diff,"
+				      " stopping at %s"), name);
+			got_output = 1;
+			if (write_in_full(1, buffer, len) < 0)
+				die(_("unable to write output of external diff,"
+				      " stopping at %s"), name);
+		}
+		close(cmd.out);
+		if (finish_command(&cmd))
+			die(_("external diff died, stopping at %s"), name);
+		if (got_output)
+			o->found_changes = 1;
+	} else {
+		if (run_command(&cmd))
+			die(_("external diff died, stopping at %s"), name);
+	}

 	remove_tempfile();
 }
@@ -4852,6 +4878,7 @@ void diff_setup_done(struct diff_options *options)
 	 */

 	if ((options->xdl_opts & XDF_WHITESPACE_FLAGS) ||
+	    options->flags.exit_with_status ||
 	    options->ignore_regex_nr)
 		options->flags.diff_from_contents = 1;
 	else
@@ -6742,7 +6769,7 @@ void diff_flush(struct diff_options *options)
 	if (output_format & DIFF_FORMAT_CALLBACK)
 		options->format_callback(q, options, options->format_callback_data);

-	if (output_format & DIFF_FORMAT_NO_OUTPUT &&
+	if ((!output_format || output_format & DIFF_FORMAT_NO_OUTPUT) &&
 	    options->flags.exit_with_status &&
 	    options->flags.diff_from_contents) {
 		/*
diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh
index fdd865f7c3..26430ca66b 100755
--- a/t/t4020-diff-external.sh
+++ b/t/t4020-diff-external.sh
@@ -172,6 +172,14 @@ test_expect_success 'no diff with -diff' '
 	grep Binary out
 '

+test_expect_success 'diff.external and --exit-code with output' '
+	test_expect_code 1 git -c diff.external=echo diff --exit-code
+'
+
+test_expect_success 'diff.external and --exit-code without output' '
+	git -c diff.external=true diff --exit-code
+'
+
 echo NULZbetweenZwords | perl -pe 'y/Z/\000/' > file

 test_expect_success 'force diff with "diff"' '
--
2.45.0
Previous: René ScharfeNext: Phillip Wood
Message 10 of 32 in “Possible git-diff bug when using exit-code with diff filters”
  1. German LashevichApr 20, 2024
  2. René ScharfeApr 21, 2024
  3. Junio C HamanoApr 21, 2024
  4. rsbecker@nexbridge.comApr 21, 2024
  5. Junio C HamanoApr 21, 2024
  6. rsbecker@nexbridge.comApr 21, 2024
  7. René ScharfeMay 5, 2024
  8. Junio C HamanoMay 6, 2024
  9. 1/2 diff: report unmerged paths as changes in run_diff_cmd()René Scharfe, May 5, 2024
  10. 2/2 diff: fix --exit-code with external diffRené Scharfe, May 5, 2024
  11. Phillip WoodMay 5, 2024
  12. Junio C HamanoMay 6, 2024
  13. René ScharfeMay 6, 2024
  14. phillip.wood123@gmail.comMay 8, 2024
  15. René ScharfeMay 11, 2024
  16. René ScharfeMay 12, 2024
  17. 0/3 diff: fix --exit-code with external diffRené Scharfe, Jun 5, 2024
  18. 1/3 t4020: test exit code with external diffsRené Scharfe, Jun 5, 2024
  19. 2/3 userdiff: add and use struct external_diffRené Scharfe, Jun 5, 2024
  20. 3/3 diff: let external diffs report that changes are uninterestingRené Scharfe, Jun 5, 2024
  21. Johannes SixtJun 6, 2024
  22. René ScharfeJun 6, 2024
  23. Junio C HamanoJun 6, 2024
  24. Phillip WoodJun 6, 2024
  25. René ScharfeJun 7, 2024
  26. Junio C HamanoJun 5, 2024
  27. 0/3 diff: fix --exit-code with external diffRené Scharfe, Jun 9, 2024
  28. 1/3 t4020: test exit code with external diffsRené Scharfe, Jun 9, 2024
  29. Junio C HamanoJun 10, 2024
  30. 2/3 userdiff: add and use struct external_diffRené Scharfe, Jun 9, 2024
  31. 3/3 diff: let external diffs report that changes are uninterestingRené Scharfe, Jun 9, 2024
  32. phillip.wood123@gmail.comJun 10, 2024

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.