From 94b5ef66b198367929e6064e6f6d041a660ce946 Mon Sep 17 00:00:00 2001 From: John Cai Date: Fri, 20 Jan 2023 10:49:53 -0500 Subject: [PATCH] diff: Teach diff to read attribute diff-algorithm It can be useful to specify diff algorithms per file type. For example, one may want to use the minimal diff algorithm for .json files, another for .c files, etc. Teach the diff machinery to check attributes for a diff algorithm. Enforce precedence by favoring the command line option, then looking at attributes, then finally the config. To enforce precedence order, swap out options parsing for "minimal" and "histogram" into `OPT_CALLBACK_F` so we can set the `xdl_opts_command_line` member. Signed-off-by: John Cai --- Documentation/gitattributes.txt | 23 +++++++++++ builtin/diff.c | 1 + diff-no-index.c | 1 + diff.c | 73 +++++++++++++++++++++++++++++---- diff.h | 2 + t/lib-diff-alternative.sh | 27 +++++++++++- 6 files changed, 117 insertions(+), 10 deletions(-) diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index c19e64ea0eff61..501dd5360372b8 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -736,6 +736,29 @@ String:: by the configuration variables in the "diff.foo" section of the Git config file. +`diff-algorithm` +^^^^^^^^^^^^^^^^ + +The attribute `diff-algorithm` affects which algorithm Git uses to generate +diffs. This allows defining diff algorithms per file extension. Precedence rules +are as follows, in order from highest to lowest: + +*Command line option* + +Pass in the `--diff-algorithm` command line option int git-diff(1) + +*Git attributes* + +------------------------ +*.json diff-algorithm=histogram +------------------------ + +*Git config* + +---------------------------------------------------------------- +[diff] + algorithm = histogram +---------------------------------------------------------------- Defining an external diff driver ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/builtin/diff.c b/builtin/diff.c index 26f1e532c66b17..45343a8f819650 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -13,6 +13,7 @@ #include "blob.h" #include "tag.h" #include "diff.h" +#include "xdiff-interface.h" #include "diff-merges.h" #include "diffcore.h" #include "revision.h" diff --git a/diff-no-index.c b/diff-no-index.c index 05fafd0019b6d5..fde17f177bcaed 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -10,6 +10,7 @@ #include "blob.h" #include "tag.h" #include "diff.h" +#include "xdiff-interface.h" #include "diffcore.h" #include "revision.h" #include "log-tree.h" diff --git a/diff.c b/diff.c index 0ff7ca95b0f12d..09a7502bdc522f 100644 --- a/diff.c +++ b/diff.c @@ -3652,6 +3652,27 @@ static void builtin_diff(const char *name_a, ecbdata.opt = o; if (header.len && !o->flags.suppress_diff_headers) ecbdata.header = &header; + + if (!o->xdl_opts_command_line) { + static struct attr_check *check; + const char *one_diff_algo; + const char *two_diff_algo; + + check = attr_check_alloc(); + attr_check_append(check, git_attr("diff-algorithm")); + + git_check_attr(the_repository->index, NULL, one->path, check); + one_diff_algo = check->items[0].value; + git_check_attr(the_repository->index, NULL, two->path, check); + two_diff_algo = check->items[0].value; + + if (!ATTR_UNSET(one_diff_algo) && !ATTR_UNSET(two_diff_algo) && + !strcmp(one_diff_algo, two_diff_algo)) + set_diff_algorithm(o, one_diff_algo); + + attr_check_free(check); + } + xpp.flags = o->xdl_opts; xpp.ignore_regex = o->ignore_regex; xpp.ignore_regex_nr = o->ignore_regex_nr; @@ -5122,9 +5143,13 @@ static int diff_opt_compact_summary(const struct option *opt, static int diff_opt_diff_algorithm(const struct option *opt, const char *arg, int unset) { + struct diff_options *options = opt->value; + BUG_ON_OPT_NEG(unset); - return set_diff_algorithm(opt->value, arg); + options->xdl_opts_command_line = 1; + + return set_diff_algorithm(options, arg); } static int diff_opt_dirstat(const struct option *opt, @@ -5256,7 +5281,6 @@ static int diff_opt_patience(const struct option *opt, BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(arg); - options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF); /* * Both --patience and --anchored use PATIENCE_DIFF * internally, so remove any anchors previously @@ -5265,7 +5289,36 @@ static int diff_opt_patience(const struct option *opt, for (i = 0; i < options->anchors_nr; i++) free(options->anchors[i]); options->anchors_nr = 0; - return 0; + + options->xdl_opts_command_line = 1; + + return set_diff_algorithm(options, "patience"); +} + +static int diff_opt_histogram(const struct option *opt, + const char *arg, int unset) +{ + struct diff_options *options = opt->value; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + + options->xdl_opts_command_line = 1; + + return set_diff_algorithm(options, "histogram"); +} + +static int diff_opt_minimal(const struct option *opt, + const char *arg, int unset) +{ + struct diff_options *options = opt->value; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + + options->xdl_opts_command_line = 1; + + return set_diff_algorithm(options, "minimal"); } static int diff_opt_ignore_regex(const struct option *opt, @@ -5568,9 +5621,10 @@ struct option *add_diff_options(const struct option *opts, N_("prevent rename/copy detection if the number of rename/copy targets exceeds given limit")), OPT_GROUP(N_("Diff algorithm options")), - OPT_BIT(0, "minimal", &options->xdl_opts, - N_("produce the smallest possible diff"), - XDF_NEED_MINIMAL), + OPT_CALLBACK_F(0, "minimal", options, NULL, + N_("produce the smallest possible diff"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + diff_opt_minimal), OPT_BIT_F('w', "ignore-all-space", &options->xdl_opts, N_("ignore whitespace when comparing lines"), XDF_IGNORE_WHITESPACE, PARSE_OPT_NONEG), @@ -5596,9 +5650,10 @@ struct option *add_diff_options(const struct option *opts, N_("generate diff using the \"patience diff\" algorithm"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, diff_opt_patience), - OPT_BITOP(0, "histogram", &options->xdl_opts, - N_("generate diff using the \"histogram diff\" algorithm"), - XDF_HISTOGRAM_DIFF, XDF_DIFF_ALGORITHM_MASK), + OPT_CALLBACK_F(0, "histogram", options, NULL, + N_("generate diff using the \"histogram diff\" algorithm"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + diff_opt_histogram), OPT_CALLBACK_F(0, "diff-algorithm", options, N_(""), N_("choose a diff algorithm"), PARSE_OPT_NONEG, diff_opt_diff_algorithm), diff --git a/diff.h b/diff.h index 41eb2c3d428801..46b565abfd4287 100644 --- a/diff.h +++ b/diff.h @@ -333,6 +333,8 @@ struct diff_options { int prefix_length; const char *stat_sep; int xdl_opts; + /* If xdl_opts has been set via the command line. */ + int xdl_opts_command_line; /* see Documentation/diff-options.txt */ char **anchors; diff --git a/t/lib-diff-alternative.sh b/t/lib-diff-alternative.sh index 8d1e408bb58f5e..630c98ea65a70b 100644 --- a/t/lib-diff-alternative.sh +++ b/t/lib-diff-alternative.sh @@ -107,8 +107,27 @@ EOF STRATEGY=$1 + test_expect_success "$STRATEGY diff from attributes" ' + echo "file* diff-algorithm=$STRATEGY" >.gitattributes && + test_must_fail git diff --no-index file1 file2 > output && + test_cmp expect output + ' + test_expect_success "$STRATEGY diff" ' - test_must_fail git diff --no-index "--$STRATEGY" file1 file2 > output && + test_must_fail git diff --no-index "--diff-algorithm=$STRATEGY" file1 file2 > output && + test_cmp expect output + ' + + test_expect_success "$STRATEGY diff command line precedence before attributes" ' + echo "file* diff-algorithm=meyers" >.gitattributes && + test_must_fail git diff --no-index "--diff-algorithm=$STRATEGY" file1 file2 > output && + test_cmp expect output + ' + + test_expect_success "$STRATEGY diff attributes precedence before config" ' + git config diff.algorithm default && + echo "file* diff-algorithm=$STRATEGY" >.gitattributes && + test_must_fail git diff --no-index "--diff-algorithm=$STRATEGY" file1 file2 > output && test_cmp expect output ' @@ -166,5 +185,11 @@ EOF test_must_fail git diff --no-index "--$STRATEGY" uniq1 uniq2 > output && test_cmp expect output ' + + test_expect_success "$STRATEGY diff from attributes" ' + echo "file* diff-algorithm=$STRATEGY" >.gitattributes && + test_must_fail git diff --no-index uniq1 uniq2 > output && + test_cmp expect output + ' }