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:
parent
28389d45fb
commit
1cfe77333f
209
builtin-blame.c
209
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 <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);
|
||||
|
||||
|
44
diff-lib.c
44
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);
|
||||
}
|
||||
|
1
diff.h
1
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 */
|
||||
|
Loading…
Reference in New Issue
Block a user