Subject: [PATCH 2/2] diffcore-pickaxe: add --pickaxe-raw-diff for use with -G

From: Ævar Arnfjörð Bjarmason <[email-masked]>
Date: Wed, 24 Apr 2019 17:22:15 +0200

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-masked]/
2. https://public-inbox.org/git/[email-masked]/

Signed-off-by: Ævar Arnfjörð Bjarmason <[email-masked]>
---
 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