Update gitweb README: gitweb is now included with git
[gitweb.git] / blame.c
diff --git a/blame.c b/blame.c
index 396defccc728db836732aa140e54620775509760..25d3bcf647f44bbaed1c273d5886df6a7cfe6e03 100644 (file)
--- a/blame.c
+++ b/blame.c
 #include "diff.h"
 #include "diffcore.h"
 #include "revision.h"
+#include "xdiff-interface.h"
 
 #define DEBUG 0
 
-static const char blame_usage[] = "[-c] [-l] [--] file [commit]\n"
+static const char blame_usage[] = "[-c] [-l] [-t] [-S <revs-file>] [--] file [commit]\n"
        "  -c, --compability Use the same output mode as git-annotate (Default: off)\n"
        "  -l, --long        Show long commit SHA1 (Default: off)\n"
+       "  -t, --time        Show raw timestamp (Default: off)\n"
+       "  -S, --revs-file   Use revisions from revs-file instead of calling git-rev-list\n"
        "  -h, --help        This message";
 
 static struct commit **blame_lines;
@@ -57,116 +60,89 @@ static int num_get_patch = 0;
 static int num_commits = 0;
 static int patch_time = 0;
 
-#define TEMPFILE_PATH_LEN 60
-static struct patch *get_patch(struct commit *commit, struct commit *other)
-{
+struct blame_diff_state {
+       struct xdiff_emit_state xm;
        struct patch *ret;
-       struct util_info *info_c = (struct util_info *)commit->object.util;
-       struct util_info *info_o = (struct util_info *)other->object.util;
-       char tmp_path1[TEMPFILE_PATH_LEN], tmp_path2[TEMPFILE_PATH_LEN];
-       char diff_cmd[TEMPFILE_PATH_LEN*2 + 20];
-       struct timeval tv_start, tv_end;
-       int fd;
-       FILE *fin;
-       char buf[1024];
-
-       ret = xmalloc(sizeof(struct patch));
-       ret->chunks = NULL;
-       ret->num = 0;
-
-       get_blob(commit);
-       get_blob(other);
+};
 
-       gettimeofday(&tv_start, NULL);
+static void process_u0_diff(void *state_, char *line, unsigned long len)
+{
+       struct blame_diff_state *state = state_;
+       struct chunk *chunk;
 
-       fd = git_mkstemp(tmp_path1, TEMPFILE_PATH_LEN, "git-blame-XXXXXX");
-       if (fd < 0)
-               die("unable to create temp-file: %s", strerror(errno));
+       if (len < 4 || line[0] != '@' || line[1] != '@')
+               return;
 
-       if (xwrite(fd, info_c->buf, info_c->size) != info_c->size)
-               die("write failed: %s", strerror(errno));
-       close(fd);
+       if (DEBUG)
+               printf("chunk line: %.*s", (int)len, line);
+       state->ret->num++;
+       state->ret->chunks = xrealloc(state->ret->chunks,
+                                     sizeof(struct chunk) * state->ret->num);
+       chunk = &state->ret->chunks[state->ret->num - 1];
+
+       assert(!strncmp(line, "@@ -", 4));
+
+       if (parse_hunk_header(line, len,
+                             &chunk->off1, &chunk->len1,
+                             &chunk->off2, &chunk->len2)) {
+               state->ret->num--;
+               return;
+       }
 
-       fd = git_mkstemp(tmp_path2, TEMPFILE_PATH_LEN, "git-blame-XXXXXX");
-       if (fd < 0)
-               die("unable to create temp-file: %s", strerror(errno));
+       if (chunk->len1 == 0)
+               chunk->off1++;
+       if (chunk->len2 == 0)
+               chunk->off2++;
 
-       if (xwrite(fd, info_o->buf, info_o->size) != info_o->size)
-               die("write failed: %s", strerror(errno));
-       close(fd);
+       if (chunk->off1 > 0)
+               chunk->off1--;
+       if (chunk->off2 > 0)
+               chunk->off2--;
 
-       sprintf(diff_cmd, "diff -U 0 %s %s", tmp_path1, tmp_path2);
-       fin = popen(diff_cmd, "r");
-       if (!fin)
-               die("popen failed: %s", strerror(errno));
+       assert(chunk->off1 >= 0);
+       assert(chunk->off2 >= 0);
+}
 
-       while (fgets(buf, sizeof(buf), fin)) {
-               struct chunk *chunk;
-               char *start, *sp;
+static struct patch *get_patch(struct commit *commit, struct commit *other)
+{
+       struct blame_diff_state state;
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       mmfile_t file_c, file_o;
+       xdemitcb_t ecb;
+       struct util_info *info_c = (struct util_info *)commit->object.util;
+       struct util_info *info_o = (struct util_info *)other->object.util;
+       struct timeval tv_start, tv_end;
 
-               if (buf[0] != '@' || buf[1] != '@')
-                       continue;
+       get_blob(commit);
+       file_c.ptr = info_c->buf;
+       file_c.size = info_c->size;
 
-               if (DEBUG)
-                       printf("chunk line: %s", buf);
-               ret->num++;
-               ret->chunks = xrealloc(ret->chunks,
-                                      sizeof(struct chunk) * ret->num);
-               chunk = &ret->chunks[ret->num - 1];
-
-               assert(!strncmp(buf, "@@ -", 4));
-
-               start = buf + 4;
-               sp = index(start, ' ');
-               *sp = '\0';
-               if (index(start, ',')) {
-                       int ret =
-                           sscanf(start, "%d,%d", &chunk->off1, &chunk->len1);
-                       assert(ret == 2);
-               } else {
-                       int ret = sscanf(start, "%d", &chunk->off1);
-                       assert(ret == 1);
-                       chunk->len1 = 1;
-               }
-               *sp = ' ';
-
-               start = sp + 1;
-               sp = index(start, ' ');
-               *sp = '\0';
-               if (index(start, ',')) {
-                       int ret =
-                           sscanf(start, "%d,%d", &chunk->off2, &chunk->len2);
-                       assert(ret == 2);
-               } else {
-                       int ret = sscanf(start, "%d", &chunk->off2);
-                       assert(ret == 1);
-                       chunk->len2 = 1;
-               }
-               *sp = ' ';
+       get_blob(other);
+       file_o.ptr = info_o->buf;
+       file_o.size = info_o->size;
 
-               if (chunk->len1 == 0)
-                       chunk->off1++;
-               if (chunk->len2 == 0)
-                       chunk->off2++;
+       gettimeofday(&tv_start, NULL);
 
-               if (chunk->off1 > 0)
-                       chunk->off1--;
-               if (chunk->off2 > 0)
-                       chunk->off2--;
+       xpp.flags = XDF_NEED_MINIMAL;
+       xecfg.ctxlen = 0;
+       xecfg.flags = 0;
+       ecb.outf = xdiff_outf;
+       ecb.priv = &state;
+       memset(&state, 0, sizeof(state));
+       state.xm.consume = process_u0_diff;
+       state.ret = xmalloc(sizeof(struct patch));
+       state.ret->chunks = NULL;
+       state.ret->num = 0;
 
-               assert(chunk->off1 >= 0);
-               assert(chunk->off2 >= 0);
-       }
-       pclose(fin);
-       unlink(tmp_path1);
-       unlink(tmp_path2);
+       xdl_diff(&file_c, &file_o, &xpp, &xecfg, &ecb);
 
        gettimeofday(&tv_end, NULL);
        patch_time += 1000000 * (tv_end.tv_sec - tv_start.tv_sec) +
                tv_end.tv_usec - tv_start.tv_usec;
 
        num_get_patch++;
-       return ret;
+       return state.ret;
 }
 
 static void free_patch(struct patch *p)
@@ -175,7 +151,7 @@ static void free_patch(struct patch *p)
        free(p);
 }
 
-static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
+static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
                                  int baselen, const char *pathname,
                                  unsigned mode, int stage);
 
@@ -204,7 +180,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname,
        return 0;
 }
 
-static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
+static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
                                  int baselen, const char *pathname,
                                  unsigned mode, int stage)
 {
@@ -229,7 +205,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 */
@@ -534,25 +510,33 @@ static void process_commits(struct rev_info *rev, const char *path,
 static int compare_tree_path(struct rev_info* revs,
                             struct commit* c1, struct commit* c2)
 {
+       int ret;
        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);
+       diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
+                             &revs->pruning);
+       ret = rev_compare_tree(revs, c1->tree, c2->tree);
+       diff_tree_release_paths(&revs->pruning);
+       return ret;
 }
 
 
 static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
                                   const char* path)
 {
+       int ret;
        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);
+       diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
+                             &revs->pruning);
+       ret = rev_same_tree_as_empty(revs, t1);
+       diff_tree_release_paths(&revs->pruning);
+       return ret;
 }
 
 static const char* find_rename(struct commit* commit, struct commit* parent)
@@ -572,7 +556,7 @@ static const char* find_rename(struct commit* commit, struct commit* parent)
        diff_opts.recursive = 1;
        diff_opts.detect_rename = DIFF_DETECT_RENAME;
        paths[0] = NULL;
-       diff_tree_setup_paths(paths);
+       diff_tree_setup_paths(paths, &diff_opts);
        if (diff_setup_done(&diff_opts) < 0)
                die("diff_setup_done failed");
 
@@ -674,7 +658,7 @@ static void get_commit_info(struct commit* commit, struct commit_info* ret)
        static char author_buf[1024];
 
        tmp = strstr(commit->buffer, "\nauthor ") + 8;
-       len = index(tmp, '\n') - tmp;
+       len = strchr(tmp, '\n') - tmp;
        ret->author = author_buf;
        memcpy(ret->author, tmp, len);
 
@@ -698,13 +682,19 @@ static void get_commit_info(struct commit* commit, struct commit_info* ret)
        *tmp = 0;
 }
 
-static const char* format_time(unsigned long time, const char* tz_str)
+static const char* format_time(unsigned long time, const char* tz_str,
+                              int show_raw_time)
 {
        static char time_buf[128];
        time_t t = time;
        int minutes, tz;
        struct tm *tm;
 
+       if (show_raw_time) {
+               sprintf(time_buf, "%lu %s", time, tz_str);
+               return time_buf;
+       }
+
        tz = atoi(tz_str);
        minutes = tz < 0 ? -tz : tz;
        minutes = (minutes / 100)*60 + (minutes % 100);
@@ -729,16 +719,36 @@ static void* topo_getter(struct commit* c)
        return util->topo_data;
 }
 
+static int read_ancestry(const char *graft_file,
+                        unsigned char **start_sha1)
+{
+       FILE *fp = fopen(graft_file, "r");
+       char buf[1024];
+       if (!fp)
+               return -1;
+       while (fgets(buf, sizeof(buf), fp)) {
+               /* The format is just "Commit Parent1 Parent2 ...\n" */
+               int len = strlen(buf);
+               struct commit_graft *graft = read_graft_line(buf, len);
+               register_commit_graft(graft, 0);
+               if (!*start_sha1)
+                       *start_sha1 = graft->sha1;
+       }
+       fclose(fp);
+       return 0;
+}
+
 int main(int argc, const char **argv)
 {
        int i;
        struct commit *initial = NULL;
-       unsigned char sha1[20];
+       unsigned char sha1[20], *sha1_p = NULL;
 
        const char *filename = NULL, *commit = NULL;
        char filename_buf[256];
        int sha1_len = 8;
        int compability = 0;
+       int show_raw_time = 0;
        int options = 1;
        struct commit* start_commit;
 
@@ -767,6 +777,18 @@ int main(int argc, const char **argv)
                                  !strcmp(argv[i], "--compability")) {
                                compability = 1;
                                continue;
+                       } else if(!strcmp(argv[i], "-t") ||
+                                 !strcmp(argv[i], "--time")) {
+                               show_raw_time = 1;
+                               continue;
+                       } else if(!strcmp(argv[i], "-S")) {
+                               if (i + 1 < argc &&
+                                   !read_ancestry(argv[i + 1], &sha1_p)) {
+                                       compability = 1;
+                                       i++;
+                                       continue;
+                               }
+                               usage(blame_usage);
                        } else if(!strcmp(argv[i], "--")) {
                                options = 0;
                                continue;
@@ -788,7 +810,9 @@ int main(int argc, const char **argv)
 
        if(!filename)
                usage(blame_usage);
-       if(!commit)
+       if (commit && sha1_p)
+               usage(blame_usage);
+       else if(!commit)
                commit = "HEAD";
 
        if(prefix)
@@ -797,9 +821,12 @@ int main(int argc, const char **argv)
                strcpy(filename_buf, filename);
        filename = filename_buf;
 
-       if (get_sha1(commit, sha1))
-               die("get_sha1 failed, commit '%s' not found", commit);
-       start_commit = lookup_commit_reference(sha1);
+       if (!sha1_p) {
+               if (get_sha1(commit, sha1))
+                       die("get_sha1 failed, commit '%s' not found", commit);
+               sha1_p = sha1;
+       }
+       start_commit = lookup_commit_reference(sha1_p);
        get_util(start_commit)->pathname = filename;
        if (fill_util_info(start_commit)) {
                printf("%s not found in %s\n", filename, commit);
@@ -813,13 +840,14 @@ int main(int argc, const char **argv)
        rev.prune_fn = simplify_commit;
        rev.topo_setter = topo_setter;
        rev.topo_getter = topo_getter;
+       rev.parents = 1;
        rev.limited = 1;
 
        commit_list_insert(start_commit, &rev.commits);
 
        args[0] = filename;
        args[1] = NULL;
-       diff_tree_setup_paths(args);
+       diff_tree_setup_paths(args, &rev.pruning);
        prepare_revision_walk(&rev);
        process_commits(&rev, filename, &initial);
 
@@ -858,14 +886,17 @@ int main(int argc, const char **argv)
                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);
+                              format_time(ci.author_time, ci.author_tz,
+                                          show_raw_time),
+                              i+1);
                } 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),
+                              format_time(ci.author_time, ci.author_tz,
+                                          show_raw_time),
                               max_digits, i+1);
                }
 
@@ -875,7 +906,7 @@ int main(int argc, const char **argv)
                        if(blame_contents[blame_len-1] != '\n')
                                putc('\n', stdout);
                } else {
-                       char* next_buf = index(buf, '\n') + 1;
+                       char* next_buf = strchr(buf, '\n') + 1;
                        fwrite(buf, next_buf - buf, 1, stdout);
                        buf = next_buf;
                }