From: Lidong Yan <yldhome2d2@gmail.com>
To: gitster@pobox.com
Cc: git@vger.kernel.org, hi@arnes.space, michal@isc.org,
peff@peff.net, yldhome2d2@gmail.com
Subject: [PATCH] diff: ensure consistent diff behavior with -I<regex> across output formats
Date: Tue, 29 Jul 2025 16:18:20 +0800 [thread overview]
Message-ID: <20250729081820.34626-1-yldhome2d2@gmail.com> (raw)
In-Reply-To: <xmqqikjg47qt.fsf@gitster.g>
Previously, the `-I<regex>` option was inconsistently applied across
various `git diff` output formats. In some cases, files would appear
in the `--name-only` output but not in the accompanying `--stat` or
`-p` outputs, despite the user explicitly requesting to ignore certain
changes using `-I<regex>`. To provide this consistency, Introduces
the diffcore_ignore() function in the new diffcore-ignore.c file, which
removes changes matching `-I<regex>`, and call diffcore_ignore() in
diffcore_std().
This patch ensures that the behavior of `-I<regex>` is applied
consistently across multiple diff output formats (`--name-only`,
`--name-status`, `--stat`, and `-p`). Only `--raw` and `--check` will
ignore `-I<regex>` and retain the original output.
Signed-off-by: Lidong Yan <yldhome2d2@gmail.com>
---
Makefile | 1 +
diff.c | 2 +
diffcore-ignore.c | 152 ++++++++++++++++++++++++++++++++++++++++
diffcore.h | 1 +
t/t4013-diff-various.sh | 57 ++++++++++++++-
5 files changed, 211 insertions(+), 2 deletions(-)
create mode 100644 diffcore-ignore.c
diff --git a/Makefile b/Makefile
index 5f7dd79dfa..2cdbd5d3fb 100644
--- a/Makefile
+++ b/Makefile
@@ -1014,6 +1014,7 @@ LIB_OBJS += diff-no-index.o
LIB_OBJS += diff.o
LIB_OBJS += diffcore-break.o
LIB_OBJS += diffcore-delta.o
+LIB_OBJS += diffcore-ignore.o
LIB_OBJS += diffcore-order.o
LIB_OBJS += diffcore-pickaxe.o
LIB_OBJS += diffcore-rename.o
diff --git a/diff.c b/diff.c
index dca87e164f..b9eed89531 100644
--- a/diff.c
+++ b/diff.c
@@ -7088,6 +7088,8 @@ void diffcore_std(struct diff_options *options)
}
if (options->pickaxe_opts & DIFF_PICKAXE_KINDS_MASK)
diffcore_pickaxe(options);
+ if (options->ignore_regex)
+ diffcore_ignore(options);
if (options->orderfile)
diffcore_order(options->orderfile);
if (options->rotate_to)
diff --git a/diffcore-ignore.c b/diffcore-ignore.c
new file mode 100644
index 0000000000..1578b264c1
--- /dev/null
+++ b/diffcore-ignore.c
@@ -0,0 +1,152 @@
+#include "git-compat-util.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "xdiff-interface.h"
+
+#define KEEP 0
+#define IGNORE 1
+
+struct diffignore_cb {
+ regex_t **regex;
+ size_t sz;
+ int miss;
+};
+
+/*
+ * Check for any added or deleted line that does not match the ignore pattern.
+ * If found, mark data->miss=1 and return early.
+ */
+static int diffignore_consume(void *priv, char *line, unsigned long len)
+{
+ struct diffignore_cb *data = priv;
+
+ if (line[0] != '+' && line[0] != '-')
+ return 0;
+ if (data->miss)
+ BUG("Already matched in diffignore_consume! Broken xdiff_emit_line_fn?");
+
+ /* check if any of the regexes match */
+ data->miss = 1;
+ for (size_t nr = 0; nr < data->sz; nr++) {
+ regmatch_t regmatch;
+ regex_t *regex = data->regex[nr];
+
+ if (!regexec_buf(regex, line + 1, len - 1, 1,
+ ®match, 0)) {
+ data->miss = 0;
+ break;
+ }
+ }
+
+ /* stop looking for miss */
+ if (data->miss)
+ return 1;
+ return 0;
+}
+
+/* return IGNORE to ignore this change, return KEEP to keep it. */
+static int diff_ignore(mmfile_t *one, mmfile_t *two, struct diff_options *o)
+{
+ struct diffignore_cb ecbdata;
+ xpparam_t xpp;
+ xdemitconf_t xecfg;
+ int ret;
+
+ /*
+ * We have both sides; need to run textual diff and check if
+ * there are any unmatched (non-ignored) added or deleted lines.
+ */
+ memset(&xpp, 0, sizeof(xpp));
+ memset(&xecfg, 0, sizeof(xecfg));
+ ecbdata.regex = o->ignore_regex;
+ ecbdata.sz = o->ignore_regex_nr;
+ ecbdata.miss = 0;
+ xecfg.flags = XDL_EMIT_NO_HUNK_HDR;
+ xecfg.ctxlen = 0;
+ xecfg.interhunkctxlen = 0;
+
+ /*
+ * An xdiff error might be our "data->miss" from above. See the
+ * comment for xdiff_emit_line_fn in xdiff-interface.h
+ */
+ ret = xdi_diff_outf(one, two, NULL, diffignore_consume,
+ &ecbdata, &xpp, &xecfg);
+
+ /* error happened, keep this change */
+ if (ret)
+ return KEEP;
+ /* If no regex matches, keep this change */
+ return ecbdata.miss ? KEEP : IGNORE;
+}
+
+/* return IGNORE to ignore this change, return KEEP to keep it. */
+static int ignore_match(struct diff_filepair *p, struct diff_options *o)
+{
+ struct userdiff_driver *textconv_one = NULL;
+ struct userdiff_driver *textconv_two = NULL;
+ mmfile_t mf1, mf2;
+ int ret;
+
+ /* keep unmerged */
+ if (!DIFF_FILE_VALID(p->one) && !DIFF_FILE_VALID(p->two))
+ return KEEP;
+
+ if (o->flags.allow_textconv) {
+ textconv_one = get_textconv(o->repo, p->one);
+ textconv_two = get_textconv(o->repo, p->two);
+ }
+
+ /* unmodified pair will be ignored anyway */
+ if (textconv_one == textconv_two && diff_unmodified_pair(p))
+ return IGNORE;
+
+ /* keep binary files if diff cannot be performed */
+ if (!o->flags.text &&
+ ((!textconv_one && diff_filespec_is_binary(o->repo, p->one)) ||
+ (!textconv_two && diff_filespec_is_binary(o->repo, p->two))))
+ return KEEP;
+
+ mf1.size = fill_textconv(o->repo, textconv_one, p->one, &mf1.ptr);
+ mf2.size = fill_textconv(o->repo, textconv_two, p->two, &mf2.ptr);
+
+ ret = diff_ignore(&mf1, &mf2, o);
+
+ if (textconv_one)
+ free(mf1.ptr);
+ if (textconv_two)
+ free(mf2.ptr);
+ diff_free_filespec_data(p->one);
+ diff_free_filespec_data(p->two);
+
+ return ret;
+}
+
+void diffcore_ignore(struct diff_options *o)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ struct diff_queue_struct outq = DIFF_QUEUE_INIT;
+
+ if (o->output_format &
+ (DIFF_FORMAT_PATCH |
+ DIFF_FORMAT_RAW |
+ DIFF_FORMAT_CHECKDIFF)) {
+ /*
+ * If we are generating a patch, let builtin_diff() ignore
+ * changes and return early here. If we are generating raw
+ * output or detecting malformed code, we cannot ignore
+ * changes, so we return early.
+ */
+ return;
+ }
+
+ for (int i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (ignore_match(p, o))
+ diff_free_filepair(p);
+ else
+ diff_q(&outq, p);
+ }
+
+ free(q->queue);
+ *q = outq;
+}
diff --git a/diffcore.h b/diffcore.h
index 9c0a0e7aaf..97e6e3553b 100644
--- a/diffcore.h
+++ b/diffcore.h
@@ -189,6 +189,7 @@ void diffcore_rename_extended(struct diff_options *options,
struct strmap *cached_pairs);
void diffcore_merge_broken(void);
void diffcore_pickaxe(struct diff_options *);
+void diffcore_ignore(struct diff_options *);
void diffcore_order(const char *orderfile);
void diffcore_rotate(struct diff_options *);
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index 8ebd170451..344623ecbe 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -609,10 +609,10 @@ test_expect_success 'show A B ... -- <pathspec>' '
test_cmp expect actual
'
-test_expect_success 'diff -I<regex>: setup' '
+test_expect_success 'diff -I<regex>: setup file0' '
git checkout master &&
test_seq 50 >file0 &&
- git commit -m "Set up -I<regex> test file" file0 &&
+ git commit -m "Set up -I<regex> test file0" file0 &&
test_seq 50 | sed -e "s/13/ten and three/" -e "/7\$/d" >file0 &&
echo >>file0
'
@@ -643,6 +643,59 @@ test_expect_success 'diff -I<regex> --stat' '
test_cmp expect actual
'
+test_expect_success 'diff -I<regex>: setup file1' '
+ test_seq 50 >file1 &&
+ git add file1 &&
+ test_seq 50 | sed -e "s/13/ten and three/" -e "/^[124-9]/ s/\$/ /" >file1
+'
+
+test_expect_success 'diff -I<regex>: ignore file1' '
+ git diff --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >actual &&
+ cat >expect <<-\EOF &&
+ diff --git a/file0 b/file0
+ --- a/file0
+ +++ b/file0
+ @@ -34,7 +31,6 @@
+ 34
+ 35
+ 36
+ -37
+ 38
+ 39
+ 40
+ EOF
+ compare_diff_patch expect actual &&
+
+ git diff --stat --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >actual &&
+ cat >expect <<-\EOF &&
+ file0 | 1 -
+ 1 file changed, 1 deletion(-)
+ EOF
+ test_cmp expect actual &&
+
+ git diff --name-status --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >actual &&
+ cat >expect <<-\EOF &&
+ M file0
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'diff -I<regex> --raw: --raw ignores -I<regex>' '
+ git diff --raw >expect &&
+ git diff --raw --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'diff -I<regex> --check: --check ignores -I<regex>' '
+ test_must_fail git diff --check 2>&1 >expect &&
+ test_must_fail git diff --check --ignore-blank-lines -I"ten.*e" -I"^[124-9]" 2>&1 >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'diff -I<regex>: rm file1' '
+ git rm -f file1
+'
+
test_expect_success 'diff -I<regex>: detect malformed regex' '
test_expect_code 129 git diff --ignore-matching-lines="^[124-9" 2>error &&
test_grep "invalid regex given to -I: " error
--
2.50.1.440.g2157409008
next prev parent reply other threads:[~2025-07-29 8:19 UTC|newest]
Thread overview: 35+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-07-23 5:47 git-diff: --ignore-matching-lines has no effect on the output when --name-only is used hi
2025-07-23 8:00 ` Lidong Yan
2025-07-23 17:09 ` Junio C Hamano
2025-07-24 1:56 ` Lidong Yan
2025-07-24 2:16 ` Eric Sunshine
2025-07-24 3:38 ` Lidong Yan
2025-07-25 6:00 ` hi
2025-07-25 6:06 ` hi
2025-07-25 6:46 ` Lidong Yan
2025-07-25 8:08 ` hi
2025-07-25 11:11 ` Jeff King
2025-07-25 15:20 ` Junio C Hamano
2025-07-29 8:18 ` Lidong Yan [this message]
2025-07-30 0:28 ` [PATCH] diff: ensure consistent diff behavior with -I<regex> across output formats Junio C Hamano
2025-08-02 10:22 ` Jeff King
2025-08-03 8:42 ` Lidong Yan
2025-08-03 15:43 ` Junio C Hamano
2025-08-04 4:39 ` Junio C Hamano
2025-08-04 12:42 ` Jeff King
2025-08-03 14:51 ` [PATCH v2] " Lidong Yan
2025-08-04 0:39 ` Junio C Hamano
2025-08-04 1:56 ` Lidong Yan
2025-08-04 4:36 ` Junio C Hamano
2025-08-05 9:23 ` Lidong Yan
2025-08-05 16:11 ` Junio C Hamano
2025-08-06 12:33 ` [PATCH v3] diff: ensure consistent diff behavior with ignore options Lidong Yan
2025-08-06 17:35 ` Junio C Hamano
2025-08-07 1:23 ` Lidong Yan
2025-08-06 20:56 ` Junio C Hamano
2025-08-07 1:39 ` Lidong Yan
2025-08-07 2:06 ` [PATCH v4] " Lidong Yan
2025-08-07 21:27 ` Junio C Hamano
2025-08-08 1:46 ` Lidong Yan
2025-08-08 3:30 ` [PATCH v5] " Lidong Yan
2025-10-16 14:55 ` Johannes Schindelin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250729081820.34626-1-yldhome2d2@gmail.com \
--to=yldhome2d2@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=hi@arnes.space \
--cc=michal@isc.org \
--cc=peff@peff.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).