Skip to content

Commit f506b8e

Browse files
committed
git log/diff: add -G<regexp> that greps in the patch text
Teach "-G<regexp>" that is similar to "-S<regexp> --pickaxe-regexp" to the "git diff" family of commands. This limits the diff queue to filepairs whose patch text actually has an added or a deleted line that matches the given regexp. Unlike "-S<regexp>", changing other parts of the line that has a substring that matches the given regexp IS counted as a change, as such a change would appear as one deletion followed by one addition in a patch text. Unlike -S (pickaxe) that is intended to be used to quickly detect a commit that changes the number of occurrences of hits between the preimage and the postimage to serve as a part of larger toolchain, this is meant to be used as the top-level Porcelain feature. The implementation unfortunately has to run "diff" twice if you are running "log" family of commands to produce patches in the final output (e.g. "git log -p" or "git format-patch"). I think we _could_ cache the result in-core if we wanted to, but that would require larger surgery to the diffcore machinery (i.e. adding an extra pointer in the filepair structure to keep a pointer to a strbuf around, stuff the textual diff to the strbuf inside diffgrep_consume(), and make use of it in later stages when it is available) and it may not be worth it. Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 382f013 commit f506b8e

File tree

4 files changed

+164
-4
lines changed

4 files changed

+164
-4
lines changed

Documentation/diff-options.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,12 @@ ifndef::git-format-patch[]
284284
appearing in diff output; see the 'pickaxe' entry in
285285
linkgit:gitdiffcore[7] for more details.
286286

287+
-G<regex>::
288+
Look for differences whose added or removed line matches
289+
the given <regex>.
290+
287291
--pickaxe-all::
288-
When `-S` finds a change, show all the changes in that
292+
When `-S` or `-G` finds a change, show all the changes in that
289293
changeset, not just the files that contain the change
290294
in <string>.
291295

diff.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3268,12 +3268,17 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
32683268
}
32693269
else if ((argcount = short_opt('S', av, &optarg))) {
32703270
options->pickaxe = optarg;
3271+
options->pickaxe_opts |= DIFF_PICKAXE_KIND_S;
3272+
return argcount;
3273+
} else if ((argcount = short_opt('G', av, &optarg))) {
3274+
options->pickaxe = optarg;
3275+
options->pickaxe_opts |= DIFF_PICKAXE_KIND_G;
32713276
return argcount;
32723277
}
32733278
else if (!strcmp(arg, "--pickaxe-all"))
3274-
options->pickaxe_opts = DIFF_PICKAXE_ALL;
3279+
options->pickaxe_opts |= DIFF_PICKAXE_ALL;
32753280
else if (!strcmp(arg, "--pickaxe-regex"))
3276-
options->pickaxe_opts = DIFF_PICKAXE_REGEX;
3281+
options->pickaxe_opts |= DIFF_PICKAXE_REGEX;
32773282
else if ((argcount = short_opt('O', av, &optarg))) {
32783283
options->orderfile = optarg;
32793284
return argcount;

diff.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ extern int diff_setup_done(struct diff_options *);
238238
#define DIFF_PICKAXE_ALL 1
239239
#define DIFF_PICKAXE_REGEX 2
240240

241+
#define DIFF_PICKAXE_KIND_S 4 /* traditional plumbing counter */
242+
#define DIFF_PICKAXE_KIND_G 8 /* grep in the patch */
243+
241244
extern void diffcore_std(struct diff_options *);
242245
extern void diffcore_fix_diff_index(struct diff_options *);
243246

diffcore-pickaxe.c

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,148 @@
11
/*
22
* Copyright (C) 2005 Junio C Hamano
3+
* Copyright (C) 2010 Google Inc.
34
*/
45
#include "cache.h"
56
#include "diff.h"
67
#include "diffcore.h"
8+
#include "xdiff-interface.h"
9+
10+
struct diffgrep_cb {
11+
regex_t *regexp;
12+
int hit;
13+
};
14+
15+
static void diffgrep_consume(void *priv, char *line, unsigned long len)
16+
{
17+
struct diffgrep_cb *data = priv;
18+
regmatch_t regmatch;
19+
int hold;
20+
21+
if (line[0] != '+' && line[0] != '-')
22+
return;
23+
if (data->hit)
24+
/*
25+
* NEEDSWORK: we should have a way to terminate the
26+
* caller early.
27+
*/
28+
return;
29+
/* Yuck -- line ought to be "const char *"! */
30+
hold = line[len];
31+
line[len] = '\0';
32+
data->hit = !regexec(data->regexp, line + 1, 1, &regmatch, 0);
33+
line[len] = hold;
34+
}
35+
36+
static void fill_one(struct diff_filespec *one,
37+
mmfile_t *mf, struct userdiff_driver **textconv)
38+
{
39+
if (DIFF_FILE_VALID(one)) {
40+
*textconv = get_textconv(one);
41+
mf->size = fill_textconv(*textconv, one, &mf->ptr);
42+
} else {
43+
memset(mf, 0, sizeof(*mf));
44+
}
45+
}
46+
47+
static int diff_grep(struct diff_filepair *p, regex_t *regexp, struct diff_options *o)
48+
{
49+
regmatch_t regmatch;
50+
struct userdiff_driver *textconv_one = NULL;
51+
struct userdiff_driver *textconv_two = NULL;
52+
mmfile_t mf1, mf2;
53+
int hit;
54+
55+
if (diff_unmodified_pair(p))
56+
return 0;
57+
58+
fill_one(p->one, &mf1, &textconv_one);
59+
fill_one(p->two, &mf2, &textconv_two);
60+
61+
if (!mf1.ptr) {
62+
if (!mf2.ptr)
63+
return 0; /* ignore unmerged */
64+
/* created "two" -- does it have what we are looking for? */
65+
hit = !regexec(regexp, p->two->data, 1, &regmatch, 0);
66+
} else if (!mf2.ptr) {
67+
/* removed "one" -- did it have what we are looking for? */
68+
hit = !regexec(regexp, p->one->data, 1, &regmatch, 0);
69+
} else {
70+
/*
71+
* We have both sides; need to run textual diff and see if
72+
* the pattern appears on added/deleted lines.
73+
*/
74+
struct diffgrep_cb ecbdata;
75+
xpparam_t xpp;
76+
xdemitconf_t xecfg;
77+
78+
memset(&xpp, 0, sizeof(xpp));
79+
memset(&xecfg, 0, sizeof(xecfg));
80+
ecbdata.regexp = regexp;
81+
ecbdata.hit = 0;
82+
xecfg.ctxlen = o->context;
83+
xecfg.interhunkctxlen = o->interhunkcontext;
84+
xdi_diff_outf(&mf1, &mf2, diffgrep_consume, &ecbdata,
85+
&xpp, &xecfg);
86+
hit = ecbdata.hit;
87+
}
88+
if (textconv_one)
89+
free(mf1.ptr);
90+
if (textconv_two)
91+
free(mf2.ptr);
92+
return hit;
93+
}
94+
95+
static void diffcore_pickaxe_grep(struct diff_options *o)
96+
{
97+
struct diff_queue_struct *q = &diff_queued_diff;
98+
int i, has_changes, err;
99+
regex_t regex;
100+
struct diff_queue_struct outq;
101+
outq.queue = NULL;
102+
outq.nr = outq.alloc = 0;
103+
104+
err = regcomp(&regex, o->pickaxe, REG_EXTENDED | REG_NEWLINE);
105+
if (err) {
106+
char errbuf[1024];
107+
regerror(err, &regex, errbuf, 1024);
108+
regfree(&regex);
109+
die("invalid log-grep regex: %s", errbuf);
110+
}
111+
112+
if (o->pickaxe_opts & DIFF_PICKAXE_ALL) {
113+
/* Showing the whole changeset if needle exists */
114+
for (i = has_changes = 0; !has_changes && i < q->nr; i++) {
115+
struct diff_filepair *p = q->queue[i];
116+
if (diff_grep(p, &regex, o))
117+
has_changes++;
118+
}
119+
if (has_changes)
120+
return; /* do not munge the queue */
121+
122+
/*
123+
* Otherwise we will clear the whole queue by copying
124+
* the empty outq at the end of this function, but
125+
* first clear the current entries in the queue.
126+
*/
127+
for (i = 0; i < q->nr; i++)
128+
diff_free_filepair(q->queue[i]);
129+
} else {
130+
/* Showing only the filepairs that has the needle */
131+
for (i = 0; i < q->nr; i++) {
132+
struct diff_filepair *p = q->queue[i];
133+
if (diff_grep(p, &regex, o))
134+
diff_q(&outq, p);
135+
else
136+
diff_free_filepair(p);
137+
}
138+
}
139+
140+
regfree(&regex);
141+
142+
free(q->queue);
143+
*q = outq;
144+
return;
145+
}
7146

8147
static unsigned int contains(struct diff_filespec *one,
9148
const char *needle, unsigned long len,
@@ -48,7 +187,7 @@ static unsigned int contains(struct diff_filespec *one,
48187
return cnt;
49188
}
50189

51-
void diffcore_pickaxe(struct diff_options *o)
190+
static void diffcore_pickaxe_count(struct diff_options *o)
52191
{
53192
const char *needle = o->pickaxe;
54193
int opts = o->pickaxe_opts;
@@ -138,3 +277,12 @@ void diffcore_pickaxe(struct diff_options *o)
138277
*q = outq;
139278
return;
140279
}
280+
281+
void diffcore_pickaxe(struct diff_options *o)
282+
{
283+
/* Might want to warn when both S and G are on; I don't care... */
284+
if (o->pickaxe_opts & DIFF_PICKAXE_KIND_G)
285+
return diffcore_pickaxe_grep(o);
286+
else
287+
return diffcore_pickaxe_count(o);
288+
}

0 commit comments

Comments
 (0)