summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--builtin-diff.c1
-rw-r--r--combine-diff.c1
-rw-r--r--diff.c87
-rw-r--r--diff.h1
-rwxr-xr-xt/t4020-diff-external.sh97
5 files changed, 182 insertions, 5 deletions
diff --git a/builtin-diff.c b/builtin-diff.c
index 21d13f0b30..2ae60097b8 100644
--- a/builtin-diff.c
+++ b/builtin-diff.c
@@ -225,6 +225,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
if (diff_setup_done(&rev.diffopt) < 0)
die("diff_setup_done failed");
}
+ rev.diffopt.allow_external = 1;
/* Do we have --cached and not have a pending object, then
* default to HEAD by hand. Eek.
diff --git a/combine-diff.c b/combine-diff.c
index 3a9b32f6b8..cff9c5dc42 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -943,6 +943,7 @@ void diff_tree_combined(const unsigned char *sha1,
diffopts = *opt;
diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
diffopts.recursive = 1;
+ diffopts.allow_external = 0;
show_log_first = !!rev->loginfo && !rev->no_commit_id;
needsep = 0;
diff --git a/diff.c b/diff.c
index f516664968..9dfded7664 100644
--- a/diff.c
+++ b/diff.c
@@ -52,6 +52,49 @@ static int parse_diff_color_slot(const char *var, int ofs)
die("bad config variable '%s'", var);
}
+static struct ll_diff_driver {
+ const char *name;
+ struct ll_diff_driver *next;
+ char *cmd;
+} *user_diff, **user_diff_tail;
+
+/*
+ * Currently there is only "diff.<drivername>.command" variable;
+ * because there are "diff.color.<slot>" variables, we are parsing
+ * this in a bit convoluted way to allow low level diff driver
+ * called "color".
+ */
+static int parse_lldiff_command(const char *var, const char *ep, const char *value)
+{
+ const char *name;
+ int namelen;
+ struct ll_diff_driver *drv;
+
+ name = var + 5;
+ namelen = ep - name;
+ for (drv = user_diff; drv; drv = drv->next)
+ if (!strncmp(drv->name, name, namelen) && !drv->name[namelen])
+ break;
+ if (!drv) {
+ char *namebuf;
+ drv = xcalloc(1, sizeof(struct ll_diff_driver));
+ namebuf = xmalloc(namelen + 1);
+ memcpy(namebuf, name, namelen);
+ namebuf[namelen] = 0;
+ drv->name = namebuf;
+ drv->next = NULL;
+ if (!user_diff_tail)
+ user_diff_tail = &user_diff;
+ *user_diff_tail = drv;
+ user_diff_tail = &(drv->next);
+ }
+
+ if (!value)
+ return error("%s: lacks value", var);
+ drv->cmd = strdup(value);
+ return 0;
+}
+
/*
* These are to give UI layer defaults.
* The core-level commands such as git-diff-files should
@@ -78,11 +121,18 @@ int git_diff_ui_config(const char *var, const char *value)
diff_detect_rename_default = DIFF_DETECT_RENAME;
return 0;
}
+ if (!prefixcmp(var, "diff.")) {
+ const char *ep = strrchr(var, '.');
+
+ if (ep != var + 4 && !strcmp(ep, ".command"))
+ return parse_lldiff_command(var, ep, value);
+ }
if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
int slot = parse_diff_color_slot(var, 11);
color_parse(value, var, diff_colors[slot]);
return 0;
}
+
return git_default_config(var, value);
}
@@ -1074,11 +1124,6 @@ static int file_is_binary(struct diff_filespec *one)
return 0;
else if (ATTR_FALSE(value))
return 1;
- else if (ATTR_UNSET(value))
- ;
- else
- die("unknown value %s given to 'diff' attribute",
- value);
}
if (!one->data) {
@@ -1752,6 +1797,30 @@ static void run_external_diff(const char *pgm,
}
}
+static const char *external_diff_attr(const char *name)
+{
+ struct git_attr_check attr_diff_check;
+
+ setup_diff_attr_check(&attr_diff_check);
+ if (!git_checkattr(name, 1, &attr_diff_check)) {
+ const char *value = attr_diff_check.value;
+ if (!ATTR_TRUE(value) &&
+ !ATTR_FALSE(value) &&
+ !ATTR_UNSET(value)) {
+ struct ll_diff_driver *drv;
+
+ if (!user_diff_tail) {
+ user_diff_tail = &user_diff;
+ git_config(git_diff_ui_config);
+ }
+ for (drv = user_diff; drv; drv = drv->next)
+ if (!strcmp(drv->name, value))
+ return drv->cmd;
+ }
+ }
+ return NULL;
+}
+
static void run_diff_cmd(const char *pgm,
const char *name,
const char *other,
@@ -1761,6 +1830,14 @@ static void run_diff_cmd(const char *pgm,
struct diff_options *o,
int complete_rewrite)
{
+ if (!o->allow_external)
+ pgm = NULL;
+ else {
+ const char *cmd = external_diff_attr(name);
+ if (cmd)
+ pgm = cmd;
+ }
+
if (pgm) {
run_external_diff(pgm, name, other, one, two, xfrm_msg,
complete_rewrite);
diff --git a/diff.h b/diff.h
index a0d2ce1399..63738c1dd4 100644
--- a/diff.h
+++ b/diff.h
@@ -59,6 +59,7 @@ struct diff_options {
color_diff_words:1,
has_changes:1,
quiet:1,
+ allow_external:1,
exit_with_status:1;
int context;
int break_opt;
diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh
new file mode 100755
index 0000000000..f0045cd788
--- /dev/null
+++ b/t/t4020-diff-external.sh
@@ -0,0 +1,97 @@
+#!/bin/sh
+
+test_description='external diff interface test'
+
+. ./test-lib.sh
+
+_z40=0000000000000000000000000000000000000000
+
+test_expect_success setup '
+
+ test_tick &&
+ echo initial >file &&
+ git add file &&
+ git commit -m initial &&
+
+ test_tick &&
+ echo second >file &&
+ git add file &&
+ git commit -m second &&
+
+ test_tick &&
+ echo third >file
+'
+
+test_expect_success 'GIT_EXTERNAL_DIFF environment' '
+
+ GIT_EXTERNAL_DIFF=echo git diff | {
+ read path oldfile oldhex oldmode newfile newhex newmode &&
+ test "z$path" = zfile &&
+ test "z$oldmode" = z100644 &&
+ test "z$newhex" = "z$_z40" &&
+ test "z$newmode" = z100644 &&
+ oh=$(git rev-parse --verify HEAD:file) &&
+ test "z$oh" = "z$oldhex"
+ }
+
+'
+
+test_expect_success 'GIT_EXTERNAL_DIFF environment should apply only to diff' '
+
+ GIT_EXTERNAL_DIFF=echo git log -p -1 HEAD |
+ grep "^diff --git a/file b/file"
+
+'
+
+test_expect_success 'diff attribute' '
+
+ git config diff.parrot.command echo &&
+
+ echo >.gitattributes "file diff=parrot" &&
+
+ git diff | {
+ read path oldfile oldhex oldmode newfile newhex newmode &&
+ test "z$path" = zfile &&
+ test "z$oldmode" = z100644 &&
+ test "z$newhex" = "z$_z40" &&
+ test "z$newmode" = z100644 &&
+ oh=$(git rev-parse --verify HEAD:file) &&
+ test "z$oh" = "z$oldhex"
+ }
+
+'
+
+test_expect_success 'diff attribute should apply only to diff' '
+
+ git log -p -1 HEAD |
+ grep "^diff --git a/file b/file"
+
+'
+
+test_expect_success 'diff attribute' '
+
+ git config --unset diff.parrot.command &&
+ git config diff.color.command echo &&
+
+ echo >.gitattributes "file diff=color" &&
+
+ git diff | {
+ read path oldfile oldhex oldmode newfile newhex newmode &&
+ test "z$path" = zfile &&
+ test "z$oldmode" = z100644 &&
+ test "z$newhex" = "z$_z40" &&
+ test "z$newmode" = z100644 &&
+ oh=$(git rev-parse --verify HEAD:file) &&
+ test "z$oh" = "z$oldhex"
+ }
+
+'
+
+test_expect_success 'diff attribute should apply only to diff' '
+
+ git log -p -1 HEAD |
+ grep "^diff --git a/file b/file"
+
+'
+
+test_done