diff: stop output garbled message in dry run mode

Earlier, b55e6d36 (diff: ensure consistent diff behavior with ignore options, 2025-08-08) introduced "dry-run" mode to the diff machinery so that content-based diff filtering (like ignoring space changes or those that match -I<regex>) can first try to produce a patch without emitting any output to see if under the given diff filtering condition we would get any output lines, and a new helper function diff_flush_patch_quietly() was introduced to use the mode to see an individual filepair needs to be shown. However, the solution was not complete. When files are deleted, file modes change, or there are unmerged entries in the index, dry-run mode still produces output because we overlooked these conditions, and as a result, dry-run mode was not quiet. To fix this, return early in emit_diff_symbol_from_struct() if we are in dry-run mode. This function will be called by all the emit functions to output the results. Returning early can avoid diff output when files are deleted or file modes are changed. Stop print message in dry-run mode if we have unmerged entries in index. Discard output of external diff tool in dry-run mode. Signed-off-by: Lidong Yan <yldhome2d2@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Lidong Yan committed Oct 20, 2025 at 00:30 UTC 3ed5d8bd7366076fd049e735e363e5a77656743c
2 files changed +43 -2
diff.c
+6 -2
@@ -1351,6 +1351,9 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
1351 int len = eds->len;
1352 unsigned flags = eds->flags;
1353
1354 + if (o->dry_run)
1355 + return;
1356 +
1357 switch (s) {
1358 case DIFF_SYMBOL_NO_LF_EOF:
1359 context = diff_get_color_opt(o, DIFF_CONTEXT);
@@ -4420,7 +4423,7 @@ static void run_external_diff(const struct external_diff *pgm,
4423 {
4424 struct child_process cmd = CHILD_PROCESS_INIT;
4425 struct diff_queue_struct *q = &diff_queued_diff;
4423 - int quiet = !(o->output_format & DIFF_FORMAT_PATCH);
4426 + int quiet = !(o->output_format & DIFF_FORMAT_PATCH) || o->dry_run;
4427 int rc;
4428
4429 /*
@@ -4615,7 +4618,8 @@ static void run_diff_cmd(const struct external_diff *pgm,
4618 p->status == DIFF_STATUS_RENAMED)
4619 o->found_changes = 1;
4620 } else {
4618 - fprintf(o->file, "* Unmerged path %s\n", name);
4621 + if (!o->dry_run)
4622 + fprintf(o->file, "* Unmerged path %s\n", name);
4623 o->found_changes = 1;
4624 }
4625 }
t/t4013-diff-various.sh
+37
@@ -661,6 +661,43 @@ test_expect_success 'diff -I<regex>: ignore matching file' '
661 test_grep ! "file1" actual
662 '
663
664 +test_expect_success 'diff -I<regex>: ignore all content changes' '
665 + test_when_finished "git rm -f file1 file2 file3" &&
666 + : >file1 &&
667 + git add file1 &&
668 + : >file2 &&
669 + git add file2 &&
670 + : >file3 &&
671 + git add file3 &&
672 +
673 + rm -f file1 file2 &&
674 + mkdir file2 &&
675 + echo "A" >file3 &&
676 + A_hash=$(git hash-object -w file3) &&
677 + echo "B" >file3 &&
678 + B_hash=$(git hash-object -w file3) &&
679 + cat <<-EOF | git update-index --index-info &&
680 + 100644 $A_hash 1 file3
681 + 100644 $B_hash 2 file3
682 + EOF
683 +
684 + test_diff_no_content_changes () {
685 + git diff $1 --ignore-blank-lines -I".*" >actual &&
686 + test_line_count = 3 actual &&
687 + test_grep "file1" actual &&
688 + test_grep "file2" actual &&
689 + test_grep "file3" actual &&
690 + test_grep ! "diff --git" actual
691 + } &&
692 + test_diff_no_content_changes "--raw" &&
693 + test_diff_no_content_changes "--name-only" &&
694 + test_diff_no_content_changes "--name-status" &&
695 +
696 + : >actual &&
697 + test_must_fail git diff --quiet -I".*" >actual &&
698 + test_must_be_empty actual
699 +'
700 +
701 # check_prefix <patch> <src> <dst>
702 # check only lines with paths to avoid dependency on exact oid/contents
703 check_prefix () {