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

[PATCH 1/4] diff: send external diff output to diff_options.file

From
Jeff King <peff@peff.net>
Date
Oct 24, 2025, 17:06 UTC
Message-ID
<20251024170649.GA2345184@coredump.intra.peff.net>
In-Reply-To
<20251024170522.GA2344972@coredump.intra.peff.net>

Diff output usually goes to the process stdout, but it can be redirected with the "--output" option. We store this in the "file" pointer of diff_options, and all of the diff code should write there instead of to stdout.

But there's one spot we missed: running an external diff cmd. We don't redirect its output at all, so it just defaults to the stdout of the parent process. We should instead point its stdout at our output file. There are a few caveats to watch out for when doing so:

  - The stdout field takes a descriptor, not a FILE pointer. We can pull
    out the descriptor with fileno().
  - The run-command API always closes the stdout descriptor we pass to
    it. So we must duplicate it (otherwise we break the FILE pointer,
    since it now points to a closed descriptor).
  - We don't need to worry about closing our dup'd descriptor, since the
    point is that run-command will do it for us (even in the case of an
    error). But we do need to make sure we skip the dup() if we set
    no_stdout (because then run-command will not look at it at all).
  - When the output is going to stdout, it would not be wrong to dup()
    the descriptor, but we don't need to. We can skip that extra work
    with a simple pointer comparison.
  - It seems like you'd need to fflush() the descriptor before handing
    off a copy to the child process to prevent out-of-order writes. But
    that was true even before this patch! It works because run-command
    always calls fflush(NULL) before running the child.

The new test shows the breakage (and fix). The need for duplicating the descriptor doesn't need a new test; that is covered by the later test "GIT_EXTERNAL_DIFF with more than one changed files".

Signed-off-by: Jeff King <peff@peff.net>
---
 diff.c                   |  5 ++++-
 t/t4020-diff-external.sh | 10 ++++++++++
 2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/diff.c b/diff.c
index 22415aecee..39029cc096 100644
--- a/diff.c
+++ b/diff.c
@@ -4457,7 +4457,10 @@ static void run_external_diff(const struct external_diff *pgm,
 	diff_free_filespec_data(one);
 	diff_free_filespec_data(two);
 	cmd.use_shell = 1;
-	cmd.no_stdout = quiet;
+	if (quiet)
+		cmd.no_stdout = 1;
+	else if (o->file != stdout)
+		cmd.out = xdup(fileno(o->file));
 	rc = run_command(&cmd);
 	if (!pgm->trust_exit_code && rc == 0)
 		o->found_changes = 1;
diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh
index c8a23d5148..7ec5854f74 100755
--- a/t/t4020-diff-external.sh
+++ b/t/t4020-diff-external.sh
@@ -44,6 +44,16 @@ test_expect_success 'GIT_EXTERNAL_DIFF environment and --no-ext-diff' '
 
 '
 
+test_expect_success 'GIT_EXTERNAL_DIFF and --output' '
+	cat >expect <<-EOF &&
+	file $(git rev-parse --verify HEAD:file) 100644 file $(test_oid zero) 100644
+	EOF
+	GIT_EXTERNAL_DIFF=echo git diff --output=out >stdout &&
+	cut -d" " -f1,3- <out >actual &&
+	test_must_be_empty stdout &&
+	test_cmp expect actual
+'
+
 test_expect_success SYMLINKS 'typechange diff' '
 	rm -f file &&
 	ln -s elif file &&
-- 
2.51.1.797.g1148beab57
Previous: Jeff KingNext: Jeff King
Message 2 of 7 in “diff dry-run cleanups”
  1. 0/4 diff dry-run cleanupsJeff King, Oct 24, 2025
  2. 1/4 diff: send external diff output to diff_options.fileJeff King, Oct 24, 2025
  3. 2/4 diff: drop save/restore of color_moved in dry-run modeJeff King, Oct 24, 2025
  4. 3/4 diff: replace diff_options.dry_run flag with NULL fileJeff King, Oct 24, 2025
  5. Junio C HamanoOct 24, 2025
  6. 4/4 diff: drop dry-run redirection to /dev/nullJeff King, Oct 24, 2025
  7. 5/4 diff: simplify run_external_diff() quiet logicJeff King, Oct 24, 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.