| /* |
| * GIT - The information manager from hell |
| * |
| * Copyright (C) Linus Torvalds, 2005 |
| */ |
| #include "cache.h" |
| |
| static char *diff_cmd = "diff -L 'a/%s' -L 'b/%s' "; |
| static char *diff_opts = "-p -u"; |
| static char *diff_arg_forward = " - '%s'"; |
| static char *diff_arg_reverse = " '%s' -"; |
| |
| static void prepare_diff_cmd(void) |
| { |
| /* |
| * Default values above are meant to match the |
| * Linux kernel development style. Examples of |
| * alternative styles you can specify via environment |
| * variables are: |
| * |
| * GIT_DIFF_CMD="diff -L '%s' -L '%s'" |
| * GIT_DIFF_OPTS="-c"; |
| */ |
| diff_cmd = getenv("GIT_DIFF_CMD") ? : diff_cmd; |
| diff_opts = getenv("GIT_DIFF_OPTS") ? : diff_opts; |
| } |
| |
| /* Help to copy the thing properly quoted for the shell safety. |
| * any single quote is replaced with '\'', and the caller is |
| * expected to enclose the result within a single quote pair. |
| * |
| * E.g. |
| * original sq_expand result |
| * name ==> name ==> 'name' |
| * a b ==> a b ==> 'a b' |
| * a'b ==> a'\''b ==> 'a'\''b' |
| */ |
| static char *sq_expand(char *src) |
| { |
| static char *buf = NULL; |
| int cnt, c; |
| char *cp; |
| |
| /* count bytes needed to store the quoted string. */ |
| for (cnt = 1, cp = src; *cp; cnt++, cp++) |
| if (*cp == '\'') |
| cnt += 3; |
| |
| if (! (buf = malloc(cnt))) |
| return buf; |
| cp = buf; |
| while ((c = *src++)) { |
| if (c != '\'') |
| *cp++ = c; |
| else { |
| cp = strcpy(cp, "'\\''"); |
| cp += 4; |
| } |
| } |
| *cp = 0; |
| return buf; |
| } |
| |
| static void show_differences(char *name, char *label, void *old_contents, |
| unsigned long long old_size, int reverse) |
| { |
| FILE *f; |
| char *name_sq = sq_expand(name); |
| 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); |
| 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(label_sq); |
| free(name_sq); |
| free(cmd); |
| } |
| |
| static void show_diff_empty(struct cache_entry *ce, int reverse) |
| { |
| char *old; |
| unsigned long int size; |
| unsigned char type[20]; |
| |
| old = read_sha1_file(ce->sha1, type, &size); |
| if (! old) { |
| error("unable to read blob object for %s (%s)", ce->name, |
| sha1_to_hex(ce->sha1)); |
| return; |
| } |
| show_differences("/dev/null", ce->name, old, size, reverse); |
| } |
| |
| static const char *show_diff_usage = "show-diff [-q] [-s] [-z] [paths...]"; |
| |
| static int matches_pathspec(struct cache_entry *ce, char **spec, int cnt) |
| { |
| int i; |
| int namelen = ce_namelen(ce); |
| for (i = 0; i < cnt; i++) { |
| int speclen = strlen(spec[i]); |
| if (! strncmp(spec[i], ce->name, speclen) && |
| speclen <= namelen && |
| (ce->name[speclen] == 0 || |
| ce->name[speclen] == '/')) |
| return 1; |
| } |
| return 0; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int silent = 0; |
| int silent_on_nonexisting_files = 0; |
| int machine_readable = 0; |
| int reverse = 0; |
| int entries = read_cache(); |
| int i; |
| |
| while (1 < argc && argv[1][0] == '-') { |
| if (!strcmp(argv[1], "-R")) |
| reverse = 1; |
| else if (!strcmp(argv[1], "-s")) |
| silent_on_nonexisting_files = silent = 1; |
| else if (!strcmp(argv[1], "-q")) |
| silent_on_nonexisting_files = 1; |
| else if (!strcmp(argv[1], "-z")) |
| machine_readable = 1; |
| else |
| usage(show_diff_usage); |
| argv++; argc--; |
| } |
| |
| /* At this point, if argc == 1, then we are doing everything. |
| * Otherwise argv[1] .. argv[argc-1] have the explicit paths. |
| */ |
| if (entries < 0) { |
| perror("read_cache"); |
| exit(1); |
| } |
| prepare_diff_cmd(); |
| for (i = 0; i < entries; i++) { |
| struct stat st; |
| struct cache_entry *ce = active_cache[i]; |
| int changed; |
| unsigned long size; |
| char type[20]; |
| void *old; |
| |
| if (1 < argc && |
| ! matches_pathspec(ce, argv+1, argc-1)) |
| continue; |
| |
| if (ce_stage(ce)) { |
| if (machine_readable) |
| printf("U %s%c", ce->name, 0); |
| else |
| printf("%s: Unmerged\n", |
| ce->name); |
| while (i < entries && |
| !strcmp(ce->name, active_cache[i]->name)) |
| i++; |
| i--; /* compensate for loop control increments */ |
| continue; |
| } |
| |
| if (stat(ce->name, &st) < 0) { |
| if (errno == ENOENT && silent_on_nonexisting_files) |
| continue; |
| if (machine_readable) |
| printf("X %s%c", ce->name, 0); |
| else { |
| printf("%s: %s\n", ce->name, strerror(errno)); |
| if (errno == ENOENT) |
| show_diff_empty(ce, reverse); |
| } |
| continue; |
| } |
| changed = cache_match_stat(ce, &st); |
| if (!changed) |
| continue; |
| if (!machine_readable) |
| printf("%s: %s\n", ce->name, sha1_to_hex(ce->sha1)); |
| else { |
| printf("%s %s%c", sha1_to_hex(ce->sha1), ce->name, 0); |
| continue; |
| } |
| if (silent) |
| continue; |
| |
| old = read_sha1_file(ce->sha1, type, &size); |
| if (! old) |
| error("unable to read blob object for %s (%s)", |
| ce->name, sha1_to_hex(ce->sha1)); |
| else |
| show_differences(ce->name, ce->name, old, size, |
| reverse); |
| free(old); |
| } |
| return 0; |
| } |