From be3cfa85f45e32722a65349d023667be906a66b6 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 26 Apr 2005 09:25:05 -0700 Subject: [PATCH] Diff-tree-helper take two. This reworks the diff-tree-helper and show-diff to further make external diff command interface simpler. These commands now honor GIT_EXTERNAL_DIFF environment variable which can point at an arbitrary program that takes 7 parameters: name file1 file1-sha1 file1-mode file2 file2-sha1 file2-mode The parameters for an external diff command are as follows: name this invocation of the command is to emit diff for the named cache/tree entry. file1 pathname that holds the contents of the first file. This can be a file inside the working tree, or a temporary file created from the blob object, or /dev/null. The command should not attempt to unlink it -- the temporary is unlinked by the caller. file1-sha1 sha1 hash if file1 is a blob object, or "." otherwise. file1-mode mode bits for file1, or "." for a deleted file. If GIT_EXTERNAL_DIFF environment variable is not set, the default is to invoke diff with the set of parameters old show-diff used to use. This built-in implementation honors the GIT_DIFF_CMD and GIT_DIFF_OPTS environment variables as before. Signed-off-by: Junio C Hamano Signed-off-by: Linus Torvalds --- diff.c | 248 +++++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 205 insertions(+), 43 deletions(-) (limited to 'diff.c') diff --git a/diff.c b/diff.c index dcefe983a..9905cdb01 100644 --- a/diff.c +++ b/diff.c @@ -1,13 +1,22 @@ +/* + * Copyright (C) 2005 Junio C Hamano + */ +#include +#include #include "cache.h" #include "diff.h" -static char *diff_cmd = "diff -L 'k/%s' -L 'l/%s' "; -static char *diff_opts = "-p -u"; -static char *diff_arg_forward = " - '%s'"; -static char *diff_arg_reverse = " '%s' -"; +static char *diff_cmd = "diff -L'k/%s' -L'l/%s'"; +static char *diff_opts = "-pu"; -void prepare_diff_cmd(void) +static const char *external_diff(void) { + static char *external_diff_cmd = NULL; + static int done_preparing = 0; + + if (done_preparing) + return external_diff_cmd; + /* * Default values above are meant to match the * Linux kernel development style. Examples of @@ -17,8 +26,15 @@ void prepare_diff_cmd(void) * GIT_DIFF_CMD="diff -L '%s' -L '%s'" * GIT_DIFF_OPTS="-c"; */ + if (getenv("GIT_EXTERNAL_DIFF")) + external_diff_cmd = getenv("GIT_EXTERNAL_DIFF"); + + /* In case external diff fails... */ diff_cmd = getenv("GIT_DIFF_CMD") ? : diff_cmd; diff_opts = getenv("GIT_DIFF_OPTS") ? : diff_opts; + + done_preparing = 1; + return external_diff_cmd; } /* Help to copy the thing properly quoted for the shell safety. @@ -58,49 +74,195 @@ static char *sq_expand(const char *src) return buf; } -void show_differences(const char *name, /* filename on the filesystem */ - const char *label, /* diff label to use */ - void *old_contents, /* contents in core */ - unsigned long long old_size, /* size in core */ - int reverse /* 0: diff core file - 1: diff file core */) +static struct diff_tempfile { + const char *name; + char hex[41]; + char mode[10]; + char tmp_path[50]; +} diff_temp[2]; + +static void builtin_diff(const char *name, + struct diff_tempfile *temp) { - FILE *f; - char *name_sq = sq_expand(name); - const char *label_sq = (name != label) ? sq_expand(label) : name_sq; - char *diff_arg = reverse ? diff_arg_reverse : diff_arg_forward; - int cmd_size = strlen(name_sq) + strlen(label_sq) * 2 + - strlen(diff_cmd) + strlen(diff_opts) + strlen(diff_arg); + static char *diff_arg = "'%s' '%s'"; + const char *name_1_sq = sq_expand(temp[0].name); + const char *name_2_sq = sq_expand(temp[1].name); + const char *name_sq = sq_expand(name); + + /* diff_cmd and diff_arg have 4 %s in total which makes + * the sum of these strings 8 bytes larger than required. + * we use 2 spaces around diff-opts, and we need to count + * terminating NUL, so we subtract 5 here. + */ + int cmd_size = (strlen(diff_cmd) + + strlen(name_sq) * 2 + + strlen(diff_opts) + + strlen(diff_arg) + + strlen(name_1_sq) + strlen(name_2_sq) + - 5); char *cmd = malloc(cmd_size); - int next_at; - - fflush(stdout); - next_at = snprintf(cmd, cmd_size, diff_cmd, label_sq, label_sq); - next_at += snprintf(cmd+next_at, cmd_size-next_at, "%s", diff_opts); - next_at += snprintf(cmd+next_at, cmd_size-next_at, diff_arg, name_sq); - f = popen(cmd, "w"); - if (old_size) - fwrite(old_contents, old_size, 1, f); - pclose(f); - if (label_sq != name_sq) - free((void*)label_sq); /* constness */ - free(name_sq); - free(cmd); + int next_at = 0; + + next_at += snprintf(cmd+next_at, cmd_size-next_at, + diff_cmd, name_sq, name_sq); + next_at += snprintf(cmd+next_at, cmd_size-next_at, + " %s ", diff_opts); + next_at += snprintf(cmd+next_at, cmd_size-next_at, + diff_arg, name_1_sq, name_2_sq); + execlp("/bin/sh","sh", "-c", cmd, NULL); } -void show_diff_empty(const unsigned char *sha1, - const char *name, - int reverse) +static void prepare_temp_file(const char *name, + struct diff_tempfile *temp, + struct diff_spec *one) { - char *old; - unsigned long int size; - unsigned char type[20]; - - old = read_sha1_file(sha1, type, &size); - if (! old) { - error("unable to read blob object for %s (%s)", name, - sha1_to_hex(sha1)); + static unsigned char null_sha1[20] = { 0, }; + + if (!one->file_valid) { + not_a_valid_file: + temp->name = "/dev/null"; + strcpy(temp->hex, "."); + strcpy(temp->mode, "."); return; } - show_differences("/dev/null", name, old, size, reverse); + + if (one->sha1_valid && + !memcmp(one->u.sha1, null_sha1, sizeof(null_sha1))) { + one->sha1_valid = 0; + one->u.name = name; + } + + if (!one->sha1_valid) { + struct stat st; + temp->name = one->u.name; + if (stat(temp->name, &st) < 0) { + if (errno == ENOENT) + goto not_a_valid_file; + die("stat(%s): %s", temp->name, strerror(errno)); + } + strcpy(temp->hex, "."); + sprintf(temp->mode, "%06o", + S_IFREG |ce_permissions(st.st_mode)); + } + else { + int fd; + void *blob; + char type[20]; + unsigned long size; + + blob = read_sha1_file(one->u.sha1, type, &size); + if (!blob || strcmp(type, "blob")) + die("unable to read blob object for %s (%s)", + name, sha1_to_hex(one->u.sha1)); + + strcpy(temp->tmp_path, ".diff_XXXXXX"); + fd = mkstemp(temp->tmp_path); + if (fd < 0) + die("unable to create temp-file"); + if (write(fd, blob, size) != size) + die("unable to write temp-file"); + close(fd); + free(blob); + temp->name = temp->tmp_path; + strcpy(temp->hex, sha1_to_hex(one->u.sha1)); + temp->hex[40] = 0; + sprintf(temp->mode, "%06o", one->mode); + } +} + +static void remove_tempfile(void) +{ + int i; + + for (i = 0; i < 2; i++) + if (diff_temp[i].name == diff_temp[i].tmp_path) { + unlink(diff_temp[i].name); + diff_temp[i].name = NULL; + } +} + +/* An external diff command takes: + * + * diff-cmd name infile1 infile1-sha1 infile1-mode \ + * infile2 infile2-sha1 infile2-mode. + * + */ +void run_external_diff(const char *name, + struct diff_spec *one, + struct diff_spec *two) +{ + struct diff_tempfile *temp = diff_temp; + int pid, status; + static int atexit_asked = 0; + + prepare_temp_file(name, &temp[0], one); + prepare_temp_file(name, &temp[1], two); + if (! atexit_asked && + (temp[0].name == temp[0].tmp_path || + temp[1].name == temp[1].tmp_path)) { + atexit_asked = 1; + atexit(remove_tempfile); + } + + fflush(NULL); + pid = fork(); + if (pid < 0) + die("unable to fork"); + if (!pid) { + const char *pgm = external_diff(); + if (pgm) + execlp(pgm, pgm, + name, + temp[0].name, temp[0].hex, temp[0].mode, + temp[1].name, temp[1].hex, temp[1].mode, + NULL); + /* + * otherwise we use the built-in one. + */ + builtin_diff(name, temp); + exit(0); + } + if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status)) + die("diff program failed"); + + remove_tempfile(); +} + +void show_diff_empty(const struct cache_entry *ce, int reverse) +{ + struct diff_spec spec[2], *one, *two; + + memcpy(spec[0].u.sha1, ce->sha1, 20); + spec[0].mode = ntohl(ce->ce_mode); + spec[0].sha1_valid = spec[0].file_valid = 1; + spec[1].file_valid = 0; + + if (reverse) { + one = spec + 1; two = spec; + } else { + one = spec; two = one + 1; + } + + run_external_diff(ce->name, one, two); +} + +void show_differences(const struct cache_entry *ce, int reverse) +{ + struct diff_spec spec[2], *one, *two; + + memcpy(spec[0].u.sha1, ce->sha1, 20); + spec[0].mode = ntohl(ce->ce_mode); + spec[0].sha1_valid = spec[0].file_valid = 1; + + spec[1].u.name = ce->name; /* the name we stated */ + spec[1].sha1_valid = 0; + spec[1].file_valid = 1; + + if (reverse) { + one = spec + 1; two = spec; + } else { + one = spec; two = one + 1; + } + + run_external_diff(ce->name, one, two); } -- cgit v1.2.1