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, set the `xdl_opts_command_line` member
during options pasing to indicate the diff algorithm was set via command
line args.

Signed-off-by: John Cai <johncai86@gmail.com>
  • Loading branch information
john-cai authored and John Cai committed Feb 4, 2023
1 parent 3055b77 commit 8e8fee8
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 2 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
29 changes: 28 additions & 1 deletion diff.c
Expand Up @@ -3653,6 +3653,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 @@ -5123,9 +5144,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_diff_algorithm_no_arg(const struct option *opt,
Expand All @@ -5148,6 +5173,8 @@ static int diff_opt_diff_algorithm_no_arg(const struct option *opt,
options->anchors_nr = 0;
}

options->xdl_opts_command_line = 1;

return set_diff_algorithm(options, opt->long_name);
}

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 8e8fee8

Please sign in to comment.