From 6973dcaee76ef7b7bfcabd2f26e76205aae07858 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 21 Apr 2006 23:57:45 -0700 Subject: Libify diff-files. This is the first installment to libify diff brothers. The updated diff-files uses revision.c::setup_revisions() infrastructure to parse its command line arguments, which means the pathname arguments are checked more strictly than before. The tests are adjusted to separate possibly missing paths from the rest of arguments with double-dashes, to show the kosher way. As Linus pointed out, renaming diff.c to diff-lib.c was simply stupid, so I am renaming it back. The new diff-lib.c is to contain pieces extracted from diff brothers. Signed-off-by: Junio C Hamano --- Makefile | 2 +- diff-files.c | 212 +---- diff-lib.c | 1862 +++-------------------------------------- diff.c | 1795 +++++++++++++++++++++++++++++++++++++++ diff.h | 7 +- t/t1001-read-tree-m-2way.sh | 2 +- t/t1002-read-tree-m-u-2way.sh | 2 +- 7 files changed, 1929 insertions(+), 1953 deletions(-) create mode 100644 diff.c diff --git a/Makefile b/Makefile index d9a3a82fe2..0924f84c4f 100644 --- a/Makefile +++ b/Makefile @@ -199,7 +199,7 @@ LIB_H = \ tree-walk.h log-tree.h DIFF_OBJS = \ - diff-lib.o diffcore-break.o diffcore-order.o \ + diff.o diff-lib.o diffcore-break.o diffcore-order.o \ diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \ diffcore-delta.o log-tree.o diff --git a/diff-files.c b/diff-files.c index ffbef48b2e..b9d193d506 100644 --- a/diff-files.c +++ b/diff-files.c @@ -12,203 +12,43 @@ static const char diff_files_usage[] = "git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [] [...]" COMMON_DIFF_OPTIONS_HELP; -static struct rev_info rev; -static int silent = 0; -static int diff_unmerged_stage = 2; -static int combine_merges = 0; -static int dense_combined_merges = 0; - -static void show_unmerge(const char *path) -{ - diff_unmerge(&rev.diffopt, path); -} - -static void show_file(int pfx, struct cache_entry *ce) -{ - diff_addremove(&rev.diffopt, pfx, ntohl(ce->ce_mode), - ce->sha1, ce->name, NULL); -} - -static void show_modified(int oldmode, int mode, - const unsigned char *old_sha1, const unsigned char *sha1, - char *path) -{ - diff_change(&rev.diffopt, oldmode, mode, old_sha1, sha1, path, NULL); -} - int main(int argc, const char **argv) { - const char **pathspec; - const char *prefix = setup_git_directory(); - int entries, i; + struct rev_info rev; + int silent = 0; git_config(git_diff_config); - diff_setup(&rev.diffopt); + init_revisions(&rev); + rev.abbrev = 0; + + argc = setup_revisions(argc, argv, &rev, NULL); while (1 < argc && argv[1][0] == '-') { - if (!strcmp(argv[1], "--")) { - argv++; - argc--; - break; - } - if (!strcmp(argv[1], "-0")) - diff_unmerged_stage = 0; - else if (!strcmp(argv[1], "-1")) - diff_unmerged_stage = 1; - else if (!strcmp(argv[1], "-2")) - diff_unmerged_stage = 2; - else if (!strcmp(argv[1], "-3")) - diff_unmerged_stage = 3; - else if (!strcmp(argv[1], "--base")) - diff_unmerged_stage = 1; + if (!strcmp(argv[1], "--base")) + rev.max_count = 1; else if (!strcmp(argv[1], "--ours")) - diff_unmerged_stage = 2; + rev.max_count = 2; else if (!strcmp(argv[1], "--theirs")) - diff_unmerged_stage = 3; + rev.max_count = 3; else if (!strcmp(argv[1], "-q")) silent = 1; - else if (!strcmp(argv[1], "-r")) - ; /* no-op */ - else if (!strcmp(argv[1], "-s")) - ; /* no-op */ - else if (!strcmp(argv[1], "-c")) - combine_merges = 1; - else if (!strcmp(argv[1], "--cc")) - dense_combined_merges = combine_merges = 1; - else { - int diff_opt_cnt; - diff_opt_cnt = diff_opt_parse(&rev.diffopt, - argv+1, argc-1); - if (diff_opt_cnt < 0) - usage(diff_files_usage); - else if (diff_opt_cnt) { - argv += diff_opt_cnt; - argc -= diff_opt_cnt; - continue; - } - else - usage(diff_files_usage); - } + else + usage(diff_files_usage); argv++; argc--; } - if (dense_combined_merges) - rev.diffopt.output_format = DIFF_FORMAT_PATCH; - - /* Find the directory, and set up the pathspec */ - pathspec = get_pathspec(prefix, argv + 1); - entries = read_cache(); - - if (diff_setup_done(&rev.diffopt) < 0) + /* + * Make sure there are NO revision (i.e. pending object) parameter, + * rev.max_count is reasonable (0 <= n <= 3), + * there is no other revision filtering parameters. + */ + if (rev.pending_objects || + rev.min_age != -1 || rev.max_age != -1) usage(diff_files_usage); - - /* At this point, if argc == 1, then we are doing everything. - * Otherwise argv[1] .. argv[argc-1] have the explicit paths. + /* + * Backward compatibility wart - "diff-files -s" used to + * defeat the common diff option "-s" which asked for + * DIFF_FORMAT_NO_OUTPUT. */ - if (entries < 0) { - perror("read_cache"); - exit(1); - } - - for (i = 0; i < entries; i++) { - struct stat st; - unsigned int oldmode, newmode; - struct cache_entry *ce = active_cache[i]; - int changed; - - if (!ce_path_match(ce, pathspec)) - continue; - - if (ce_stage(ce)) { - struct { - struct combine_diff_path p; - struct combine_diff_parent filler[5]; - } combine; - int num_compare_stages = 0; - - combine.p.next = NULL; - combine.p.len = ce_namelen(ce); - combine.p.path = xmalloc(combine.p.len + 1); - memcpy(combine.p.path, ce->name, combine.p.len); - combine.p.path[combine.p.len] = 0; - combine.p.mode = 0; - memset(combine.p.sha1, 0, 20); - memset(&combine.p.parent[0], 0, - sizeof(combine.filler)); - - while (i < entries) { - struct cache_entry *nce = active_cache[i]; - int stage; - - if (strcmp(ce->name, nce->name)) - break; - - /* Stage #2 (ours) is the first parent, - * stage #3 (theirs) is the second. - */ - stage = ce_stage(nce); - if (2 <= stage) { - int mode = ntohl(nce->ce_mode); - num_compare_stages++; - memcpy(combine.p.parent[stage-2].sha1, - nce->sha1, 20); - combine.p.parent[stage-2].mode = - canon_mode(mode); - combine.p.parent[stage-2].status = - DIFF_STATUS_MODIFIED; - } - - /* diff against the proper unmerged stage */ - if (stage == diff_unmerged_stage) - ce = nce; - i++; - } - /* - * Compensate for loop update - */ - i--; - - if (combine_merges && num_compare_stages == 2) { - show_combined_diff(&combine.p, 2, - dense_combined_merges, - &rev); - free(combine.p.path); - continue; - } - free(combine.p.path); - - /* - * Show the diff for the 'ce' if we found the one - * from the desired stage. - */ - show_unmerge(ce->name); - if (ce_stage(ce) != diff_unmerged_stage) - continue; - } - - if (lstat(ce->name, &st) < 0) { - if (errno != ENOENT && errno != ENOTDIR) { - perror(ce->name); - continue; - } - if (silent) - continue; - show_file('-', ce); - continue; - } - changed = ce_match_stat(ce, &st, 0); - if (!changed && !rev.diffopt.find_copies_harder) - continue; - oldmode = ntohl(ce->ce_mode); - - newmode = canon_mode(st.st_mode); - if (!trust_executable_bit && - S_ISREG(newmode) && S_ISREG(oldmode) && - ((newmode ^ oldmode) == 0111)) - newmode = oldmode; - show_modified(oldmode, newmode, - ce->sha1, (changed ? null_sha1 : ce->sha1), - ce->name); - } - diffcore_std(&rev.diffopt); - diff_flush(&rev.diffopt); - return 0; + if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT) + rev.diffopt.output_format = DIFF_FORMAT_RAW; + return run_diff_files(&rev, silent); } diff --git a/diff-lib.c b/diff-lib.c index 13b216f273..a28dd3dca7 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -1,1795 +1,133 @@ /* * Copyright (C) 2005 Junio C Hamano */ -#include -#include -#include #include "cache.h" #include "quote.h" +#include "commit.h" #include "diff.h" #include "diffcore.h" -#include "xdiff-interface.h" - -static int use_size_cache; - -int diff_rename_limit_default = -1; - -int git_diff_config(const char *var, const char *value) -{ - if (!strcmp(var, "diff.renamelimit")) { - diff_rename_limit_default = git_config_int(var, value); - return 0; - } - - return git_default_config(var, value); -} - -static char *quote_one(const char *str) -{ - int needlen; - char *xp; - - if (!str) - return NULL; - needlen = quote_c_style(str, NULL, NULL, 0); - if (!needlen) - return strdup(str); - xp = xmalloc(needlen + 1); - quote_c_style(str, xp, NULL, 0); - return xp; -} - -static char *quote_two(const char *one, const char *two) -{ - int need_one = quote_c_style(one, NULL, NULL, 1); - int need_two = quote_c_style(two, NULL, NULL, 1); - char *xp; - - if (need_one + need_two) { - if (!need_one) need_one = strlen(one); - if (!need_two) need_one = strlen(two); - - xp = xmalloc(need_one + need_two + 3); - xp[0] = '"'; - quote_c_style(one, xp + 1, NULL, 1); - quote_c_style(two, xp + need_one + 1, NULL, 1); - strcpy(xp + need_one + need_two + 1, "\""); - return xp; - } - need_one = strlen(one); - need_two = strlen(two); - xp = xmalloc(need_one + need_two + 1); - strcpy(xp, one); - strcpy(xp + need_one, two); - return xp; -} - -static const char *external_diff(void) -{ - static const char *external_diff_cmd = NULL; - static int done_preparing = 0; - - if (done_preparing) - return external_diff_cmd; - external_diff_cmd = getenv("GIT_EXTERNAL_DIFF"); - done_preparing = 1; - return external_diff_cmd; -} - -#define TEMPFILE_PATH_LEN 50 - -static struct diff_tempfile { - const char *name; /* filename external diff should read from */ - char hex[41]; - char mode[10]; - char tmp_path[TEMPFILE_PATH_LEN]; -} diff_temp[2]; - -static int count_lines(const char *data, int size) -{ - int count, ch, completely_empty = 1, nl_just_seen = 0; - count = 0; - while (0 < size--) { - ch = *data++; - if (ch == '\n') { - count++; - nl_just_seen = 1; - completely_empty = 0; - } - else { - nl_just_seen = 0; - completely_empty = 0; - } - } - if (completely_empty) - return 0; - if (!nl_just_seen) - count++; /* no trailing newline */ - return count; -} - -static void print_line_count(int count) -{ - switch (count) { - case 0: - printf("0,0"); - break; - case 1: - printf("1"); - break; - default: - printf("1,%d", count); - break; - } -} - -static void copy_file(int prefix, const char *data, int size) -{ - int ch, nl_just_seen = 1; - while (0 < size--) { - ch = *data++; - if (nl_just_seen) - putchar(prefix); - putchar(ch); - if (ch == '\n') - nl_just_seen = 1; - else - nl_just_seen = 0; - } - if (!nl_just_seen) - printf("\n\\ No newline at end of file\n"); -} - -static void emit_rewrite_diff(const char *name_a, - const char *name_b, - struct diff_filespec *one, - struct diff_filespec *two) -{ - int lc_a, lc_b; - diff_populate_filespec(one, 0); - diff_populate_filespec(two, 0); - lc_a = count_lines(one->data, one->size); - lc_b = count_lines(two->data, two->size); - printf("--- %s\n+++ %s\n@@ -", name_a, name_b); - print_line_count(lc_a); - printf(" +"); - print_line_count(lc_b); - printf(" @@\n"); - if (lc_a) - copy_file('-', one->data, one->size); - if (lc_b) - copy_file('+', two->data, two->size); -} - -static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) -{ - if (!DIFF_FILE_VALID(one)) { - mf->ptr = ""; /* does not matter */ - mf->size = 0; - return 0; - } - else if (diff_populate_filespec(one, 0)) - return -1; - mf->ptr = one->data; - mf->size = one->size; - return 0; -} - -struct emit_callback { - const char **label_path; -}; - -static int fn_out(void *priv, mmbuffer_t *mb, int nbuf) -{ - int i; - struct emit_callback *ecbdata = priv; - - if (ecbdata->label_path[0]) { - printf("--- %s\n", ecbdata->label_path[0]); - printf("+++ %s\n", ecbdata->label_path[1]); - ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; - } - for (i = 0; i < nbuf; i++) - if (!fwrite(mb[i].ptr, mb[i].size, 1, stdout)) - return -1; - return 0; -} - -static char *pprint_rename(const char *a, const char *b) -{ - const char *old = a; - const char *new = b; - char *name = NULL; - int pfx_length, sfx_length; - int len_a = strlen(a); - int len_b = strlen(b); - - /* Find common prefix */ - pfx_length = 0; - while (*old && *new && *old == *new) { - if (*old == '/') - pfx_length = old - a + 1; - old++; - new++; - } - - /* Find common suffix */ - old = a + len_a; - new = b + len_b; - sfx_length = 0; - while (a <= old && b <= new && *old == *new) { - if (*old == '/') - sfx_length = len_a - (old - a); - old--; - new--; - } - - /* - * pfx{mid-a => mid-b}sfx - * {pfx-a => pfx-b}sfx - * pfx{sfx-a => sfx-b} - * name-a => name-b - */ - if (pfx_length + sfx_length) { - name = xmalloc(len_a + len_b - pfx_length - sfx_length + 7); - sprintf(name, "%.*s{%.*s => %.*s}%s", - pfx_length, a, - len_a - pfx_length - sfx_length, a + pfx_length, - len_b - pfx_length - sfx_length, b + pfx_length, - a + len_a - sfx_length); - } - else { - name = xmalloc(len_a + len_b + 5); - sprintf(name, "%s => %s", a, b); - } - return name; -} - -struct diffstat_t { - struct xdiff_emit_state xm; - - int nr; - int alloc; - struct diffstat_file { - char *name; - unsigned is_unmerged:1; - unsigned is_binary:1; - unsigned is_renamed:1; - unsigned int added, deleted; - } **files; -}; - -static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, - const char *name_a, - const char *name_b) -{ - struct diffstat_file *x; - x = xcalloc(sizeof (*x), 1); - if (diffstat->nr == diffstat->alloc) { - diffstat->alloc = alloc_nr(diffstat->alloc); - diffstat->files = xrealloc(diffstat->files, - diffstat->alloc * sizeof(x)); - } - diffstat->files[diffstat->nr++] = x; - if (name_b) { - x->name = pprint_rename(name_a, name_b); - x->is_renamed = 1; - } - else - x->name = strdup(name_a); - return x; -} - -static void diffstat_consume(void *priv, char *line, unsigned long len) -{ - struct diffstat_t *diffstat = priv; - struct diffstat_file *x = diffstat->files[diffstat->nr - 1]; - - if (line[0] == '+') - x->added++; - else if (line[0] == '-') - x->deleted++; -} - -static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; -static const char minuses[]= "----------------------------------------------------------------------"; - -static void show_stats(struct diffstat_t* data) -{ - char *prefix = ""; - int i, len, add, del, total, adds = 0, dels = 0; - int max, max_change = 0, max_len = 0; - int total_files = data->nr; - - if (data->nr == 0) - return; - - for (i = 0; i < data->nr; i++) { - struct diffstat_file *file = data->files[i]; - - len = strlen(file->name); - if (max_len < len) - max_len = len; - - if (file->is_binary || file->is_unmerged) - continue; - if (max_change < file->added + file->deleted) - max_change = file->added + file->deleted; - } - - for (i = 0; i < data->nr; i++) { - char *name = data->files[i]->name; - int added = data->files[i]->added; - int deleted = data->files[i]->deleted; - - if (0 < (len = quote_c_style(name, NULL, NULL, 0))) { - char *qname = xmalloc(len + 1); - quote_c_style(name, qname, NULL, 0); - free(name); - data->files[i]->name = name = qname; - } - - /* - * "scale" the filename - */ - len = strlen(name); - max = max_len; - if (max > 50) - max = 50; - if (len > max) { - char *slash; - prefix = "..."; - max -= 3; - name += len - max; - slash = strchr(name, '/'); - if (slash) - name = slash; - } - len = max; - - /* - * scale the add/delete - */ - max = max_change; - if (max + len > 70) - max = 70 - len; - - if (data->files[i]->is_binary) { - printf(" %s%-*s | Bin\n", prefix, len, name); - goto free_diffstat_file; - } - else if (data->files[i]->is_unmerged) { - printf(" %s%-*s | Unmerged\n", prefix, len, name); - goto free_diffstat_file; - } - else if (!data->files[i]->is_renamed && - (added + deleted == 0)) { - total_files--; - goto free_diffstat_file; - } - - add = added; - del = deleted; - total = add + del; - adds += add; - dels += del; - - if (max_change > 0) { - total = (total * max + max_change / 2) / max_change; - add = (add * max + max_change / 2) / max_change; - del = total - add; - } - printf(" %s%-*s |%5d %.*s%.*s\n", prefix, - len, name, added + deleted, - add, pluses, del, minuses); - free_diffstat_file: - free(data->files[i]->name); - free(data->files[i]); - } - free(data->files); - printf(" %d files changed, %d insertions(+), %d deletions(-)\n", - total_files, adds, dels); -} - -#define FIRST_FEW_BYTES 8000 -static int mmfile_is_binary(mmfile_t *mf) -{ - long sz = mf->size; - if (FIRST_FEW_BYTES < sz) - sz = FIRST_FEW_BYTES; - if (memchr(mf->ptr, 0, sz)) - return 1; - return 0; -} - -static void builtin_diff(const char *name_a, - const char *name_b, - struct diff_filespec *one, - struct diff_filespec *two, - const char *xfrm_msg, - int complete_rewrite) -{ - mmfile_t mf1, mf2; - const char *lbl[2]; - char *a_one, *b_two; - - a_one = quote_two("a/", name_a); - b_two = quote_two("b/", name_b); - lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; - lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; - printf("diff --git %s %s\n", a_one, b_two); - if (lbl[0][0] == '/') { - /* /dev/null */ - printf("new file mode %06o\n", two->mode); - if (xfrm_msg && xfrm_msg[0]) - puts(xfrm_msg); - } - else if (lbl[1][0] == '/') { - printf("deleted file mode %06o\n", one->mode); - if (xfrm_msg && xfrm_msg[0]) - puts(xfrm_msg); - } - else { - if (one->mode != two->mode) { - printf("old mode %06o\n", one->mode); - printf("new mode %06o\n", two->mode); - } - if (xfrm_msg && xfrm_msg[0]) - puts(xfrm_msg); - /* - * we do not run diff between different kind - * of objects. - */ - if ((one->mode ^ two->mode) & S_IFMT) - goto free_ab_and_return; - if (complete_rewrite) { - emit_rewrite_diff(name_a, name_b, one, two); - goto free_ab_and_return; - } - } - - if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) - die("unable to read files to diff"); - - if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2)) - printf("Binary files %s and %s differ\n", lbl[0], lbl[1]); - else { - /* Crazy xdl interfaces.. */ - const char *diffopts = getenv("GIT_DIFF_OPTS"); - xpparam_t xpp; - xdemitconf_t xecfg; - xdemitcb_t ecb; - struct emit_callback ecbdata; - - ecbdata.label_path = lbl; - xpp.flags = XDF_NEED_MINIMAL; - xecfg.ctxlen = 3; - xecfg.flags = XDL_EMIT_FUNCNAMES; - if (!diffopts) - ; - else if (!strncmp(diffopts, "--unified=", 10)) - xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10); - else if (!strncmp(diffopts, "-u", 2)) - xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10); - ecb.outf = fn_out; - ecb.priv = &ecbdata; - xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); - } - - free_ab_and_return: - free(a_one); - free(b_two); - return; -} - -static void builtin_diffstat(const char *name_a, const char *name_b, - struct diff_filespec *one, - struct diff_filespec *two, - struct diffstat_t *diffstat) -{ - mmfile_t mf1, mf2; - struct diffstat_file *data; - - data = diffstat_add(diffstat, name_a, name_b); - - if (!one || !two) { - data->is_unmerged = 1; - return; - } - - if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) - die("unable to read files to diff"); - - if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2)) - data->is_binary = 1; - else { - /* Crazy xdl interfaces.. */ - xpparam_t xpp; - xdemitconf_t xecfg; - xdemitcb_t ecb; - - xpp.flags = XDF_NEED_MINIMAL; - xecfg.ctxlen = 0; - xecfg.flags = 0; - ecb.outf = xdiff_outf; - ecb.priv = diffstat; - xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); - } -} - -struct diff_filespec *alloc_filespec(const char *path) -{ - int namelen = strlen(path); - struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1); - - memset(spec, 0, sizeof(*spec)); - spec->path = (char *)(spec + 1); - memcpy(spec->path, path, namelen+1); - return spec; -} - -void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, - unsigned short mode) -{ - if (mode) { - spec->mode = canon_mode(mode); - memcpy(spec->sha1, sha1, 20); - spec->sha1_valid = !!memcmp(sha1, null_sha1, 20); - } -} +#include "revision.h" /* - * Given a name and sha1 pair, if the dircache tells us the file in - * the work tree has that object contents, return true, so that - * prepare_temp_file() does not have to inflate and extract. + * diff-files */ -static int work_tree_matches(const char *name, const unsigned char *sha1) -{ - struct cache_entry *ce; - struct stat st; - int pos, len; - - /* We do not read the cache ourselves here, because the - * benchmark with my previous version that always reads cache - * shows that it makes things worse for diff-tree comparing - * two linux-2.6 kernel trees in an already checked out work - * tree. This is because most diff-tree comparisons deal with - * only a small number of files, while reading the cache is - * expensive for a large project, and its cost outweighs the - * savings we get by not inflating the object to a temporary - * file. Practically, this code only helps when we are used - * by diff-cache --cached, which does read the cache before - * calling us. - */ - if (!active_cache) - return 0; - - len = strlen(name); - pos = cache_name_pos(name, len); - if (pos < 0) - return 0; - ce = active_cache[pos]; - if ((lstat(name, &st) < 0) || - !S_ISREG(st.st_mode) || /* careful! */ - ce_match_stat(ce, &st, 0) || - memcmp(sha1, ce->sha1, 20)) - return 0; - /* we return 1 only when we can stat, it is a regular file, - * stat information matches, and sha1 recorded in the cache - * matches. I.e. we know the file in the work tree really is - * the same as the pair. - */ - return 1; -} - -static struct sha1_size_cache { - unsigned char sha1[20]; - unsigned long size; -} **sha1_size_cache; -static int sha1_size_cache_nr, sha1_size_cache_alloc; -static struct sha1_size_cache *locate_size_cache(unsigned char *sha1, - int find_only, - unsigned long size) +int run_diff_files(struct rev_info *revs, int silent_on_removed) { - int first, last; - struct sha1_size_cache *e; + int entries, i; + int diff_unmerged_stage = revs->max_count; - first = 0; - last = sha1_size_cache_nr; - while (last > first) { - int cmp, next = (last + first) >> 1; - e = sha1_size_cache[next]; - cmp = memcmp(e->sha1, sha1, 20); - if (!cmp) - return e; - if (cmp < 0) { - last = next; - continue; - } - first = next+1; - } - /* not found */ - if (find_only) - return NULL; - /* insert to make it at "first" */ - if (sha1_size_cache_alloc <= sha1_size_cache_nr) { - sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc); - sha1_size_cache = xrealloc(sha1_size_cache, - sha1_size_cache_alloc * - sizeof(*sha1_size_cache)); - } - sha1_size_cache_nr++; - if (first < sha1_size_cache_nr) - memmove(sha1_size_cache + first + 1, sha1_size_cache + first, - (sha1_size_cache_nr - first - 1) * - sizeof(*sha1_size_cache)); - e = xmalloc(sizeof(struct sha1_size_cache)); - sha1_size_cache[first] = e; - memcpy(e->sha1, sha1, 20); - e->size = size; - return e; -} - -/* - * While doing rename detection and pickaxe operation, we may need to - * grab the data for the blob (or file) for our own in-core comparison. - * diff_filespec has data and size fields for this purpose. - */ -int diff_populate_filespec(struct diff_filespec *s, int size_only) -{ - int err = 0; - if (!DIFF_FILE_VALID(s)) - die("internal error: asking to populate invalid file."); - if (S_ISDIR(s->mode)) + if (diff_unmerged_stage < 0) + diff_unmerged_stage = 2; + entries = read_cache(); + if (entries < 0) { + perror("read_cache"); return -1; - - if (!use_size_cache) - size_only = 0; - - if (s->data) - return err; - if (!s->sha1_valid || - work_tree_matches(s->path, s->sha1)) { - struct stat st; - int fd; - if (lstat(s->path, &st) < 0) { - if (errno == ENOENT) { - err_empty: - err = -1; - empty: - s->data = ""; - s->size = 0; - return err; - } - } - s->size = st.st_size; - if (!s->size) - goto empty; - if (size_only) - return 0; - if (S_ISLNK(st.st_mode)) { - int ret; - s->data = xmalloc(s->size); - s->should_free = 1; - ret = readlink(s->path, s->data, s->size); - if (ret < 0) { - free(s->data); - goto err_empty; - } - return 0; - } - fd = open(s->path, O_RDONLY); - if (fd < 0) - goto err_empty; - s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0); - close(fd); - if (s->data == MAP_FAILED) - goto err_empty; - s->should_munmap = 1; } - else { - char type[20]; - struct sha1_size_cache *e; - - if (size_only) { - e = locate_size_cache(s->sha1, 1, 0); - if (e) { - s->size = e->size; - return 0; - } - if (!sha1_object_info(s->sha1, type, &s->size)) - locate_size_cache(s->sha1, 0, s->size); - } - else { - s->data = read_sha1_file(s->sha1, type, &s->size); - s->should_free = 1; - } - } - return 0; -} - -void diff_free_filespec_data(struct diff_filespec *s) -{ - if (s->should_free) - free(s->data); - else if (s->should_munmap) - munmap(s->data, s->size); - s->should_free = s->should_munmap = 0; - s->data = NULL; - free(s->cnt_data); - s->cnt_data = NULL; -} - -static void prep_temp_blob(struct diff_tempfile *temp, - void *blob, - unsigned long size, - const unsigned char *sha1, - int mode) -{ - int fd; - - fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX"); - if (fd < 0) - die("unable to create temp-file"); - if (write(fd, blob, size) != size) - die("unable to write temp-file"); - close(fd); - temp->name = temp->tmp_path; - strcpy(temp->hex, sha1_to_hex(sha1)); - temp->hex[40] = 0; - sprintf(temp->mode, "%06o", mode); -} - -static void prepare_temp_file(const char *name, - struct diff_tempfile *temp, - struct diff_filespec *one) -{ - if (!DIFF_FILE_VALID(one)) { - not_a_valid_file: - /* A '-' entry produces this for file-2, and - * a '+' entry produces this for file-1. - */ - temp->name = "/dev/null"; - strcpy(temp->hex, "."); - strcpy(temp->mode, "."); - return; - } - - if (!one->sha1_valid || - work_tree_matches(name, one->sha1)) { + for (i = 0; i < entries; i++) { struct stat st; - if (lstat(name, &st) < 0) { - if (errno == ENOENT) - goto not_a_valid_file; - die("stat(%s): %s", name, strerror(errno)); - } - if (S_ISLNK(st.st_mode)) { - int ret; - char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */ - if (sizeof(buf) <= st.st_size) - die("symlink too long: %s", name); - ret = readlink(name, buf, st.st_size); - if (ret < 0) - die("readlink(%s)", name); - prep_temp_blob(temp, buf, st.st_size, - (one->sha1_valid ? - one->sha1 : null_sha1), - (one->sha1_valid ? - one->mode : S_IFLNK)); - } - else { - /* we can borrow from the file in the work tree */ - temp->name = name; - if (!one->sha1_valid) - strcpy(temp->hex, sha1_to_hex(null_sha1)); - else - strcpy(temp->hex, sha1_to_hex(one->sha1)); - /* Even though we may sometimes borrow the - * contents from the work tree, we always want - * one->mode. mode is trustworthy even when - * !(one->sha1_valid), as long as - * DIFF_FILE_VALID(one). - */ - sprintf(temp->mode, "%06o", one->mode); - } - return; - } - else { - if (diff_populate_filespec(one, 0)) - die("cannot read data blob for %s", one->path); - prep_temp_blob(temp, one->data, one->size, - one->sha1, 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; - } -} - -static void remove_tempfile_on_signal(int signo) -{ - remove_tempfile(); - signal(SIGINT, SIG_DFL); - raise(signo); -} - -static int spawn_prog(const char *pgm, const char **arg) -{ - pid_t pid; - int status; + unsigned int oldmode, newmode; + struct cache_entry *ce = active_cache[i]; + int changed; - fflush(NULL); - pid = fork(); - if (pid < 0) - die("unable to fork"); - if (!pid) { - execvp(pgm, (char *const*) arg); - exit(255); - } - - while (waitpid(pid, &status, 0) < 0) { - if (errno == EINTR) + if (!ce_path_match(ce, revs->prune_data)) continue; - return -1; - } - - /* Earlier we did not check the exit status because - * diff exits non-zero if files are different, and - * we are not interested in knowing that. It was a - * mistake which made it harder to quit a diff-* - * session that uses the git-apply-patch-script as - * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF - * should also exit non-zero only when it wants to - * abort the entire diff-* session. - */ - if (WIFEXITED(status) && !WEXITSTATUS(status)) - return 0; - return -1; -} - -/* An external diff command takes: - * - * diff-cmd name infile1 infile1-sha1 infile1-mode \ - * infile2 infile2-sha1 infile2-mode [ rename-to ] - * - */ -static void run_external_diff(const char *pgm, - const char *name, - const char *other, - struct diff_filespec *one, - struct diff_filespec *two, - const char *xfrm_msg, - int complete_rewrite) -{ - const char *spawn_arg[10]; - struct diff_tempfile *temp = diff_temp; - int retval; - static int atexit_asked = 0; - const char *othername; - const char **arg = &spawn_arg[0]; - - othername = (other? other : name); - if (one && two) { - prepare_temp_file(name, &temp[0], one); - prepare_temp_file(othername, &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); - } - signal(SIGINT, remove_tempfile_on_signal); - } - if (one && two) { - *arg++ = pgm; - *arg++ = name; - *arg++ = temp[0].name; - *arg++ = temp[0].hex; - *arg++ = temp[0].mode; - *arg++ = temp[1].name; - *arg++ = temp[1].hex; - *arg++ = temp[1].mode; - if (other) { - *arg++ = other; - *arg++ = xfrm_msg; - } - } else { - *arg++ = pgm; - *arg++ = name; - } - *arg = NULL; - retval = spawn_prog(pgm, spawn_arg); - remove_tempfile(); - if (retval) { - fprintf(stderr, "external diff died, stopping at %s.\n", name); - exit(1); - } -} - -static void run_diff_cmd(const char *pgm, - const char *name, - const char *other, - struct diff_filespec *one, - struct diff_filespec *two, - const char *xfrm_msg, - int complete_rewrite) -{ - if (pgm) { - run_external_diff(pgm, name, other, one, two, xfrm_msg, - complete_rewrite); - return; - } - if (one && two) - builtin_diff(name, other ? other : name, - one, two, xfrm_msg, complete_rewrite); - else - printf("* Unmerged path %s\n", name); -} - -static void diff_fill_sha1_info(struct diff_filespec *one) -{ - if (DIFF_FILE_VALID(one)) { - if (!one->sha1_valid) { - struct stat st; - if (lstat(one->path, &st) < 0) - die("stat %s", one->path); - if (index_path(one->sha1, one->path, &st, 0)) - die("cannot hash %s\n", one->path); - } - } - else - memset(one->sha1, 0, 20); -} - -static void run_diff(struct diff_filepair *p, struct diff_options *o) -{ - const char *pgm = external_diff(); - char msg[PATH_MAX*2+300], *xfrm_msg; - struct diff_filespec *one; - struct diff_filespec *two; - const char *name; - const char *other; - char *name_munged, *other_munged; - int complete_rewrite = 0; - int len; - - if (DIFF_PAIR_UNMERGED(p)) { - /* unmerged */ - run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, 0); - return; - } - - name = p->one->path; - other = (strcmp(name, p->two->path) ? p->two->path : NULL); - name_munged = quote_one(name); - other_munged = quote_one(other); - one = p->one; two = p->two; - - diff_fill_sha1_info(one); - diff_fill_sha1_info(two); - - len = 0; - switch (p->status) { - case DIFF_STATUS_COPIED: - len += snprintf(msg + len, sizeof(msg) - len, - "similarity index %d%%\n" - "copy from %s\n" - "copy to %s\n", - (int)(0.5 + p->score * 100.0/MAX_SCORE), - name_munged, other_munged); - break; - case DIFF_STATUS_RENAMED: - len += snprintf(msg + len, sizeof(msg) - len, - "similarity index %d%%\n" - "rename from %s\n" - "rename to %s\n", - (int)(0.5 + p->score * 100.0/MAX_SCORE), - name_munged, other_munged); - break; - case DIFF_STATUS_MODIFIED: - if (p->score) { - len += snprintf(msg + len, sizeof(msg) - len, - "dissimilarity index %d%%\n", - (int)(0.5 + p->score * - 100.0/MAX_SCORE)); - complete_rewrite = 1; - break; - } - /* fallthru */ - default: - /* nothing */ - ; - } - - if (memcmp(one->sha1, two->sha1, 20)) { - char one_sha1[41]; - int abbrev = o->full_index ? 40 : DEFAULT_ABBREV; - memcpy(one_sha1, sha1_to_hex(one->sha1), 41); - - len += snprintf(msg + len, sizeof(msg) - len, - "index %.*s..%.*s", - abbrev, one_sha1, abbrev, - sha1_to_hex(two->sha1)); - if (one->mode == two->mode) - len += snprintf(msg + len, sizeof(msg) - len, - " %06o", one->mode); - len += snprintf(msg + len, sizeof(msg) - len, "\n"); - } - - if (len) - msg[--len] = 0; - xfrm_msg = len ? msg : NULL; - - if (!pgm && - DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) && - (S_IFMT & one->mode) != (S_IFMT & two->mode)) { - /* a filepair that changes between file and symlink - * needs to be split into deletion and creation. - */ - struct diff_filespec *null = alloc_filespec(two->path); - run_diff_cmd(NULL, name, other, one, null, xfrm_msg, 0); - free(null); - null = alloc_filespec(one->path); - run_diff_cmd(NULL, name, other, null, two, xfrm_msg, 0); - free(null); - } - else - run_diff_cmd(pgm, name, other, one, two, xfrm_msg, - complete_rewrite); - - free(name_munged); - free(other_munged); -} - -static void run_diffstat(struct diff_filepair *p, struct diff_options *o, - struct diffstat_t *diffstat) -{ - const char *name; - const char *other; - - if (DIFF_PAIR_UNMERGED(p)) { - /* unmerged */ - builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat); - return; - } - - name = p->one->path; - other = (strcmp(name, p->two->path) ? p->two->path : NULL); - - diff_fill_sha1_info(p->one); - diff_fill_sha1_info(p->two); - - builtin_diffstat(name, other, p->one, p->two, diffstat); -} - -void diff_setup(struct diff_options *options) -{ - memset(options, 0, sizeof(*options)); - options->output_format = DIFF_FORMAT_RAW; - options->line_termination = '\n'; - options->break_opt = -1; - options->rename_limit = -1; - - options->change = diff_change; - options->add_remove = diff_addremove; -} - -int diff_setup_done(struct diff_options *options) -{ - if ((options->find_copies_harder && - options->detect_rename != DIFF_DETECT_COPY) || - (0 <= options->rename_limit && !options->detect_rename)) - return -1; - - /* - * These cases always need recursive; we do not drop caller-supplied - * recursive bits for other formats here. - */ - if ((options->output_format == DIFF_FORMAT_PATCH) || - (options->output_format == DIFF_FORMAT_DIFFSTAT)) - options->recursive = 1; - - if (options->detect_rename && options->rename_limit < 0) - options->rename_limit = diff_rename_limit_default; - if (options->setup & DIFF_SETUP_USE_CACHE) { - if (!active_cache) - /* read-cache does not die even when it fails - * so it is safe for us to do this here. Also - * it does not smudge active_cache or active_nr - * when it fails, so we do not have to worry about - * cleaning it up ourselves either. + if (ce_stage(ce)) { + struct { + struct combine_diff_path p; + struct combine_diff_parent filler[5]; + } combine; + int num_compare_stages = 0; + + combine.p.next = NULL; + combine.p.len = ce_namelen(ce); + combine.p.path = xmalloc(combine.p.len + 1); + memcpy(combine.p.path, ce->name, combine.p.len); + combine.p.path[combine.p.len] = 0; + combine.p.mode = 0; + memset(combine.p.sha1, 0, 20); + memset(&combine.p.parent[0], 0, + sizeof(combine.filler)); + + while (i < entries) { + struct cache_entry *nce = active_cache[i]; + int stage; + + if (strcmp(ce->name, nce->name)) + break; + + /* Stage #2 (ours) is the first parent, + * stage #3 (theirs) is the second. + */ + stage = ce_stage(nce); + if (2 <= stage) { + int mode = ntohl(nce->ce_mode); + num_compare_stages++; + memcpy(combine.p.parent[stage-2].sha1, + nce->sha1, 20); + combine.p.parent[stage-2].mode = + canon_mode(mode); + combine.p.parent[stage-2].status = + DIFF_STATUS_MODIFIED; + } + + /* diff against the proper unmerged stage */ + if (stage == diff_unmerged_stage) + ce = nce; + i++; + } + /* + * Compensate for loop update */ - read_cache(); - } - if (options->setup & DIFF_SETUP_USE_SIZE_CACHE) - use_size_cache = 1; - if (options->abbrev <= 0 || 40 < options->abbrev) - options->abbrev = 40; /* full */ + i--; - return 0; -} - -int diff_opt_parse(struct diff_options *options, const char **av, int ac) -{ - const char *arg = av[0]; - if (!strcmp(arg, "-p") || !strcmp(arg, "-u")) - options->output_format = DIFF_FORMAT_PATCH; - else if (!strcmp(arg, "--patch-with-raw")) { - options->output_format = DIFF_FORMAT_PATCH; - options->with_raw = 1; - } - else if (!strcmp(arg, "--stat")) - options->output_format = DIFF_FORMAT_DIFFSTAT; - else if (!strcmp(arg, "--patch-with-stat")) { - options->output_format = DIFF_FORMAT_PATCH; - options->with_stat = 1; - } - else if (!strcmp(arg, "-z")) - options->line_termination = 0; - else if (!strncmp(arg, "-l", 2)) - options->rename_limit = strtoul(arg+2, NULL, 10); - else if (!strcmp(arg, "--full-index")) - options->full_index = 1; - else if (!strcmp(arg, "--name-only")) - options->output_format = DIFF_FORMAT_NAME; - else if (!strcmp(arg, "--name-status")) - options->output_format = DIFF_FORMAT_NAME_STATUS; - else if (!strcmp(arg, "-R")) - options->reverse_diff = 1; - else if (!strncmp(arg, "-S", 2)) - options->pickaxe = arg + 2; - else if (!strcmp(arg, "-s")) - options->output_format = DIFF_FORMAT_NO_OUTPUT; - else if (!strncmp(arg, "-O", 2)) - options->orderfile = arg + 2; - else if (!strncmp(arg, "--diff-filter=", 14)) - options->filter = arg + 14; - else if (!strcmp(arg, "--pickaxe-all")) - options->pickaxe_opts = DIFF_PICKAXE_ALL; - else if (!strcmp(arg, "--pickaxe-regex")) - options->pickaxe_opts = DIFF_PICKAXE_REGEX; - else if (!strncmp(arg, "-B", 2)) { - if ((options->break_opt = - diff_scoreopt_parse(arg)) == -1) - return -1; - } - else if (!strncmp(arg, "-M", 2)) { - if ((options->rename_score = - diff_scoreopt_parse(arg)) == -1) - return -1; - options->detect_rename = DIFF_DETECT_RENAME; - } - else if (!strncmp(arg, "-C", 2)) { - if ((options->rename_score = - diff_scoreopt_parse(arg)) == -1) - return -1; - options->detect_rename = DIFF_DETECT_COPY; - } - else if (!strcmp(arg, "--find-copies-harder")) - options->find_copies_harder = 1; - else if (!strcmp(arg, "--abbrev")) - options->abbrev = DEFAULT_ABBREV; - else if (!strncmp(arg, "--abbrev=", 9)) { - options->abbrev = strtoul(arg + 9, NULL, 10); - if (options->abbrev < MINIMUM_ABBREV) - options->abbrev = MINIMUM_ABBREV; - else if (40 < options->abbrev) - options->abbrev = 40; - } - else - return 0; - return 1; -} - -static int parse_num(const char **cp_p) -{ - unsigned long num, scale; - int ch, dot; - const char *cp = *cp_p; - - num = 0; - scale = 1; - dot = 0; - for(;;) { - ch = *cp; - if ( !dot && ch == '.' ) { - scale = 1; - dot = 1; - } else if ( ch == '%' ) { - scale = dot ? scale*100 : 100; - cp++; /* % is always at the end */ - break; - } else if ( ch >= '0' && ch <= '9' ) { - if ( scale < 100000 ) { - scale *= 10; - num = (num*10) + (ch-'0'); + if (revs->combine_merges && num_compare_stages == 2) { + show_combined_diff(&combine.p, 2, + revs->dense_combined_merges, + revs); + free(combine.p.path); + continue; } - } else { - break; - } - cp++; - } - *cp_p = cp; - - /* user says num divided by scale and we say internally that - * is MAX_SCORE * num / scale. - */ - return (num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale); -} - -int diff_scoreopt_parse(const char *opt) -{ - int opt1, opt2, cmd; + free(combine.p.path); - if (*opt++ != '-') - return -1; - cmd = *opt++; - if (cmd != 'M' && cmd != 'C' && cmd != 'B') - return -1; /* that is not a -M, -C nor -B option */ - - opt1 = parse_num(&opt); - if (cmd != 'B') - opt2 = 0; - else { - if (*opt == 0) - opt2 = 0; - else if (*opt != '/') - return -1; /* we expect -B80/99 or -B80 */ - else { - opt++; - opt2 = parse_num(&opt); + /* + * Show the diff for the 'ce' if we found the one + * from the desired stage. + */ + diff_unmerge(&revs->diffopt, ce->name); + if (ce_stage(ce) != diff_unmerged_stage) + continue; } - } - if (*opt != 0) - return -1; - return opt1 | (opt2 << 16); -} - -struct diff_queue_struct diff_queued_diff; -void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp) -{ - if (queue->alloc <= queue->nr) { - queue->alloc = alloc_nr(queue->alloc); - queue->queue = xrealloc(queue->queue, - sizeof(dp) * queue->alloc); - } - queue->queue[queue->nr++] = dp; -} - -struct diff_filepair *diff_queue(struct diff_queue_struct *queue, - struct diff_filespec *one, - struct diff_filespec *two) -{ - struct diff_filepair *dp = xmalloc(sizeof(*dp)); - dp->one = one; - dp->two = two; - dp->score = 0; - dp->status = 0; - dp->source_stays = 0; - dp->broken_pair = 0; - if (queue) - diff_q(queue, dp); - return dp; -} - -void diff_free_filepair(struct diff_filepair *p) -{ - diff_free_filespec_data(p->one); - diff_free_filespec_data(p->two); - free(p->one); - free(p->two); - free(p); -} - -/* This is different from find_unique_abbrev() in that - * it stuffs the result with dots for alignment. - */ -const char *diff_unique_abbrev(const unsigned char *sha1, int len) -{ - int abblen; - const char *abbrev; - if (len == 40) - return sha1_to_hex(sha1); - - abbrev = find_unique_abbrev(sha1, len); - if (!abbrev) - return sha1_to_hex(sha1); - abblen = strlen(abbrev); - if (abblen < 37) { - static char hex[41]; - if (len < abblen && abblen <= len + 2) - sprintf(hex, "%s%.*s", abbrev, len+3-abblen, ".."); - else - sprintf(hex, "%s...", abbrev); - return hex; - } - return sha1_to_hex(sha1); -} - -static void diff_flush_raw(struct diff_filepair *p, - int line_termination, - int inter_name_termination, - struct diff_options *options, - int output_format) -{ - int two_paths; - char status[10]; - int abbrev = options->abbrev; - const char *path_one, *path_two; - - path_one = p->one->path; - path_two = p->two->path; - if (line_termination) { - path_one = quote_one(path_one); - path_two = quote_one(path_two); - } - - if (p->score) - sprintf(status, "%c%03d", p->status, - (int)(0.5 + p->score * 100.0/MAX_SCORE)); - else { - status[0] = p->status; - status[1] = 0; - } - switch (p->status) { - case DIFF_STATUS_COPIED: - case DIFF_STATUS_RENAMED: - two_paths = 1; - break; - case DIFF_STATUS_ADDED: - case DIFF_STATUS_DELETED: - two_paths = 0; - break; - default: - two_paths = 0; - break; - } - if (output_format != DIFF_FORMAT_NAME_STATUS) { - printf(":%06o %06o %s ", - p->one->mode, p->two->mode, - diff_unique_abbrev(p->one->sha1, abbrev)); - printf("%s ", - diff_unique_abbrev(p->two->sha1, abbrev)); - } - printf("%s%c%s", status, inter_name_termination, path_one); - if (two_paths) - printf("%c%s", inter_name_termination, path_two); - putchar(line_termination); - if (path_one != p->one->path) - free((void*)path_one); - if (path_two != p->two->path) - free((void*)path_two); -} - -static void diff_flush_name(struct diff_filepair *p, - int inter_name_termination, - int line_termination) -{ - char *path = p->two->path; - - if (line_termination) - path = quote_one(p->two->path); - else - path = p->two->path; - printf("%s%c", path, line_termination); - if (p->two->path != path) - free(path); -} - -int diff_unmodified_pair(struct diff_filepair *p) -{ - /* This function is written stricter than necessary to support - * the currently implemented transformers, but the idea is to - * let transformers to produce diff_filepairs any way they want, - * and filter and clean them up here before producing the output. - */ - struct diff_filespec *one, *two; - - if (DIFF_PAIR_UNMERGED(p)) - return 0; /* unmerged is interesting */ - - one = p->one; - two = p->two; - - /* deletion, addition, mode or type change - * and rename are all interesting. - */ - if (DIFF_FILE_VALID(one) != DIFF_FILE_VALID(two) || - DIFF_PAIR_MODE_CHANGED(p) || - strcmp(one->path, two->path)) - return 0; - - /* both are valid and point at the same path. that is, we are - * dealing with a change. - */ - if (one->sha1_valid && two->sha1_valid && - !memcmp(one->sha1, two->sha1, sizeof(one->sha1))) - return 1; /* no change */ - if (!one->sha1_valid && !two->sha1_valid) - return 1; /* both look at the same file on the filesystem. */ - return 0; -} - -static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o) -{ - if (diff_unmodified_pair(p)) - return; - - if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) || - (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode))) - return; /* no tree diffs in patch format */ - - run_diff(p, o); -} - -static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o, - struct diffstat_t *diffstat) -{ - if (diff_unmodified_pair(p)) - return; - - if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) || - (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode))) - return; /* no tree diffs in patch format */ - - run_diffstat(p, o, diffstat); -} - -int diff_queue_is_empty(void) -{ - struct diff_queue_struct *q = &diff_queued_diff; - int i; - for (i = 0; i < q->nr; i++) - if (!diff_unmodified_pair(q->queue[i])) - return 0; - return 1; -} - -#if DIFF_DEBUG -void diff_debug_filespec(struct diff_filespec *s, int x, const char *one) -{ - fprintf(stderr, "queue[%d] %s (%s) %s %06o %s\n", - x, one ? one : "", - s->path, - DIFF_FILE_VALID(s) ? "valid" : "invalid", - s->mode, - s->sha1_valid ? sha1_to_hex(s->sha1) : ""); - fprintf(stderr, "queue[%d] %s size %lu flags %d\n", - x, one ? one : "", - s->size, s->xfrm_flags); -} - -void diff_debug_filepair(const struct diff_filepair *p, int i) -{ - diff_debug_filespec(p->one, i, "one"); - diff_debug_filespec(p->two, i, "two"); - fprintf(stderr, "score %d, status %c stays %d broken %d\n", - p->score, p->status ? p->status : '?', - p->source_stays, p->broken_pair); -} - -void diff_debug_queue(const char *msg, struct diff_queue_struct *q) -{ - int i; - if (msg) - fprintf(stderr, "%s\n", msg); - fprintf(stderr, "q->nr = %d\n", q->nr); - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - diff_debug_filepair(p, i); - } -} -#endif - -static void diff_resolve_rename_copy(void) -{ - int i, j; - struct diff_filepair *p, *pp; - struct diff_queue_struct *q = &diff_queued_diff; - - diff_debug_queue("resolve-rename-copy", q); - - for (i = 0; i < q->nr; i++) { - p = q->queue[i]; - p->status = 0; /* undecided */ - if (DIFF_PAIR_UNMERGED(p)) - p->status = DIFF_STATUS_UNMERGED; - else if (!DIFF_FILE_VALID(p->one)) - p->status = DIFF_STATUS_ADDED; - else if (!DIFF_FILE_VALID(p->two)) - p->status = DIFF_STATUS_DELETED; - else if (DIFF_PAIR_TYPE_CHANGED(p)) - p->status = DIFF_STATUS_TYPE_CHANGED; - - /* from this point on, we are dealing with a pair - * whose both sides are valid and of the same type, i.e. - * either in-place edit or rename/copy edit. - */ - else if (DIFF_PAIR_RENAME(p)) { - if (p->source_stays) { - p->status = DIFF_STATUS_COPIED; + if (lstat(ce->name, &st) < 0) { + if (errno != ENOENT && errno != ENOTDIR) { + perror(ce->name); continue; } - /* See if there is some other filepair that - * copies from the same source as us. If so - * we are a copy. Otherwise we are either a - * copy if the path stays, or a rename if it - * does not, but we already handled "stays" case. - */ - for (j = i + 1; j < q->nr; j++) { - pp = q->queue[j]; - if (strcmp(pp->one->path, p->one->path)) - continue; /* not us */ - if (!DIFF_PAIR_RENAME(pp)) - continue; /* not a rename/copy */ - /* pp is a rename/copy from the same source */ - p->status = DIFF_STATUS_COPIED; - break; - } - if (!p->status) - p->status = DIFF_STATUS_RENAMED; - } - else if (memcmp(p->one->sha1, p->two->sha1, 20) || - p->one->mode != p->two->mode) - p->status = DIFF_STATUS_MODIFIED; - else { - /* This is a "no-change" entry and should not - * happen anymore, but prepare for broken callers. - */ - error("feeding unmodified %s to diffcore", - p->one->path); - p->status = DIFF_STATUS_UNKNOWN; - } - } - diff_debug_queue("resolve-rename-copy done", q); -} - -static void flush_one_pair(struct diff_filepair *p, - int diff_output_format, - struct diff_options *options, - struct diffstat_t *diffstat) -{ - int inter_name_termination = '\t'; - int line_termination = options->line_termination; - if (!line_termination) - inter_name_termination = 0; - - switch (p->status) { - case DIFF_STATUS_UNKNOWN: - break; - case 0: - die("internal error in diff-resolve-rename-copy"); - break; - default: - switch (diff_output_format) { - case DIFF_FORMAT_DIFFSTAT: - diff_flush_stat(p, options, diffstat); - break; - case DIFF_FORMAT_PATCH: - diff_flush_patch(p, options); - break; - case DIFF_FORMAT_RAW: - case DIFF_FORMAT_NAME_STATUS: - diff_flush_raw(p, line_termination, - inter_name_termination, - options, diff_output_format); - break; - case DIFF_FORMAT_NAME: - diff_flush_name(p, - inter_name_termination, - line_termination); - break; - case DIFF_FORMAT_NO_OUTPUT: - break; - } - } -} - -void diff_flush(struct diff_options *options) -{ - struct diff_queue_struct *q = &diff_queued_diff; - int i; - int diff_output_format = options->output_format; - struct diffstat_t *diffstat = NULL; - - if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) { - diffstat = xcalloc(sizeof (struct diffstat_t), 1); - diffstat->xm.consume = diffstat_consume; - } - - if (options->with_raw) { - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL); - } - putchar(options->line_termination); - } - if (options->with_stat) { - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options, - diffstat); - } - show_stats(diffstat); - free(diffstat); - diffstat = NULL; - putchar(options->line_termination); - } - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - flush_one_pair(p, diff_output_format, options, diffstat); - diff_free_filepair(p); - } - - if (diffstat) { - show_stats(diffstat); - free(diffstat); - } - - free(q->queue); - q->queue = NULL; - q->nr = q->alloc = 0; -} - -static void diffcore_apply_filter(const char *filter) -{ - int i; - struct diff_queue_struct *q = &diff_queued_diff; - struct diff_queue_struct outq; - outq.queue = NULL; - outq.nr = outq.alloc = 0; - - if (!filter) - return; - - if (strchr(filter, DIFF_STATUS_FILTER_AON)) { - int found; - for (i = found = 0; !found && i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (((p->status == DIFF_STATUS_MODIFIED) && - ((p->score && - strchr(filter, DIFF_STATUS_FILTER_BROKEN)) || - (!p->score && - strchr(filter, DIFF_STATUS_MODIFIED)))) || - ((p->status != DIFF_STATUS_MODIFIED) && - strchr(filter, p->status))) - found++; - } - if (found) - return; - - /* otherwise we will clear the whole queue - * by copying the empty outq at the end of this - * function, but first clear the current entries - * in the queue. - */ - for (i = 0; i < q->nr; i++) - diff_free_filepair(q->queue[i]); - } - else { - /* Only the matching ones */ - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - - if (((p->status == DIFF_STATUS_MODIFIED) && - ((p->score && - strchr(filter, DIFF_STATUS_FILTER_BROKEN)) || - (!p->score && - strchr(filter, DIFF_STATUS_MODIFIED)))) || - ((p->status != DIFF_STATUS_MODIFIED) && - strchr(filter, p->status))) - diff_q(&outq, p); - else - diff_free_filepair(p); + if (silent_on_removed) + continue; + diff_addremove(&revs->diffopt, '-', ntohl(ce->ce_mode), + ce->sha1, ce->name, NULL); + continue; } - } - free(q->queue); - *q = outq; -} - -void diffcore_std(struct diff_options *options) -{ - if (options->break_opt != -1) - diffcore_break(options->break_opt); - if (options->detect_rename) - diffcore_rename(options); - if (options->break_opt != -1) - diffcore_merge_broken(); - if (options->pickaxe) - diffcore_pickaxe(options->pickaxe, options->pickaxe_opts); - if (options->orderfile) - diffcore_order(options->orderfile); - diff_resolve_rename_copy(); - diffcore_apply_filter(options->filter); -} - - -void diffcore_std_no_resolve(struct diff_options *options) -{ - if (options->pickaxe) - diffcore_pickaxe(options->pickaxe, options->pickaxe_opts); - if (options->orderfile) - diffcore_order(options->orderfile); - diffcore_apply_filter(options->filter); -} - -void diff_addremove(struct diff_options *options, - int addremove, unsigned mode, - const unsigned char *sha1, - const char *base, const char *path) -{ - char concatpath[PATH_MAX]; - struct diff_filespec *one, *two; - - /* This may look odd, but it is a preparation for - * feeding "there are unchanged files which should - * not produce diffs, but when you are doing copy - * detection you would need them, so here they are" - * entries to the diff-core. They will be prefixed - * with something like '=' or '*' (I haven't decided - * which but should not make any difference). - * Feeding the same new and old to diff_change() - * also has the same effect. - * Before the final output happens, they are pruned after - * merged into rename/copy pairs as appropriate. - */ - if (options->reverse_diff) - addremove = (addremove == '+' ? '-' : - addremove == '-' ? '+' : addremove); - - if (!path) path = ""; - sprintf(concatpath, "%s%s", base, path); - one = alloc_filespec(concatpath); - two = alloc_filespec(concatpath); - - if (addremove != '+') - fill_filespec(one, sha1, mode); - if (addremove != '-') - fill_filespec(two, sha1, mode); - - diff_queue(&diff_queued_diff, one, two); -} + changed = ce_match_stat(ce, &st, 0); + if (!changed && !revs->diffopt.find_copies_harder) + continue; + oldmode = ntohl(ce->ce_mode); -void diff_change(struct diff_options *options, - unsigned old_mode, unsigned new_mode, - const unsigned char *old_sha1, - const unsigned char *new_sha1, - const char *base, const char *path) -{ - char concatpath[PATH_MAX]; - struct diff_filespec *one, *two; + newmode = canon_mode(st.st_mode); + if (!trust_executable_bit && + S_ISREG(newmode) && S_ISREG(oldmode) && + ((newmode ^ oldmode) == 0111)) + newmode = oldmode; + diff_change(&revs->diffopt, oldmode, newmode, + ce->sha1, (changed ? null_sha1 : ce->sha1), + ce->name, NULL); - if (options->reverse_diff) { - unsigned tmp; - const unsigned char *tmp_c; - tmp = old_mode; old_mode = new_mode; new_mode = tmp; - tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c; } - if (!path) path = ""; - sprintf(concatpath, "%s%s", base, path); - one = alloc_filespec(concatpath); - two = alloc_filespec(concatpath); - fill_filespec(one, old_sha1, old_mode); - fill_filespec(two, new_sha1, new_mode); - - diff_queue(&diff_queued_diff, one, two); + diffcore_std(&revs->diffopt); + diff_flush(&revs->diffopt); + return 0; } -void diff_unmerge(struct diff_options *options, - const char *path) -{ - struct diff_filespec *one, *two; - one = alloc_filespec(path); - two = alloc_filespec(path); - diff_queue(&diff_queued_diff, one, two); -} diff --git a/diff.c b/diff.c new file mode 100644 index 0000000000..13b216f273 --- /dev/null +++ b/diff.c @@ -0,0 +1,1795 @@ +/* + * Copyright (C) 2005 Junio C Hamano + */ +#include +#include +#include +#include "cache.h" +#include "quote.h" +#include "diff.h" +#include "diffcore.h" +#include "xdiff-interface.h" + +static int use_size_cache; + +int diff_rename_limit_default = -1; + +int git_diff_config(const char *var, const char *value) +{ + if (!strcmp(var, "diff.renamelimit")) { + diff_rename_limit_default = git_config_int(var, value); + return 0; + } + + return git_default_config(var, value); +} + +static char *quote_one(const char *str) +{ + int needlen; + char *xp; + + if (!str) + return NULL; + needlen = quote_c_style(str, NULL, NULL, 0); + if (!needlen) + return strdup(str); + xp = xmalloc(needlen + 1); + quote_c_style(str, xp, NULL, 0); + return xp; +} + +static char *quote_two(const char *one, const char *two) +{ + int need_one = quote_c_style(one, NULL, NULL, 1); + int need_two = quote_c_style(two, NULL, NULL, 1); + char *xp; + + if (need_one + need_two) { + if (!need_one) need_one = strlen(one); + if (!need_two) need_one = strlen(two); + + xp = xmalloc(need_one + need_two + 3); + xp[0] = '"'; + quote_c_style(one, xp + 1, NULL, 1); + quote_c_style(two, xp + need_one + 1, NULL, 1); + strcpy(xp + need_one + need_two + 1, "\""); + return xp; + } + need_one = strlen(one); + need_two = strlen(two); + xp = xmalloc(need_one + need_two + 1); + strcpy(xp, one); + strcpy(xp + need_one, two); + return xp; +} + +static const char *external_diff(void) +{ + static const char *external_diff_cmd = NULL; + static int done_preparing = 0; + + if (done_preparing) + return external_diff_cmd; + external_diff_cmd = getenv("GIT_EXTERNAL_DIFF"); + done_preparing = 1; + return external_diff_cmd; +} + +#define TEMPFILE_PATH_LEN 50 + +static struct diff_tempfile { + const char *name; /* filename external diff should read from */ + char hex[41]; + char mode[10]; + char tmp_path[TEMPFILE_PATH_LEN]; +} diff_temp[2]; + +static int count_lines(const char *data, int size) +{ + int count, ch, completely_empty = 1, nl_just_seen = 0; + count = 0; + while (0 < size--) { + ch = *data++; + if (ch == '\n') { + count++; + nl_just_seen = 1; + completely_empty = 0; + } + else { + nl_just_seen = 0; + completely_empty = 0; + } + } + if (completely_empty) + return 0; + if (!nl_just_seen) + count++; /* no trailing newline */ + return count; +} + +static void print_line_count(int count) +{ + switch (count) { + case 0: + printf("0,0"); + break; + case 1: + printf("1"); + break; + default: + printf("1,%d", count); + break; + } +} + +static void copy_file(int prefix, const char *data, int size) +{ + int ch, nl_just_seen = 1; + while (0 < size--) { + ch = *data++; + if (nl_just_seen) + putchar(prefix); + putchar(ch); + if (ch == '\n') + nl_just_seen = 1; + else + nl_just_seen = 0; + } + if (!nl_just_seen) + printf("\n\\ No newline at end of file\n"); +} + +static void emit_rewrite_diff(const char *name_a, + const char *name_b, + struct diff_filespec *one, + struct diff_filespec *two) +{ + int lc_a, lc_b; + diff_populate_filespec(one, 0); + diff_populate_filespec(two, 0); + lc_a = count_lines(one->data, one->size); + lc_b = count_lines(two->data, two->size); + printf("--- %s\n+++ %s\n@@ -", name_a, name_b); + print_line_count(lc_a); + printf(" +"); + print_line_count(lc_b); + printf(" @@\n"); + if (lc_a) + copy_file('-', one->data, one->size); + if (lc_b) + copy_file('+', two->data, two->size); +} + +static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) +{ + if (!DIFF_FILE_VALID(one)) { + mf->ptr = ""; /* does not matter */ + mf->size = 0; + return 0; + } + else if (diff_populate_filespec(one, 0)) + return -1; + mf->ptr = one->data; + mf->size = one->size; + return 0; +} + +struct emit_callback { + const char **label_path; +}; + +static int fn_out(void *priv, mmbuffer_t *mb, int nbuf) +{ + int i; + struct emit_callback *ecbdata = priv; + + if (ecbdata->label_path[0]) { + printf("--- %s\n", ecbdata->label_path[0]); + printf("+++ %s\n", ecbdata->label_path[1]); + ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; + } + for (i = 0; i < nbuf; i++) + if (!fwrite(mb[i].ptr, mb[i].size, 1, stdout)) + return -1; + return 0; +} + +static char *pprint_rename(const char *a, const char *b) +{ + const char *old = a; + const char *new = b; + char *name = NULL; + int pfx_length, sfx_length; + int len_a = strlen(a); + int len_b = strlen(b); + + /* Find common prefix */ + pfx_length = 0; + while (*old && *new && *old == *new) { + if (*old == '/') + pfx_length = old - a + 1; + old++; + new++; + } + + /* Find common suffix */ + old = a + len_a; + new = b + len_b; + sfx_length = 0; + while (a <= old && b <= new && *old == *new) { + if (*old == '/') + sfx_length = len_a - (old - a); + old--; + new--; + } + + /* + * pfx{mid-a => mid-b}sfx + * {pfx-a => pfx-b}sfx + * pfx{sfx-a => sfx-b} + * name-a => name-b + */ + if (pfx_length + sfx_length) { + name = xmalloc(len_a + len_b - pfx_length - sfx_length + 7); + sprintf(name, "%.*s{%.*s => %.*s}%s", + pfx_length, a, + len_a - pfx_length - sfx_length, a + pfx_length, + len_b - pfx_length - sfx_length, b + pfx_length, + a + len_a - sfx_length); + } + else { + name = xmalloc(len_a + len_b + 5); + sprintf(name, "%s => %s", a, b); + } + return name; +} + +struct diffstat_t { + struct xdiff_emit_state xm; + + int nr; + int alloc; + struct diffstat_file { + char *name; + unsigned is_unmerged:1; + unsigned is_binary:1; + unsigned is_renamed:1; + unsigned int added, deleted; + } **files; +}; + +static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, + const char *name_a, + const char *name_b) +{ + struct diffstat_file *x; + x = xcalloc(sizeof (*x), 1); + if (diffstat->nr == diffstat->alloc) { + diffstat->alloc = alloc_nr(diffstat->alloc); + diffstat->files = xrealloc(diffstat->files, + diffstat->alloc * sizeof(x)); + } + diffstat->files[diffstat->nr++] = x; + if (name_b) { + x->name = pprint_rename(name_a, name_b); + x->is_renamed = 1; + } + else + x->name = strdup(name_a); + return x; +} + +static void diffstat_consume(void *priv, char *line, unsigned long len) +{ + struct diffstat_t *diffstat = priv; + struct diffstat_file *x = diffstat->files[diffstat->nr - 1]; + + if (line[0] == '+') + x->added++; + else if (line[0] == '-') + x->deleted++; +} + +static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; +static const char minuses[]= "----------------------------------------------------------------------"; + +static void show_stats(struct diffstat_t* data) +{ + char *prefix = ""; + int i, len, add, del, total, adds = 0, dels = 0; + int max, max_change = 0, max_len = 0; + int total_files = data->nr; + + if (data->nr == 0) + return; + + for (i = 0; i < data->nr; i++) { + struct diffstat_file *file = data->files[i]; + + len = strlen(file->name); + if (max_len < len) + max_len = len; + + if (file->is_binary || file->is_unmerged) + continue; + if (max_change < file->added + file->deleted) + max_change = file->added + file->deleted; + } + + for (i = 0; i < data->nr; i++) { + char *name = data->files[i]->name; + int added = data->files[i]->added; + int deleted = data->files[i]->deleted; + + if (0 < (len = quote_c_style(name, NULL, NULL, 0))) { + char *qname = xmalloc(len + 1); + quote_c_style(name, qname, NULL, 0); + free(name); + data->files[i]->name = name = qname; + } + + /* + * "scale" the filename + */ + len = strlen(name); + max = max_len; + if (max > 50) + max = 50; + if (len > max) { + char *slash; + prefix = "..."; + max -= 3; + name += len - max; + slash = strchr(name, '/'); + if (slash) + name = slash; + } + len = max; + + /* + * scale the add/delete + */ + max = max_change; + if (max + len > 70) + max = 70 - len; + + if (data->files[i]->is_binary) { + printf(" %s%-*s | Bin\n", prefix, len, name); + goto free_diffstat_file; + } + else if (data->files[i]->is_unmerged) { + printf(" %s%-*s | Unmerged\n", prefix, len, name); + goto free_diffstat_file; + } + else if (!data->files[i]->is_renamed && + (added + deleted == 0)) { + total_files--; + goto free_diffstat_file; + } + + add = added; + del = deleted; + total = add + del; + adds += add; + dels += del; + + if (max_change > 0) { + total = (total * max + max_change / 2) / max_change; + add = (add * max + max_change / 2) / max_change; + del = total - add; + } + printf(" %s%-*s |%5d %.*s%.*s\n", prefix, + len, name, added + deleted, + add, pluses, del, minuses); + free_diffstat_file: + free(data->files[i]->name); + free(data->files[i]); + } + free(data->files); + printf(" %d files changed, %d insertions(+), %d deletions(-)\n", + total_files, adds, dels); +} + +#define FIRST_FEW_BYTES 8000 +static int mmfile_is_binary(mmfile_t *mf) +{ + long sz = mf->size; + if (FIRST_FEW_BYTES < sz) + sz = FIRST_FEW_BYTES; + if (memchr(mf->ptr, 0, sz)) + return 1; + return 0; +} + +static void builtin_diff(const char *name_a, + const char *name_b, + struct diff_filespec *one, + struct diff_filespec *two, + const char *xfrm_msg, + int complete_rewrite) +{ + mmfile_t mf1, mf2; + const char *lbl[2]; + char *a_one, *b_two; + + a_one = quote_two("a/", name_a); + b_two = quote_two("b/", name_b); + lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; + lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; + printf("diff --git %s %s\n", a_one, b_two); + if (lbl[0][0] == '/') { + /* /dev/null */ + printf("new file mode %06o\n", two->mode); + if (xfrm_msg && xfrm_msg[0]) + puts(xfrm_msg); + } + else if (lbl[1][0] == '/') { + printf("deleted file mode %06o\n", one->mode); + if (xfrm_msg && xfrm_msg[0]) + puts(xfrm_msg); + } + else { + if (one->mode != two->mode) { + printf("old mode %06o\n", one->mode); + printf("new mode %06o\n", two->mode); + } + if (xfrm_msg && xfrm_msg[0]) + puts(xfrm_msg); + /* + * we do not run diff between different kind + * of objects. + */ + if ((one->mode ^ two->mode) & S_IFMT) + goto free_ab_and_return; + if (complete_rewrite) { + emit_rewrite_diff(name_a, name_b, one, two); + goto free_ab_and_return; + } + } + + if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) + die("unable to read files to diff"); + + if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2)) + printf("Binary files %s and %s differ\n", lbl[0], lbl[1]); + else { + /* Crazy xdl interfaces.. */ + const char *diffopts = getenv("GIT_DIFF_OPTS"); + xpparam_t xpp; + xdemitconf_t xecfg; + xdemitcb_t ecb; + struct emit_callback ecbdata; + + ecbdata.label_path = lbl; + xpp.flags = XDF_NEED_MINIMAL; + xecfg.ctxlen = 3; + xecfg.flags = XDL_EMIT_FUNCNAMES; + if (!diffopts) + ; + else if (!strncmp(diffopts, "--unified=", 10)) + xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10); + else if (!strncmp(diffopts, "-u", 2)) + xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10); + ecb.outf = fn_out; + ecb.priv = &ecbdata; + xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); + } + + free_ab_and_return: + free(a_one); + free(b_two); + return; +} + +static void builtin_diffstat(const char *name_a, const char *name_b, + struct diff_filespec *one, + struct diff_filespec *two, + struct diffstat_t *diffstat) +{ + mmfile_t mf1, mf2; + struct diffstat_file *data; + + data = diffstat_add(diffstat, name_a, name_b); + + if (!one || !two) { + data->is_unmerged = 1; + return; + } + + if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) + die("unable to read files to diff"); + + if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2)) + data->is_binary = 1; + else { + /* Crazy xdl interfaces.. */ + xpparam_t xpp; + xdemitconf_t xecfg; + xdemitcb_t ecb; + + xpp.flags = XDF_NEED_MINIMAL; + xecfg.ctxlen = 0; + xecfg.flags = 0; + ecb.outf = xdiff_outf; + ecb.priv = diffstat; + xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); + } +} + +struct diff_filespec *alloc_filespec(const char *path) +{ + int namelen = strlen(path); + struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1); + + memset(spec, 0, sizeof(*spec)); + spec->path = (char *)(spec + 1); + memcpy(spec->path, path, namelen+1); + return spec; +} + +void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, + unsigned short mode) +{ + if (mode) { + spec->mode = canon_mode(mode); + memcpy(spec->sha1, sha1, 20); + spec->sha1_valid = !!memcmp(sha1, null_sha1, 20); + } +} + +/* + * Given a name and sha1 pair, if the dircache tells us the file in + * the work tree has that object contents, return true, so that + * prepare_temp_file() does not have to inflate and extract. + */ +static int work_tree_matches(const char *name, const unsigned char *sha1) +{ + struct cache_entry *ce; + struct stat st; + int pos, len; + + /* We do not read the cache ourselves here, because the + * benchmark with my previous version that always reads cache + * shows that it makes things worse for diff-tree comparing + * two linux-2.6 kernel trees in an already checked out work + * tree. This is because most diff-tree comparisons deal with + * only a small number of files, while reading the cache is + * expensive for a large project, and its cost outweighs the + * savings we get by not inflating the object to a temporary + * file. Practically, this code only helps when we are used + * by diff-cache --cached, which does read the cache before + * calling us. + */ + if (!active_cache) + return 0; + + len = strlen(name); + pos = cache_name_pos(name, len); + if (pos < 0) + return 0; + ce = active_cache[pos]; + if ((lstat(name, &st) < 0) || + !S_ISREG(st.st_mode) || /* careful! */ + ce_match_stat(ce, &st, 0) || + memcmp(sha1, ce->sha1, 20)) + return 0; + /* we return 1 only when we can stat, it is a regular file, + * stat information matches, and sha1 recorded in the cache + * matches. I.e. we know the file in the work tree really is + * the same as the pair. + */ + return 1; +} + +static struct sha1_size_cache { + unsigned char sha1[20]; + unsigned long size; +} **sha1_size_cache; +static int sha1_size_cache_nr, sha1_size_cache_alloc; + +static struct sha1_size_cache *locate_size_cache(unsigned char *sha1, + int find_only, + unsigned long size) +{ + int first, last; + struct sha1_size_cache *e; + + first = 0; + last = sha1_size_cache_nr; + while (last > first) { + int cmp, next = (last + first) >> 1; + e = sha1_size_cache[next]; + cmp = memcmp(e->sha1, sha1, 20); + if (!cmp) + return e; + if (cmp < 0) { + last = next; + continue; + } + first = next+1; + } + /* not found */ + if (find_only) + return NULL; + /* insert to make it at "first" */ + if (sha1_size_cache_alloc <= sha1_size_cache_nr) { + sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc); + sha1_size_cache = xrealloc(sha1_size_cache, + sha1_size_cache_alloc * + sizeof(*sha1_size_cache)); + } + sha1_size_cache_nr++; + if (first < sha1_size_cache_nr) + memmove(sha1_size_cache + first + 1, sha1_size_cache + first, + (sha1_size_cache_nr - first - 1) * + sizeof(*sha1_size_cache)); + e = xmalloc(sizeof(struct sha1_size_cache)); + sha1_size_cache[first] = e; + memcpy(e->sha1, sha1, 20); + e->size = size; + return e; +} + +/* + * While doing rename detection and pickaxe operation, we may need to + * grab the data for the blob (or file) for our own in-core comparison. + * diff_filespec has data and size fields for this purpose. + */ +int diff_populate_filespec(struct diff_filespec *s, int size_only) +{ + int err = 0; + if (!DIFF_FILE_VALID(s)) + die("internal error: asking to populate invalid file."); + if (S_ISDIR(s->mode)) + return -1; + + if (!use_size_cache) + size_only = 0; + + if (s->data) + return err; + if (!s->sha1_valid || + work_tree_matches(s->path, s->sha1)) { + struct stat st; + int fd; + if (lstat(s->path, &st) < 0) { + if (errno == ENOENT) { + err_empty: + err = -1; + empty: + s->data = ""; + s->size = 0; + return err; + } + } + s->size = st.st_size; + if (!s->size) + goto empty; + if (size_only) + return 0; + if (S_ISLNK(st.st_mode)) { + int ret; + s->data = xmalloc(s->size); + s->should_free = 1; + ret = readlink(s->path, s->data, s->size); + if (ret < 0) { + free(s->data); + goto err_empty; + } + return 0; + } + fd = open(s->path, O_RDONLY); + if (fd < 0) + goto err_empty; + s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + if (s->data == MAP_FAILED) + goto err_empty; + s->should_munmap = 1; + } + else { + char type[20]; + struct sha1_size_cache *e; + + if (size_only) { + e = locate_size_cache(s->sha1, 1, 0); + if (e) { + s->size = e->size; + return 0; + } + if (!sha1_object_info(s->sha1, type, &s->size)) + locate_size_cache(s->sha1, 0, s->size); + } + else { + s->data = read_sha1_file(s->sha1, type, &s->size); + s->should_free = 1; + } + } + return 0; +} + +void diff_free_filespec_data(struct diff_filespec *s) +{ + if (s->should_free) + free(s->data); + else if (s->should_munmap) + munmap(s->data, s->size); + s->should_free = s->should_munmap = 0; + s->data = NULL; + free(s->cnt_data); + s->cnt_data = NULL; +} + +static void prep_temp_blob(struct diff_tempfile *temp, + void *blob, + unsigned long size, + const unsigned char *sha1, + int mode) +{ + int fd; + + fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX"); + if (fd < 0) + die("unable to create temp-file"); + if (write(fd, blob, size) != size) + die("unable to write temp-file"); + close(fd); + temp->name = temp->tmp_path; + strcpy(temp->hex, sha1_to_hex(sha1)); + temp->hex[40] = 0; + sprintf(temp->mode, "%06o", mode); +} + +static void prepare_temp_file(const char *name, + struct diff_tempfile *temp, + struct diff_filespec *one) +{ + if (!DIFF_FILE_VALID(one)) { + not_a_valid_file: + /* A '-' entry produces this for file-2, and + * a '+' entry produces this for file-1. + */ + temp->name = "/dev/null"; + strcpy(temp->hex, "."); + strcpy(temp->mode, "."); + return; + } + + if (!one->sha1_valid || + work_tree_matches(name, one->sha1)) { + struct stat st; + if (lstat(name, &st) < 0) { + if (errno == ENOENT) + goto not_a_valid_file; + die("stat(%s): %s", name, strerror(errno)); + } + if (S_ISLNK(st.st_mode)) { + int ret; + char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */ + if (sizeof(buf) <= st.st_size) + die("symlink too long: %s", name); + ret = readlink(name, buf, st.st_size); + if (ret < 0) + die("readlink(%s)", name); + prep_temp_blob(temp, buf, st.st_size, + (one->sha1_valid ? + one->sha1 : null_sha1), + (one->sha1_valid ? + one->mode : S_IFLNK)); + } + else { + /* we can borrow from the file in the work tree */ + temp->name = name; + if (!one->sha1_valid) + strcpy(temp->hex, sha1_to_hex(null_sha1)); + else + strcpy(temp->hex, sha1_to_hex(one->sha1)); + /* Even though we may sometimes borrow the + * contents from the work tree, we always want + * one->mode. mode is trustworthy even when + * !(one->sha1_valid), as long as + * DIFF_FILE_VALID(one). + */ + sprintf(temp->mode, "%06o", one->mode); + } + return; + } + else { + if (diff_populate_filespec(one, 0)) + die("cannot read data blob for %s", one->path); + prep_temp_blob(temp, one->data, one->size, + one->sha1, 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; + } +} + +static void remove_tempfile_on_signal(int signo) +{ + remove_tempfile(); + signal(SIGINT, SIG_DFL); + raise(signo); +} + +static int spawn_prog(const char *pgm, const char **arg) +{ + pid_t pid; + int status; + + fflush(NULL); + pid = fork(); + if (pid < 0) + die("unable to fork"); + if (!pid) { + execvp(pgm, (char *const*) arg); + exit(255); + } + + while (waitpid(pid, &status, 0) < 0) { + if (errno == EINTR) + continue; + return -1; + } + + /* Earlier we did not check the exit status because + * diff exits non-zero if files are different, and + * we are not interested in knowing that. It was a + * mistake which made it harder to quit a diff-* + * session that uses the git-apply-patch-script as + * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF + * should also exit non-zero only when it wants to + * abort the entire diff-* session. + */ + if (WIFEXITED(status) && !WEXITSTATUS(status)) + return 0; + return -1; +} + +/* An external diff command takes: + * + * diff-cmd name infile1 infile1-sha1 infile1-mode \ + * infile2 infile2-sha1 infile2-mode [ rename-to ] + * + */ +static void run_external_diff(const char *pgm, + const char *name, + const char *other, + struct diff_filespec *one, + struct diff_filespec *two, + const char *xfrm_msg, + int complete_rewrite) +{ + const char *spawn_arg[10]; + struct diff_tempfile *temp = diff_temp; + int retval; + static int atexit_asked = 0; + const char *othername; + const char **arg = &spawn_arg[0]; + + othername = (other? other : name); + if (one && two) { + prepare_temp_file(name, &temp[0], one); + prepare_temp_file(othername, &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); + } + signal(SIGINT, remove_tempfile_on_signal); + } + + if (one && two) { + *arg++ = pgm; + *arg++ = name; + *arg++ = temp[0].name; + *arg++ = temp[0].hex; + *arg++ = temp[0].mode; + *arg++ = temp[1].name; + *arg++ = temp[1].hex; + *arg++ = temp[1].mode; + if (other) { + *arg++ = other; + *arg++ = xfrm_msg; + } + } else { + *arg++ = pgm; + *arg++ = name; + } + *arg = NULL; + retval = spawn_prog(pgm, spawn_arg); + remove_tempfile(); + if (retval) { + fprintf(stderr, "external diff died, stopping at %s.\n", name); + exit(1); + } +} + +static void run_diff_cmd(const char *pgm, + const char *name, + const char *other, + struct diff_filespec *one, + struct diff_filespec *two, + const char *xfrm_msg, + int complete_rewrite) +{ + if (pgm) { + run_external_diff(pgm, name, other, one, two, xfrm_msg, + complete_rewrite); + return; + } + if (one && two) + builtin_diff(name, other ? other : name, + one, two, xfrm_msg, complete_rewrite); + else + printf("* Unmerged path %s\n", name); +} + +static void diff_fill_sha1_info(struct diff_filespec *one) +{ + if (DIFF_FILE_VALID(one)) { + if (!one->sha1_valid) { + struct stat st; + if (lstat(one->path, &st) < 0) + die("stat %s", one->path); + if (index_path(one->sha1, one->path, &st, 0)) + die("cannot hash %s\n", one->path); + } + } + else + memset(one->sha1, 0, 20); +} + +static void run_diff(struct diff_filepair *p, struct diff_options *o) +{ + const char *pgm = external_diff(); + char msg[PATH_MAX*2+300], *xfrm_msg; + struct diff_filespec *one; + struct diff_filespec *two; + const char *name; + const char *other; + char *name_munged, *other_munged; + int complete_rewrite = 0; + int len; + + if (DIFF_PAIR_UNMERGED(p)) { + /* unmerged */ + run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, 0); + return; + } + + name = p->one->path; + other = (strcmp(name, p->two->path) ? p->two->path : NULL); + name_munged = quote_one(name); + other_munged = quote_one(other); + one = p->one; two = p->two; + + diff_fill_sha1_info(one); + diff_fill_sha1_info(two); + + len = 0; + switch (p->status) { + case DIFF_STATUS_COPIED: + len += snprintf(msg + len, sizeof(msg) - len, + "similarity index %d%%\n" + "copy from %s\n" + "copy to %s\n", + (int)(0.5 + p->score * 100.0/MAX_SCORE), + name_munged, other_munged); + break; + case DIFF_STATUS_RENAMED: + len += snprintf(msg + len, sizeof(msg) - len, + "similarity index %d%%\n" + "rename from %s\n" + "rename to %s\n", + (int)(0.5 + p->score * 100.0/MAX_SCORE), + name_munged, other_munged); + break; + case DIFF_STATUS_MODIFIED: + if (p->score) { + len += snprintf(msg + len, sizeof(msg) - len, + "dissimilarity index %d%%\n", + (int)(0.5 + p->score * + 100.0/MAX_SCORE)); + complete_rewrite = 1; + break; + } + /* fallthru */ + default: + /* nothing */ + ; + } + + if (memcmp(one->sha1, two->sha1, 20)) { + char one_sha1[41]; + int abbrev = o->full_index ? 40 : DEFAULT_ABBREV; + memcpy(one_sha1, sha1_to_hex(one->sha1), 41); + + len += snprintf(msg + len, sizeof(msg) - len, + "index %.*s..%.*s", + abbrev, one_sha1, abbrev, + sha1_to_hex(two->sha1)); + if (one->mode == two->mode) + len += snprintf(msg + len, sizeof(msg) - len, + " %06o", one->mode); + len += snprintf(msg + len, sizeof(msg) - len, "\n"); + } + + if (len) + msg[--len] = 0; + xfrm_msg = len ? msg : NULL; + + if (!pgm && + DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) && + (S_IFMT & one->mode) != (S_IFMT & two->mode)) { + /* a filepair that changes between file and symlink + * needs to be split into deletion and creation. + */ + struct diff_filespec *null = alloc_filespec(two->path); + run_diff_cmd(NULL, name, other, one, null, xfrm_msg, 0); + free(null); + null = alloc_filespec(one->path); + run_diff_cmd(NULL, name, other, null, two, xfrm_msg, 0); + free(null); + } + else + run_diff_cmd(pgm, name, other, one, two, xfrm_msg, + complete_rewrite); + + free(name_munged); + free(other_munged); +} + +static void run_diffstat(struct diff_filepair *p, struct diff_options *o, + struct diffstat_t *diffstat) +{ + const char *name; + const char *other; + + if (DIFF_PAIR_UNMERGED(p)) { + /* unmerged */ + builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat); + return; + } + + name = p->one->path; + other = (strcmp(name, p->two->path) ? p->two->path : NULL); + + diff_fill_sha1_info(p->one); + diff_fill_sha1_info(p->two); + + builtin_diffstat(name, other, p->one, p->two, diffstat); +} + +void diff_setup(struct diff_options *options) +{ + memset(options, 0, sizeof(*options)); + options->output_format = DIFF_FORMAT_RAW; + options->line_termination = '\n'; + options->break_opt = -1; + options->rename_limit = -1; + + options->change = diff_change; + options->add_remove = diff_addremove; +} + +int diff_setup_done(struct diff_options *options) +{ + if ((options->find_copies_harder && + options->detect_rename != DIFF_DETECT_COPY) || + (0 <= options->rename_limit && !options->detect_rename)) + return -1; + + /* + * These cases always need recursive; we do not drop caller-supplied + * recursive bits for other formats here. + */ + if ((options->output_format == DIFF_FORMAT_PATCH) || + (options->output_format == DIFF_FORMAT_DIFFSTAT)) + options->recursive = 1; + + if (options->detect_rename && options->rename_limit < 0) + options->rename_limit = diff_rename_limit_default; + if (options->setup & DIFF_SETUP_USE_CACHE) { + if (!active_cache) + /* read-cache does not die even when it fails + * so it is safe for us to do this here. Also + * it does not smudge active_cache or active_nr + * when it fails, so we do not have to worry about + * cleaning it up ourselves either. + */ + read_cache(); + } + if (options->setup & DIFF_SETUP_USE_SIZE_CACHE) + use_size_cache = 1; + if (options->abbrev <= 0 || 40 < options->abbrev) + options->abbrev = 40; /* full */ + + return 0; +} + +int diff_opt_parse(struct diff_options *options, const char **av, int ac) +{ + const char *arg = av[0]; + if (!strcmp(arg, "-p") || !strcmp(arg, "-u")) + options->output_format = DIFF_FORMAT_PATCH; + else if (!strcmp(arg, "--patch-with-raw")) { + options->output_format = DIFF_FORMAT_PATCH; + options->with_raw = 1; + } + else if (!strcmp(arg, "--stat")) + options->output_format = DIFF_FORMAT_DIFFSTAT; + else if (!strcmp(arg, "--patch-with-stat")) { + options->output_format = DIFF_FORMAT_PATCH; + options->with_stat = 1; + } + else if (!strcmp(arg, "-z")) + options->line_termination = 0; + else if (!strncmp(arg, "-l", 2)) + options->rename_limit = strtoul(arg+2, NULL, 10); + else if (!strcmp(arg, "--full-index")) + options->full_index = 1; + else if (!strcmp(arg, "--name-only")) + options->output_format = DIFF_FORMAT_NAME; + else if (!strcmp(arg, "--name-status")) + options->output_format = DIFF_FORMAT_NAME_STATUS; + else if (!strcmp(arg, "-R")) + options->reverse_diff = 1; + else if (!strncmp(arg, "-S", 2)) + options->pickaxe = arg + 2; + else if (!strcmp(arg, "-s")) + options->output_format = DIFF_FORMAT_NO_OUTPUT; + else if (!strncmp(arg, "-O", 2)) + options->orderfile = arg + 2; + else if (!strncmp(arg, "--diff-filter=", 14)) + options->filter = arg + 14; + else if (!strcmp(arg, "--pickaxe-all")) + options->pickaxe_opts = DIFF_PICKAXE_ALL; + else if (!strcmp(arg, "--pickaxe-regex")) + options->pickaxe_opts = DIFF_PICKAXE_REGEX; + else if (!strncmp(arg, "-B", 2)) { + if ((options->break_opt = + diff_scoreopt_parse(arg)) == -1) + return -1; + } + else if (!strncmp(arg, "-M", 2)) { + if ((options->rename_score = + diff_scoreopt_parse(arg)) == -1) + return -1; + options->detect_rename = DIFF_DETECT_RENAME; + } + else if (!strncmp(arg, "-C", 2)) { + if ((options->rename_score = + diff_scoreopt_parse(arg)) == -1) + return -1; + options->detect_rename = DIFF_DETECT_COPY; + } + else if (!strcmp(arg, "--find-copies-harder")) + options->find_copies_harder = 1; + else if (!strcmp(arg, "--abbrev")) + options->abbrev = DEFAULT_ABBREV; + else if (!strncmp(arg, "--abbrev=", 9)) { + options->abbrev = strtoul(arg + 9, NULL, 10); + if (options->abbrev < MINIMUM_ABBREV) + options->abbrev = MINIMUM_ABBREV; + else if (40 < options->abbrev) + options->abbrev = 40; + } + else + return 0; + return 1; +} + +static int parse_num(const char **cp_p) +{ + unsigned long num, scale; + int ch, dot; + const char *cp = *cp_p; + + num = 0; + scale = 1; + dot = 0; + for(;;) { + ch = *cp; + if ( !dot && ch == '.' ) { + scale = 1; + dot = 1; + } else if ( ch == '%' ) { + scale = dot ? scale*100 : 100; + cp++; /* % is always at the end */ + break; + } else if ( ch >= '0' && ch <= '9' ) { + if ( scale < 100000 ) { + scale *= 10; + num = (num*10) + (ch-'0'); + } + } else { + break; + } + cp++; + } + *cp_p = cp; + + /* user says num divided by scale and we say internally that + * is MAX_SCORE * num / scale. + */ + return (num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale); +} + +int diff_scoreopt_parse(const char *opt) +{ + int opt1, opt2, cmd; + + if (*opt++ != '-') + return -1; + cmd = *opt++; + if (cmd != 'M' && cmd != 'C' && cmd != 'B') + return -1; /* that is not a -M, -C nor -B option */ + + opt1 = parse_num(&opt); + if (cmd != 'B') + opt2 = 0; + else { + if (*opt == 0) + opt2 = 0; + else if (*opt != '/') + return -1; /* we expect -B80/99 or -B80 */ + else { + opt++; + opt2 = parse_num(&opt); + } + } + if (*opt != 0) + return -1; + return opt1 | (opt2 << 16); +} + +struct diff_queue_struct diff_queued_diff; + +void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp) +{ + if (queue->alloc <= queue->nr) { + queue->alloc = alloc_nr(queue->alloc); + queue->queue = xrealloc(queue->queue, + sizeof(dp) * queue->alloc); + } + queue->queue[queue->nr++] = dp; +} + +struct diff_filepair *diff_queue(struct diff_queue_struct *queue, + struct diff_filespec *one, + struct diff_filespec *two) +{ + struct diff_filepair *dp = xmalloc(sizeof(*dp)); + dp->one = one; + dp->two = two; + dp->score = 0; + dp->status = 0; + dp->source_stays = 0; + dp->broken_pair = 0; + if (queue) + diff_q(queue, dp); + return dp; +} + +void diff_free_filepair(struct diff_filepair *p) +{ + diff_free_filespec_data(p->one); + diff_free_filespec_data(p->two); + free(p->one); + free(p->two); + free(p); +} + +/* This is different from find_unique_abbrev() in that + * it stuffs the result with dots for alignment. + */ +const char *diff_unique_abbrev(const unsigned char *sha1, int len) +{ + int abblen; + const char *abbrev; + if (len == 40) + return sha1_to_hex(sha1); + + abbrev = find_unique_abbrev(sha1, len); + if (!abbrev) + return sha1_to_hex(sha1); + abblen = strlen(abbrev); + if (abblen < 37) { + static char hex[41]; + if (len < abblen && abblen <= len + 2) + sprintf(hex, "%s%.*s", abbrev, len+3-abblen, ".."); + else + sprintf(hex, "%s...", abbrev); + return hex; + } + return sha1_to_hex(sha1); +} + +static void diff_flush_raw(struct diff_filepair *p, + int line_termination, + int inter_name_termination, + struct diff_options *options, + int output_format) +{ + int two_paths; + char status[10]; + int abbrev = options->abbrev; + const char *path_one, *path_two; + + path_one = p->one->path; + path_two = p->two->path; + if (line_termination) { + path_one = quote_one(path_one); + path_two = quote_one(path_two); + } + + if (p->score) + sprintf(status, "%c%03d", p->status, + (int)(0.5 + p->score * 100.0/MAX_SCORE)); + else { + status[0] = p->status; + status[1] = 0; + } + switch (p->status) { + case DIFF_STATUS_COPIED: + case DIFF_STATUS_RENAMED: + two_paths = 1; + break; + case DIFF_STATUS_ADDED: + case DIFF_STATUS_DELETED: + two_paths = 0; + break; + default: + two_paths = 0; + break; + } + if (output_format != DIFF_FORMAT_NAME_STATUS) { + printf(":%06o %06o %s ", + p->one->mode, p->two->mode, + diff_unique_abbrev(p->one->sha1, abbrev)); + printf("%s ", + diff_unique_abbrev(p->two->sha1, abbrev)); + } + printf("%s%c%s", status, inter_name_termination, path_one); + if (two_paths) + printf("%c%s", inter_name_termination, path_two); + putchar(line_termination); + if (path_one != p->one->path) + free((void*)path_one); + if (path_two != p->two->path) + free((void*)path_two); +} + +static void diff_flush_name(struct diff_filepair *p, + int inter_name_termination, + int line_termination) +{ + char *path = p->two->path; + + if (line_termination) + path = quote_one(p->two->path); + else + path = p->two->path; + printf("%s%c", path, line_termination); + if (p->two->path != path) + free(path); +} + +int diff_unmodified_pair(struct diff_filepair *p) +{ + /* This function is written stricter than necessary to support + * the currently implemented transformers, but the idea is to + * let transformers to produce diff_filepairs any way they want, + * and filter and clean them up here before producing the output. + */ + struct diff_filespec *one, *two; + + if (DIFF_PAIR_UNMERGED(p)) + return 0; /* unmerged is interesting */ + + one = p->one; + two = p->two; + + /* deletion, addition, mode or type change + * and rename are all interesting. + */ + if (DIFF_FILE_VALID(one) != DIFF_FILE_VALID(two) || + DIFF_PAIR_MODE_CHANGED(p) || + strcmp(one->path, two->path)) + return 0; + + /* both are valid and point at the same path. that is, we are + * dealing with a change. + */ + if (one->sha1_valid && two->sha1_valid && + !memcmp(one->sha1, two->sha1, sizeof(one->sha1))) + return 1; /* no change */ + if (!one->sha1_valid && !two->sha1_valid) + return 1; /* both look at the same file on the filesystem. */ + return 0; +} + +static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o) +{ + if (diff_unmodified_pair(p)) + return; + + if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) || + (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode))) + return; /* no tree diffs in patch format */ + + run_diff(p, o); +} + +static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o, + struct diffstat_t *diffstat) +{ + if (diff_unmodified_pair(p)) + return; + + if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) || + (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode))) + return; /* no tree diffs in patch format */ + + run_diffstat(p, o, diffstat); +} + +int diff_queue_is_empty(void) +{ + struct diff_queue_struct *q = &diff_queued_diff; + int i; + for (i = 0; i < q->nr; i++) + if (!diff_unmodified_pair(q->queue[i])) + return 0; + return 1; +} + +#if DIFF_DEBUG +void diff_debug_filespec(struct diff_filespec *s, int x, const char *one) +{ + fprintf(stderr, "queue[%d] %s (%s) %s %06o %s\n", + x, one ? one : "", + s->path, + DIFF_FILE_VALID(s) ? "valid" : "invalid", + s->mode, + s->sha1_valid ? sha1_to_hex(s->sha1) : ""); + fprintf(stderr, "queue[%d] %s size %lu flags %d\n", + x, one ? one : "", + s->size, s->xfrm_flags); +} + +void diff_debug_filepair(const struct diff_filepair *p, int i) +{ + diff_debug_filespec(p->one, i, "one"); + diff_debug_filespec(p->two, i, "two"); + fprintf(stderr, "score %d, status %c stays %d broken %d\n", + p->score, p->status ? p->status : '?', + p->source_stays, p->broken_pair); +} + +void diff_debug_queue(const char *msg, struct diff_queue_struct *q) +{ + int i; + if (msg) + fprintf(stderr, "%s\n", msg); + fprintf(stderr, "q->nr = %d\n", q->nr); + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + diff_debug_filepair(p, i); + } +} +#endif + +static void diff_resolve_rename_copy(void) +{ + int i, j; + struct diff_filepair *p, *pp; + struct diff_queue_struct *q = &diff_queued_diff; + + diff_debug_queue("resolve-rename-copy", q); + + for (i = 0; i < q->nr; i++) { + p = q->queue[i]; + p->status = 0; /* undecided */ + if (DIFF_PAIR_UNMERGED(p)) + p->status = DIFF_STATUS_UNMERGED; + else if (!DIFF_FILE_VALID(p->one)) + p->status = DIFF_STATUS_ADDED; + else if (!DIFF_FILE_VALID(p->two)) + p->status = DIFF_STATUS_DELETED; + else if (DIFF_PAIR_TYPE_CHANGED(p)) + p->status = DIFF_STATUS_TYPE_CHANGED; + + /* from this point on, we are dealing with a pair + * whose both sides are valid and of the same type, i.e. + * either in-place edit or rename/copy edit. + */ + else if (DIFF_PAIR_RENAME(p)) { + if (p->source_stays) { + p->status = DIFF_STATUS_COPIED; + continue; + } + /* See if there is some other filepair that + * copies from the same source as us. If so + * we are a copy. Otherwise we are either a + * copy if the path stays, or a rename if it + * does not, but we already handled "stays" case. + */ + for (j = i + 1; j < q->nr; j++) { + pp = q->queue[j]; + if (strcmp(pp->one->path, p->one->path)) + continue; /* not us */ + if (!DIFF_PAIR_RENAME(pp)) + continue; /* not a rename/copy */ + /* pp is a rename/copy from the same source */ + p->status = DIFF_STATUS_COPIED; + break; + } + if (!p->status) + p->status = DIFF_STATUS_RENAMED; + } + else if (memcmp(p->one->sha1, p->two->sha1, 20) || + p->one->mode != p->two->mode) + p->status = DIFF_STATUS_MODIFIED; + else { + /* This is a "no-change" entry and should not + * happen anymore, but prepare for broken callers. + */ + error("feeding unmodified %s to diffcore", + p->one->path); + p->status = DIFF_STATUS_UNKNOWN; + } + } + diff_debug_queue("resolve-rename-copy done", q); +} + +static void flush_one_pair(struct diff_filepair *p, + int diff_output_format, + struct diff_options *options, + struct diffstat_t *diffstat) +{ + int inter_name_termination = '\t'; + int line_termination = options->line_termination; + if (!line_termination) + inter_name_termination = 0; + + switch (p->status) { + case DIFF_STATUS_UNKNOWN: + break; + case 0: + die("internal error in diff-resolve-rename-copy"); + break; + default: + switch (diff_output_format) { + case DIFF_FORMAT_DIFFSTAT: + diff_flush_stat(p, options, diffstat); + break; + case DIFF_FORMAT_PATCH: + diff_flush_patch(p, options); + break; + case DIFF_FORMAT_RAW: + case DIFF_FORMAT_NAME_STATUS: + diff_flush_raw(p, line_termination, + inter_name_termination, + options, diff_output_format); + break; + case DIFF_FORMAT_NAME: + diff_flush_name(p, + inter_name_termination, + line_termination); + break; + case DIFF_FORMAT_NO_OUTPUT: + break; + } + } +} + +void diff_flush(struct diff_options *options) +{ + struct diff_queue_struct *q = &diff_queued_diff; + int i; + int diff_output_format = options->output_format; + struct diffstat_t *diffstat = NULL; + + if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) { + diffstat = xcalloc(sizeof (struct diffstat_t), 1); + diffstat->xm.consume = diffstat_consume; + } + + if (options->with_raw) { + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL); + } + putchar(options->line_termination); + } + if (options->with_stat) { + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options, + diffstat); + } + show_stats(diffstat); + free(diffstat); + diffstat = NULL; + putchar(options->line_termination); + } + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + flush_one_pair(p, diff_output_format, options, diffstat); + diff_free_filepair(p); + } + + if (diffstat) { + show_stats(diffstat); + free(diffstat); + } + + free(q->queue); + q->queue = NULL; + q->nr = q->alloc = 0; +} + +static void diffcore_apply_filter(const char *filter) +{ + int i; + struct diff_queue_struct *q = &diff_queued_diff; + struct diff_queue_struct outq; + outq.queue = NULL; + outq.nr = outq.alloc = 0; + + if (!filter) + return; + + if (strchr(filter, DIFF_STATUS_FILTER_AON)) { + int found; + for (i = found = 0; !found && i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (((p->status == DIFF_STATUS_MODIFIED) && + ((p->score && + strchr(filter, DIFF_STATUS_FILTER_BROKEN)) || + (!p->score && + strchr(filter, DIFF_STATUS_MODIFIED)))) || + ((p->status != DIFF_STATUS_MODIFIED) && + strchr(filter, p->status))) + found++; + } + if (found) + return; + + /* otherwise we will clear the whole queue + * by copying the empty outq at the end of this + * function, but first clear the current entries + * in the queue. + */ + for (i = 0; i < q->nr; i++) + diff_free_filepair(q->queue[i]); + } + else { + /* Only the matching ones */ + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + + if (((p->status == DIFF_STATUS_MODIFIED) && + ((p->score && + strchr(filter, DIFF_STATUS_FILTER_BROKEN)) || + (!p->score && + strchr(filter, DIFF_STATUS_MODIFIED)))) || + ((p->status != DIFF_STATUS_MODIFIED) && + strchr(filter, p->status))) + diff_q(&outq, p); + else + diff_free_filepair(p); + } + } + free(q->queue); + *q = outq; +} + +void diffcore_std(struct diff_options *options) +{ + if (options->break_opt != -1) + diffcore_break(options->break_opt); + if (options->detect_rename) + diffcore_rename(options); + if (options->break_opt != -1) + diffcore_merge_broken(); + if (options->pickaxe) + diffcore_pickaxe(options->pickaxe, options->pickaxe_opts); + if (options->orderfile) + diffcore_order(options->orderfile); + diff_resolve_rename_copy(); + diffcore_apply_filter(options->filter); +} + + +void diffcore_std_no_resolve(struct diff_options *options) +{ + if (options->pickaxe) + diffcore_pickaxe(options->pickaxe, options->pickaxe_opts); + if (options->orderfile) + diffcore_order(options->orderfile); + diffcore_apply_filter(options->filter); +} + +void diff_addremove(struct diff_options *options, + int addremove, unsigned mode, + const unsigned char *sha1, + const char *base, const char *path) +{ + char concatpath[PATH_MAX]; + struct diff_filespec *one, *two; + + /* This may look odd, but it is a preparation for + * feeding "there are unchanged files which should + * not produce diffs, but when you are doing copy + * detection you would need them, so here they are" + * entries to the diff-core. They will be prefixed + * with something like '=' or '*' (I haven't decided + * which but should not make any difference). + * Feeding the same new and old to diff_change() + * also has the same effect. + * Before the final output happens, they are pruned after + * merged into rename/copy pairs as appropriate. + */ + if (options->reverse_diff) + addremove = (addremove == '+' ? '-' : + addremove == '-' ? '+' : addremove); + + if (!path) path = ""; + sprintf(concatpath, "%s%s", base, path); + one = alloc_filespec(concatpath); + two = alloc_filespec(concatpath); + + if (addremove != '+') + fill_filespec(one, sha1, mode); + if (addremove != '-') + fill_filespec(two, sha1, mode); + + diff_queue(&diff_queued_diff, one, two); +} + +void diff_change(struct diff_options *options, + unsigned old_mode, unsigned new_mode, + const unsigned char *old_sha1, + const unsigned char *new_sha1, + const char *base, const char *path) +{ + char concatpath[PATH_MAX]; + struct diff_filespec *one, *two; + + if (options->reverse_diff) { + unsigned tmp; + const unsigned char *tmp_c; + tmp = old_mode; old_mode = new_mode; new_mode = tmp; + tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c; + } + if (!path) path = ""; + sprintf(concatpath, "%s%s", base, path); + one = alloc_filespec(concatpath); + two = alloc_filespec(concatpath); + fill_filespec(one, old_sha1, old_mode); + fill_filespec(two, new_sha1, new_mode); + + diff_queue(&diff_queued_diff, one, two); +} + +void diff_unmerge(struct diff_options *options, + const char *path) +{ + struct diff_filespec *one, *two; + one = alloc_filespec(path); + two = alloc_filespec(path); + diff_queue(&diff_queued_diff, one, two); +} diff --git a/diff.h b/diff.h index 52fff6643f..da4bd421fe 100644 --- a/diff.h +++ b/diff.h @@ -28,10 +28,11 @@ struct diff_options { with_raw:1, with_stat:1, tree_in_recursive:1, - full_index:1; + full_index:1, + silent_on_remove:1, + find_copies_harder:1; int break_opt; int detect_rename; - int find_copies_harder; int line_termination; int output_format; int pickaxe_opts; @@ -168,4 +169,6 @@ extern void diff_flush(struct diff_options*); extern const char *diff_unique_abbrev(const unsigned char *, int); +extern int run_diff_files(struct rev_info *revs, int silent_on_removed); + #endif /* DIFF_H */ diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index d0ed24275e..75e4c9a886 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -37,7 +37,7 @@ compare_change () { } check_cache_at () { - clean_if_empty=`git-diff-files "$1"` + clean_if_empty=`git-diff-files -- "$1"` case "$clean_if_empty" in '') echo "$1: clean" ;; ?*) echo "$1: dirty" ;; diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh index 861ef4c0c6..4d175d8ea1 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -20,7 +20,7 @@ compare_change () { } check_cache_at () { - clean_if_empty=`git-diff-files "$1"` + clean_if_empty=`git-diff-files -- "$1"` case "$clean_if_empty" in '') echo "$1: clean" ;; ?*) echo "$1: dirty" ;; -- cgit v1.2.3 From e09ad6e1e3308fde346b4b6287d9441363806832 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 22 Apr 2006 02:43:00 -0700 Subject: Libify diff-index. The second installment to libify diff brothers. The pathname arguments are checked more strictly than before because we now use the revision.c::setup_revisions() infrastructure. Signed-off-by: Junio C Hamano --- diff-index.c | 254 ++++------------------------------------------- diff-lib.c | 203 +++++++++++++++++++++++++++++++++++++ diff.h | 2 + t/t4010-diff-pathspec.sh | 10 +- 4 files changed, 231 insertions(+), 238 deletions(-) diff --git a/diff-index.c b/diff-index.c index e376d65f80..4a243b3624 100644 --- a/diff-index.c +++ b/diff-index.c @@ -1,166 +1,7 @@ #include "cache.h" -#include "tree.h" #include "diff.h" - -static int cached_only = 0; -static int match_nonexisting = 0; -static struct diff_options diff_options; - -/* A file entry went away or appeared */ -static void show_file(const char *prefix, - struct cache_entry *ce, - unsigned char *sha1, unsigned int mode) -{ - diff_addremove(&diff_options, prefix[0], ntohl(mode), - sha1, ce->name, NULL); -} - -static int get_stat_data(struct cache_entry *ce, - unsigned char ** sha1p, unsigned int *modep) -{ - unsigned char *sha1 = ce->sha1; - unsigned int mode = ce->ce_mode; - - if (!cached_only) { - static unsigned char no_sha1[20]; - int changed; - struct stat st; - if (lstat(ce->name, &st) < 0) { - if (errno == ENOENT && match_nonexisting) { - *sha1p = sha1; - *modep = mode; - return 0; - } - return -1; - } - changed = ce_match_stat(ce, &st, 0); - if (changed) { - mode = create_ce_mode(st.st_mode); - if (!trust_executable_bit && S_ISREG(st.st_mode)) - mode = ce->ce_mode; - sha1 = no_sha1; - } - } - - *sha1p = sha1; - *modep = mode; - return 0; -} - -static void show_new_file(struct cache_entry *new) -{ - unsigned char *sha1; - unsigned int mode; - - /* New file in the index: it might actually be different in - * the working copy. - */ - if (get_stat_data(new, &sha1, &mode) < 0) - return; - - show_file("+", new, sha1, mode); -} - -static int show_modified(struct cache_entry *old, - struct cache_entry *new, - int report_missing) -{ - unsigned int mode, oldmode; - unsigned char *sha1; - - if (get_stat_data(new, &sha1, &mode) < 0) { - if (report_missing) - show_file("-", old, old->sha1, old->ce_mode); - return -1; - } - - oldmode = old->ce_mode; - if (mode == oldmode && !memcmp(sha1, old->sha1, 20) && - !diff_options.find_copies_harder) - return 0; - - mode = ntohl(mode); - oldmode = ntohl(oldmode); - - diff_change(&diff_options, oldmode, mode, - old->sha1, sha1, old->name, NULL); - return 0; -} - -static int diff_cache(struct cache_entry **ac, int entries, const char **pathspec) -{ - while (entries) { - struct cache_entry *ce = *ac; - int same = (entries > 1) && ce_same_name(ce, ac[1]); - - if (!ce_path_match(ce, pathspec)) - goto skip_entry; - - switch (ce_stage(ce)) { - case 0: - /* No stage 1 entry? That means it's a new file */ - if (!same) { - show_new_file(ce); - break; - } - /* Show difference between old and new */ - show_modified(ac[1], ce, 1); - break; - case 1: - /* No stage 3 (merge) entry? That means it's been deleted */ - if (!same) { - show_file("-", ce, ce->sha1, ce->ce_mode); - break; - } - /* We come here with ce pointing at stage 1 - * (original tree) and ac[1] pointing at stage - * 3 (unmerged). show-modified with - * report-missing set to false does not say the - * file is deleted but reports true if work - * tree does not have it, in which case we - * fall through to report the unmerged state. - * Otherwise, we show the differences between - * the original tree and the work tree. - */ - if (!cached_only && !show_modified(ce, ac[1], 0)) - break; - /* fallthru */ - case 3: - diff_unmerge(&diff_options, ce->name); - break; - - default: - die("impossible cache entry stage"); - } - -skip_entry: - /* - * Ignore all the different stages for this file, - * we've handled the relevant cases now. - */ - do { - ac++; - entries--; - } while (entries && ce_same_name(ce, ac[0])); - } - return 0; -} - -/* - * This turns all merge entries into "stage 3". That guarantees that - * when we read in the new tree (into "stage 1"), we won't lose sight - * of the fact that we had unmerged entries. - */ -static void mark_merge_entries(void) -{ - int i; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (!ce_stage(ce)) - continue; - ce->ce_flags |= htons(CE_STAGEMASK); - } -} +#include "commit.h" +#include "revision.h" static const char diff_cache_usage[] = "git-diff-index [-m] [--cached] " @@ -169,85 +10,32 @@ COMMON_DIFF_OPTIONS_HELP; int main(int argc, const char **argv) { - const char *tree_name = NULL; - unsigned char sha1[20]; - const char *prefix = setup_git_directory(); - const char **pathspec = NULL; - struct tree *tree; - int ret; - int allow_options = 1; + struct rev_info rev; + int match_missing = 0; + int cached = 0; int i; git_config(git_diff_config); - diff_setup(&diff_options); + init_revisions(&rev); + rev.abbrev = 0; + + argc = setup_revisions(argc, argv, &rev, NULL); for (i = 1; i < argc; i++) { const char *arg = argv[i]; - int diff_opt_cnt; - - if (!allow_options || *arg != '-') { - if (tree_name) - break; - tree_name = arg; - continue; - } - if (!strcmp(arg, "--")) { - allow_options = 0; - continue; - } - if (!strcmp(arg, "-r")) { - /* We accept the -r flag just to look like git-diff-tree */ - continue; - } - if (!strcmp(arg, "--cc")) - /* - * I _think_ "diff-index --cached HEAD" with an - * unmerged index could show something else - * later, but pretend --cc is the same as -p for - * now. "git diff" uses --cc by default. - */ - argv[i] = arg = "-p"; - diff_opt_cnt = diff_opt_parse(&diff_options, argv + i, - argc - i); - if (diff_opt_cnt < 0) + if (!strcmp(arg, "-m")) + match_missing = 1; + else if (!strcmp(arg, "--cached")) + cached = 1; + else usage(diff_cache_usage); - else if (diff_opt_cnt) { - i += diff_opt_cnt - 1; - continue; - } - - if (!strcmp(arg, "-m")) { - match_nonexisting = 1; - continue; - } - if (!strcmp(arg, "--cached")) { - cached_only = 1; - continue; - } - usage(diff_cache_usage); } - - pathspec = get_pathspec(prefix, argv + i); - - if (diff_setup_done(&diff_options) < 0) - usage(diff_cache_usage); - - if (!tree_name || get_sha1(tree_name, sha1)) + /* + * Make sure there is one revision (i.e. pending object), + * and there is no revision filtering parameters. + */ + if (!rev.pending_objects || rev.pending_objects->next || + rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) usage(diff_cache_usage); - - read_cache(); - - mark_merge_entries(); - - tree = parse_tree_indirect(sha1); - if (!tree) - die("bad tree object %s", tree_name); - if (read_tree(tree, 1, pathspec)) - die("unable to read tree object %s", tree_name); - - ret = diff_cache(active_cache, active_nr, pathspec); - - diffcore_std(&diff_options); - diff_flush(&diff_options); - return ret; + return run_diff_index(&rev, cached, match_missing); } diff --git a/diff-lib.c b/diff-lib.c index a28dd3dca7..63da3b521d 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -131,3 +131,206 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed) return 0; } +/* + * diff-index + */ + +/* A file entry went away or appeared */ +static void diff_index_show_file(struct rev_info *revs, + const char *prefix, + struct cache_entry *ce, + unsigned char *sha1, unsigned int mode) +{ + diff_addremove(&revs->diffopt, prefix[0], ntohl(mode), + sha1, ce->name, NULL); +} + +static int get_stat_data(struct cache_entry *ce, + unsigned char **sha1p, + unsigned int *modep, + int cached, int match_missing) +{ + unsigned char *sha1 = ce->sha1; + unsigned int mode = ce->ce_mode; + + if (!cached) { + static unsigned char no_sha1[20]; + int changed; + struct stat st; + if (lstat(ce->name, &st) < 0) { + if (errno == ENOENT && match_missing) { + *sha1p = sha1; + *modep = mode; + return 0; + } + return -1; + } + changed = ce_match_stat(ce, &st, 0); + if (changed) { + mode = create_ce_mode(st.st_mode); + if (!trust_executable_bit && S_ISREG(st.st_mode)) + mode = ce->ce_mode; + sha1 = no_sha1; + } + } + + *sha1p = sha1; + *modep = mode; + return 0; +} + +static void show_new_file(struct rev_info *revs, + struct cache_entry *new, + int cached, int match_missing) +{ + unsigned char *sha1; + unsigned int mode; + + /* New file in the index: it might actually be different in + * the working copy. + */ + if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) + return; + + diff_index_show_file(revs, "+", new, sha1, mode); +} + +static int show_modified(struct rev_info *revs, + struct cache_entry *old, + struct cache_entry *new, + int report_missing, + int cached, int match_missing) +{ + unsigned int mode, oldmode; + unsigned char *sha1; + + if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) { + if (report_missing) + diff_index_show_file(revs, "-", old, + old->sha1, old->ce_mode); + return -1; + } + + oldmode = old->ce_mode; + if (mode == oldmode && !memcmp(sha1, old->sha1, 20) && + !revs->diffopt.find_copies_harder) + return 0; + + mode = ntohl(mode); + oldmode = ntohl(oldmode); + + diff_change(&revs->diffopt, oldmode, mode, + old->sha1, sha1, old->name, NULL); + return 0; +} + +static int diff_cache(struct rev_info *revs, + struct cache_entry **ac, int entries, + const char **pathspec, + int cached, int match_missing) +{ + while (entries) { + struct cache_entry *ce = *ac; + int same = (entries > 1) && ce_same_name(ce, ac[1]); + + if (!ce_path_match(ce, pathspec)) + goto skip_entry; + + switch (ce_stage(ce)) { + case 0: + /* No stage 1 entry? That means it's a new file */ + if (!same) { + show_new_file(revs, ce, cached, match_missing); + break; + } + /* Show difference between old and new */ + show_modified(revs,ac[1], ce, 1, + cached, match_missing); + break; + case 1: + /* No stage 3 (merge) entry? + * That means it's been deleted. + */ + if (!same) { + diff_index_show_file(revs, "-", ce, + ce->sha1, ce->ce_mode); + break; + } + /* We come here with ce pointing at stage 1 + * (original tree) and ac[1] pointing at stage + * 3 (unmerged). show-modified with + * report-missing set to false does not say the + * file is deleted but reports true if work + * tree does not have it, in which case we + * fall through to report the unmerged state. + * Otherwise, we show the differences between + * the original tree and the work tree. + */ + if (!cached && + !show_modified(revs, ce, ac[1], 0, + cached, match_missing)) + break; + /* fallthru */ + case 3: + diff_unmerge(&revs->diffopt, ce->name); + break; + + default: + die("impossible cache entry stage"); + } + +skip_entry: + /* + * Ignore all the different stages for this file, + * we've handled the relevant cases now. + */ + do { + ac++; + entries--; + } while (entries && ce_same_name(ce, ac[0])); + } + return 0; +} + +/* + * This turns all merge entries into "stage 3". That guarantees that + * when we read in the new tree (into "stage 1"), we won't lose sight + * of the fact that we had unmerged entries. + */ +static void mark_merge_entries(void) +{ + int i; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (!ce_stage(ce)) + continue; + ce->ce_flags |= htons(CE_STAGEMASK); + } +} + +int run_diff_index(struct rev_info *revs, int cached, int match_missing) +{ + int ret; + struct object *ent; + struct tree *tree; + const char *tree_name; + + if (read_cache() < 0) { + perror("read_cache"); + return -1; + } + mark_merge_entries(); + + ent = revs->pending_objects->item; + tree_name = revs->pending_objects->name; + tree = parse_tree_indirect(ent->sha1); + if (!tree) + return error("bad tree object %s", tree_name); + if (read_tree(tree, 1, revs->prune_data)) + return error("unable to read tree object %s", tree_name); + ret = diff_cache(revs, active_cache, active_nr, revs->prune_data, + cached, match_missing); + diffcore_std(&revs->diffopt); + diff_flush(&revs->diffopt); + return ret; +} diff --git a/diff.h b/diff.h index da4bd421fe..837d449f81 100644 --- a/diff.h +++ b/diff.h @@ -171,4 +171,6 @@ extern const char *diff_unique_abbrev(const unsigned char *, int); extern int run_diff_files(struct rev_info *revs, int silent_on_removed); +extern int run_diff_index(struct rev_info *revs, int cached, int match_missing); + #endif /* DIFF_H */ diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh index 8db329d7ff..9e1544df9d 100755 --- a/t/t4010-diff-pathspec.sh +++ b/t/t4010-diff-pathspec.sh @@ -28,7 +28,7 @@ cat >expected <<\EOF EOF test_expect_success \ 'limit to path should show nothing' \ - 'git-diff-index --cached $tree path >current && + 'git-diff-index --cached $tree -- path >current && compare_diff_raw current expected' cat >expected <<\EOF @@ -36,7 +36,7 @@ cat >expected <<\EOF EOF test_expect_success \ 'limit to path1 should show path1/file1' \ - 'git-diff-index --cached $tree path1 >current && + 'git-diff-index --cached $tree -- path1 >current && compare_diff_raw current expected' cat >expected <<\EOF @@ -44,7 +44,7 @@ cat >expected <<\EOF EOF test_expect_success \ 'limit to path1/ should show path1/file1' \ - 'git-diff-index --cached $tree path1/ >current && + 'git-diff-index --cached $tree -- path1/ >current && compare_diff_raw current expected' cat >expected <<\EOF @@ -52,14 +52,14 @@ cat >expected <<\EOF EOF test_expect_success \ 'limit to file0 should show file0' \ - 'git-diff-index --cached $tree file0 >current && + 'git-diff-index --cached $tree -- file0 >current && compare_diff_raw current expected' cat >expected <<\EOF EOF test_expect_success \ 'limit to file0/ should emit nothing.' \ - 'git-diff-index --cached $tree file0/ >current && + 'git-diff-index --cached $tree -- file0/ >current && compare_diff_raw current expected' test_done -- cgit v1.2.3 From 5c21ac0e7c475c82039ab604ee9d7d8430889346 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 22 Apr 2006 03:58:04 -0700 Subject: Libified diff-index: backward compatibility fix. "diff-index -m" does not mean "do not ignore merges", but means "pretend missing files match the index". The previous round tried to address this, but failed because setup_revisions() ate "-m" flag before the caller had a chance to intervene. Signed-off-by: Junio C Hamano --- diff-index.c | 6 ++---- diff-lib.c | 10 +++++++++- diff.h | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/diff-index.c b/diff-index.c index 4a243b3624..86940123b3 100644 --- a/diff-index.c +++ b/diff-index.c @@ -23,9 +23,7 @@ int main(int argc, const char **argv) for (i = 1; i < argc; i++) { const char *arg = argv[i]; - if (!strcmp(arg, "-m")) - match_missing = 1; - else if (!strcmp(arg, "--cached")) + if (!strcmp(arg, "--cached")) cached = 1; else usage(diff_cache_usage); @@ -37,5 +35,5 @@ int main(int argc, const char **argv) if (!rev.pending_objects || rev.pending_objects->next || rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) usage(diff_cache_usage); - return run_diff_index(&rev, cached, match_missing); + return run_diff_index(&rev, cached); } diff --git a/diff-lib.c b/diff-lib.c index 63da3b521d..2183b41b03 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -308,12 +308,20 @@ static void mark_merge_entries(void) } } -int run_diff_index(struct rev_info *revs, int cached, int match_missing) +int run_diff_index(struct rev_info *revs, int cached) { int ret; struct object *ent; struct tree *tree; const char *tree_name; + int match_missing = 0; + + /* + * Backward compatibility wart - "diff-index -m" does + * not mean "do not ignore merges", but totally different. + */ + if (!revs->ignore_merges) + match_missing = 1; if (read_cache() < 0) { perror("read_cache"); diff --git a/diff.h b/diff.h index 837d449f81..7150b90385 100644 --- a/diff.h +++ b/diff.h @@ -171,6 +171,6 @@ extern const char *diff_unique_abbrev(const unsigned char *, int); extern int run_diff_files(struct rev_info *revs, int silent_on_removed); -extern int run_diff_index(struct rev_info *revs, int cached, int match_missing); +extern int run_diff_index(struct rev_info *revs, int cached); #endif /* DIFF_H */ -- cgit v1.2.3 From de791f15a1b01e63f3c870c8497c594c47452fab Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Tue, 25 Apr 2006 00:59:25 +0200 Subject: git-repo-config --list support This adds git-repo-config --list (or git-repo-config -l) support, similar to what git-var -l does now (to be phased out so that we have a single sane interface to the config file instead of fragmented and confused API). Signed-off-by: Petr Baudis --- Documentation/git-repo-config.txt | 4 ++++ repo-config.c | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index 71f96bdd10..566cfa1836 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -15,6 +15,7 @@ SYNOPSIS 'git-repo-config' [type] --get-all name [value_regex] 'git-repo-config' [type] --unset name [value_regex] 'git-repo-config' [type] --unset-all name [value_regex] +'git-repo-config' -l | --list DESCRIPTION ----------- @@ -64,6 +65,9 @@ OPTIONS --unset-all:: Remove all matching lines from .git/config. +-l, --list:: + List all variables set in .git/config. + EXAMPLE ------- diff --git a/repo-config.c b/repo-config.c index c5ebb7668a..fa8aba7a1b 100644 --- a/repo-config.c +++ b/repo-config.c @@ -2,7 +2,7 @@ #include static const char git_config_set_usage[] = -"git-repo-config [ --bool | --int ] [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]]"; +"git-repo-config [ --bool | --int ] [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list"; static char* key = NULL; static char* value = NULL; @@ -12,6 +12,15 @@ static int do_not_match = 0; static int seen = 0; static enum { T_RAW, T_INT, T_BOOL } type = T_RAW; +static int show_all_config(const char *key_, const char *value_) +{ + if (value_) + printf("%s=%s\n", key_, value_); + else + printf("%s\n", key_); + return 0; +} + static int show_config(const char* key_, const char* value_) { if (value_ == NULL) @@ -67,7 +76,7 @@ static int get_value(const char* key_, const char* regex_) } } - i = git_config(show_config); + git_config(show_config); if (value) { printf("%s\n", value); free(value); @@ -99,6 +108,9 @@ int main(int argc, const char **argv) argv++; } + if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l")) + return git_config(show_all_config); + switch (argc) { case 2: return get_value(argv[1], NULL); -- cgit v1.2.3 From e1cbc46d12a0524fd5e710cbfaf3f178fc3da504 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Tue, 25 Apr 2006 00:59:30 +0200 Subject: Deprecate usage of git-var -l for getting config vars list This has been an unfortunate sideway in the git API evolution. We use git-repo-config for all the other .git/config interaction so let's also use git-repo-config -l for the variable listing. Signed-off-by: Petr Baudis --- Documentation/git-var.txt | 3 ++- git-cvsserver.perl | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt index 379571eef0..a5b1a0dbab 100644 --- a/Documentation/git-var.txt +++ b/Documentation/git-var.txt @@ -19,7 +19,8 @@ OPTIONS -l:: Cause the logical variables to be listed. In addition, all the variables of the git configuration file .git/config are listed - as well. + as well. (However, the configuration variables listing functionality + is deprecated in favor of `git-repo-config -l`.) EXAMPLE -------- diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 7d3f78e375..0b37d26e07 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -171,11 +171,11 @@ sub req_Root return 0; } - my @gitvars = `git-var -l`; + my @gitvars = `git-repo-config -l`; if ($?) { - print "E problems executing git-var on the server -- this is not a git repository or the PATH is not set correcly.\n"; + print "E problems executing git-repo-config on the server -- this is not a git repository or the PATH is not set correcly.\n"; print "E \n"; - print "error 1 - problem executing git-var\n"; + print "error 1 - problem executing git-repo-config\n"; return 0; } foreach my $line ( @gitvars ) -- cgit v1.2.3 From 7d09fbe4ab7f080a8f8f5dcef7e0f3edf5e26019 Mon Sep 17 00:00:00 2001 From: "Serge E. Hallyn" Date: Tue, 18 Apr 2006 08:11:06 -0500 Subject: socksetup: don't return on set_reuse_addr() error The set_reuse_addr() error case was the only error case in socklist() where we returned rather than continued. Not sure why. Either we must free the socklist, or continue. This patch continues on error. Signed-off-by: Serge E. Hallyn Signed-off-by: Junio C Hamano (cherry picked from 0032d548db56eac9ea09b4ba05843365f6325b85 commit) --- daemon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon.c b/daemon.c index a1ccda30e2..776749e343 100644 --- a/daemon.c +++ b/daemon.c @@ -535,7 +535,7 @@ static int socksetup(int port, int **socklist_p) if (set_reuse_addr(sockfd)) { close(sockfd); - return 0; /* not fatal */ + continue; } if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) { -- cgit v1.2.3 From 71459c193d04870076efa0a387c317390b53e3e2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 25 Apr 2006 23:11:17 -0700 Subject: Makefile: remove and create libgit.a from scratch. Foolishly I renamed diff.o around which caused an old diff.o taken out of libgit.a and got linked into resulting binary and exhibited mysterious breakage for many people. This borrows from the kernel Makefile (scripts/Makefile.build) to first remove the target and then recreate. Signed-off-by: Junio C Hamano --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d9a3a82fe2..809383c51b 100644 --- a/Makefile +++ b/Makefile @@ -575,7 +575,7 @@ $(patsubst git-%$X,%.o,$(PROGRAMS)): $(GITLIBS) $(DIFF_OBJS): diffcore.h $(LIB_FILE): $(LIB_OBJS) - $(AR) rcs $@ $(LIB_OBJS) + rm -f $@ && $(AR) rcs $@ $(LIB_OBJS) XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o -- cgit v1.2.3 From 710158e3ca3f45a30e08a50dadf427fe95311b9c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 25 Apr 2006 23:40:09 -0700 Subject: diff --stat: show complete rewrites consistently. The patch format shows complete rewrite as deletion of all old lines followed by addition of all new lines. Count lines consistenly with that when doing diffstat. Signed-off-by: Junio C Hamano --- diff.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/diff.c b/diff.c index 13b216f273..6762fcee5a 100644 --- a/diff.c +++ b/diff.c @@ -485,7 +485,8 @@ static void builtin_diff(const char *name_a, static void builtin_diffstat(const char *name_a, const char *name_b, struct diff_filespec *one, struct diff_filespec *two, - struct diffstat_t *diffstat) + struct diffstat_t *diffstat, + int complete_rewrite) { mmfile_t mf1, mf2; struct diffstat_file *data; @@ -496,7 +497,13 @@ static void builtin_diffstat(const char *name_a, const char *name_b, data->is_unmerged = 1; return; } - + if (complete_rewrite) { + diff_populate_filespec(one, 0); + diff_populate_filespec(two, 0); + data->deleted = count_lines(one->data, one->size); + data->added = count_lines(two->data, two->size); + return; + } if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); @@ -1055,10 +1062,11 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o, { const char *name; const char *other; + int complete_rewrite = 0; if (DIFF_PAIR_UNMERGED(p)) { /* unmerged */ - builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat); + builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, 0); return; } @@ -1068,7 +1076,9 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o, diff_fill_sha1_info(p->one); diff_fill_sha1_info(p->two); - builtin_diffstat(name, other, p->one, p->two, diffstat); + if (p->status == DIFF_STATUS_MODIFIED && p->score) + complete_rewrite = 1; + builtin_diffstat(name, other, p->one, p->two, diffstat, complete_rewrite); } void diff_setup(struct diff_options *options) -- cgit v1.2.3 From 5348b6e7f4f07b75b2ea23a3c8fee62919fa254c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 25 Apr 2006 23:59:28 -0700 Subject: git-cvsserver: typofixes Signed-off-by: Junio C Hamano --- git-cvsserver.perl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 0b37d26e07..11d153c4cd 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -88,7 +88,7 @@ my $TEMP_DIR = tempdir( CLEANUP => 1 ); $log->debug("Temporary directory is '$TEMP_DIR'"); # if we are called with a pserver argument, -# deal with the authentication cat before entereing the +# deal with the authentication cat before entering the # main loop if (@ARGV && $ARGV[0] eq 'pserver') { my $line = ; chomp $line; @@ -117,7 +117,7 @@ while () { chomp; - # Check to see if we've seen this method, and call appropiate function. + # Check to see if we've seen this method, and call appropriate function. if ( /^([\w-]+)(?:\s+(.*))?$/ and defined($methods->{$1}) ) { # use the $methods hash to call the appropriate sub for this command @@ -173,7 +173,7 @@ sub req_Root my @gitvars = `git-repo-config -l`; if ($?) { - print "E problems executing git-repo-config on the server -- this is not a git repository or the PATH is not set correcly.\n"; + print "E problems executing git-repo-config on the server -- this is not a git repository or the PATH is not set correctly.\n"; print "E \n"; print "error 1 - problem executing git-repo-config\n"; return 0; @@ -224,7 +224,7 @@ sub req_Globaloption sub req_Validresponses { my ( $cmd, $data ) = @_; - $log->debug("req_Validrepsonses : $data"); + $log->debug("req_Validresponses : $data"); # TODO : re-enable this, currently it's not particularly useful #$state->{validresponses} = [ split /\s+/, $data ]; @@ -733,7 +733,7 @@ sub req_update argsplit("update"); # - # It may just be a client exploring the available heads/modukles + # It may just be a client exploring the available heads/modules # in that case, list them as top level directories and leave it # at that. Eclipse uses this technique to offer you a list of # projects (heads in this case) to checkout. @@ -1731,7 +1731,7 @@ sub transmitfile } # This method takes a file name, and returns ( $dirpart, $filepart ) which -# refers to the directory porition and the file portion of the filename +# refers to the directory portion and the file portion of the filename # respectively sub filenamesplit { @@ -1790,7 +1790,7 @@ Log::Log4perl =head2 new Creates a new log object, optionally you can specify a filename here to -indicate the file to log to. If no log file is specified, you can specifiy one +indicate the file to log to. If no log file is specified, you can specify one later with method setfile, or indicate you no longer want logging with method nofile. @@ -2595,7 +2595,7 @@ sub in_array =head2 safe_pipe_capture -an alterative to `command` that allows input to be passed as an array +an alternative to `command` that allows input to be passed as an array to work around shell problems with weird characters in arguments =cut -- cgit v1.2.3 From fdeb6bf55b7c578ce1735092c6dfbf6968cbee5c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 26 Apr 2006 02:27:59 -0700 Subject: t0000-basic: Add ls-tree recursive test back. When we updated ls-tree recursive output to omit the tree nodes, 246cc52f388cae8ca99e5a12b8458c9bfa467765 adjusted the old test so that we do not expect to see trees in its output. Later, with 0f8f45cb4a7e664b396f73c25891da46b953b8b8, we added back the ability to show both with -t option, but we forgot to update the test as well. Signed-off-by: Junio C Hamano --- t/t0000-basic.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 6729a18266..c69706fb38 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -174,6 +174,27 @@ test_expect_success \ 'git-ls-tree -r output for a known tree.' \ 'diff current expected' +# But with -r -t we can have both. +test_expect_success \ + 'showing tree with git-ls-tree -r -t' \ + 'git-ls-tree -r -t $tree >current' +cat >expected <<\EOF +100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 +120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym +040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2 +100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2 +120000 blob d8ce161addc5173867a3c3c730924388daedbc38 path2/file2sym +040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3 +100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376 path3/file3 +120000 blob 8599103969b43aff7e430efea79ca4636466794f path3/file3sym +040000 tree 3c5e5399f3a333eddecce7a9b9465b63f65f51e2 path3/subp3 +100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f path3/subp3/file3 +120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym +EOF +test_expect_success \ + 'git-ls-tree -r output for a known tree.' \ + 'diff current expected' + ################################################################ rm .git/index test_expect_success \ -- cgit v1.2.3 From 2d86d2c6fcbd62696447fe3dbfac664e6c82b0ce Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 26 Apr 2006 03:12:58 -0700 Subject: Makefile: remove and create xdiff library from scratch. ... in the same spirit as 71459c193d04870076efa0a387c317390b53e3e2. Signed-off-by: Junio C Hamano --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 087ff27c28..8ce27a65fb 100644 --- a/Makefile +++ b/Makefile @@ -580,7 +580,7 @@ $(LIB_FILE): $(LIB_OBJS) XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o $(XDIFF_LIB): $(XDIFF_OBJS) - $(AR) rcs $@ $(XDIFF_OBJS) + rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS) doc: -- cgit v1.2.3 From 3496277a561307c3d31d2085347af8eb4c667c36 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 26 Apr 2006 03:18:51 -0700 Subject: commit-tree: allow generic object name for the tree as well. We use get_sha1() for -p (parent) objects, but still used get_sha1_hex() for the tree. Just to be consistent, allow extended SHA1 expression for the tree object name. Note that this is not to encourage funky things like this: git-commit-tree HEAD^{tree} -p HEAD Signed-off-by: Junio C Hamano --- commit-tree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commit-tree.c b/commit-tree.c index 2d8651894a..e91af4bd36 100644 --- a/commit-tree.c +++ b/commit-tree.c @@ -92,7 +92,7 @@ int main(int argc, char **argv) git_config(git_default_config); - if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0) + if (argc < 2 || get_sha1(argv[1], tree_sha1) < 0) usage(commit_tree_usage); check_valid(tree_sha1, tree_type); -- cgit v1.2.3 From b176e6ba5bc37466ffcb6c8c0f38c47ec6e9e73a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 26 Apr 2006 12:07:42 -0700 Subject: rebase: typofix. Noticed by Sean. Signed-off-by: Junio C Hamano --- git-rebase.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-rebase.sh b/git-rebase.sh index 86dfe9cb96..f7b2b9401a 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -107,7 +107,7 @@ onto=$(git-rev-parse --verify "${onto_name}^0") || exit # Check if we are already based on $onto, but this should be # done only when upstream and onto are the same. -if test "$upstream" = "onto" +if test "$upstream" = "$onto" then mb=$(git-merge-base "$onto" "$branch") if test "$mb" = "$onto" -- cgit v1.2.3 From e23d0b4a4a55cc07e133905f0e9526b3550dd61b Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 26 Apr 2006 10:15:54 -0700 Subject: Fix filename verification when in a subdirectory When we are in a subdirectory of a git archive, we need to take the prefix of that subdirectory into accoung when we verify filename arguments. Noted by Matthias Lederhofer This also uses the improved error reporting for all the other git commands that use the revision parsing interfaces, not just git-rev-parse. Also, it makes the error reporting for mixed filenames and argument flags clearer (you cannot put flags after the start of the pathname list). [jc: with fix to a trivial typo noticed by Timo Hirvonen] Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- cache.h | 1 + rev-parse.c | 15 ++------------- revision.c | 8 +++----- setup.c | 23 +++++++++++++++++++++++ 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/cache.h b/cache.h index 69801b02d5..4d8fabc6d8 100644 --- a/cache.h +++ b/cache.h @@ -134,6 +134,7 @@ extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory(void); extern const char *prefix_path(const char *prefix, int len, const char *path); extern const char *prefix_filename(const char *prefix, int len, const char *path); +extern void verify_filename(const char *prefix, const char *name); #define alloc_nr(x) (((x)+16)*3/2) diff --git a/rev-parse.c b/rev-parse.c index 7f66ae2db8..62e16af33c 100644 --- a/rev-parse.c +++ b/rev-parse.c @@ -160,14 +160,6 @@ static int show_file(const char *arg) return 0; } -static void die_badfile(const char *arg) -{ - if (errno != ENOENT) - die("'%s': %s", arg, strerror(errno)); - die("'%s' is ambiguous - revision name or file/directory name?\n" - "Please put '--' before the list of filenames.", arg); -} - int main(int argc, char **argv) { int i, as_is = 0, verify = 0; @@ -177,14 +169,12 @@ int main(int argc, char **argv) git_config(git_default_config); for (i = 1; i < argc; i++) { - struct stat st; char *arg = argv[i]; char *dotdot; if (as_is) { if (show_file(arg) && as_is < 2) - if (lstat(arg, &st) < 0) - die_badfile(arg); + verify_filename(prefix, arg); continue; } if (!strcmp(arg,"-n")) { @@ -350,8 +340,7 @@ int main(int argc, char **argv) continue; if (verify) die("Needed a single revision"); - if (lstat(arg, &st) < 0) - die_badfile(arg); + verify_filename(prefix, arg); } show_default(); if (verify && revs_count != 1) diff --git a/revision.c b/revision.c index e1f9816bd7..03dd238939 100644 --- a/revision.c +++ b/revision.c @@ -675,17 +675,15 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch arg++; } if (get_sha1(arg, sha1) < 0) { - struct stat st; int j; if (seen_dashdash || local_flags) die("bad revision '%s'", arg); /* If we didn't have a "--", all filenames must exist */ - for (j = i; j < argc; j++) { - if (lstat(argv[j], &st) < 0) - die("'%s': %s", argv[j], strerror(errno)); - } + for (j = i; j < argc; j++) + verify_filename(revs->prefix, argv[j]); + revs->prune_data = get_pathspec(revs->prefix, argv + i); break; } diff --git a/setup.c b/setup.c index 36ede3d874..cce9bb8069 100644 --- a/setup.c +++ b/setup.c @@ -62,6 +62,29 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) return path; } +/* + * Verify a filename that we got as an argument for a pathspec + * entry. Note that a filename that begins with "-" never verifies + * as true, because even if such a filename were to exist, we want + * it to be preceded by the "--" marker (or we want the user to + * use a format like "./-filename") + */ +void verify_filename(const char *prefix, const char *arg) +{ + const char *name; + struct stat st; + + if (*arg == '-') + die("bad flag '%s' used after filename", arg); + name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg; + if (!lstat(name, &st)) + return; + if (errno == ENOENT) + die("ambiguous argument '%s': unknown revision or filename\n" + "Use '--' to separate filenames from revisions", arg); + die("'%s': %s", arg, strerror(errno)); +} + const char **get_pathspec(const char *prefix, const char **pathspec) { const char *entry = *pathspec; -- cgit v1.2.3 From 5981e09999e90b389a02843671529a0faaf72143 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 26 Apr 2006 16:55:25 -0700 Subject: commit-tree.c: check_valid() microoptimization. There is no point reading the whole object just to make sure it exists and it is of the expected type. We added sha1_object_info() for such need after this code was written, so use it. Signed-off-by: Junio C Hamano --- commit-tree.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/commit-tree.c b/commit-tree.c index 2d8651894a..2595850970 100644 --- a/commit-tree.c +++ b/commit-tree.c @@ -45,14 +45,13 @@ static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...) static void check_valid(unsigned char *sha1, const char *expect) { - void *buf; char type[20]; - unsigned long size; - buf = read_sha1_file(sha1, type, &size); - if (!buf || strcmp(type, expect)) - die("%s is not a valid '%s' object", sha1_to_hex(sha1), expect); - free(buf); + if (sha1_object_info(sha1, type, NULL)) + die("%s is not a valid object", sha1_to_hex(sha1)); + if (expect && strcmp(type, expect)) + die("%s is not a valid '%s' object", sha1_to_hex(sha1), + expect); } /* -- cgit v1.2.3 From 9af0b8dbe2fb252262412a11254e2bcc6ffb87bb Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 26 Apr 2006 18:25:15 -0700 Subject: t0000-basic: more commit-tree tests. Signed-off-by: Junio C Hamano --- t/t0000-basic.sh | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index c69706fb38..cf33989b56 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -226,4 +226,32 @@ test_expect_success \ 'no diff after checkout and git-update-index --refresh.' \ 'git-diff-files >current && cmp -s current /dev/null' +################################################################ +P=087704a96baf1c2d1c869a8b084481e121c88b5b +test_expect_success \ + 'git-commit-tree records the correct tree in a commit.' \ + 'commit0=$(echo NO | git-commit-tree $P) && + tree=$(git show --pretty=raw $commit0 | + sed -n -e "s/^tree //p" -e "/^author /q") && + test "z$tree" = "z$P"' + +test_expect_success \ + 'git-commit-tree records the correct parent in a commit.' \ + 'commit1=$(echo NO | git-commit-tree $P -p $commit0) && + parent=$(git show --pretty=raw $commit1 | + sed -n -e "s/^parent //p" -e "/^author /q") && + test "z$commit0" = "z$parent"' + +test_expect_success \ + 'git-commit-tree omits duplicated parent in a commit.' \ + 'commit2=$(echo NO | git-commit-tree $P -p $commit0 -p $commit0) && + parent=$(git show --pretty=raw $commit2 | + sed -n -e "s/^parent //p" -e "/^author /q" | + sort -u) && + test "z$commit0" = "z$parent" && + numparent=$(git show --pretty=raw $commit2 | + sed -n -e "s/^parent //p" -e "/^author /q" | + wc -l) && + test $numparent = 1' + test_done -- cgit v1.2.3 From 61678d87c2aad3bd668196993d8c32f298ddf5ff Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 27 Apr 2006 01:59:00 -0700 Subject: diff-index: fix compilation warnings. Signed-off-by: Junio C Hamano --- diff-index.c | 1 - 1 file changed, 1 deletion(-) diff --git a/diff-index.c b/diff-index.c index 86940123b3..8c9f60173b 100644 --- a/diff-index.c +++ b/diff-index.c @@ -11,7 +11,6 @@ COMMON_DIFF_OPTIONS_HELP; int main(int argc, const char **argv) { struct rev_info rev; - int match_missing = 0; int cached = 0; int i; -- cgit v1.2.3 From 83aa18eade572679319406d6dc12a81599a4d57e Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 27 Apr 2006 09:02:54 -0700 Subject: Fix "git help -a" terminal autosizing When I split out the builtin commands into their own files, I left the include of in git.c rather than moving it to the file that needed it (builtin-help.c). Nobody seems to have noticed, because everything still worked, but because the TIOCGWINSZ macro was now no longer defined when compiling the "term_columns()" function, it would no longer automatically notice the terminal size unless your system used the ancient "COLUMNS" environment variable approach. Trivially fixed by just moving the header include to the file that actually needs it. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- builtin-help.c | 1 + git.c | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-help.c b/builtin-help.c index 10a59cc403..7470faa566 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -3,6 +3,7 @@ * * Builtin help-related commands (help, usage, version) */ +#include #include "cache.h" #include "builtin.h" #include "exec_cmd.h" diff --git a/git.c b/git.c index aa2b814d93..01b7e28b8c 100644 --- a/git.c +++ b/git.c @@ -8,7 +8,6 @@ #include #include #include -#include #include "git-compat-util.h" #include "exec_cmd.h" -- cgit v1.2.3 From 55e1805dffeb5d2a8ccd717b2d07ca8887436a69 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 27 Apr 2006 15:42:01 -0700 Subject: verify-pack: check integrity in a saner order. Check internal integrity to report corrupt pack or idx, and then check cross-integrity between idx and pack. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- pack-check.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pack-check.c b/pack-check.c index 84ed90d369..e57587909e 100644 --- a/pack-check.c +++ b/pack-check.c @@ -29,12 +29,12 @@ static int verify_packfile(struct packed_git *p) pack_base = p->pack_base; SHA1_Update(&ctx, pack_base, pack_size - 20); SHA1_Final(sha1, &ctx); - if (memcmp(sha1, index_base + index_size - 40, 20)) - return error("Packfile %s SHA1 mismatch with idx", - p->pack_name); if (memcmp(sha1, pack_base + pack_size - 20, 20)) return error("Packfile %s SHA1 mismatch with itself", p->pack_name); + if (memcmp(sha1, index_base + index_size - 40, 20)) + return error("Packfile %s SHA1 mismatch with idx", + p->pack_name); /* Make sure everything reachable from idx is valid. Since we * have verified that nr_objects matches between idx and pack, -- cgit v1.2.3 From c1d1128bef97ae16a6740538abe5fb81c67ed239 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 28 Apr 2006 02:32:44 -0700 Subject: git-am --resolved: more usable error message. After doing the hard work of hand resolving the conflicts in the working tree, if the user forgets to run update-index to mark the paths that have been resolved, the command gave an unfriendly "fatal: git-write-tree: not able to write tree" error message. Catch the situation early and give more meaningful message and suggestion. Noticed and suggested by Len Brown. Signed-off-by: Junio C Hamano --- git-am.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/git-am.sh b/git-am.sh index eab4aa891e..872145b92d 100755 --- a/git-am.sh +++ b/git-am.sh @@ -376,6 +376,13 @@ do echo "No changes - did you forget update-index?" stop_here $this fi + unmerged=$(git-ls-files -u) + if test -n "$unmerged" + then + echo "You still have unmerged paths in your index" + echo "did you forget update-index?" + stop_here $this + fi apply_status=0 ;; esac -- cgit v1.2.3 From d0ad1653662a214baddc1d1ce669a28b91a3ae76 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 28 Apr 2006 10:42:28 +0200 Subject: annotate: fix warning about uninitialized scalar Use of uninitialized value in scalar chomp at ./git-annotate.perl line 212, <$kid> chunk 4. Signed-off-by: Matthias Kestenholz --- git-annotate.perl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git-annotate.perl b/git-annotate.perl index 9df72a1662..a7aab2566d 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -208,6 +208,9 @@ sub find_parent_renames { while (my $change = <$patch>) { chomp $change; my $filename = <$patch>; + if (!defined $filename) { + next; + } chomp $filename; if ($change =~ m/^[AMD]$/ ) { -- cgit v1.2.3 From fe77bb1a02d5b8dd3915cd27a83b660debaecda4 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 28 Apr 2006 10:41:19 +0200 Subject: annotate: display usage information if no filename was given Signed-off-by: Matthias Kestenholz --- git-annotate.perl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/git-annotate.perl b/git-annotate.perl index a7aab2566d..a6a7a482cd 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -10,9 +10,10 @@ use warnings; use strict; use Getopt::Long; use POSIX qw(strftime gmtime); +use File::Basename qw(basename dirname); sub usage() { - print STDERR 'Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ] + print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ] -l, --long Show long rev (Defaults off) -t, --time @@ -23,7 +24,7 @@ sub usage() { Use revs from revs-file instead of calling git-rev-list -h, --help This message. -'; +"; exit(1); } @@ -35,7 +36,7 @@ my $rc = GetOptions( "long|l" => \$longrev, "help|h" => \$help, "rename|r" => \$rename, "rev-file|S=s" => \$rev_file); -if (!$rc or $help) { +if (!$rc or $help or !@ARGV) { usage(); } -- cgit v1.2.3 From 2eaf273d518717c84d748051e05210656f7b7e88 Mon Sep 17 00:00:00 2001 From: Sean Estabrooks Date: Fri, 28 Apr 2006 09:15:04 -0400 Subject: Update the git-branch man page to include the "-r" option, and fix up asciidoc "callouts" Signed-off-by: Sean Estabrooks --- Documentation/git-branch.txt | 57 ++++++++++++++++++++++++++++++-------------- git-branch.sh | 2 +- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 71ecd858aa..72fb2f89b4 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -3,22 +3,27 @@ git-branch(1) NAME ---- -git-branch - Create a new branch, or remove an old one +git-branch - List, create, or delete branches. SYNOPSIS -------- [verse] -'git-branch' [[-f] []] -'git-branch' (-d | -D) +'git-branch' [-r] +'git-branch' [-f] [] +'git-branch' (-d | -D) ... DESCRIPTION ----------- -If no argument is provided, show available branches and mark current -branch with star. Otherwise, create a new branch of name . -If a starting point is also specified, that will be where the branch is -created, otherwise it will be created at the current HEAD. +With no arguments given (or just `-r`) a list of available branches +will be shown, the current branch will be highlighted with an asterisk. -With a `-d` or `-D` option, `` will be deleted. +In its second form, a new branch named will be created. +It will start out with a head equal to the one given as . +If no is given, the branch will be created with a head +equal to that of the currently checked out branch. + +With a `-d` or `-D` option, `` will be deleted. You may +specify more than one branch for deletion. OPTIONS @@ -30,40 +35,56 @@ OPTIONS Delete a branch irrespective of its index status. -f:: - Force a reset of to (or current head). + Force the creation of a new branch even if it means deleting + a branch that already exists with the same name. + +-r:: + List only the "remote" branches. :: The name of the branch to create or delete. :: - Where to create the branch; defaults to HEAD. This - option has no meaning with -d and -D. + The new branch will be created with a HEAD equal to this. It may + be given as a branch name, a commit-id, or a tag. If this option + is omitted, the current branch is assumed. + Examples -~~~~~~~~ +-------- Start development off of a known tag:: + ------------ $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6 $ cd my2.6 -$ git branch my2.6.14 v2.6.14 <1> +$ git branch my2.6.14 v2.6.14 <1> $ git checkout my2.6.14 - -<1> These two steps are the same as "checkout -b my2.6.14 v2.6.14". ------------ ++ +<1> This step and the next one could be combined into a single step with +"checkout -b my2.6.14 v2.6.14". Delete unneeded branch:: + ------------ $ git clone git://git.kernel.org/.../git.git my.git $ cd my.git -$ git branch -D todo <1> - +$ git branch -D todo <1> +------------ ++ <1> delete todo branch even if the "master" branch does not have all commits from todo branch. ------------- + + +Notes +----- + +If you are creating a branch that you want to immediately checkout, it's +easier to use the git checkout command with its `-b` option to create +a branch and check it out with a single command. + Author ------ diff --git a/git-branch.sh b/git-branch.sh index 663a3a370c..ebcc8989d8 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -1,6 +1,6 @@ #!/bin/sh -USAGE='[(-d | -D) ] | [[-f] []]' +USAGE='[(-d | -D) ] | [[-f] []] | -r' LONG_USAGE='If no arguments, show available branches and mark current branch with a star. If one argument, create a new branch based off of current HEAD. If two arguments, create a new branch based off of .' -- cgit v1.2.3 From 48aeecdcc14684111ddb8ac0ec3bfdc5245ee75e Mon Sep 17 00:00:00 2001 From: Sean Estabrooks Date: Fri, 28 Apr 2006 09:15:05 -0400 Subject: Fix up remaining man pages that use asciidoc "callouts". Unfortunately docbook does not allow a callout to be referenced from inside a callout list description. Rewrite one paragraph in git-reset man page to work around this limitation. Signed-off-by: Sean Estabrooks --- Documentation/everyday.txt | 45 ++++++++++++------------ Documentation/git-checkout.txt | 18 +++++----- Documentation/git-diff.txt | 38 ++++++++++---------- Documentation/git-init-db.txt | 8 ++--- Documentation/git-reset.txt | 72 ++++++++++++++++++-------------------- Documentation/git-update-index.txt | 31 ++++++++-------- 6 files changed, 104 insertions(+), 108 deletions(-) diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt index 3ab9b916c2..4b56370937 100644 --- a/Documentation/everyday.txt +++ b/Documentation/everyday.txt @@ -61,7 +61,8 @@ $ git prune $ git count-objects <2> $ git repack <3> $ git prune <4> - +------------ ++ <1> running without "--full" is usually cheap and assures the repository health reasonably well. <2> check how many loose objects there are and how much @@ -69,17 +70,16 @@ diskspace is wasted by not repacking. <3> without "-a" repacks incrementally. repacking every 4-5MB of loose objects accumulation may be a good rule of thumb. <4> after repack, prune removes the duplicate loose objects. ------------- Repack a small project into single pack.:: + ------------ $ git repack -a -d <1> $ git prune - +------------ ++ <1> pack all the objects reachable from the refs into one pack and remove unneeded other packs ------------- Individual Developer (Standalone)[[Individual Developer (Standalone)]] @@ -129,10 +129,10 @@ $ git-init-db $ git add . <1> $ git commit -m 'import of frotz source tree.' $ git tag v2.43 <2> - +------------ ++ <1> add everything under the current directory. <2> make a lightweight, unannotated tag. ------------- Create a topic branch and develop.:: + @@ -153,7 +153,8 @@ $ git checkout master <9> $ git pull . alsa-audio <10> $ git log --since='3 days ago' <11> $ git log v2.43.. curses/ <12> - +------------ ++ <1> create a new topic branch. <2> revert your botched changes in "curses/ux_audio_oss.c". <3> you need to tell git if you added a new file; removal and @@ -170,7 +171,6 @@ you originally wrote. combined and include --max-count=10 (show 10 commits), --until='2005-12-10'. <12> view only the changes that touch what's in curses/ directory, since v2.43 tag. ------------- Individual Developer (Participant)[[Individual Developer (Participant)]] @@ -208,7 +208,8 @@ $ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL <5> $ git reset --hard ORIG_HEAD <6> $ git prune <7> $ git fetch --tags <8> - +------------ ++ <1> repeat as needed. <2> extract patches from your branch for e-mail submission. <3> "pull" fetches from "origin" by default and merges into the @@ -221,7 +222,6 @@ area we are interested in. <7> garbage collect leftover objects from reverted pull. <8> from time to time, obtain official tags from the "origin" and store them under .git/refs/tags/. ------------- Push into another repository.:: @@ -239,7 +239,8 @@ satellite$ git push origin <4> mothership$ cd frotz mothership$ git checkout master mothership$ git pull . satellite <5> - +------------ ++ <1> mothership machine has a frotz repository under your home directory; clone from it to start a repository on the satellite machine. @@ -252,7 +253,6 @@ to local "origin" branch. mothership machine. You could use this as a back-up method. <5> on mothership machine, merge the work done on the satellite machine into the master branch. ------------- Branch off of a specific tag.:: + @@ -262,12 +262,12 @@ $ edit/compile/test; git commit -a $ git checkout master $ git format-patch -k -m --stdout v2.6.14..private2.6.14 | git am -3 -k <2> - +------------ ++ <1> create a private branch based on a well known (but somewhat behind) tag. <2> forward port all changes in private2.6.14 branch to master branch without a formal "merging". ------------- Integrator[[Integrator]] @@ -317,7 +317,8 @@ $ git tag -s -m 'GIT 0.99.9x' v0.99.9x <10> $ git fetch ko && git show-branch master maint 'tags/ko-*' <11> $ git push ko <12> $ git push ko v0.99.9x <13> - +------------ ++ <1> see what I was in the middle of doing, if any. <2> see what topic branches I have and think about how ready they are. @@ -346,7 +347,6 @@ In the output from "git show-branch", "master" should have everything "ko-master" has. <12> push out the bleeding edge. <13> push the tag out, too. ------------- Repository Administration[[Repository Administration]] @@ -367,7 +367,6 @@ example of managing a shared central repository. Examples ~~~~~~~~ - Run git-daemon to serve /pub/scm from inetd.:: + ------------ @@ -388,13 +387,13 @@ cindy:x:1002:1002::/home/cindy:/usr/bin/git-shell david:x:1003:1003::/home/david:/usr/bin/git-shell $ grep git /etc/shells <2> /usr/bin/git-shell - +------------ ++ <1> log-in shell is set to /usr/bin/git-shell, which does not allow anything but "git push" and "git pull". The users should get an ssh access to the machine. <2> in many distributions /etc/shells needs to list what is used as the login shell. ------------- CVS-style shared repository.:: + @@ -419,7 +418,8 @@ $ cat info/allowed-users <4> refs/heads/master alice\|cindy refs/heads/doc-update bob refs/tags/v[0-9]* david - +------------ ++ <1> place the developers into the same git group. <2> and make the shared repository writable by the group. <3> use update-hook example by Carl from Documentation/howto/ @@ -427,7 +427,6 @@ for branch policy control. <4> alice and cindy can push into master, only bob can push into doc-update. david is the release manager and is the only person who can create and push version tags. ------------- HTTP server to support dumb protocol transfer.:: + @@ -435,7 +434,7 @@ HTTP server to support dumb protocol transfer.:: dev$ git update-server-info <1> dev$ ftp user@isp.example.com <2> ftp> cp -r .git /home/user/myproject.git - +------------ ++ <1> make sure your info/refs and objects/info/packs are up-to-date <2> upload to public HTTP server hosted by your ISP. ------------- diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 985bb2f827..095128906a 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -66,19 +66,19 @@ the `Makefile` to two revisions back, deletes hello.c by mistake, and gets it back from the index. + ------------ -$ git checkout master <1> -$ git checkout master~2 Makefile <2> +$ git checkout master <1> +$ git checkout master~2 Makefile <2> $ rm -f hello.c -$ git checkout hello.c <3> - +$ git checkout hello.c <3> +------------ ++ <1> switch branch <2> take out a file out of other commit -<3> or "git checkout -- hello.c", as in the next example. ------------- +<3> restore hello.c from HEAD of current branch + -If you have an unfortunate branch that is named `hello.c`, the -last step above would be confused as an instruction to switch to -that branch. You should instead write: +If you have an unfortunate branch that is named `hello.c`, this +step would be confused as an instruction to switch to that branch. +You should instead write: + ------------ $ git checkout -- hello.c diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 890931c891..7267bcd7a0 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -46,40 +46,41 @@ EXAMPLES Various ways to check your working tree:: + ------------ -$ git diff <1> -$ git diff --cached <2> -$ git diff HEAD <3> - +$ git diff <1> +$ git diff --cached <2> +$ git diff HEAD <3> +------------ ++ <1> changes in the working tree since your last git-update-index. <2> changes between the index and your last commit; what you would be committing if you run "git commit" without "-a" option. <3> changes in the working tree since your last commit; what you would be committing if you run "git commit -a" ------------- Comparing with arbitrary commits:: + ------------ -$ git diff test <1> -$ git diff HEAD -- ./test <2> -$ git diff HEAD^ HEAD <3> - +$ git diff test <1> +$ git diff HEAD -- ./test <2> +$ git diff HEAD^ HEAD <3> +------------ ++ <1> instead of using the tip of the current branch, compare with the tip of "test" branch. <2> instead of comparing with the tip of "test" branch, compare with the tip of the current branch, but limit the comparison to the file "test". <3> compare the version before the last commit and the last commit. ------------- Limiting the diff output:: + ------------ -$ git diff --diff-filter=MRC <1> -$ git diff --name-status -r <2> -$ git diff arch/i386 include/asm-i386 <3> - +$ git diff --diff-filter=MRC <1> +$ git diff --name-status -r <2> +$ git diff arch/i386 include/asm-i386 <3> +------------ ++ <1> show only modification, rename and copy, but not addition nor deletion. <2> show only names and the nature of change, but not actual @@ -88,18 +89,17 @@ which in turn also disables recursive behaviour, so without -r you would only see the directory name if there is a change in a file in a subdirectory. <3> limit diff output to named subtrees. ------------- Munging the diff output:: + ------------ -$ git diff --find-copies-harder -B -C <1> -$ git diff -R <2> - +$ git diff --find-copies-harder -B -C <1> +$ git diff -R <2> +------------ ++ <1> spend extra cycles to find renames, copies and complete rewrites (very expensive). <2> output diff in reverse. ------------- Author diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt index aeb1115af9..8a150d861f 100644 --- a/Documentation/git-init-db.txt +++ b/Documentation/git-init-db.txt @@ -60,12 +60,12 @@ Start a new git repository for an existing code base:: + ---------------- $ cd /path/to/my/codebase -$ git-init-db <1> -$ git-add . <2> - +$ git-init-db <1> +$ git-add . <2> +---------------- ++ <1> prepare /path/to/my/codebase/.git directory <2> add all existing file to the index ----------------- Author diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index b7b9798bf9..ebcfe5edb7 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -49,10 +49,11 @@ Undo a commit and redo:: + ------------ $ git commit ... -$ git reset --soft HEAD^ <1> -$ edit <2> -$ git commit -a -c ORIG_HEAD <3> - +$ git reset --soft HEAD^ <1> +$ edit <2> +$ git commit -a -c ORIG_HEAD <3> +------------ ++ <1> This is most often done when you remembered what you just committed is incomplete, or you misspelled your commit message, or both. Leaves working tree as it was before "reset". @@ -60,43 +61,43 @@ message, or both. Leaves working tree as it was before "reset". <3> "reset" copies the old head to .git/ORIG_HEAD; redo the commit by starting with its log message. If you do not need to edit the message further, you can give -C option instead. ------------- Undo commits permanently:: + ------------ $ git commit ... -$ git reset --hard HEAD~3 <1> - +$ git reset --hard HEAD~3 <1> +------------ ++ <1> The last three commits (HEAD, HEAD^, and HEAD~2) were bad and you do not want to ever see them again. Do *not* do this if you have already given these commits to somebody else. ------------- Undo a commit, making it a topic branch:: + ------------ -$ git branch topic/wip <1> -$ git reset --hard HEAD~3 <2> -$ git checkout topic/wip <3> - +$ git branch topic/wip <1> +$ git reset --hard HEAD~3 <2> +$ git checkout topic/wip <3> +------------ ++ <1> You have made some commits, but realize they were premature to be in the "master" branch. You want to continue polishing them in a topic branch, so create "topic/wip" branch off of the current HEAD. <2> Rewind the master branch to get rid of those three commits. <3> Switch to "topic/wip" branch and keep working. ------------- Undo update-index:: + ------------ -$ edit <1> +$ edit <1> $ git-update-index frotz.c filfre.c -$ mailx <2> -$ git reset <3> -$ git pull git://info.example.com/ nitfol <4> - +$ mailx <2> +$ git reset <3> +$ git pull git://info.example.com/ nitfol <4> +------------ ++ <1> you are happily working on something, and find the changes in these files are in good order. You do not want to see them when you run "git diff", because you plan to work on other files @@ -109,12 +110,11 @@ index changes for these two files. Your changes in working tree remain there. <4> then you can pull and merge, leaving frotz.c and filfre.c changes still in the working tree. ------------- Undo a merge or pull:: + ------------ -$ git pull <1> +$ git pull <1> Trying really trivial in-index merge... fatal: Merge requires file-level merging Nope. @@ -122,20 +122,19 @@ Nope. Auto-merging nitfol CONFLICT (content): Merge conflict in nitfol Automatic merge failed/prevented; fix up by hand -$ git reset --hard <2> - +$ git reset --hard <2> +$ git pull . topic/branch <3> +Updating from 41223... to 13134... +Fast forward +$ git reset --hard ORIG_HEAD <4> +------------ ++ <1> try to update from the upstream resulted in a lot of conflicts; you were not ready to spend a lot of time merging right now, so you decide to do that later. <2> "pull" has not made merge commit, so "git reset --hard" which is a synonym for "git reset --hard HEAD" clears the mess from the index file and the working tree. - -$ git pull . topic/branch <3> -Updating from 41223... to 13134... -Fast forward -$ git reset --hard ORIG_HEAD <4> - <3> merge a topic branch into the current branch, which resulted in a fast forward. <4> but you decided that the topic branch is not ready for public @@ -143,7 +142,6 @@ consumption yet. "pull" or "merge" always leaves the original tip of the current branch in ORIG_HEAD, so resetting hard to it brings your index file and the working tree back to that state, and resets the tip of the branch to that commit. ------------- Interrupted workflow:: + @@ -155,21 +153,21 @@ need to get to the other branch for a quick bugfix. ------------ $ git checkout feature ;# you were working in "feature" branch and $ work work work ;# got interrupted -$ git commit -a -m 'snapshot WIP' <1> +$ git commit -a -m 'snapshot WIP' <1> $ git checkout master $ fix fix fix $ git commit ;# commit with real log $ git checkout feature -$ git reset --soft HEAD^ ;# go back to WIP state <2> -$ git reset <3> - +$ git reset --soft HEAD^ ;# go back to WIP state <2> +$ git reset <3> +------------ ++ <1> This commit will get blown away so a throw-away log message is OK. <2> This removes the 'WIP' commit from the commit history, and sets your working tree to the state just before you made that snapshot. -<3> After <2>, the index file still has all the WIP changes you - committed in <1>. This sets it to the last commit you were - basing the WIP changes on. ------------- +<3> At this point the index file still has all the WIP changes you + committed as 'snapshot WIP'. This updates the index to show your + WIP files as uncommitted. Author ------ diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 0a1b0ad56d..d4137fc87e 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -247,34 +247,33 @@ To update and refresh only the files already checked out: $ git-checkout-index -n -f -a && git-update-index --ignore-missing --refresh ---------------- -On an inefficient filesystem with `core.ignorestat` set: - +On an inefficient filesystem with `core.ignorestat` set:: ++ ------------ -$ git update-index --really-refresh <1> -$ git update-index --no-assume-unchanged foo.c <2> -$ git diff --name-only <3> +$ git update-index --really-refresh <1> +$ git update-index --no-assume-unchanged foo.c <2> +$ git diff --name-only <3> $ edit foo.c -$ git diff --name-only <4> +$ git diff --name-only <4> M foo.c -$ git update-index foo.c <5> -$ git diff --name-only <6> +$ git update-index foo.c <5> +$ git diff --name-only <6> $ edit foo.c -$ git diff --name-only <7> -$ git update-index --no-assume-unchanged foo.c <8> -$ git diff --name-only <9> +$ git diff --name-only <7> +$ git update-index --no-assume-unchanged foo.c <8> +$ git diff --name-only <9> M foo.c - -<1> forces lstat(2) to set "assume unchanged" bits for paths - that match index. +------------ ++ +<1> forces lstat(2) to set "assume unchanged" bits for paths that match index. <2> mark the path to be edited. <3> this does lstat(2) and finds index matches the path. -<4> this does lstat(2) and finds index does not match the path. +<4> this does lstat(2) and finds index does *not* match the path. <5> registering the new version to index sets "assume unchanged" bit. <6> and it is assumed unchanged. <7> even after you edit it. <8> you can tell about the change after the fact. <9> now it checks with lstat(2) and finds it has been changed. ------------- Configuration -- cgit v1.2.3 From 776e994af5b85d77424581bda37fb615897063cc Mon Sep 17 00:00:00 2001 From: Sean Estabrooks Date: Fri, 28 Apr 2006 09:15:06 -0400 Subject: Properly render asciidoc "callouts" in git man pages. Adds an xsl fragment to render docbook callouts when converting to man page format. Update the Makefile to have "xmlto" use it when generating man pages. Signed-off-by: Sean Estabrooks --- Documentation/Makefile | 2 +- Documentation/callouts.xsl | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 Documentation/callouts.xsl diff --git a/Documentation/Makefile b/Documentation/Makefile index f4cbf7e159..c1af22ce04 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -79,7 +79,7 @@ clean: asciidoc -b xhtml11 -d manpage -f asciidoc.conf $< %.1 %.7 : %.xml - xmlto man $< + xmlto -m callouts.xsl man $< %.xml : %.txt asciidoc -b docbook -d manpage -f asciidoc.conf $< diff --git a/Documentation/callouts.xsl b/Documentation/callouts.xsl new file mode 100644 index 0000000000..ad03755d8f --- /dev/null +++ b/Documentation/callouts.xsl @@ -0,0 +1,16 @@ + + + + + + + .sp + + + + + + + .br + + -- cgit v1.2.3 From aa6bf0eb6489d652c5877d65160ed33c857afa74 Mon Sep 17 00:00:00 2001 From: Sean Estabrooks Date: Fri, 28 Apr 2006 09:15:03 -0400 Subject: Fix trivial typo in git-log man page. Signed-off-by: Sean Estabrooks --- Documentation/git-log.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 76cb894caa..af378ffcf9 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -14,13 +14,12 @@ DESCRIPTION ----------- Shows the commit logs. -The command takes options applicable to the gitlink::git-rev-list[1] +The command takes options applicable to the gitlink:git-rev-list[1] command to control what is shown and how, and options applicable to -the gitlink::git-diff-tree[1] commands to control how the change +the gitlink:git-diff-tree[1] commands to control how the change each commit introduces are shown. -This manual page describes only the most frequently used -options. +This manual page describes only the most frequently used options. OPTIONS -- cgit v1.2.3 From 262a6ef76a1dde97ab50d79fa5cd6d3f9f125765 Mon Sep 17 00:00:00 2001 From: Huw Davies Date: Sat, 29 Apr 2006 15:50:28 +0000 Subject: git-format-patch: Use rfc2822 compliant date. Signed-off-by: Huw Davies Signed-off-by: Junio C Hamano --- git-format-patch.sh | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/git-format-patch.sh b/git-format-patch.sh index c7133bc126..c077f44ca1 100755 --- a/git-format-patch.sh +++ b/git-format-patch.sh @@ -205,11 +205,10 @@ sub show_date { } my $t = $time + $minutes * 60; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($t); - return sprintf("%s %s %d %02d:%02d:%02d %d %+05d", - $weekday_names[$wday], - $month_names[$mon], - $mday, $hour, $min, $sec, - $year+1900, $tz); + return sprintf("%s, %d %s %d %02d:%02d:%02d %+05d", + $weekday_names[$wday], $mday, + $month_names[$mon], $year+1900, + $hour, $min, $sec, $tz); } print "From nobody Mon Sep 17 00:00:00 2001\n"; -- cgit v1.2.3