Add the ability for the -G<regex> pickaxe to search only through added
or removed lines in the diff, or even through an arbitrary amount of
context lines when combined with -U<n>.

This has been requested[1][2] a few times in the past, and isn't
currently possible. Instead users need to do -G<regex> and then write
their own post-parsing script to see if the <regex> matched added or
removed lines, or both. There was no way to match the adjacent context
lines other than running and grepping the equivalent of a "log -p -U<n>".

1. https://public-inbox.org/git/[email protected]/
2. https://public-inbox.org/git/[email protected]/

Signed-off-by: Ævar Arnfjörð Bjarmason <[email protected]>
---
 Documentation/diff-options.txt | 17 +++++++++++++
 diff.c                         |  3 +++
 diff.h                         |  2 ++
 diffcore-pickaxe.c             | 34 ++++++++++++++++++++++---
 t/t4013-diff-various.sh        |  1 +
 t/t4209-log-pickaxe.sh         | 45 ++++++++++++++++++++++++++++++++++
 6 files changed, 98 insertions(+), 4 deletions(-)

diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 09faee3b44..f367b40362 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -579,6 +579,9 @@ occurrences of that string did not change).
 Unless `--text` is supplied patches of binary files without a textconv
 filter will be ignored.
 +
+When `--pickaxe-raw-diff` is supplied the whole diff is searched
+instead of just added/removed lines. See below.
++
 See the 'pickaxe' entry in linkgit:gitdiffcore[7] for more
 information.
 
@@ -600,6 +603,20 @@ The object can be a blob or a submodule commit. It implies 
the `-t` option in
        Treat the <string> given to `-S` as an extended POSIX regular
        expression to match.
 
+--pickaxe-raw-diff::
+       When `-G` looks for a change a diff will be generated, and
+       only the added/removed lines will be matched against with the
+       "+" or "-" stripped.
++
+Supplying this option skips that pre-processing. This makes it
+possible to match only lines that added or removed something matching
+a <regex> with "\^\+<regex>" and "^-<regex>", respectively.
++
+It also allows for finding something in the diff context. E.g. "\^
+<regex>" will match the context lines (see `-U<n>` above) around the
+added/removed lines, and doing an unanchored match will match any of
+the the added/removed lines & diff context.
+
 endif::git-format-patch[]
 
 -O<orderfile>::
diff --git a/diff.c b/diff.c
index 4d3cf83a27..4cdc000ee5 100644
--- a/diff.c
+++ b/diff.c
@@ -5503,6 +5503,9 @@ static void prep_parse_options(struct diff_options 
*options)
                OPT_BIT_F(0, "pickaxe-regex", &options->pickaxe_opts,
                          N_("treat <string> in -S as extended POSIX regular 
expression"),
                          DIFF_PICKAXE_REGEX, PARSE_OPT_NONEG),
+               OPT_BIT_F(0, "pickaxe-raw-diff", &options->pickaxe_opts,
+                         N_("have <string> in -G match the raw diff output"),
+                         DIFF_PICKAXE_G_RAW_DIFF, PARSE_OPT_NONEG),
                OPT_FILENAME('O', NULL, &options->orderfile,
                             N_("control the order in which files appear in the 
output")),
                OPT_CALLBACK_F(0, "find-object", options, N_("<object-id>"),
diff --git a/diff.h b/diff.h
index b20cbcc091..d431fbc602 100644
--- a/diff.h
+++ b/diff.h
@@ -370,6 +370,8 @@ int git_config_rename(const char *var, const char *value);
                                 DIFF_PICKAXE_KIND_OBJFIND)
 
 #define DIFF_PICKAXE_IGNORE_CASE       32
+#define DIFF_PICKAXE_G_RAW_DIFF                64
+
 
 void diffcore_std(struct diff_options *);
 void diffcore_fix_diff_index(void);
diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c
index 3c6416bfe2..e23f04b4f0 100644
--- a/diffcore-pickaxe.c
+++ b/diffcore-pickaxe.c
@@ -17,14 +17,18 @@ typedef int (*pickaxe_fn)(mmfile_t *one, mmfile_t *two,
 struct diffgrep_cb {
        regex_t *regexp;
        int hit;
+       int raw_diff;
 };
 
 static void diffgrep_consume(void *priv, char *line, unsigned long len)
 {
        struct diffgrep_cb *data = priv;
        regmatch_t regmatch;
+       int raw_diff = data->raw_diff;
+       const char *string = raw_diff ? line : line + 1;
+       size_t size = raw_diff ? len : len - 1;
 
-       if (line[0] != '+' && line[0] != '-')
+       if (!raw_diff && line[0] != '+' && line[0] != '-')
                return;
        if (data->hit)
                /*
@@ -32,7 +36,7 @@ static void diffgrep_consume(void *priv, char *line, unsigned 
long len)
                 * caller early.
                 */
                return;
-       data->hit = !regexec_buf(data->regexp, line + 1, len - 1, 1,
+       data->hit = !regexec_buf(data->regexp, string, size, 1,
                                 &regmatch, 0);
 }
 
@@ -44,15 +48,36 @@ static int diff_grep(mmfile_t *one, mmfile_t *two,
        struct diffgrep_cb ecbdata;
        xpparam_t xpp;
        xdemitconf_t xecfg;
+       int raw_diff = o->pickaxe_opts & DIFF_PICKAXE_G_RAW_DIFF;
+       struct strbuf sb = STRBUF_INIT;
+       char *string;
+       size_t size;
 
        if (!one || !two) {
                mmfile_t *which = one ? one : two;
                int ret;
-               char *string = which->ptr;
-               size_t size = which->size;
                assert(!(!one && !two));
+               if (raw_diff) {
+                       /*
+                        * When we have created/deleted files with
+                        * --pickaxe-raw-diff we need to fake up the
+                        * "+" and "-" at the start of the lines, a
+                        * plain -G without --pickaxe-raw-diff didn't
+                        * care since it would indiscriminately search
+                        * through both added and removed lines.
+                        */
+                       strbuf_add_lines(&sb, !one ? "+" : "-", which->ptr,
+                                        which->size);
+                       string = sb.buf;
+                       size = sb.len;
+               } else {
+                       string = which->ptr;
+                       size = which->size;
+               }
                ret = !regexec_buf(regexp, string, size,
                                   1, &regmatch, 0);
+               if (raw_diff)
+                       strbuf_release(&sb);
                return ret;
        }
 
@@ -64,6 +89,7 @@ static int diff_grep(mmfile_t *one, mmfile_t *two,
        memset(&xecfg, 0, sizeof(xecfg));
        ecbdata.regexp = regexp;
        ecbdata.hit = 0;
+       ecbdata.raw_diff = raw_diff;
        xecfg.ctxlen = o->context;
        xecfg.interhunkctxlen = o->interhunkcontext;
        if (xdi_diff_outf(one, two, discard_hunk_line, diffgrep_consume,
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index 9f8f0e84ad..39a1f6c230 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -276,6 +276,7 @@ log -SF master --max-count=1
 log -SF master --max-count=2
 log -GF master
 log -GF -p master
+log -GF -p --pickaxe-raw-diff master
 log -GF -p --pickaxe-all master
 log --decorate --all
 log --decorate=full --all
diff --git a/t/t4209-log-pickaxe.sh b/t/t4209-log-pickaxe.sh
index 5d06f5f45e..2d98318d23 100755
--- a/t/t4209-log-pickaxe.sh
+++ b/t/t4209-log-pickaxe.sh
@@ -141,4 +141,49 @@ test_expect_success 'log -S looks into binary files' '
        test_cmp log full-log
 '
 
+test_expect_success 'setup log -G --pickaxe-raw-diff' '
+       git checkout --orphan G-raw-diff &&
+       test_write_lines A B C D E F G >file &&
+       git add file &&
+       git commit --allow-empty-message file &&
+       sed -i -e "s/B/2/" file &&
+       git add file &&
+       git commit --allow-empty-message file &&
+       sed -i -e "s/D/4/" file &&
+       git add file &&
+       git commit --allow-empty-message file &&
+       git rm file &&
+       git commit --allow-empty-message &&
+       git log --oneline -1 HEAD~0 >file.fourth &&
+       git log --oneline -1 HEAD~1 >file.third &&
+       git log --oneline -1 HEAD~2 >file.second &&
+       git log --oneline -1 HEAD~3 >file.first
+'
+
+test_expect_success 'log -G --pickaxe-raw-diff skips header and range 
information' '
+       git log --pickaxe-raw-diff -p -G"(@@|file)" >log &&
+       test_must_be_empty log
+'
+
+test_expect_success 'log -G --pickaxe-raw-diff searching in context' '
+       git log --oneline --pickaxe-raw-diff -G"^ F" -U2 -s >log &&
+       test_cmp file.third log &&
+       git log --oneline --pickaxe-raw-diff -G"^ F" -U1 -s >log &&
+       test_must_be_empty log
+'
+
+test_expect_success 'log -G --pickaxe-raw-diff searching added / removed lines 
(skip create/delete)' '
+       git log --oneline --pickaxe-raw-diff -G"^-[D2]" -s HEAD~1 >log &&
+       test_cmp file.third log &&
+       git log --oneline --pickaxe-raw-diff -G"^\+[D2]" -s -1 >log &&
+       test_cmp file.second log
+'
+
+test_expect_success 'log -G --pickaxe-raw-diff searching created / deleted 
files' '
+       git log --oneline --pickaxe-raw-diff -G"^\+A" -s >log &&
+       test_cmp file.first log &&
+       git log --oneline --pickaxe-raw-diff -G"^\-A" -s >log &&
+       test_cmp file.fourth log
+'
+
 test_done
-- 
2.21.0.593.g511ec345e18

Reply via email to