+
Patch formatting options such as `--word-diff`, `--color-moved`,
`--no-prefix`, and whitespace options (`-w`, `-b`) are supported,
-as are pickaxe options (`-S`, `-G`) and `--diff-filter`.
+as are pickaxe options (`-S`, `-G`) and `--diff-filter`. `-G` is
+scoped to the tracked range; `-S` is still evaluated over the whole
+file, so an `-S` query may select a commit for a change outside the
+range.
+
include::line-range-format.adoc[]
return ret;
}
+/*
+ * Expose the in-file line-range filter to callers outside diff.c (e.g.
+ * pickaxe -G); see xdiff-interface.h for the contract.
+ */
+int diff_emit_line_ranges(mmfile_t *one, mmfile_t *two,
+ const struct range_set *ranges,
+ xdiff_emit_line_fn line_fn, void *cb_data,
+ xpparam_t *xpp, xdemitconf_t *xecfg)
+{
+ struct line_range_filter filter;
+
+ line_range_filter_init(&filter, ranges, line_fn, cb_data);
+ return line_range_filter_diff(&filter, one, two, xpp, xecfg);
+}
+
static void pprint_rename(struct strbuf *name, const char *a, const char *b)
{
const char *old_name = a;
typedef int (*pickaxe_fn)(mmfile_t *one, mmfile_t *two,
struct diff_options *o,
- regex_t *regexp, kwset_t kws);
+ regex_t *regexp, kwset_t kws,
+ const struct range_set *ranges);
struct diffgrep_cb {
regex_t *regexp;
static int diff_grep(mmfile_t *one, mmfile_t *two,
struct diff_options *o,
- regex_t *regexp, kwset_t kws UNUSED)
+ regex_t *regexp, kwset_t kws UNUSED,
+ const struct range_set *ranges)
{
struct diffgrep_cb ecbdata;
xpparam_t xpp;
int ret;
/*
- * We have both sides; need to run textual diff and see if
- * the pattern appears on added/deleted lines.
+ * We have both sides; need to run textual diff and see if the
+ * pattern appears on added/deleted lines. Under -L (ranges set),
+ * forward only the tracked range's lines so the match is scoped.
+ * -G needs only a hit/no-hit answer, so the line-number bookkeeping
+ * the filter does for -L patch and check output is irrelevant here.
*/
memset(&xpp, 0, sizeof(xpp));
memset(&xecfg, 0, sizeof(xecfg));
* An xdiff error might be our "data->hit" from above. See the
* comment for xdiff_emit_line_fn in xdiff-interface.h
*/
- ret = xdi_diff_outf(one, two, NULL, diffgrep_consume,
- &ecbdata, &xpp, &xecfg);
+ if (ranges)
+ ret = diff_emit_line_ranges(one, two, ranges, diffgrep_consume,
+ &ecbdata, &xpp, &xecfg);
+ else
+ ret = xdi_diff_outf(one, two, NULL, diffgrep_consume,
+ &ecbdata, &xpp, &xecfg);
if (ecbdata.hit)
return 1;
if (ret)
static int has_changes(mmfile_t *one, mmfile_t *two,
struct diff_options *o UNUSED,
- regex_t *regexp, kwset_t kws)
+ regex_t *regexp, kwset_t kws,
+ const struct range_set *ranges UNUSED)
{
+ /*
+ * -S counts needle occurrences in each whole blob. Scoping this to
+ * a -L range is left to a follow-up; for now -S ignores the range.
+ */
unsigned int c1 = one ? contains(one, regexp, kws, 0) : 0;
unsigned int c2 = two ? contains(two, regexp, kws, c1 + 1) : 0;
return c1 != c2;
struct userdiff_driver *textconv_one = NULL;
struct userdiff_driver *textconv_two = NULL;
mmfile_t mf1, mf2;
+ const struct range_set *ranges;
int ret;
/* ignore unmerged */
mf1.size = fill_textconv(o->repo, textconv_one, p->one, &mf1.ptr);
mf2.size = fill_textconv(o->repo, textconv_two, p->two, &mf2.ptr);
- ret = fn(&mf1, &mf2, o, regexp, kws);
+ /*
+ * -L scopes the search to the tracked range, but the range is in
+ * original-file line coordinates that do not map onto textconv
+ * output, so search the whole file when textconv is in play.
+ */
+ ranges = (textconv_one || textconv_two) ? NULL : p->line_ranges;
+ ret = fn(&mf1, &mf2, o, regexp, kws, ranges);
if (textconv_one)
free(mf1.ptr);
test_expect_success '-L with -G filters to diff-text matches' '
git checkout parent-oids &&
git log -L:func2:file.c -G "F2 [+] 2" --format= >actual &&
- # -G greps the whole-file diff text, not just the tracked range;
- # combined with -L, this selects commits that both touch func2
- # and have "F2 + 2" in their diff.
+ # -G greps the diff text, and under -L only the lines in the
+ # tracked range (unlike -S above, which searches the whole file);
+ # this selects commits whose change to func2 contains "F2 + 2".
test $(grep -c "^diff --git" actual) = 1 &&
grep "F2 + 2" actual
'
test_grep ! "blank line at EOF" actual
'
+test_expect_success '-L -G is scoped to the tracked range' '
+ git checkout --orphan grep-scope &&
+ git reset --hard &&
+ cat >gp.c <<-\EOF &&
+ int func1()
+ {
+ return ALPHA;
+ }
+
+ int func2()
+ {
+ return BETA;
+ }
+ EOF
+ git add gp.c &&
+ test_tick &&
+ git commit -m "add gp.c" &&
+ sed -e "s/ALPHA/ALPHA2/" -e "s/BETA/BETA2/" gp.c >tmp &&
+ mv tmp gp.c &&
+ git commit -a -m "touch both functions" &&
+ # The commit changes ALPHA (func1) and BETA (func2). Tracking func2,
+ # -G BETA matches its in-range change; -G ALPHA must not, since ALPHA
+ # changes only outside the tracked range.
+ git log -L:func2:gp.c -G BETA --format=%s >actual &&
+ test_grep "touch both functions" actual &&
+ git log -L:func2:gp.c -G ALPHA --format=%s >actual &&
+ test_grep ! "touch both functions" actual
+'
+
+test_expect_success '-L -G searches the whole file under textconv' '
+ git checkout --orphan grep-textconv &&
+ git reset --hard &&
+ cat >tc.c <<-\EOF &&
+ int func1()
+ {
+ return F1;
+ }
+
+ int func2()
+ {
+ return F2;
+ }
+ EOF
+ git add tc.c &&
+ test_tick &&
+ git commit -m "add tc.c" &&
+ # One commit changes func1 and func2; MAGIC lands only in the
+ # func2 change, outside func1.
+ sed -e "s/F1/F1 + 1/" -e "s/return F2/return MAGIC/" tc.c >tmp &&
+ mv tmp tc.c &&
+ git commit -a -m "change both funcs" &&
+ echo "tc.c diff=tc" >.gitattributes &&
+
+ # Without a textconv driver, -G is scoped to func1, so MAGIC (only
+ # in the func2 change) does not select the commit.
+ git log -L:func1:tc.c -G MAGIC --format=%s --no-patch >actual &&
+ test_must_be_empty actual &&
+
+ # A textconv driver makes the range (original-file line numbers)
+ # meaningless against the driver output, so -G falls back to the
+ # whole file and MAGIC now selects the commit.
+ git config diff.tc.textconv cat &&
+ git log -L:func1:tc.c -G MAGIC --format=%s --no-patch >actual &&
+ test_grep "change both funcs" actual
+'
+
test_done
xdiff_emit_line_fn line_fn,
void *consume_callback_data,
xpparam_t const *xpp, xdemitconf_t const *xecfg);
+
+struct range_set;
+/*
+ * Like xdi_diff_outf(), but forwards only the lines within the given
+ * (post-image) line ranges to line_fn, as "git log -L" scopes its output.
+ * Returns line_fn's latched return value (so a consumer can signal a hit
+ * with a non-zero return), or non-zero on xdiff failure. Defined in
+ * diff.c (it reuses the line-range filter there).
+ */
+int diff_emit_line_ranges(mmfile_t *mf1, mmfile_t *mf2,
+ const struct range_set *ranges,
+ xdiff_emit_line_fn line_fn, void *cb_data,
+ xpparam_t *xpp, xdemitconf_t *xecfg);
int read_mmfile(mmfile_t *ptr, const char *filename);
void read_mmblob(mmfile_t *ptr, struct object_database *odb,
const struct object_id *oid);