Merge branch 'pe/cleanup' into next
[gitweb.git] / blame.c
diff --git a/blame.c b/blame.c
index 168b1f5fe5555b9e0697f211fb10859dbef327a3..98f99922a78578a84468f1296d6e56b999829659 100644 (file)
--- 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 {
@@ -92,7 +95,7 @@ static struct patch *get_patch(struct commit *commit, struct commit *other)
                die("write failed: %s", strerror(errno));
        close(fd);
 
-       sprintf(diff_cmd, "diff -u0 %s %s", tmp_path1, tmp_path2);
+       sprintf(diff_cmd, "diff -0 %s %s", tmp_path1, tmp_path2);
        fin = popen(diff_cmd, "r");
        if (!fin)
                die("popen failed: %s", strerror(errno));
@@ -177,11 +180,13 @@ static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
                                  unsigned mode, int stage);
 
 static unsigned char blob_sha1[20];
+static const char* blame_file;
 static int get_blob_sha1(struct tree *t, const char *pathname,
                         unsigned char *sha1)
 {
        int i;
        const char *pathspec[2];
+       blame_file = pathname;
        pathspec[0] = pathname;
        pathspec[1] = NULL;
        memset(blob_sha1, 0, sizeof(blob_sha1));
@@ -206,6 +211,10 @@ static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
        if (S_ISDIR(mode))
                return READ_TREE_RECURSIVE;
 
+       if (strncmp(blame_file, base, baselen) ||
+           strcmp(blame_file + baselen, pathname))
+               return -1;
+
        memcpy(blob_sha1, sha1, 20);
        return -1;
 }
@@ -220,7 +229,7 @@ static void get_blob(struct commit *commit)
 
        info->buf = read_sha1_file(info->sha1, type, &info->size);
 
-       assert(!strcmp(type, "blob"));
+       assert(!strcmp(type, blob_type));
 }
 
 /* For debugging only */
@@ -234,7 +243,7 @@ static void print_patch(struct patch *p)
        }
 }
 
-#if 0
+#if DEBUG
 /* For debugging only */
 static void print_map(struct commit *cmit, struct commit *other)
 {
@@ -342,25 +351,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 +407,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 +472,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 +490,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 +530,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;
@@ -550,16 +698,37 @@ static void get_commit_info(struct commit* commit, struct commit_info* ret)
        *tmp = 0;
 }
 
-char* format_time(unsigned long time, const char* tz)
+static const char* format_time(unsigned long time, const char* tz_str)
 {
        static char time_buf[128];
        time_t t = time;
-
-       strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", gmtime(&t));
-       strcat(time_buf, tz);
+       int minutes, tz;
+       struct tm *tm;
+
+       tz = atoi(tz_str);
+       minutes = tz < 0 ? -tz : tz;
+       minutes = (minutes / 100)*60 + (minutes % 100);
+       minutes = tz < 0 ? -minutes : minutes;
+       t = time + minutes * 60;
+       tm = gmtime(&t);
+
+       strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
+       strcat(time_buf, 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;
@@ -571,16 +740,19 @@ 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;
 
        struct commit_info ci;
        const char *buf;
        int max_digits;
+       int longest_file, longest_author;
+       int found_rename;
 
        const char* prefix = setup_git_directory();
+       git_config(git_default_config);
 
        for(i = 1; i < argc; i++) {
                if(options) {
@@ -589,7 +761,7 @@ int main(int argc, const char **argv)
                                usage(blame_usage);
                        else if(!strcmp(argv[i], "-l") ||
                                !strcmp(argv[i], "--long")) {
-                               sha1_len = 20;
+                               sha1_len = 40;
                                continue;
                        } else if(!strcmp(argv[i], "-c") ||
                                  !strcmp(argv[i], "--compability")) {
@@ -625,47 +797,77 @@ 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);
 
        buf = blame_contents;
-       max_digits = 1 + log(num_blame_lines+1)/log(10);
+       for (max_digits = 1, i = 10; i <= num_blame_lines + 1; max_digits++)
+               i *= 10;
+
+       longest_file = 0;
+       longest_author = 0;
+       found_rename = 0;
        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;
 
+               if (!found_rename && strcmp(filename, u->pathname))
+                       found_rename = 1;
+               if (longest_file < strlen(u->pathname))
+                       longest_file = strlen(u->pathname);
+               get_commit_info(c, &ci);
+               if (longest_author < strlen(ci.author))
+                       longest_author = strlen(ci.author);
+       }
+
+       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)
+               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,
+               } else {
+                       if (found_rename)
+                               printf(" %-*.*s", longest_file, longest_file,
+                                      u->pathname);
+                       printf(" (%-*.*s %10s %*d) ",
+                              longest_author, longest_author, ci.author,
                               format_time(ci.author_time, ci.author_tz),
                               max_digits, i+1);
+               }
 
                if(i == num_blame_lines - 1) {
                        fwrite(buf, blame_len - (buf - blame_contents),