git-blame: no rev means start from the working tree file.

Warning: this changes the semantics.

This makes "git blame" without any positive rev to start digging
from the working tree copy, which is made into a fake commit
whose sole parent is the HEAD.

It also adds --contents <file> option to pretend as if the
working tree copy has the contents of the named file.  You can
use '-' to make the command read from the standard input.

If you want the command to start annotating from the HEAD
commit, you need to explicitly give HEAD parameter.

Signed-off-by: Junio C Hamano <junkio@cox.net>
This commit is contained in:
Junio C Hamano 2007-01-30 01:11:08 -08:00
parent 28389d45fb
commit 1cfe77333f
3 changed files with 228 additions and 26 deletions

View File

@ -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 <revs-file>] [-M] [-C] [-C] [commit] [--] file\n"
"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [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 <file>'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);

View File

@ -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);
}

1
diff.h
View File

@ -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 */