Support 'diff=pgm' attribute
authorJunio C Hamano <junkio@cox.net>
Mon, 23 Apr 2007 00:52:55 +0000 (17:52 -0700)
committerJunio C Hamano <junkio@cox.net>
Mon, 23 Apr 2007 05:16:14 +0000 (22:16 -0700)
This enhances the attributes mechanism so that external programs
meant for existing GIT_EXTERNAL_DIFF interface can be specifed
per path.

To configure such a custom diff driver, first define a custom
diff driver in the configuration:

[diff "my-c-diff"]
command = <<your command string comes here>>

Then mark the paths that you want to use this custom driver
using the attribute mechanism.

*.c diff=my-c-diff

The intent of this separation is that the attribute mechanism is
used for specifying the type of the contents, while the
configuration mechanism is used to define what needs to be done
to that type of the contents, which would be specific to both
platform and personal taste.

Signed-off-by: Junio C Hamano <junkio@cox.net>
builtin-diff.c
combine-diff.c
diff.c
diff.h
t/t4020-diff-external.sh [new file with mode: 0755]

index 21d13f0b30359295b8385754fccb4bb71f995dba..2ae60097b8cfddd873a38f3988185d9fde77f511 100644 (file)
@@ -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.
index 3a9b32f6b8882f3adb91e5833c205635657a98b7..cff9c5dc426cae9bd517614e00739edc4b40d635 100644 (file)
@@ -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 f516664968a6cb448611c24489254cbb2c9947ff..9dfded76642c1136701fb05557c182dc92448394 100644 (file)
--- 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 a0d2ce13994c1a8751bf7b207671e95c5bc5db97..63738c1dd4c71cb1beacaffea40bf51377a137ea 100644 (file)
--- 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 (executable)
index 0000000..f0045cd
--- /dev/null
@@ -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