Skip to content

Commit

Permalink
Support 'diff=pgm' attribute
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Junio C Hamano committed Apr 23, 2007
1 parent d83c9af commit f1af60b
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 5 deletions.
1 change: 1 addition & 0 deletions builtin-diff.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions combine-diff.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
87 changes: 82 additions & 5 deletions diff.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions diff.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
97 changes: 97 additions & 0 deletions t/t4020-diff-external.sh
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit f1af60b

Please sign in to comment.