From 27e7304567c92c9771efa7a8b787c487391a5fcd Mon Sep 17 00:00:00 2001 From: Fredrik Kuivinen Date: Fri, 10 Mar 2006 10:21:41 +0100 Subject: [PATCH] blame: Rename detection (take 2) Signed-off-by: Fredrik Kuivinen Signed-off-by: Junio C Hamano --- blame.c | 239 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 199 insertions(+), 40 deletions(-) diff --git a/blame.c b/blame.c index 90338af31c..1fb507028b 100644 --- a/blame.c +++ b/blame.c @@ -14,6 +14,7 @@ #include "tree.h" #include "blob.h" #include "diff.h" +#include "diffcore.h" #include "revision.h" #define DEBUG 0 @@ -34,7 +35,9 @@ struct util_info { char *buf; unsigned long size; int num_lines; -// const char* path; + const char* pathname; + + void* topo_data; }; struct chunk { @@ -342,25 +345,34 @@ static int map_line(struct commit *commit, int line) return info->line_map[line]; } -static int fill_util_info(struct commit *commit, const char *path) +static struct util_info* get_util(struct commit *commit) { - struct util_info *util; - if (commit->object.util) - return 0; + struct util_info *util = commit->object.util; + + if (util) + return util; util = xmalloc(sizeof(struct util_info)); + util->buf = NULL; + util->size = 0; + util->line_map = NULL; + util->num_lines = -1; + util->pathname = NULL; + commit->object.util = util; + return util; +} + +static int fill_util_info(struct commit *commit) +{ + struct util_info *util = commit->object.util; + + assert(util); + assert(util->pathname); - if (get_blob_sha1(commit->tree, path, util->sha1)) { - free(util); + if (get_blob_sha1(commit->tree, util->pathname, util->sha1)) return 1; - } else { - util->buf = NULL; - util->size = 0; - util->line_map = NULL; - util->num_lines = -1; - commit->object.util = util; + else return 0; - } } static void alloc_line_map(struct commit *commit) @@ -389,10 +401,11 @@ static void alloc_line_map(struct commit *commit) static void init_first_commit(struct commit* commit, const char* filename) { - struct util_info* util; + struct util_info* util = commit->object.util; int i; - if (fill_util_info(commit, filename)) + util->pathname = filename; + if (fill_util_info(commit)) die("fill_util_info failed"); alloc_line_map(commit); @@ -453,7 +466,7 @@ static void process_commits(struct rev_info *rev, const char *path, if(num_parents == 0) *initial = commit; - if(fill_util_info(commit, path)) + if (fill_util_info(commit)) continue; alloc_line_map(commit); @@ -471,7 +484,7 @@ static void process_commits(struct rev_info *rev, const char *path, printf("parent: %s\n", sha1_to_hex(parent->object.sha1)); - if(fill_util_info(parent, path)) { + if (fill_util_info(parent)) { num_parents--; continue; } @@ -511,6 +524,135 @@ static void process_commits(struct rev_info *rev, const char *path, } while ((commit = get_revision(rev)) != NULL); } + +static int compare_tree_path(struct rev_info* revs, + struct commit* c1, struct commit* c2) +{ + const char* paths[2]; + struct util_info* util = c2->object.util; + paths[0] = util->pathname; + paths[1] = NULL; + + diff_tree_setup_paths(get_pathspec(revs->prefix, paths)); + return rev_compare_tree(c1->tree, c2->tree); +} + + +static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1, + const char* path) +{ + const char* paths[2]; + paths[0] = path; + paths[1] = NULL; + + diff_tree_setup_paths(get_pathspec(revs->prefix, paths)); + return rev_same_tree_as_empty(t1); +} + +static const char* find_rename(struct commit* commit, struct commit* parent) +{ + struct util_info* cutil = commit->object.util; + struct diff_options diff_opts; + const char *paths[1]; + int i; + + if (DEBUG) { + printf("find_rename commit: %s ", + sha1_to_hex(commit->object.sha1)); + puts(sha1_to_hex(parent->object.sha1)); + } + + diff_setup(&diff_opts); + diff_opts.recursive = 1; + diff_opts.detect_rename = DIFF_DETECT_RENAME; + paths[0] = NULL; + diff_tree_setup_paths(paths); + if (diff_setup_done(&diff_opts) < 0) + die("diff_setup_done failed"); + + diff_tree_sha1(commit->tree->object.sha1, parent->tree->object.sha1, + "", &diff_opts); + diffcore_std(&diff_opts); + + for (i = 0; i < diff_queued_diff.nr; i++) { + struct diff_filepair *p = diff_queued_diff.queue[i]; + + if (p->status == 'R' && !strcmp(p->one->path, cutil->pathname)) { + if (DEBUG) + printf("rename %s -> %s\n", p->one->path, p->two->path); + return p->two->path; + } + } + + return 0; +} + +static void simplify_commit(struct rev_info *revs, struct commit *commit) +{ + struct commit_list **pp, *parent; + + if (!commit->tree) + return; + + if (!commit->parents) { + struct util_info* util = commit->object.util; + if (!same_tree_as_empty_path(revs, commit->tree, + util->pathname)) + commit->object.flags |= TREECHANGE; + return; + } + + pp = &commit->parents; + while ((parent = *pp) != NULL) { + struct commit *p = parent->item; + + if (p->object.flags & UNINTERESTING) { + pp = &parent->next; + continue; + } + + parse_commit(p); + switch (compare_tree_path(revs, p, commit)) { + case REV_TREE_SAME: + parent->next = NULL; + commit->parents = parent; + get_util(p)->pathname = get_util(commit)->pathname; + return; + + case REV_TREE_NEW: + { + + struct util_info* util = commit->object.util; + if (revs->remove_empty_trees && + same_tree_as_empty_path(revs, p->tree, + util->pathname)) { + const char* new_name = find_rename(commit, p); + if (new_name) { + struct util_info* putil = get_util(p); + if (!putil->pathname) + putil->pathname = strdup(new_name); + } else { + *pp = parent->next; + continue; + } + } + } + + /* fallthrough */ + case REV_TREE_DIFFERENT: + pp = &parent->next; + if (!get_util(p)->pathname) + get_util(p)->pathname = + get_util(commit)->pathname; + continue; + } + die("bad tree compare for commit %s", + sha1_to_hex(commit->object.sha1)); + } + commit->object.flags |= TREECHANGE; +} + + struct commit_info { char* author; @@ -569,6 +711,18 @@ static const char* format_time(unsigned long time, const char* tz_str) return time_buf; } +static void topo_setter(struct commit* c, void* data) +{ + struct util_info* util = c->object.util; + util->topo_data = data; +} + +static void* topo_getter(struct commit* c) +{ + struct util_info* util = c->object.util; + return util->topo_data; +} + int main(int argc, const char **argv) { int i; @@ -580,8 +734,8 @@ int main(int argc, const char **argv) int sha1_len = 8; int compability = 0; int options = 1; + struct commit* start_commit; - int num_args; const char* args[10]; struct rev_info rev; @@ -634,28 +788,29 @@ int main(int argc, const char **argv) strcpy(filename_buf, filename); filename = filename_buf; - { - struct commit* c; - if (get_sha1(commit, sha1)) - die("get_sha1 failed, commit '%s' not found", commit); - c = lookup_commit_reference(sha1); - - if (fill_util_info(c, filename)) { - printf("%s not found in %s\n", filename, commit); - return 1; - } + if (get_sha1(commit, sha1)) + die("get_sha1 failed, commit '%s' not found", commit); + start_commit = lookup_commit_reference(sha1); + get_util(start_commit)->pathname = filename; + if (fill_util_info(start_commit)) { + printf("%s not found in %s\n", filename, commit); + return 1; } - num_args = 0; - args[num_args++] = NULL; - args[num_args++] = "--topo-order"; - args[num_args++] = "--remove-empty"; - args[num_args++] = commit; - args[num_args++] = "--"; - args[num_args++] = filename; - args[num_args] = NULL; - setup_revisions(num_args, args, &rev, "HEAD"); + init_revisions(&rev); + rev.remove_empty_trees = 1; + rev.topo_order = 1; + rev.prune_fn = simplify_commit; + rev.topo_setter = topo_setter; + rev.topo_getter = topo_getter; + rev.limited = 1; + + commit_list_insert(start_commit, &rev.commits); + + args[0] = filename; + args[1] = NULL; + diff_tree_setup_paths(args); prepare_revision_walk(&rev); process_commits(&rev, filename, &initial); @@ -665,17 +820,21 @@ int main(int argc, const char **argv) for (i = 0; i < num_blame_lines; i++) { struct commit *c = blame_lines[i]; + struct util_info* u; + if (!c) c = initial; + u = c->object.util; get_commit_info(c, &ci); fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); if(compability) printf("\t(%10s\t%10s\t%d)", ci.author, format_time(ci.author_time, ci.author_tz), i+1); else - printf(" (%-15.15s %10s %*d) ", ci.author, - format_time(ci.author_time, ci.author_tz), + printf(" %s (%-15.15s %10s %*d) ", u->pathname, + ci.author, format_time(ci.author_time, + ci.author_tz), max_digits, i+1); if(i == num_blame_lines - 1) {