Skip to content

Commit

Permalink
diff: Teach diff to read attribute diff-algorithm
Browse files Browse the repository at this point in the history
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 <johncai86@gmail.com>
  • Loading branch information
john-cai authored and John Cai committed Feb 3, 2023
1 parent df25166 commit 94b5ef6
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 10 deletions.
23 changes: 23 additions & 0 deletions Documentation/gitattributes.txt
Expand Up @@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
1 change: 1 addition & 0 deletions builtin/diff.c
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions diff-no-index.c
Expand Up @@ -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"
Expand Down
73 changes: 64 additions & 9 deletions diff.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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),
Expand All @@ -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_("<algorithm>"),
N_("choose a diff algorithm"),
PARSE_OPT_NONEG, diff_opt_diff_algorithm),
Expand Down
2 changes: 2 additions & 0 deletions diff.h
Expand Up @@ -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;
Expand Down
27 changes: 26 additions & 1 deletion t/lib-diff-alternative.sh
Expand Up @@ -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
'

Expand Down Expand Up @@ -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
'
}

0 comments on commit 94b5ef6

Please sign in to comment.