diff --git a/builtin-blame.c b/builtin-blame.c index 1c21204a2ccee9de06d71fa7a87d9869451b5340..897323a4b23201150b675ed34bb1cba9479d988d 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 ] [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; @@ -333,9 +335,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 +408,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++) { @@ -1047,9 +1057,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); @@ -1336,9 +1349,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; @@ -1910,6 +1923,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 = 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; + write_sha1_file(buf, fin_size, blob_type, 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; @@ -1924,6 +2068,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) const char *final_commit_name = NULL; char type[10]; const char *bottomtop = NULL; + const char *contents_from = NULL; git_config(git_blame_config); save_commit_buffer = 0; @@ -1968,6 +2113,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)) @@ -2087,7 +2237,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 +2264,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 +2280,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); diff --git a/diff-lib.c b/diff-lib.c index 2c9be60ed9b47c2e563f69e6c8b60195fd51a917..91cd87742f9efd2c0fcf878c179bc6ed989cb50a 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -7,6 +7,7 @@ #include "diff.h" #include "diffcore.h" #include "revision.h" +#include "cache-tree.h" /* * diff-files @@ -271,7 +272,7 @@ static int diff_cache(struct rev_info *revs, break; } /* Show difference between old and new */ - show_modified(revs,ac[1], ce, 1, + show_modified(revs, ac[1], ce, 1, cached, match_missing); break; case 1: @@ -372,3 +373,44 @@ int run_diff_index(struct rev_info *revs, int cached) diff_flush(&revs->diffopt); return ret; } + +int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) +{ + struct tree *tree; + struct rev_info revs; + int i; + struct cache_entry **dst; + struct cache_entry *last = NULL; + + /* + * This is used by git-blame to run diff-cache internally; + * it potentially needs to repeatedly run this, so we will + * start by removing the higher order entries the last round + * left behind. + */ + dst = active_cache; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce)) { + if (last && !strcmp(ce->name, last->name)) + continue; + cache_tree_invalidate_path(active_cache_tree, + ce->name); + last = ce; + ce->ce_mode = 0; + ce->ce_flags &= ~htons(CE_STAGEMASK); + } + *dst++ = ce; + } + active_nr = dst - active_cache; + + init_revisions(&revs, NULL); + revs.prune_data = opt->paths; + tree = parse_tree_indirect(tree_sha1); + if (!tree) + die("bad tree object %s", sha1_to_hex(tree_sha1)); + if (read_tree(tree, 1, opt->paths)) + return error("unable to read tree %s", sha1_to_hex(tree_sha1)); + return diff_cache(&revs, active_cache, active_nr, revs.prune_data, + 1, 0); +} diff --git a/diff.h b/diff.h index 7a347cf77d448817014ceeaed2d3cd99b5894ac6..eece65ddccdb7bd8bf51af80af92e29b4eb76f37 100644 --- a/diff.h +++ b/diff.h @@ -222,6 +222,7 @@ extern int run_diff_files(struct rev_info *revs, int silent_on_removed); extern int run_diff_index(struct rev_info *revs, int cached); +extern int do_diff_cache(const unsigned char *, struct diff_options *); extern int diff_flush_patch_id(struct diff_options *, unsigned char *); #endif /* DIFF_H */