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>maint
parent
28389d45fb
commit
1cfe77333f
209
builtin-blame.c
209
builtin-blame.c
|
@ -15,9 +15,10 @@
|
||||||
#include "revision.h"
|
#include "revision.h"
|
||||||
#include "quote.h"
|
#include "quote.h"
|
||||||
#include "xdiff-interface.h"
|
#include "xdiff-interface.h"
|
||||||
|
#include "cache-tree.h"
|
||||||
|
|
||||||
static char blame_usage[] =
|
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"
|
" -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
|
||||||
" -b Show blank SHA-1 for boundary commits (Default: off)\n"
|
" -b Show blank SHA-1 for boundary commits (Default: off)\n"
|
||||||
" -l, --long Show long commit SHA1 (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"
|
" -L n,m Process only line range n,m, counting from 1\n"
|
||||||
" -M, -C Find line movements within and across files\n"
|
" -M, -C Find line movements within and across files\n"
|
||||||
" --incremental Show blame entries as we find them, incrementally\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";
|
" -S revs-file Use revisions from revs-file instead of calling git-rev-list\n";
|
||||||
|
|
||||||
static int longest_file;
|
static int longest_file;
|
||||||
|
@ -333,9 +335,13 @@ static struct origin *find_origin(struct scoreboard *sb,
|
||||||
diff_tree_setup_paths(paths, &diff_opts);
|
diff_tree_setup_paths(paths, &diff_opts);
|
||||||
if (diff_setup_done(&diff_opts) < 0)
|
if (diff_setup_done(&diff_opts) < 0)
|
||||||
die("diff-setup");
|
die("diff-setup");
|
||||||
diff_tree_sha1(parent->tree->object.sha1,
|
|
||||||
origin->commit->tree->object.sha1,
|
if (is_null_sha1(origin->commit->object.sha1))
|
||||||
"", &diff_opts);
|
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);
|
diffcore_std(&diff_opts);
|
||||||
|
|
||||||
/* It is either one entry that says "modified", or "created",
|
/* 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);
|
diff_tree_setup_paths(paths, &diff_opts);
|
||||||
if (diff_setup_done(&diff_opts) < 0)
|
if (diff_setup_done(&diff_opts) < 0)
|
||||||
die("diff-setup");
|
die("diff-setup");
|
||||||
diff_tree_sha1(parent->tree->object.sha1,
|
|
||||||
origin->commit->tree->object.sha1,
|
if (is_null_sha1(origin->commit->object.sha1))
|
||||||
"", &diff_opts);
|
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);
|
diffcore_std(&diff_opts);
|
||||||
|
|
||||||
for (i = 0; i < diff_queued_diff.nr; i++) {
|
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)))
|
(!porigin || strcmp(target->path, porigin->path)))
|
||||||
diff_opts.find_copies_harder = 1;
|
diff_opts.find_copies_harder = 1;
|
||||||
|
|
||||||
diff_tree_sha1(parent->tree->object.sha1,
|
if (is_null_sha1(target->commit->object.sha1))
|
||||||
target->commit->tree->object.sha1,
|
do_diff_cache(parent->tree->object.sha1, &diff_opts);
|
||||||
"", &diff_opts);
|
else
|
||||||
|
diff_tree_sha1(parent->tree->object.sha1,
|
||||||
|
target->commit->tree->object.sha1,
|
||||||
|
"", &diff_opts);
|
||||||
|
|
||||||
if (!diff_opts.find_copies_harder)
|
if (!diff_opts.find_copies_harder)
|
||||||
diffcore_std(&diff_opts);
|
diffcore_std(&diff_opts);
|
||||||
|
@ -1336,9 +1349,9 @@ static void get_commit_info(struct commit *commit,
|
||||||
tmp += 2;
|
tmp += 2;
|
||||||
endp = strchr(tmp, '\n');
|
endp = strchr(tmp, '\n');
|
||||||
if (!endp)
|
if (!endp)
|
||||||
goto error_out;
|
endp = tmp + strlen(tmp);
|
||||||
len = endp - tmp;
|
len = endp - tmp;
|
||||||
if (len >= sizeof(summary_buf))
|
if (len >= sizeof(summary_buf) || len == 0)
|
||||||
goto error_out;
|
goto error_out;
|
||||||
memcpy(summary_buf, tmp, len);
|
memcpy(summary_buf, tmp, len);
|
||||||
summary_buf[len] = 0;
|
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);
|
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)
|
int cmd_blame(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
struct rev_info revs;
|
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;
|
const char *final_commit_name = NULL;
|
||||||
char type[10];
|
char type[10];
|
||||||
const char *bottomtop = NULL;
|
const char *bottomtop = NULL;
|
||||||
|
const char *contents_from = NULL;
|
||||||
|
|
||||||
git_config(git_blame_config);
|
git_config(git_blame_config);
|
||||||
save_commit_buffer = 0;
|
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");
|
die("More than one '-L n,m' option given");
|
||||||
bottomtop = arg;
|
bottomtop = arg;
|
||||||
}
|
}
|
||||||
|
else if (!strcmp("--contents", arg)) {
|
||||||
|
if (++i >= argc)
|
||||||
|
usage(blame_usage);
|
||||||
|
contents_from = argv[i];
|
||||||
|
}
|
||||||
else if (!strcmp("--incremental", arg))
|
else if (!strcmp("--incremental", arg))
|
||||||
incremental = 1;
|
incremental = 1;
|
||||||
else if (!strcmp("--score-debug", arg))
|
else if (!strcmp("--score-debug", arg))
|
||||||
|
@ -2087,7 +2237,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
|
||||||
argv[unk] = NULL;
|
argv[unk] = NULL;
|
||||||
|
|
||||||
init_revisions(&revs, NULL);
|
init_revisions(&revs, NULL);
|
||||||
setup_revisions(unk, argv, &revs, "HEAD");
|
setup_revisions(unk, argv, &revs, NULL);
|
||||||
memset(&sb, 0, sizeof(sb));
|
memset(&sb, 0, sizeof(sb));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2114,16 +2264,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
|
||||||
if (!sb.final) {
|
if (!sb.final) {
|
||||||
/*
|
/*
|
||||||
* "--not A B -- path" without anything positive;
|
* "--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];
|
sb.final = fake_working_tree_commit(path, contents_from);
|
||||||
|
add_pending_object(&revs, &(sb.final->object), ":");
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
else if (contents_from)
|
||||||
|
die("Cannot use --contents with final commit object name");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If we have bottom, this will mark the ancestors of the
|
* 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);
|
prepare_revision_walk(&revs);
|
||||||
|
|
||||||
o = get_origin(&sb, sb.final, path);
|
if (is_null_sha1(sb.final->object.sha1)) {
|
||||||
if (fill_blob_sha1(o))
|
char *buf;
|
||||||
die("no such path %s in %s", path, final_commit_name);
|
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++;
|
num_read_blob++;
|
||||||
lno = prepare_lines(&sb);
|
lno = prepare_lines(&sb);
|
||||||
|
|
||||||
|
|
44
diff-lib.c
44
diff-lib.c
|
@ -7,6 +7,7 @@
|
||||||
#include "diff.h"
|
#include "diff.h"
|
||||||
#include "diffcore.h"
|
#include "diffcore.h"
|
||||||
#include "revision.h"
|
#include "revision.h"
|
||||||
|
#include "cache-tree.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* diff-files
|
* diff-files
|
||||||
|
@ -271,7 +272,7 @@ static int diff_cache(struct rev_info *revs,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* Show difference between old and new */
|
/* Show difference between old and new */
|
||||||
show_modified(revs,ac[1], ce, 1,
|
show_modified(revs, ac[1], ce, 1,
|
||||||
cached, match_missing);
|
cached, match_missing);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -372,3 +373,44 @@ int run_diff_index(struct rev_info *revs, int cached)
|
||||||
diff_flush(&revs->diffopt);
|
diff_flush(&revs->diffopt);
|
||||||
return ret;
|
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 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 *);
|
extern int diff_flush_patch_id(struct diff_options *, unsigned char *);
|
||||||
|
|
||||||
#endif /* DIFF_H */
|
#endif /* DIFF_H */
|
||||||
|
|
Loading…
Reference in New Issue