Linus Torvalds <torvalds@linux-foundation.org> writes:
This is still very rough; the existing diff frontends are mess
and making diff-cache and diff-tree behave more or less
interchangeably is quite a pain. I am not proud of the new
do_diff_cache() interface I had to add, which is probably
totally useless for anybody other than the three calling sites
this patch has.
I tested only the most trivial case that exercises the
do_diff_cache() cal in find_origin() before I got too tired, and
I am retiring to bed now.
-- >8 --
[PATCH] git-blame: no rev means start from the working tree file.
Warning: this changes the semantics.
This is a WIP to make "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 might make sense to give "git-blame --cached" to start
digging from the index as well, which should be trivial.
The calls to do_diff_cache() in find_copy_in_parent() and
find_rename() need to be vetted, as I haven't checked them yet.
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
builtin-blame.c | 119 ++++++++++++++++++++++++++++++++++++++++++++----------
cache.h | 1 +
diff-lib.c | 22 ++++++++++-
diff.h | 1 +
ident.c | 8 ++--
5 files changed, 124 insertions(+), 27 deletions(-)
diff --git a/builtin-blame.c b/builtin-blame.c
index 3033e9b..a8668c0 100644
--- a/builtin-blame.c
+++ b/builtin-blame.c
@@ -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, 0);
+ 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, 0);
+ 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 +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, 0);
+ 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);
@@ -1910,6 +1921,64 @@ 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)
+{
+ struct stat st;
+ struct commit *commit;
+ struct origin *origin;
+ unsigned char head_sha1[20];
+ char *buf;
+ const char *ident;
+ int fd;
+
+ if (lstat(path, &st) < 0)
+ die("Cannot lstat %s", path);
+ if (get_sha1("HEAD", head_sha1))
+ die("No such ref: HEAD");
+
+ 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 = st.st_mtime;
+ commit->object.type = OBJ_COMMIT;
+
+ origin = make_origin(commit, path);
+ origin->file.ptr = buf = xmalloc(st.st_size+1);
+ origin->file.size = st.st_size;
+ buf[st.st_size] = 0;
+
+ switch (st.st_mode & S_IFMT) {
+ case S_IFREG:
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ die("cannot open %s", path);
+ if (read_in_full(fd, buf, st.st_size) != st.st_size)
+ die("cannot read %s", path);
+ break;
+ case S_IFLNK:
+ if (readlink(path, buf, st.st_size+1) != st.st_size)
+ die("cannot readlink %s", path);
+ break;
+ default:
+ die("unsupported file type %s", path);
+ }
+ hash_sha1_file(buf, st.st_size, blob_type, origin->blob_sha1);
+ commit->util = origin;
+
+ 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 the working tree",
+ sha1_to_hex(head_sha1),
+ ident, ident, path);
+ return commit;
+}
+
int cmd_blame(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
@@ -2087,7 +2156,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,15 +2183,10 @@ 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 cache.
*/
- 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);
+ add_pending_object(&revs, &(sb.final->object), ":");
}
/*
@@ -2132,11 +2196,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/cache.h b/cache.h
index 9873ee9..dcceea4 100644
--- a/cache.h
+++ b/cache.h
@@ -321,6 +321,7 @@ unsigned long approxidate(const char *);
extern const char *git_author_info(int);
extern const char *git_committer_info(int);
+extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int);
struct checkout {
const char *base_dir;
diff --git a/diff-lib.c b/diff-lib.c
index 2c9be60..b93f7a3 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -271,7 +271,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 +372,23 @@ 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, int cached)
+{
+ struct tree *tree;
+ struct rev_info revs;
+
+ init_revisions(&revs, NULL);
+ revs.prune_data = opt->paths;
+ discard_cache();
+ if (read_cache() < 0)
+ die("cannot read index");
+ mark_merge_entries();
+ 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,
+ cached, 0);
+}
diff --git a/diff.h b/diff.h
index 7a347cf..dd180b8 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 *, int);
extern int diff_flush_patch_id(struct diff_options *, unsigned char *);
#endif /* DIFF_H */
diff --git a/ident.c b/ident.c
index a6fc7b5..bb03bdd 100644
--- a/ident.c
+++ b/ident.c
@@ -185,8 +185,8 @@ static const char *env_hint =
"Add --global to set your account\'s default\n"
"\n";
-static const char *get_ident(const char *name, const char *email,
- const char *date_str, int error_on_no_name)
+const char *fmt_ident(const char *name, const char *email,
+ const char *date_str, int error_on_no_name)
{
static char buffer[1000];
char date[50];
@@ -233,7 +233,7 @@ static const char *get_ident(const char *name, const char *email,
const char *git_author_info(int error_on_no_name)
{
- return get_ident(getenv("GIT_AUTHOR_NAME"),
+ return fmt_ident(getenv("GIT_AUTHOR_NAME"),
getenv("GIT_AUTHOR_EMAIL"),
getenv("GIT_AUTHOR_DATE"),
error_on_no_name);
@@ -241,7 +241,7 @@ const char *git_author_info(int error_on_no_name)
const char *git_committer_info(int error_on_no_name)
{
- return get_ident(getenv("GIT_COMMITTER_NAME"),
+ return fmt_ident(getenv("GIT_COMMITTER_NAME"),
getenv("GIT_COMMITTER_EMAIL"),
getenv("GIT_COMMITTER_DATE"),
error_on_no_name);
--
1.5.0.rc2.77.g1732a
-
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to
majordomo@vger.kernel.org
More majordomo info at
http://vger.kernel.org/majordomo-info.html