Skip to content

Commit ece8dc9

Browse files
committed
Merge branch 'jc/diff-algo-attribute'
The "diff" drivers specified by the "diff" attribute attached to paths can now specify which algorithm (e.g. histogram) to use. * jc/diff-algo-attribute: diff: teach diff to read algorithm from diff driver diff: consolidate diff algorithm option parsing
2 parents 21522cf + a4cf900 commit ece8dc9

File tree

6 files changed

+140
-25
lines changed

6 files changed

+140
-25
lines changed

Documentation/gitattributes.txt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,37 @@ with the above configuration, i.e. `j-c-diff`, with 7
758758
parameters, just like `GIT_EXTERNAL_DIFF` program is called.
759759
See linkgit:git[1] for details.
760760

761+
Setting the internal diff algorithm
762+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
763+
764+
The diff algorithm can be set through the `diff.algorithm` config key, but
765+
sometimes it may be helpful to set the diff algorithm per path. For example,
766+
one may want to use the `minimal` diff algorithm for .json files, and the
767+
`histogram` for .c files, and so on without having to pass in the algorithm
768+
through the command line each time.
769+
770+
First, in `.gitattributes`, assign the `diff` attribute for paths.
771+
772+
------------------------
773+
*.json diff=<name>
774+
------------------------
775+
776+
Then, define a "diff.<name>.algorithm" configuration to specify the diff
777+
algorithm, choosing from `myers`, `patience`, `minimal`, or `histogram`.
778+
779+
----------------------------------------------------------------
780+
[diff "<name>"]
781+
algorithm = histogram
782+
----------------------------------------------------------------
783+
784+
This diff algorithm applies to user facing diff output like git-diff(1),
785+
git-show(1) and is used for the `--stat` output as well. The merge machinery
786+
will not use the diff algorithm set through this method.
787+
788+
NOTE: If `diff.<name>.command` is defined for path with the
789+
`diff=<name>` attribute, it is executed as an external diff driver
790+
(see above), and adding `diff.<name>.algorithm` has no effect, as the
791+
algorithm is not passed to the external diff driver.
761792

762793
Defining a custom hunk-header
763794
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

diff.c

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3437,6 +3437,22 @@ static int diff_filepair_is_phoney(struct diff_filespec *one,
34373437
return !DIFF_FILE_VALID(one) && !DIFF_FILE_VALID(two);
34383438
}
34393439

3440+
static int set_diff_algorithm(struct diff_options *opts,
3441+
const char *alg)
3442+
{
3443+
long value = parse_algorithm_value(alg);
3444+
3445+
if (value < 0)
3446+
return -1;
3447+
3448+
/* clear out previous settings */
3449+
DIFF_XDL_CLR(opts, NEED_MINIMAL);
3450+
opts->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
3451+
opts->xdl_opts |= value;
3452+
3453+
return 0;
3454+
}
3455+
34403456
static void builtin_diff(const char *name_a,
34413457
const char *name_b,
34423458
struct diff_filespec *one,
@@ -4440,15 +4456,13 @@ static void run_diff_cmd(const char *pgm,
44404456
const char *xfrm_msg = NULL;
44414457
int complete_rewrite = (p->status == DIFF_STATUS_MODIFIED) && p->score;
44424458
int must_show_header = 0;
4459+
struct userdiff_driver *drv = NULL;
44434460

4444-
4445-
if (o->flags.allow_external) {
4446-
struct userdiff_driver *drv;
4447-
4461+
if (o->flags.allow_external || !o->ignore_driver_algorithm)
44484462
drv = userdiff_find_by_path(o->repo->index, attr_path);
4449-
if (drv && drv->external)
4450-
pgm = drv->external;
4451-
}
4463+
4464+
if (o->flags.allow_external && drv && drv->external)
4465+
pgm = drv->external;
44524466

44534467
if (msg) {
44544468
/*
@@ -4465,12 +4479,16 @@ static void run_diff_cmd(const char *pgm,
44654479
run_external_diff(pgm, name, other, one, two, xfrm_msg, o);
44664480
return;
44674481
}
4468-
if (one && two)
4482+
if (one && two) {
4483+
if (!o->ignore_driver_algorithm && drv && drv->algorithm)
4484+
set_diff_algorithm(o, drv->algorithm);
4485+
44694486
builtin_diff(name, other ? other : name,
44704487
one, two, xfrm_msg, must_show_header,
44714488
o, complete_rewrite);
4472-
else
4489+
} else {
44734490
fprintf(o->file, "* Unmerged path %s\n", name);
4491+
}
44744492
}
44754493

44764494
static void diff_fill_oid_info(struct diff_filespec *one, struct index_state *istate)
@@ -4567,6 +4585,14 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
45674585
const char *name;
45684586
const char *other;
45694587

4588+
if (!o->ignore_driver_algorithm) {
4589+
struct userdiff_driver *drv = userdiff_find_by_path(o->repo->index,
4590+
p->one->path);
4591+
4592+
if (drv && drv->algorithm)
4593+
set_diff_algorithm(o, drv->algorithm);
4594+
}
4595+
45704596
if (DIFF_PAIR_UNMERGED(p)) {
45714597
/* unmerged */
45724598
builtin_diffstat(p->one->path, NULL, NULL, NULL,
@@ -5107,17 +5133,32 @@ static int diff_opt_diff_algorithm(const struct option *opt,
51075133
const char *arg, int unset)
51085134
{
51095135
struct diff_options *options = opt->value;
5110-
long value = parse_algorithm_value(arg);
51115136

51125137
BUG_ON_OPT_NEG(unset);
5113-
if (value < 0)
5138+
5139+
if (set_diff_algorithm(options, arg))
51145140
return error(_("option diff-algorithm accepts \"myers\", "
51155141
"\"minimal\", \"patience\" and \"histogram\""));
51165142

5117-
/* clear out previous settings */
5118-
DIFF_XDL_CLR(options, NEED_MINIMAL);
5119-
options->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
5120-
options->xdl_opts |= value;
5143+
options->ignore_driver_algorithm = 1;
5144+
5145+
return 0;
5146+
}
5147+
5148+
static int diff_opt_diff_algorithm_no_arg(const struct option *opt,
5149+
const char *arg, int unset)
5150+
{
5151+
struct diff_options *options = opt->value;
5152+
5153+
BUG_ON_OPT_NEG(unset);
5154+
BUG_ON_OPT_ARG(arg);
5155+
5156+
if (set_diff_algorithm(options, opt->long_name))
5157+
BUG("available diff algorithms include \"myers\", "
5158+
"\"minimal\", \"patience\" and \"histogram\"");
5159+
5160+
options->ignore_driver_algorithm = 1;
5161+
51215162
return 0;
51225163
}
51235164

@@ -5250,7 +5291,6 @@ static int diff_opt_patience(const struct option *opt,
52505291

52515292
BUG_ON_OPT_NEG(unset);
52525293
BUG_ON_OPT_ARG(arg);
5253-
options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
52545294
/*
52555295
* Both --patience and --anchored use PATIENCE_DIFF
52565296
* internally, so remove any anchors previously
@@ -5259,7 +5299,9 @@ static int diff_opt_patience(const struct option *opt,
52595299
for (i = 0; i < options->anchors_nr; i++)
52605300
free(options->anchors[i]);
52615301
options->anchors_nr = 0;
5262-
return 0;
5302+
options->ignore_driver_algorithm = 1;
5303+
5304+
return set_diff_algorithm(options, "patience");
52635305
}
52645306

52655307
static int diff_opt_ignore_regex(const struct option *opt,
@@ -5562,9 +5604,10 @@ struct option *add_diff_options(const struct option *opts,
55625604
N_("prevent rename/copy detection if the number of rename/copy targets exceeds given limit")),
55635605

55645606
OPT_GROUP(N_("Diff algorithm options")),
5565-
OPT_BIT(0, "minimal", &options->xdl_opts,
5566-
N_("produce the smallest possible diff"),
5567-
XDF_NEED_MINIMAL),
5607+
OPT_CALLBACK_F(0, "minimal", options, NULL,
5608+
N_("produce the smallest possible diff"),
5609+
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
5610+
diff_opt_diff_algorithm_no_arg),
55685611
OPT_BIT_F('w', "ignore-all-space", &options->xdl_opts,
55695612
N_("ignore whitespace when comparing lines"),
55705613
XDF_IGNORE_WHITESPACE, PARSE_OPT_NONEG),
@@ -5590,9 +5633,10 @@ struct option *add_diff_options(const struct option *opts,
55905633
N_("generate diff using the \"patience diff\" algorithm"),
55915634
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
55925635
diff_opt_patience),
5593-
OPT_BITOP(0, "histogram", &options->xdl_opts,
5594-
N_("generate diff using the \"histogram diff\" algorithm"),
5595-
XDF_HISTOGRAM_DIFF, XDF_DIFF_ALGORITHM_MASK),
5636+
OPT_CALLBACK_F(0, "histogram", options, NULL,
5637+
N_("generate diff using the \"histogram diff\" algorithm"),
5638+
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
5639+
diff_opt_diff_algorithm_no_arg),
55965640
OPT_CALLBACK_F(0, "diff-algorithm", options, N_("<algorithm>"),
55975641
N_("choose a diff algorithm"),
55985642
PARSE_OPT_NONEG, diff_opt_diff_algorithm),

diff.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ struct diff_options {
333333
int prefix_length;
334334
const char *stat_sep;
335335
int xdl_opts;
336+
int ignore_driver_algorithm;
336337

337338
/* see Documentation/diff-options.txt */
338339
char **anchors;

t/lib-diff-alternative.sh

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,46 @@ index $file1..$file2 100644
105105
}
106106
EOF
107107

108+
cat >expect_diffstat <<EOF
109+
file1 => file2 | 21 ++++++++++-----------
110+
1 file changed, 10 insertions(+), 11 deletions(-)
111+
EOF
112+
108113
STRATEGY=$1
109114

115+
test_expect_success "$STRATEGY diff from attributes" '
116+
echo "file* diff=driver" >.gitattributes &&
117+
git config diff.driver.algorithm "$STRATEGY" &&
118+
test_must_fail git diff --no-index file1 file2 > output &&
119+
cat expect &&
120+
cat output &&
121+
test_cmp expect output
122+
'
123+
124+
test_expect_success "$STRATEGY diff from attributes has valid diffstat" '
125+
echo "file* diff=driver" >.gitattributes &&
126+
git config diff.driver.algorithm "$STRATEGY" &&
127+
test_must_fail git diff --stat --no-index file1 file2 > output &&
128+
test_cmp expect_diffstat output
129+
'
130+
110131
test_expect_success "$STRATEGY diff" '
111-
test_must_fail git diff --no-index "--$STRATEGY" file1 file2 > output &&
132+
test_must_fail git diff --no-index "--diff-algorithm=$STRATEGY" file1 file2 > output &&
133+
test_cmp expect output
134+
'
135+
136+
test_expect_success "$STRATEGY diff command line precedence before attributes" '
137+
echo "file* diff=driver" >.gitattributes &&
138+
git config diff.driver.algorithm myers &&
139+
test_must_fail git diff --no-index "--diff-algorithm=$STRATEGY" file1 file2 > output &&
140+
test_cmp expect output
141+
'
142+
143+
test_expect_success "$STRATEGY diff attributes precedence before config" '
144+
git config diff.algorithm default &&
145+
echo "file* diff=driver" >.gitattributes &&
146+
git config diff.driver.algorithm "$STRATEGY" &&
147+
test_must_fail git diff --no-index file1 file2 > output &&
112148
test_cmp expect output
113149
'
114150

userdiff.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ PATTERNS("scheme",
293293
"|([^][)(}{[ \t])+"),
294294
PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
295295
"\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+"),
296-
{ "default", NULL, -1, { NULL, 0 } },
296+
{ "default", NULL, NULL, -1, { NULL, 0 } },
297297
};
298298
#undef PATTERNS
299299
#undef IPATTERN
@@ -394,6 +394,8 @@ int userdiff_config(const char *k, const char *v)
394394
return parse_bool(&drv->textconv_want_cache, k, v);
395395
if (!strcmp(type, "wordregex"))
396396
return git_config_string(&drv->word_regex, k, v);
397+
if (!strcmp(type, "algorithm"))
398+
return git_config_string(&drv->algorithm, k, v);
397399

398400
return 0;
399401
}

userdiff.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ struct userdiff_funcname {
1414
struct userdiff_driver {
1515
const char *name;
1616
const char *external;
17+
const char *algorithm;
1718
int binary;
1819
struct userdiff_funcname funcname;
1920
const char *word_regex;

0 commit comments

Comments
 (0)