diff: add -I<regex> that ignores matching changes

Add a new diff option that enables ignoring changes whose all lines
(changed, removed, and added) match a given regular expression.  This is
similar to the -I/--ignore-matching-lines option in standalone diff
utilities and can be used e.g. to ignore changes which only affect code
comments or to look for unrelated changes in commits containing a large
number of automatically applied modifications (e.g. a tree-wide string
replacement).  The difference between -G/-S and the new -I option is
that the latter filters output on a per-change basis.

Use the 'ignore' field of xdchange_t for marking a change as ignored or
not.  Since the same field is used by --ignore-blank-lines, identical
hunk emitting rules apply for --ignore-blank-lines and -I.  These two
options can also be used together in the same git invocation (they are
complementary to each other).

Rename xdl_mark_ignorable() to xdl_mark_ignorable_lines(), to indicate
that it is logically a "sibling" of xdl_mark_ignorable_regex() rather
than its "parent".

Signed-off-by: Michał Kępień <michal@isc.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Michał Kępień 2020-10-20 08:48:09 +02:00 committed by Junio C Hamano
parent ec7967cfaf
commit 296d4a94e7
7 changed files with 221 additions and 2 deletions

View File

@ -681,6 +681,11 @@ endif::git-format-patch[]
--ignore-blank-lines:: --ignore-blank-lines::
Ignore changes whose lines are all blank. Ignore changes whose lines are all blank.
-I<regex>::
--ignore-matching-lines=<regex>::
Ignore changes whose all lines match <regex>. This option may
be specified more than once.
--inter-hunk-context=<lines>:: --inter-hunk-context=<lines>::
Show the context between diff hunks, up to the specified number Show the context between diff hunks, up to the specified number
of lines, thereby fusing hunks that are close to each other. of lines, thereby fusing hunks that are close to each other.

23
diff.c
View File

@ -3584,6 +3584,8 @@ static void builtin_diff(const char *name_a,
if (header.len && !o->flags.suppress_diff_headers) if (header.len && !o->flags.suppress_diff_headers)
ecbdata.header = &header; ecbdata.header = &header;
xpp.flags = o->xdl_opts; xpp.flags = o->xdl_opts;
xpp.ignore_regex = o->ignore_regex;
xpp.ignore_regex_nr = o->ignore_regex_nr;
xpp.anchors = o->anchors; xpp.anchors = o->anchors;
xpp.anchors_nr = o->anchors_nr; xpp.anchors_nr = o->anchors_nr;
xecfg.ctxlen = o->context; xecfg.ctxlen = o->context;
@ -3711,6 +3713,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
memset(&xpp, 0, sizeof(xpp)); memset(&xpp, 0, sizeof(xpp));
memset(&xecfg, 0, sizeof(xecfg)); memset(&xecfg, 0, sizeof(xecfg));
xpp.flags = o->xdl_opts; xpp.flags = o->xdl_opts;
xpp.ignore_regex = o->ignore_regex;
xpp.ignore_regex_nr = o->ignore_regex_nr;
xpp.anchors = o->anchors; xpp.anchors = o->anchors;
xpp.anchors_nr = o->anchors_nr; xpp.anchors_nr = o->anchors_nr;
xecfg.ctxlen = o->context; xecfg.ctxlen = o->context;
@ -5174,6 +5178,22 @@ static int diff_opt_patience(const struct option *opt,
return 0; return 0;
} }
static int diff_opt_ignore_regex(const struct option *opt,
const char *arg, int unset)
{
struct diff_options *options = opt->value;
regex_t *regex;
BUG_ON_OPT_NEG(unset);
regex = xmalloc(sizeof(*regex));
if (regcomp(regex, arg, REG_EXTENDED | REG_NEWLINE))
return error(_("invalid regex given to -I: '%s'"), arg);
ALLOC_GROW(options->ignore_regex, options->ignore_regex_nr + 1,
options->ignore_regex_alloc);
options->ignore_regex[options->ignore_regex_nr++] = regex;
return 0;
}
static int diff_opt_pickaxe_regex(const struct option *opt, static int diff_opt_pickaxe_regex(const struct option *opt,
const char *arg, int unset) const char *arg, int unset)
{ {
@ -5462,6 +5482,9 @@ static void prep_parse_options(struct diff_options *options)
OPT_BIT_F(0, "ignore-blank-lines", &options->xdl_opts, OPT_BIT_F(0, "ignore-blank-lines", &options->xdl_opts,
N_("ignore changes whose lines are all blank"), N_("ignore changes whose lines are all blank"),
XDF_IGNORE_BLANK_LINES, PARSE_OPT_NONEG), XDF_IGNORE_BLANK_LINES, PARSE_OPT_NONEG),
OPT_CALLBACK_F('I', "ignore-matching-lines", options, N_("<regex>"),
N_("ignore changes whose all lines match <regex>"),
0, diff_opt_ignore_regex),
OPT_BIT(0, "indent-heuristic", &options->xdl_opts, OPT_BIT(0, "indent-heuristic", &options->xdl_opts,
N_("heuristic to shift diff hunk boundaries for easy reading"), N_("heuristic to shift diff hunk boundaries for easy reading"),
XDF_INDENT_HEURISTIC), XDF_INDENT_HEURISTIC),

4
diff.h
View File

@ -234,6 +234,10 @@ struct diff_options {
*/ */
const char *pickaxe; const char *pickaxe;
/* -I<regex> */
regex_t **ignore_regex;
size_t ignore_regex_nr, ignore_regex_alloc;
const char *single_follow; const char *single_follow;
const char *a_prefix, *b_prefix; const char *a_prefix, *b_prefix;
const char *line_prefix; const char *line_prefix;

View File

@ -6,6 +6,7 @@
test_description='Various diff formatting options' test_description='Various diff formatting options'
. ./test-lib.sh . ./test-lib.sh
. "$TEST_DIRECTORY"/diff-lib.sh
test_expect_success setup ' test_expect_success setup '
@ -309,6 +310,7 @@ log -SF master --max-count=2
log -GF master log -GF master
log -GF -p master log -GF -p master
log -GF -p --pickaxe-all master log -GF -p --pickaxe-all master
log -IA -IB -I1 -I2 -p master
log --decorate --all log --decorate --all
log --decorate=full --all log --decorate=full --all
@ -449,4 +451,43 @@ test_expect_success 'diff-tree --stdin with log formatting' '
test_cmp expect actual test_cmp expect actual
' '
test_expect_success 'diff -I<regex>: setup' '
git checkout master &&
test_seq 50 >file0 &&
git commit -m "Set up -I<regex> test file" file0 &&
test_seq 50 | sed -e "s/13/ten and three/" -e "/7\$/d" >file0 &&
echo >>file0
'
test_expect_success 'diff -I<regex>' '
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
'
test_expect_success 'diff -I<regex> --stat' '
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
'
test_expect_success 'diff -I<regex>: detect malformed regex' '
test_expect_code 129 git diff --ignore-matching-lines="^[124-9" 2>error &&
test_i18ngrep "invalid regex given to -I: " error
'
test_done test_done

View File

@ -0,0 +1,99 @@
$ git log -IA -IB -I1 -I2 -p master
commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
Merge: 9a6d494 c7a2ab9
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:04:00 2006 +0000
Merge branch 'side'
commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:03:00 2006 +0000
Side
diff --git a/file0 b/file0
index 01e79c3..f4615da 100644
--- a/file0
+++ b/file0
@@ -1,3 +1,6 @@
1
2
3
+A
+B
+C
diff --git a/file3 b/file3
new file mode 100644
index 0000000..7289e35
commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:02:00 2006 +0000
Third
diff --git a/dir/sub b/dir/sub
index 8422d40..cead32e 100644
--- a/dir/sub
+++ b/dir/sub
@@ -2,3 +2,5 @@ A
B
C
D
+E
+F
diff --git a/file1 b/file1
new file mode 100644
index 0000000..b1e6722
--- /dev/null
+++ b/file1
@@ -0,0 +1,3 @@
+A
+B
+C
commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:01:00 2006 +0000
Second
This is the second commit.
diff --git a/dir/sub b/dir/sub
index 35d242b..8422d40 100644
--- a/dir/sub
+++ b/dir/sub
@@ -1,2 +1,4 @@
A
B
+C
+D
diff --git a/file0 b/file0
index 01e79c3..b414108 100644
--- a/file0
+++ b/file0
@@ -1,3 +1,6 @@
1
2
3
+4
+5
+6
diff --git a/file2 b/file2
deleted file mode 100644
index 01e79c3..0000000
--- a/file2
+++ /dev/null
@@ -1,3 +0,0 @@
-1
-2
-3
commit 444ac553ac7612cc88969031b02b3767fb8a353a
Author: A U Thor <author@example.com>
Date: Mon Jun 26 00:00:00 2006 +0000
Initial
$

View File

@ -79,6 +79,10 @@ typedef struct s_mmbuffer {
typedef struct s_xpparam { typedef struct s_xpparam {
unsigned long flags; unsigned long flags;
/* -I<regex> */
regex_t **ignore_regex;
size_t ignore_regex_nr;
/* See Documentation/diff-options.txt. */ /* See Documentation/diff-options.txt. */
char **anchors; char **anchors;
size_t anchors_nr; size_t anchors_nr;

View File

@ -998,7 +998,7 @@ static int xdl_call_hunk_func(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
return 0; return 0;
} }
static void xdl_mark_ignorable(xdchange_t *xscr, xdfenv_t *xe, long flags) static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags)
{ {
xdchange_t *xch; xdchange_t *xch;
@ -1019,6 +1019,46 @@ static void xdl_mark_ignorable(xdchange_t *xscr, xdfenv_t *xe, long flags)
} }
} }
static int record_matches_regex(xrecord_t *rec, xpparam_t const *xpp) {
regmatch_t regmatch;
int i;
for (i = 0; i < xpp->ignore_regex_nr; i++)
if (!regexec_buf(xpp->ignore_regex[i], rec->ptr, rec->size, 1,
&regmatch, 0))
return 1;
return 0;
}
static void xdl_mark_ignorable_regex(xdchange_t *xscr, const xdfenv_t *xe,
xpparam_t const *xpp)
{
xdchange_t *xch;
for (xch = xscr; xch; xch = xch->next) {
xrecord_t **rec;
int ignore = 1;
long i;
/*
* Do not override --ignore-blank-lines.
*/
if (xch->ignore)
continue;
rec = &xe->xdf1.recs[xch->i1];
for (i = 0; i < xch->chg1 && ignore; i++)
ignore = record_matches_regex(rec[i], xpp);
rec = &xe->xdf2.recs[xch->i2];
for (i = 0; i < xch->chg2 && ignore; i++)
ignore = record_matches_regex(rec[i], xpp);
xch->ignore = ignore;
}
}
int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
xdemitconf_t const *xecfg, xdemitcb_t *ecb) { xdemitconf_t const *xecfg, xdemitcb_t *ecb) {
xdchange_t *xscr; xdchange_t *xscr;
@ -1038,7 +1078,10 @@ int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
} }
if (xscr) { if (xscr) {
if (xpp->flags & XDF_IGNORE_BLANK_LINES) if (xpp->flags & XDF_IGNORE_BLANK_LINES)
xdl_mark_ignorable(xscr, &xe, xpp->flags); xdl_mark_ignorable_lines(xscr, &xe, xpp->flags);
if (xpp->ignore_regex)
xdl_mark_ignorable_regex(xscr, &xe, xpp);
if (ef(&xe, xscr, ecb, xecfg) < 0) { if (ef(&xe, xscr, ecb, xecfg) < 0) {