X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=builtin-blame.c;h=60ec5354f11c61c49829d41e8c07d22573f16bc7;hb=885b98107547fe3f6d17ca0af0578e040f7600d0;hp=3033e9bdad55bdccc560a10996620654fe2c9b7e;hpb=1732a1fd942f00b9a77a47acc09df0cd62c770bd;p=git.git diff --git a/builtin-blame.c b/builtin-blame.c index 3033e9bda..60ec5354f 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -15,9 +15,10 @@ #include "revision.h" #include "quote.h" #include "xdiff-interface.h" +#include "cache-tree.h" static char blame_usage[] = -"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [commit] [--] file\n" +"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [--contents ] [--incremental] [commit] [--] file\n" " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" " -b Show blank SHA-1 for boundary commits (Default: off)\n" " -l, --long Show long commit SHA1 (Default: off)\n" @@ -29,6 +30,7 @@ static char blame_usage[] = " -L n,m Process only line range n,m, counting from 1\n" " -M, -C Find line movements within and across files\n" " --incremental Show blame entries as we find them, incrementally\n" +" --contents file Use 's contents as the final image\n" " -S revs-file Use revisions from revs-file instead of calling git-rev-list\n"; static int longest_file; @@ -39,6 +41,7 @@ static int max_score_digits; static int show_root; static int blank_boundary; static int incremental; +static int cmd_is_annotate; #ifndef DEBUG #define DEBUG 0 @@ -84,9 +87,9 @@ struct origin { static char *fill_origin_blob(struct origin *o, mmfile_t *file) { if (!o->file.ptr) { - char type[10]; + enum object_type type; num_read_blob++; - file->ptr = read_sha1_file(o->blob_sha1, type, + file->ptr = read_sha1_file(o->blob_sha1, &type, (unsigned long *)(&(file->size))); o->file = *file; } @@ -177,16 +180,15 @@ struct scoreboard { int *lineno; }; -static int cmp_suspect(struct origin *a, struct origin *b) +static inline int same_suspect(struct origin *a, struct origin *b) { - int cmp = hashcmp(a->commit->object.sha1, b->commit->object.sha1); - if (cmp) - return cmp; - return strcmp(a->path, b->path); + if (a == b) + return 1; + if (a->commit != b->commit) + return 0; + return !strcmp(a->path, b->path); } -#define cmp_suspect(a, b) ( ((a)==(b)) ? 0 : cmp_suspect(a,b) ) - static void sanity_check_refcnt(struct scoreboard *); /* @@ -199,7 +201,7 @@ static void coalesce(struct scoreboard *sb) struct blame_entry *ent, *next; for (ent = sb->ent; ent && (next = ent->next); ent = next) { - if (!cmp_suspect(ent->suspect, next->suspect) && + if (same_suspect(ent->suspect, next->suspect) && ent->guilty == next->guilty && ent->s_lno + ent->num_lines == next->s_lno) { ent->num_lines += next->num_lines; @@ -260,7 +262,6 @@ static struct origin *get_origin(struct scoreboard *sb, static int fill_blob_sha1(struct origin *origin) { unsigned mode; - char type[10]; if (!is_null_sha1(origin->blob_sha1)) return 0; @@ -268,8 +269,7 @@ static int fill_blob_sha1(struct origin *origin) origin->path, origin->blob_sha1, &mode)) goto error_out; - if (sha1_object_info(origin->blob_sha1, type, NULL) || - strcmp(type, blob_type)) + if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB) goto error_out; return 0; error_out: @@ -333,9 +333,13 @@ static struct origin *find_origin(struct scoreboard *sb, diff_tree_setup_paths(paths, &diff_opts); if (diff_setup_done(&diff_opts) < 0) die("diff-setup"); - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, - "", &diff_opts); + + if (is_null_sha1(origin->commit->object.sha1)) + do_diff_cache(parent->tree->object.sha1, &diff_opts); + else + diff_tree_sha1(parent->tree->object.sha1, + origin->commit->tree->object.sha1, + "", &diff_opts); diffcore_std(&diff_opts); /* It is either one entry that says "modified", or "created", @@ -402,9 +406,13 @@ static struct origin *find_rename(struct scoreboard *sb, diff_tree_setup_paths(paths, &diff_opts); if (diff_setup_done(&diff_opts) < 0) die("diff-setup"); - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, - "", &diff_opts); + + if (is_null_sha1(origin->commit->object.sha1)) + do_diff_cache(parent->tree->object.sha1, &diff_opts); + else + diff_tree_sha1(parent->tree->object.sha1, + origin->commit->tree->object.sha1, + "", &diff_opts); diffcore_std(&diff_opts); for (i = 0; i < diff_queued_diff.nr; i++) { @@ -550,7 +558,7 @@ static void free_patch(struct patch *p) } /* - * Link in a new blame entry to the scorebord. Entries that cover the + * Link in a new blame entry to the scoreboard. Entries that cover the * same line range have been removed from the scoreboard previously. */ static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e) @@ -766,7 +774,7 @@ static int find_last_in_target(struct scoreboard *sb, struct origin *target) int last_in_target = -1; for (e = sb->ent; e; e = e->next) { - if (e->guilty || cmp_suspect(e->suspect, target)) + if (e->guilty || !same_suspect(e->suspect, target)) continue; if (last_in_target < e->s_lno + e->num_lines) last_in_target = e->s_lno + e->num_lines; @@ -786,7 +794,7 @@ static void blame_chunk(struct scoreboard *sb, struct blame_entry *e; for (e = sb->ent; e; e = e->next) { - if (e->guilty || cmp_suspect(e->suspect, target)) + if (e->guilty || !same_suspect(e->suspect, target)) continue; if (same <= e->s_lno) continue; @@ -961,7 +969,7 @@ static int find_move_in_parent(struct scoreboard *sb, while (made_progress) { made_progress = 0; for (e = sb->ent; e; e = e->next) { - if (e->guilty || cmp_suspect(e->suspect, target)) + if (e->guilty || !same_suspect(e->suspect, target)) continue; find_copy_in_blob(sb, e, parent, split, &file_p); if (split[1].suspect && @@ -993,12 +1001,12 @@ static struct blame_list *setup_blame_list(struct scoreboard *sb, struct blame_list *blame_list = NULL; for (e = sb->ent, num_ents = 0; e; e = e->next) - if (!e->guilty && !cmp_suspect(e->suspect, target)) + if (!e->guilty && same_suspect(e->suspect, target)) num_ents++; if (num_ents) { blame_list = xcalloc(num_ents, sizeof(struct blame_list)); for (e = sb->ent, i = 0; e; e = e->next) - if (!e->guilty && !cmp_suspect(e->suspect, target)) + if (!e->guilty && same_suspect(e->suspect, target)) blame_list[i++].ent = e; } *num_ents_p = num_ents; @@ -1047,9 +1055,12 @@ static int find_copy_in_parent(struct scoreboard *sb, (!porigin || strcmp(target->path, porigin->path))) diff_opts.find_copies_harder = 1; - diff_tree_sha1(parent->tree->object.sha1, - target->commit->tree->object.sha1, - "", &diff_opts); + if (is_null_sha1(target->commit->object.sha1)) + do_diff_cache(parent->tree->object.sha1, &diff_opts); + else + diff_tree_sha1(parent->tree->object.sha1, + target->commit->tree->object.sha1, + "", &diff_opts); if (!diff_opts.find_copies_harder) diffcore_std(&diff_opts); @@ -1125,7 +1136,7 @@ static void pass_whole_blame(struct scoreboard *sb, origin->file.ptr = NULL; } for (e = sb->ent; e; e = e->next) { - if (cmp_suspect(e->suspect, origin)) + if (!same_suspect(e->suspect, origin)) continue; origin_incref(porigin); origin_decref(e->suspect); @@ -1232,26 +1243,26 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) */ struct commit_info { - char *author; - char *author_mail; + const char *author; + const char *author_mail; unsigned long author_time; - char *author_tz; + const char *author_tz; /* filled only when asked for details */ - char *committer; - char *committer_mail; + const char *committer; + const char *committer_mail; unsigned long committer_time; - char *committer_tz; + const char *committer_tz; - char *summary; + const char *summary; }; /* * Parse author/committer line in the commit object buffer */ static void get_ac_line(const char *inbuf, const char *what, - int bufsz, char *person, char **mail, - unsigned long *time, char **tz) + int bufsz, char *person, const char **mail, + unsigned long *time, const char **tz) { int len; char *tmp, *endp; @@ -1268,7 +1279,7 @@ static void get_ac_line(const char *inbuf, const char *what, if (bufsz <= len) { error_out: /* Ugh */ - person = *mail = *tz = "(unknown)"; + *mail = *tz = "(unknown)"; *time = 0; return; } @@ -1308,10 +1319,10 @@ static void get_commit_info(struct commit *commit, * we now need to populate them for output. */ if (!commit->buffer) { - char type[20]; + enum object_type type; unsigned long size; commit->buffer = - read_sha1_file(commit->object.sha1, type, &size); + read_sha1_file(commit->object.sha1, &type, &size); } ret->author = author_buf; get_ac_line(commit->buffer, "\nauthor ", @@ -1336,9 +1347,9 @@ static void get_commit_info(struct commit *commit, tmp += 2; endp = strchr(tmp, '\n'); if (!endp) - goto error_out; + endp = tmp + strlen(tmp); len = endp - tmp; - if (len >= sizeof(summary_buf)) + if (len >= sizeof(summary_buf) || len == 0) goto error_out; memcpy(summary_buf, tmp, len); summary_buf[len] = 0; @@ -1392,7 +1403,7 @@ static void found_guilty_entry(struct blame_entry *ent) /* * The main loop -- while the scoreboard has lines whose true origin - * is still unknown, pick one brame_entry, and allow its current + * is still unknown, pick one blame_entry, and allow its current * suspect to pass blames to its parents. */ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) @@ -1431,7 +1442,7 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) /* Take responsibility for the remaining entries */ for (ent = sb->ent; ent; ent = ent->next) - if (!cmp_suspect(ent->suspect, suspect)) + if (same_suspect(ent->suspect, suspect)) found_guilty_entry(ent); origin_decref(suspect); @@ -1541,12 +1552,12 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8; if (suspect->commit->object.flags & UNINTERESTING) { - if (!blank_boundary) { + if (blank_boundary) + memset(hex, ' ', length); + else if (!cmd_is_annotate) { length--; putchar('^'); } - else - memset(hex, ' ', length); } printf("%.*s", length, hex); @@ -1910,6 +1921,137 @@ static int git_blame_config(const char *var, const char *value) return git_default_config(var, value); } +static struct commit *fake_working_tree_commit(const char *path, const char *contents_from) +{ + struct commit *commit; + struct origin *origin; + unsigned char head_sha1[20]; + char *buf; + const char *ident; + int fd; + time_t now; + unsigned long fin_size; + int size, len; + struct cache_entry *ce; + unsigned mode; + + if (get_sha1("HEAD", head_sha1)) + die("No such ref: HEAD"); + + time(&now); + commit = xcalloc(1, sizeof(*commit)); + commit->parents = xcalloc(1, sizeof(*commit->parents)); + commit->parents->item = lookup_commit_reference(head_sha1); + commit->object.parsed = 1; + commit->date = now; + commit->object.type = OBJ_COMMIT; + + origin = make_origin(commit, path); + + if (!contents_from || strcmp("-", contents_from)) { + struct stat st; + const char *read_from; + + if (contents_from) { + if (stat(contents_from, &st) < 0) + die("Cannot stat %s", contents_from); + read_from = contents_from; + } + else { + if (lstat(path, &st) < 0) + die("Cannot lstat %s", path); + read_from = path; + } + fin_size = xsize_t(st.st_size); + buf = xmalloc(fin_size+1); + mode = canon_mode(st.st_mode); + switch (st.st_mode & S_IFMT) { + case S_IFREG: + fd = open(read_from, O_RDONLY); + if (fd < 0) + die("cannot open %s", read_from); + if (read_in_full(fd, buf, fin_size) != fin_size) + die("cannot read %s", read_from); + break; + case S_IFLNK: + if (readlink(read_from, buf, fin_size+1) != fin_size) + die("cannot readlink %s", read_from); + break; + default: + die("unsupported file type %s", read_from); + } + } + else { + /* Reading from stdin */ + contents_from = "standard input"; + buf = NULL; + fin_size = 0; + mode = 0; + while (1) { + ssize_t cnt = 8192; + buf = xrealloc(buf, fin_size + cnt); + cnt = xread(0, buf + fin_size, cnt); + if (cnt < 0) + die("read error %s from stdin", + strerror(errno)); + if (!cnt) + break; + fin_size += cnt; + } + buf = xrealloc(buf, fin_size + 1); + } + buf[fin_size] = 0; + origin->file.ptr = buf; + origin->file.size = fin_size; + pretend_sha1_file(buf, fin_size, OBJ_BLOB, origin->blob_sha1); + commit->util = origin; + + /* + * Read the current index, replace the path entry with + * origin->blob_sha1 without mucking with its mode or type + * bits; we are not going to write this index out -- we just + * want to run "diff-index --cached". + */ + discard_cache(); + read_cache(); + + len = strlen(path); + if (!mode) { + int pos = cache_name_pos(path, len); + if (0 <= pos) + mode = ntohl(active_cache[pos]->ce_mode); + else + /* Let's not bother reading from HEAD tree */ + mode = S_IFREG | 0644; + } + size = cache_entry_size(len); + ce = xcalloc(1, size); + hashcpy(ce->sha1, origin->blob_sha1); + memcpy(ce->name, path, len); + ce->ce_flags = create_ce_flags(len, 0); + ce->ce_mode = create_ce_mode(mode); + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + + /* + * We are not going to write this out, so this does not matter + * right now, but someday we might optimize diff-index --cached + * with cache-tree information. + */ + cache_tree_invalidate_path(active_cache_tree, path); + + commit->buffer = xmalloc(400); + ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0); + sprintf(commit->buffer, + "tree 0000000000000000000000000000000000000000\n" + "parent %s\n" + "author %s\n" + "committer %s\n\n" + "Version of %s from %s\n", + sha1_to_hex(head_sha1), + ident, ident, path, contents_from ? contents_from : path); + return commit; +} + int cmd_blame(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1920,10 +2062,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix) int i, seen_dashdash, unk, opt; long bottom, top, lno; int output_option = 0; + int show_stats = 0; const char *revs_file = NULL; const char *final_commit_name = NULL; - char type[10]; + enum object_type type; const char *bottomtop = NULL; + const char *contents_from = NULL; + + cmd_is_annotate = !strcmp(argv[0], "annotate"); git_config(git_blame_config); save_commit_buffer = 0; @@ -1938,6 +2084,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) blank_boundary = 1; else if (!strcmp("--root", arg)) show_root = 1; + else if (!strcmp(arg, "--show-stats")) + show_stats = 1; else if (!strcmp("-c", arg)) output_option |= OUTPUT_ANNOTATE_COMPAT; else if (!strcmp("-t", arg)) @@ -1946,17 +2094,17 @@ int cmd_blame(int argc, const char **argv, const char *prefix) output_option |= OUTPUT_LONG_OBJECT_NAME; else if (!strcmp("-S", arg) && ++i < argc) revs_file = argv[i]; - else if (!strncmp("-M", arg, 2)) { + else if (!prefixcmp(arg, "-M")) { opt |= PICKAXE_BLAME_MOVE; blame_move_score = parse_score(arg+2); } - else if (!strncmp("-C", arg, 2)) { + else if (!prefixcmp(arg, "-C")) { if (opt & PICKAXE_BLAME_COPY) opt |= PICKAXE_BLAME_COPY_HARDER; opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE; blame_copy_score = parse_score(arg+2); } - else if (!strncmp("-L", arg, 2)) { + else if (!prefixcmp(arg, "-L")) { if (!arg[2]) { if (++i >= argc) usage(blame_usage); @@ -1968,6 +2116,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix) die("More than one '-L n,m' option given"); bottomtop = arg; } + else if (!strcmp("--contents", arg)) { + if (++i >= argc) + usage(blame_usage); + contents_from = argv[i]; + } else if (!strcmp("--incremental", arg)) incremental = 1; else if (!strcmp("--score-debug", arg)) @@ -2001,7 +2154,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) /* * We have collected options unknown to us in argv[1..unk] * which are to be passed to revision machinery if we are - * going to do the "bottom" procesing. + * going to do the "bottom" processing. * * The remaining are: * @@ -2047,6 +2200,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) if (!strcmp(argv[j], "--")) seen_dashdash = j; if (seen_dashdash) { + /* (2) */ if (seen_dashdash + 1 != argc - 1) usage(blame_usage); path = add_prefix(prefix, argv[seen_dashdash + 1]); @@ -2055,6 +2209,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) } else { /* (3) */ + if (argc <= i) + usage(blame_usage); path = add_prefix(prefix, argv[i]); if (i + 1 == argc - 1) { final_commit_name = argv[i + 1]; @@ -2087,7 +2243,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) argv[unk] = NULL; init_revisions(&revs, NULL); - setup_revisions(unk, argv, &revs, "HEAD"); + setup_revisions(unk, argv, &revs, NULL); memset(&sb, 0, sizeof(sb)); /* @@ -2114,16 +2270,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix) if (!sb.final) { /* * "--not A B -- path" without anything positive; - * default to HEAD. + * do not default to HEAD, but use the working tree + * or "--contents". */ - unsigned char head_sha1[20]; - - final_commit_name = "HEAD"; - if (get_sha1(final_commit_name, head_sha1)) - die("No such ref: HEAD"); - sb.final = lookup_commit_reference(head_sha1); - add_pending_object(&revs, &(sb.final->object), "HEAD"); + sb.final = fake_working_tree_commit(path, contents_from); + add_pending_object(&revs, &(sb.final->object), ":"); } + else if (contents_from) + die("Cannot use --contents with final commit object name"); /* * If we have bottom, this will mark the ancestors of the @@ -2132,11 +2286,22 @@ int cmd_blame(int argc, const char **argv, const char *prefix) */ prepare_revision_walk(&revs); - o = get_origin(&sb, sb.final, path); - if (fill_blob_sha1(o)) - die("no such path %s in %s", path, final_commit_name); + if (is_null_sha1(sb.final->object.sha1)) { + char *buf; + o = sb.final->util; + buf = xmalloc(o->file.size + 1); + memcpy(buf, o->file.ptr, o->file.size + 1); + sb.final_buf = buf; + sb.final_buf_size = o->file.size; + } + else { + o = get_origin(&sb, sb.final, path); + if (fill_blob_sha1(o)) + die("no such path %s in %s", path, final_commit_name); - sb.final_buf = read_sha1_file(o->blob_sha1, type, &sb.final_buf_size); + sb.final_buf = read_sha1_file(o->blob_sha1, &type, + &sb.final_buf_size); + } num_read_blob++; lno = prepare_lines(&sb); @@ -2186,7 +2351,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) ent = e; } - if (DEBUG) { + if (show_stats) { printf("num read blob: %d\n", num_read_blob); printf("num get patch: %d\n", num_get_patch); printf("num commits: %d\n", num_commits);