diffcore-pickaxe.c 7 KB
Newer Older
1 2
/*
 * Copyright (C) 2005 Junio C Hamano
3
 * Copyright (C) 2010 Google Inc.
4 5 6 7
 */
#include "cache.h"
#include "diff.h"
#include "diffcore.h"
8
#include "xdiff-interface.h"
Fredrik K's avatar
Fredrik K committed
9
#include "kwset.h"
10

René Scharfe's avatar
René Scharfe committed
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
typedef int (*pickaxe_fn)(struct diff_filepair *p, struct diff_options *o, regex_t *regexp, kwset_t kws);

static void pickaxe(struct diff_queue_struct *q, struct diff_options *o,
		    regex_t *regexp, kwset_t kws, pickaxe_fn fn)
{
	int i;
	struct diff_queue_struct outq;

	DIFF_QUEUE_CLEAR(&outq);

	if (o->pickaxe_opts & DIFF_PICKAXE_ALL) {
		/* Showing the whole changeset if needle exists */
		for (i = 0; i < q->nr; i++) {
			struct diff_filepair *p = q->queue[i];
			if (fn(p, o, regexp, kws))
				return; /* do not munge the queue */
		}

		/*
		 * Otherwise we will clear the whole queue by copying
		 * the empty outq at the end of this function, but
		 * first clear the current entries in the queue.
		 */
		for (i = 0; i < q->nr; i++)
			diff_free_filepair(q->queue[i]);
	} else {
		/* Showing only the filepairs that has the needle */
		for (i = 0; i < q->nr; i++) {
			struct diff_filepair *p = q->queue[i];
			if (fn(p, o, regexp, kws))
				diff_q(&outq, p);
			else
				diff_free_filepair(p);
		}
	}

	free(q->queue);
	*q = outq;
}

51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
struct diffgrep_cb {
	regex_t *regexp;
	int hit;
};

static void diffgrep_consume(void *priv, char *line, unsigned long len)
{
	struct diffgrep_cb *data = priv;
	regmatch_t regmatch;
	int hold;

	if (line[0] != '+' && line[0] != '-')
		return;
	if (data->hit)
		/*
		 * NEEDSWORK: we should have a way to terminate the
		 * caller early.
		 */
		return;
	/* Yuck -- line ought to be "const char *"! */
	hold = line[len];
	line[len] = '\0';
	data->hit = !regexec(data->regexp, line + 1, 1, &regmatch, 0);
	line[len] = hold;
}

77 78
static int diff_grep(struct diff_filepair *p, struct diff_options *o,
		     regex_t *regexp, kwset_t kws)
79 80 81 82 83 84 85
{
	regmatch_t regmatch;
	struct userdiff_driver *textconv_one = NULL;
	struct userdiff_driver *textconv_two = NULL;
	mmfile_t mf1, mf2;
	int hit;

86
	if (!o->pickaxe[0])
87 88
		return 0;

89 90 91 92
	if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
		textconv_one = get_textconv(p->one);
		textconv_two = get_textconv(p->two);
	}
93

94 95 96
	if (textconv_one == textconv_two && diff_unmodified_pair(p))
		return 0;

97 98
	mf1.size = fill_textconv(textconv_one, p->one, &mf1.ptr);
	mf2.size = fill_textconv(textconv_two, p->two, &mf2.ptr);
99

100 101
	if (!DIFF_FILE_VALID(p->one)) {
		if (!DIFF_FILE_VALID(p->two))
102 103 104 105
			hit = 0; /* ignore unmerged */
		else
			/* created "two" -- does it have what we are looking for? */
			hit = !regexec(regexp, mf2.ptr, 1, &regmatch, 0);
106
	} else if (!DIFF_FILE_VALID(p->two)) {
107
		/* removed "one" -- did it have what we are looking for? */
108
		hit = !regexec(regexp, mf1.ptr, 1, &regmatch, 0);
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
	} else {
		/*
		 * We have both sides; need to run textual diff and see if
		 * the pattern appears on added/deleted lines.
		 */
		struct diffgrep_cb ecbdata;
		xpparam_t xpp;
		xdemitconf_t xecfg;

		memset(&xpp, 0, sizeof(xpp));
		memset(&xecfg, 0, sizeof(xecfg));
		ecbdata.regexp = regexp;
		ecbdata.hit = 0;
		xecfg.ctxlen = o->context;
		xecfg.interhunkctxlen = o->interhunkcontext;
		xdi_diff_outf(&mf1, &mf2, diffgrep_consume, &ecbdata,
			      &xpp, &xecfg);
		hit = ecbdata.hit;
	}
	if (textconv_one)
		free(mf1.ptr);
	if (textconv_two)
		free(mf2.ptr);
132 133
	diff_free_filespec_data(p->one);
	diff_free_filespec_data(p->two);
134 135 136 137 138
	return hit;
}

static void diffcore_pickaxe_grep(struct diff_options *o)
{
René Scharfe's avatar
René Scharfe committed
139
	int err;
140
	regex_t regex;
141
	int cflags = REG_EXTENDED | REG_NEWLINE;
142

143 144 145 146
	if (DIFF_OPT_TST(o, PICKAXE_IGNORE_CASE))
		cflags |= REG_ICASE;

	err = regcomp(&regex, o->pickaxe, cflags);
147 148 149 150 151 152 153
	if (err) {
		char errbuf[1024];
		regerror(err, &regex, errbuf, 1024);
		regfree(&regex);
		die("invalid log-grep regex: %s", errbuf);
	}

René Scharfe's avatar
René Scharfe committed
154
	pickaxe(&diff_queued_diff, o, &regex, NULL, diff_grep);
155

René Scharfe's avatar
René Scharfe committed
156
	regfree(&regex);
157 158
	return;
}
159

160
static unsigned int contains(mmfile_t *mf, struct diff_options *o,
Fredrik K's avatar
Fredrik K committed
161
			     regex_t *regexp, kwset_t kws)
162
{
163
	unsigned int cnt;
164
	unsigned long sz;
165
	const char *data;
166

167 168
	sz = mf->size;
	data = mf->ptr;
169 170
	cnt = 0;

171 172 173 174
	if (regexp) {
		regmatch_t regmatch;
		int flags = 0;

175
		assert(data[sz] == '\0');
176 177
		while (*data && !regexec(regexp, data, 1, &regmatch, flags)) {
			flags |= REG_NOTBOL;
178 179 180
			data += regmatch.rm_eo;
			if (*data && regmatch.rm_so == regmatch.rm_eo)
				data++;
181 182
			cnt++;
		}
183 184

	} else { /* Classic exact string match */
185
		while (sz) {
186 187
			struct kwsmatch kwsm;
			size_t offset = kwsexec(kws, data, sz, &kwsm);
Fredrik K's avatar
Fredrik K committed
188 189
			const char *found;
			if (offset == -1)
190
				break;
Fredrik K's avatar
Fredrik K committed
191 192
			else
				found = data + offset;
193 194
			sz -= found - data + kwsm.size[0];
			data = found + kwsm.size[0];
195
			cnt++;
196
		}
197 198
	}
	return cnt;
199 200
}

201 202
static int has_changes(struct diff_filepair *p, struct diff_options *o,
		       regex_t *regexp, kwset_t kws)
203
{
204 205
	struct userdiff_driver *textconv_one = NULL;
	struct userdiff_driver *textconv_two = NULL;
206 207 208
	mmfile_t mf1, mf2;
	int ret;

209 210 211
	if (!o->pickaxe[0])
		return 0;

212 213 214 215
	if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
		textconv_one = get_textconv(p->one);
		textconv_two = get_textconv(p->two);
	}
216

217 218 219 220 221 222 223 224 225 226
	/*
	 * If we have an unmodified pair, we know that the count will be the
	 * same and don't even have to load the blobs. Unless textconv is in
	 * play, _and_ we are using two different textconv filters (e.g.,
	 * because a pair is an exact rename with different textconv attributes
	 * for each side, which might generate different content).
	 */
	if (textconv_one == textconv_two && diff_unmodified_pair(p))
		return 0;

227 228
	mf1.size = fill_textconv(textconv_one, p->one, &mf1.ptr);
	mf2.size = fill_textconv(textconv_two, p->two, &mf2.ptr);
229

230 231
	if (!DIFF_FILE_VALID(p->one)) {
		if (!DIFF_FILE_VALID(p->two))
232
			ret = 0; /* ignore unmerged */
233 234 235
		else
			/* created */
			ret = contains(&mf2, o, regexp, kws) != 0;
236
	}
237
	else if (!DIFF_FILE_VALID(p->two)) /* removed */
238 239 240 241 242 243 244 245 246 247 248 249 250
		ret = contains(&mf1, o, regexp, kws) != 0;
	else
		ret = contains(&mf1, o, regexp, kws) !=
		      contains(&mf2, o, regexp, kws);

	if (textconv_one)
		free(mf1.ptr);
	if (textconv_two)
		free(mf2.ptr);
	diff_free_filespec_data(p->one);
	diff_free_filespec_data(p->two);

	return ret;
251 252
}

253
static void diffcore_pickaxe_count(struct diff_options *o)
254
{
255 256
	const char *needle = o->pickaxe;
	int opts = o->pickaxe_opts;
257
	unsigned long len = strlen(needle);
258
	regex_t regex, *regexp = NULL;
Fredrik K's avatar
Fredrik K committed
259
	kwset_t kws = NULL;
260

261 262 263 264 265 266 267 268 269 270 271
	if (opts & DIFF_PICKAXE_REGEX) {
		int err;
		err = regcomp(&regex, needle, REG_EXTENDED | REG_NEWLINE);
		if (err) {
			/* The POSIX.2 people are surely sick */
			char errbuf[1024];
			regerror(err, &regex, errbuf, 1024);
			regfree(&regex);
			die("invalid pickaxe regex: %s", errbuf);
		}
		regexp = &regex;
Fredrik K's avatar
Fredrik K committed
272
	} else {
273 274
		kws = kwsalloc(DIFF_OPT_TST(o, PICKAXE_IGNORE_CASE)
			       ? tolower_trans_tbl : NULL);
Fredrik K's avatar
Fredrik K committed
275 276
		kwsincr(kws, needle, len);
		kwsprep(kws);
277 278
	}

René Scharfe's avatar
René Scharfe committed
279
	pickaxe(&diff_queued_diff, o, regexp, kws, has_changes);
René Scharfe's avatar
René Scharfe committed
280

281
	if (opts & DIFF_PICKAXE_REGEX)
282
		regfree(&regex);
Fredrik K's avatar
Fredrik K committed
283 284
	else
		kwsfree(kws);
285 286
	return;
}
287 288 289 290 291

void diffcore_pickaxe(struct diff_options *o)
{
	/* Might want to warn when both S and G are on; I don't care... */
	if (o->pickaxe_opts & DIFF_PICKAXE_KIND_G)
292
		diffcore_pickaxe_grep(o);
293
	else
294
		diffcore_pickaxe_count(o);
295
}