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 option in standalone diff utilities and can be used e.g. 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 at the same time. Apart from adding a new field to struct diff_options, also define a new flag, XDF_IGNORE_REGEX, for the 'xdl_opts' field of that structure. This is done because the xpparam_t structure (which is used for passing around the regular expression supplied to -I) is not zeroed out in all call stacks involving xdl_diff() and thus only performing a NULL check on xpp->ignore_regex could result in xdl_mark_ignorable_regex() treating garbage on the stack as a regular expression. As the 'flags' field of xpparam_t is initialized in all call stacks involving xdl_diff(), adding a flag check avoids that problem. Signed-off-by: Michał Kępień <michal@xxxxxxx> --- Documentation/diff-options.txt | 3 +++ diff.c | 16 +++++++++++++++ diff.h | 2 ++ xdiff/xdiff.h | 2 ++ xdiff/xdiffi.c | 36 ++++++++++++++++++++++++++++++++++ 5 files changed, 59 insertions(+) diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 573fb9bb71..a7ef3f5645 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -687,6 +687,9 @@ endif::git-format-patch[] --ignore-blank-lines:: Ignore changes whose lines are all blank. +-I<regex>:: + Ignore changes whose all lines match <regex>. + --inter-hunk-context=<lines>:: Show the context between diff hunks, up to the specified number of lines, thereby fusing hunks that are close to each other. diff --git a/diff.c b/diff.c index 2bb2f8f57e..c9603c941e 100644 --- a/diff.c +++ b/diff.c @@ -3587,6 +3587,7 @@ static void builtin_diff(const char *name_a, if (header.len && !o->flags.suppress_diff_headers) ecbdata.header = &header; xpp.flags = o->xdl_opts; + xpp.ignore_regex = o->ignore_regex; xpp.anchors = o->anchors; xpp.anchors_nr = o->anchors_nr; xecfg.ctxlen = o->context; @@ -3716,6 +3717,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b, memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xpp.flags = o->xdl_opts; + xpp.ignore_regex = o->ignore_regex; xpp.anchors = o->anchors; xpp.anchors_nr = o->anchors_nr; xecfg.ctxlen = o->context; @@ -5203,6 +5205,17 @@ static int diff_opt_patience(const struct option *opt, return 0; } +static int diff_opt_ignore_regex(const struct option *opt, + const char *arg, int unset) +{ + struct diff_options *options = opt->value; + + BUG_ON_OPT_NEG(unset); + options->xdl_opts |= XDF_IGNORE_REGEX; + options->ignore_regex = arg; + return 0; +} + static int diff_opt_pickaxe_regex(const struct option *opt, const char *arg, int unset) { @@ -5491,6 +5504,9 @@ static void prep_parse_options(struct diff_options *options) OPT_BIT_F(0, "ignore-blank-lines", &options->xdl_opts, N_("ignore changes whose lines are all blank"), XDF_IGNORE_BLANK_LINES, PARSE_OPT_NONEG), + OPT_CALLBACK_F('I', NULL, options, N_("<regex>"), + N_("ignore changes whose all lines match <regex>"), + 0, diff_opt_ignore_regex), OPT_BIT(0, "indent-heuristic", &options->xdl_opts, N_("heuristic to shift diff hunk boundaries for easy reading"), XDF_INDENT_HEURISTIC), diff --git a/diff.h b/diff.h index 3de343270f..0b8871c0c1 100644 --- a/diff.h +++ b/diff.h @@ -234,6 +234,8 @@ struct diff_options { */ const char *pickaxe; + const char *ignore_regex; + const char *single_follow; const char *a_prefix, *b_prefix; const char *line_prefix; diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 032e3a9f41..db28055a5e 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -40,6 +40,7 @@ extern "C" { XDF_IGNORE_CR_AT_EOL) #define XDF_IGNORE_BLANK_LINES (1 << 7) +#define XDF_IGNORE_REGEX (1 << 8) #define XDF_PATIENCE_DIFF (1 << 14) #define XDF_HISTOGRAM_DIFF (1 << 15) @@ -78,6 +79,7 @@ typedef struct s_mmbuffer { typedef struct s_xpparam { unsigned long flags; + const char *ignore_regex; /* See Documentation/diff-options.txt. */ char **anchors; diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index bd035139f9..13f7ab95ac 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -1019,6 +1019,39 @@ static void xdl_mark_ignorable(xdchange_t *xscr, xdfenv_t *xe, long flags) } } +static void +xdl_mark_ignorable_regex(xdchange_t *xscr, const xdfenv_t *xe, + const char *ignore_regex) +{ + xdchange_t *xch; + regex_t regex; + + if (regcomp(®ex, ignore_regex, REG_EXTENDED | REG_NEWLINE)) + die("invalid regex: %s", ignore_regex); + + for (xch = xscr; xch; xch = xch->next) { + regmatch_t regmatch; + xrecord_t **rec; + int ignore = 1; + long i; + + rec = &xe->xdf1.recs[xch->i1]; + for (i = 0; i < xch->chg1 && ignore; i++) + ignore = !regexec_buf(®ex, rec[i]->ptr, rec[i]->size, + 1, ®match, 0); + + rec = &xe->xdf2.recs[xch->i2]; + for (i = 0; i < xch->chg2 && ignore; i++) + ignore = !regexec_buf(®ex, rec[i]->ptr, rec[i]->size, + 1, ®match, 0); + + /* + * Do not override --ignore-blank-lines. + */ + xch->ignore |= ignore; + } +} + int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb) { xdchange_t *xscr; @@ -1040,6 +1073,9 @@ int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, if (xpp->flags & XDF_IGNORE_BLANK_LINES) xdl_mark_ignorable(xscr, &xe, xpp->flags); + if ((xpp->flags & XDF_IGNORE_REGEX) && xpp->ignore_regex) + xdl_mark_ignorable_regex(xscr, &xe, xpp->ignore_regex); + if (ef(&xe, xscr, ecb, xecfg) < 0) { xdl_free_script(xscr); -- 2.28.0