Merge branch 'gp/branch'
[gitweb.git] / builtin-blame.c
index 1a752b95bb32578cd3ecb69153b55f605b8bfdc3..f7e2c13885a384d58c3481fa7259a64bf9525045 100644 (file)
 #include "quote.h"
 #include "xdiff-interface.h"
 #include "cache-tree.h"
+#include "path-list.h"
+#include "mailmap.h"
 
 static char blame_usage[] =
-"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--incremental] [commit] [--] file\n"
-"  -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
+"git-blame [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--incremental] [commit] [--] file\n"
+"  -c                  Use the same output mode as git-annotate (Default: off)\n"
 "  -b                  Show blank SHA-1 for boundary commits (Default: off)\n"
-"  -l, --long          Show long commit SHA1 (Default: off)\n"
+"  -l                  Show long commit SHA1 (Default: off)\n"
 "  --root              Do not treat root commits as boundaries (Default: off)\n"
-"  -t, --time          Show raw timestamp (Default: off)\n"
+"  -t                  Show raw timestamp (Default: off)\n"
 "  -f, --show-name     Show original filename (Default: auto)\n"
 "  -n, --show-number   Show original linenumber (Default: off)\n"
+"  -s                  Suppress author name and timestamp (Default: off)\n"
 "  -p, --porcelain     Show in a format designed for machine consumption\n"
+"  -w                  Ignore whitespace differences\n"
 "  -L n,m              Process only line range n,m, counting from 1\n"
 "  -M, -C              Find line movements within and across files\n"
 "  --incremental       Show blame entries as we find them, incrementally\n"
@@ -42,6 +46,8 @@ static int show_root;
 static int blank_boundary;
 static int incremental;
 static int cmd_is_annotate;
+static int xdl_opts = XDF_NEED_MINIMAL;
+static struct path_list mailmap;
 
 #ifndef DEBUG
 #define DEBUG 0
@@ -55,6 +61,7 @@ static int num_commits;
 #define PICKAXE_BLAME_MOVE             01
 #define PICKAXE_BLAME_COPY             02
 #define PICKAXE_BLAME_COPY_HARDER      04
+#define PICKAXE_BLAME_COPY_HARDEST     010
 
 /*
  * blame for a blame_entry with score lower than these thresholds
@@ -87,9 +94,9 @@ struct origin {
 static char *fill_origin_blob(struct origin *o, mmfile_t *file)
 {
        if (!o->file.ptr) {
-               char type[10];
+               enum object_type type;
                num_read_blob++;
-               file->ptr = read_sha1_file(o->blob_sha1, type,
+               file->ptr = read_sha1_file(o->blob_sha1, &type,
                                           (unsigned long *)(&(file->size)));
                o->file = *file;
        }
@@ -180,16 +187,15 @@ struct scoreboard {
        int *lineno;
 };
 
-static int cmp_suspect(struct origin *a, struct origin *b)
+static inline int same_suspect(struct origin *a, struct origin *b)
 {
-       int cmp = hashcmp(a->commit->object.sha1, b->commit->object.sha1);
-       if (cmp)
-               return cmp;
-       return strcmp(a->path, b->path);
+       if (a == b)
+               return 1;
+       if (a->commit != b->commit)
+               return 0;
+       return !strcmp(a->path, b->path);
 }
 
-#define cmp_suspect(a, b) ( ((a)==(b)) ? 0 : cmp_suspect(a,b) )
-
 static void sanity_check_refcnt(struct scoreboard *);
 
 /*
@@ -202,7 +208,7 @@ static void coalesce(struct scoreboard *sb)
        struct blame_entry *ent, *next;
 
        for (ent = sb->ent; ent && (next = ent->next); ent = next) {
-               if (!cmp_suspect(ent->suspect, next->suspect) &&
+               if (same_suspect(ent->suspect, next->suspect) &&
                    ent->guilty == next->guilty &&
                    ent->s_lno + ent->num_lines == next->s_lno) {
                        ent->num_lines += next->num_lines;
@@ -263,7 +269,6 @@ static struct origin *get_origin(struct scoreboard *sb,
 static int fill_blob_sha1(struct origin *origin)
 {
        unsigned mode;
-       char type[10];
 
        if (!is_null_sha1(origin->blob_sha1))
                return 0;
@@ -271,8 +276,7 @@ static int fill_blob_sha1(struct origin *origin)
                           origin->path,
                           origin->blob_sha1, &mode))
                goto error_out;
-       if (sha1_object_info(origin->blob_sha1, type, NULL) ||
-           strcmp(type, blob_type))
+       if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB)
                goto error_out;
        return 0;
  error_out:
@@ -513,7 +517,7 @@ static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o,
        xdemitconf_t xecfg;
        xdemitcb_t ecb;
 
-       xpp.flags = XDF_NEED_MINIMAL;
+       xpp.flags = xdl_opts;
        xecfg.ctxlen = context;
        xecfg.flags = 0;
        ecb.outf = xdiff_outf;
@@ -777,7 +781,7 @@ static int find_last_in_target(struct scoreboard *sb, struct origin *target)
        int last_in_target = -1;
 
        for (e = sb->ent; e; e = e->next) {
-               if (e->guilty || cmp_suspect(e->suspect, target))
+               if (e->guilty || !same_suspect(e->suspect, target))
                        continue;
                if (last_in_target < e->s_lno + e->num_lines)
                        last_in_target = e->s_lno + e->num_lines;
@@ -797,7 +801,7 @@ static void blame_chunk(struct scoreboard *sb,
        struct blame_entry *e;
 
        for (e = sb->ent; e; e = e->next) {
-               if (e->guilty || cmp_suspect(e->suspect, target))
+               if (e->guilty || !same_suspect(e->suspect, target))
                        continue;
                if (same <= e->s_lno)
                        continue;
@@ -893,6 +897,39 @@ static void copy_split_if_better(struct scoreboard *sb,
        memcpy(best_so_far, this, sizeof(struct blame_entry [3]));
 }
 
+/*
+ * We are looking at a part of the final image represented by
+ * ent (tlno and same are offset by ent->s_lno).
+ * tlno is where we are looking at in the final image.
+ * up to (but not including) same match preimage.
+ * plno is where we are looking at in the preimage.
+ *
+ * <-------------- final image ---------------------->
+ *       <------ent------>
+ *         ^tlno ^same
+ *    <---------preimage----->
+ *         ^plno
+ *
+ * All line numbers are 0-based.
+ */
+static void handle_split(struct scoreboard *sb,
+                        struct blame_entry *ent,
+                        int tlno, int plno, int same,
+                        struct origin *parent,
+                        struct blame_entry *split)
+{
+       if (ent->num_lines <= tlno)
+               return;
+       if (tlno < same) {
+               struct blame_entry this[3];
+               tlno += ent->s_lno;
+               same += ent->s_lno;
+               split_overlap(this, ent, tlno, plno, same, parent);
+               copy_split_if_better(sb, split, this);
+               decref_split(this);
+       }
+}
+
 /*
  * Find the lines from parent that are the same as ent so that
  * we can pass blames to it.  file_p has the blob contents for
@@ -925,26 +962,21 @@ static void find_copy_in_blob(struct scoreboard *sb,
 
        patch = compare_buffer(file_p, &file_o, 1);
 
+       /*
+        * file_o is a part of final image we are annotating.
+        * file_p partially may match that image.
+        */
        memset(split, 0, sizeof(struct blame_entry [3]));
        plno = tlno = 0;
        for (i = 0; i < patch->num; i++) {
                struct chunk *chunk = &patch->chunks[i];
 
-               /* tlno to chunk->same are the same as ent */
-               if (ent->num_lines <= tlno)
-                       break;
-               if (tlno < chunk->same) {
-                       struct blame_entry this[3];
-                       split_overlap(this, ent,
-                                     tlno + ent->s_lno, plno,
-                                     chunk->same + ent->s_lno,
-                                     parent);
-                       copy_split_if_better(sb, split, this);
-                       decref_split(this);
-               }
+               handle_split(sb, ent, tlno, plno, chunk->same, parent, split);
                plno = chunk->p_next;
                tlno = chunk->t_next;
        }
+       /* remainder, if any, all match the preimage */
+       handle_split(sb, ent, tlno, plno, ent->num_lines, parent, split);
        free_patch(patch);
 }
 
@@ -972,7 +1004,7 @@ static int find_move_in_parent(struct scoreboard *sb,
        while (made_progress) {
                made_progress = 0;
                for (e = sb->ent; e; e = e->next) {
-                       if (e->guilty || cmp_suspect(e->suspect, target))
+                       if (e->guilty || !same_suspect(e->suspect, target))
                                continue;
                        find_copy_in_blob(sb, e, parent, split, &file_p);
                        if (split[1].suspect &&
@@ -1004,12 +1036,12 @@ static struct blame_list *setup_blame_list(struct scoreboard *sb,
        struct blame_list *blame_list = NULL;
 
        for (e = sb->ent, num_ents = 0; e; e = e->next)
-               if (!e->guilty && !cmp_suspect(e->suspect, target))
+               if (!e->guilty && same_suspect(e->suspect, target))
                        num_ents++;
        if (num_ents) {
                blame_list = xcalloc(num_ents, sizeof(struct blame_list));
                for (e = sb->ent, i = 0; e; e = e->next)
-                       if (!e->guilty && !cmp_suspect(e->suspect, target))
+                       if (!e->guilty && same_suspect(e->suspect, target))
                                blame_list[i++].ent = e;
        }
        *num_ents_p = num_ents;
@@ -1054,8 +1086,9 @@ static int find_copy_in_parent(struct scoreboard *sb,
         * and this code needs to be after diff_setup_done(), which
         * usually makes find-copies-harder imply copy detection.
         */
-       if ((opt & PICKAXE_BLAME_COPY_HARDER) &&
-           (!porigin || strcmp(target->path, porigin->path)))
+       if ((opt & PICKAXE_BLAME_COPY_HARDEST)
+           || ((opt & PICKAXE_BLAME_COPY_HARDER)
+               && (!porigin || strcmp(target->path, porigin->path))))
                diff_opts.find_copies_harder = 1;
 
        if (is_null_sha1(target->commit->object.sha1))
@@ -1139,7 +1172,7 @@ static void pass_whole_blame(struct scoreboard *sb,
                origin->file.ptr = NULL;
        }
        for (e = sb->ent; e; e = e->next) {
-               if (cmp_suspect(e->suspect, origin))
+               if (!same_suspect(e->suspect, origin))
                        continue;
                origin_incref(porigin);
                origin_decref(e->suspect);
@@ -1246,29 +1279,29 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
  */
 struct commit_info
 {
-       char *author;
-       char *author_mail;
+       const char *author;
+       const char *author_mail;
        unsigned long author_time;
-       char *author_tz;
+       const char *author_tz;
 
        /* filled only when asked for details */
-       char *committer;
-       char *committer_mail;
+       const char *committer;
+       const char *committer_mail;
        unsigned long committer_time;
-       char *committer_tz;
+       const char *committer_tz;
 
-       char *summary;
+       const char *summary;
 };
 
 /*
  * Parse author/committer line in the commit object buffer
  */
 static void get_ac_line(const char *inbuf, const char *what,
-                       int bufsz, char *person, char **mail,
-                       unsigned long *time, char **tz)
+                       int bufsz, char *person, const char **mail,
+                       unsigned long *time, const char **tz)
 {
-       int len;
-       char *tmp, *endp;
+       int len, tzlen, maillen;
+       char *tmp, *endp, *timepos;
 
        tmp = strstr(inbuf, what);
        if (!tmp)
@@ -1282,7 +1315,7 @@ static void get_ac_line(const char *inbuf, const char *what,
        if (bufsz <= len) {
        error_out:
                /* Ugh */
-               person = *mail = *tz = "(unknown)";
+               *mail = *tz = "(unknown)";
                *time = 0;
                return;
        }
@@ -1294,17 +1327,42 @@ static void get_ac_line(const char *inbuf, const char *what,
        while (*tmp != ' ')
                tmp--;
        *tz = tmp+1;
+       tzlen = (person+len)-(tmp+1);
 
        *tmp = 0;
        while (*tmp != ' ')
                tmp--;
        *time = strtoul(tmp, NULL, 10);
+       timepos = tmp;
 
        *tmp = 0;
        while (*tmp != ' ')
                tmp--;
        *mail = tmp + 1;
        *tmp = 0;
+       maillen = timepos - tmp;
+
+       if (!mailmap.nr)
+               return;
+
+       /*
+        * mailmap expansion may make the name longer.
+        * make room by pushing stuff down.
+        */
+       tmp = person + bufsz - (tzlen + 1);
+       memmove(tmp, *tz, tzlen);
+       tmp[tzlen] = 0;
+       *tz = tmp;
+
+       tmp = tmp - (maillen + 1);
+       memmove(tmp, *mail, maillen);
+       tmp[maillen] = 0;
+       *mail = tmp;
+
+       /*
+        * Now, convert e-mail using mailmap
+        */
+       map_email(&mailmap, tmp + 1, person, tmp-person-1);
 }
 
 static void get_commit_info(struct commit *commit,
@@ -1322,10 +1380,10 @@ static void get_commit_info(struct commit *commit,
         * we now need to populate them for output.
         */
        if (!commit->buffer) {
-               char type[20];
+               enum object_type type;
                unsigned long size;
                commit->buffer =
-                       read_sha1_file(commit->object.sha1, type, &size);
+                       read_sha1_file(commit->object.sha1, &type, &size);
        }
        ret->author = author_buf;
        get_ac_line(commit->buffer, "\nauthor ",
@@ -1445,7 +1503,7 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
 
                /* Take responsibility for the remaining entries */
                for (ent = sb->ent; ent; ent = ent->next)
-                       if (!cmp_suspect(ent->suspect, suspect))
+                       if (same_suspect(ent->suspect, suspect))
                                found_guilty_entry(ent);
                origin_decref(suspect);
 
@@ -1486,6 +1544,7 @@ static const char *format_time(unsigned long time, const char *tz_str,
 #define OUTPUT_SHOW_NAME       020
 #define OUTPUT_SHOW_NUMBER     040
 #define OUTPUT_SHOW_SCORE      0100
+#define OUTPUT_NO_AUTHOR       0200
 
 static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
 {
@@ -1580,10 +1639,15 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
                        if (opt & OUTPUT_SHOW_NUMBER)
                                printf(" %*d", max_orig_digits,
                                       ent->s_lno + 1 + cnt);
-                       printf(" (%-*.*s %10s %*d) ",
-                              longest_author, longest_author, ci.author,
-                              format_time(ci.author_time, ci.author_tz,
-                                          show_raw_time),
+
+                       if (!(opt & OUTPUT_NO_AUTHOR))
+                               printf(" (%-*.*s %10s",
+                                      longest_author, longest_author,
+                                      ci.author,
+                                      format_time(ci.author_time,
+                                                  ci.author_tz,
+                                                  show_raw_time));
+                       printf(" %*d) ",
                               max_digits, ent->lno + 1 + cnt);
                }
                do {
@@ -1682,11 +1746,11 @@ static int read_ancestry(const char *graft_file)
  */
 static int lineno_width(int lines)
 {
-        int i, width;
+       int i, width;
 
-        for (width = 1, i = 10; i <= lines + 1; width++)
-                i *= 10;
-        return width;
+       for (width = 1, i = 10; i <= lines + 1; width++)
+               i *= 10;
+       return width;
 }
 
 /*
@@ -1965,7 +2029,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
                                die("Cannot lstat %s", path);
                        read_from = path;
                }
-               fin_size = st.st_size;
+               fin_size = xsize_t(st.st_size);
                buf = xmalloc(fin_size+1);
                mode = canon_mode(st.st_mode);
                switch (st.st_mode & S_IFMT) {
@@ -2006,7 +2070,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
        buf[fin_size] = 0;
        origin->file.ptr = buf;
        origin->file.size = fin_size;
-       pretend_sha1_file(buf, fin_size, blob_type, origin->blob_sha1);
+       pretend_sha1_file(buf, fin_size, OBJ_BLOB, origin->blob_sha1);
        commit->util = origin;
 
        /*
@@ -2044,7 +2108,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
 
        commit->buffer = xmalloc(400);
        ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
-       sprintf(commit->buffer,
+       snprintf(commit->buffer, 400,
                "tree 0000000000000000000000000000000000000000\n"
                "parent %s\n"
                "author %s\n"
@@ -2065,9 +2129,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        int i, seen_dashdash, unk, opt;
        long bottom, top, lno;
        int output_option = 0;
+       int show_stats = 0;
        const char *revs_file = NULL;
        const char *final_commit_name = NULL;
-       char type[10];
+       enum object_type type;
        const char *bottomtop = NULL;
        const char *contents_from = NULL;
 
@@ -2086,25 +2151,40 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                        blank_boundary = 1;
                else if (!strcmp("--root", arg))
                        show_root = 1;
+               else if (!strcmp(arg, "--show-stats"))
+                       show_stats = 1;
                else if (!strcmp("-c", arg))
                        output_option |= OUTPUT_ANNOTATE_COMPAT;
                else if (!strcmp("-t", arg))
                        output_option |= OUTPUT_RAW_TIMESTAMP;
                else if (!strcmp("-l", arg))
                        output_option |= OUTPUT_LONG_OBJECT_NAME;
+               else if (!strcmp("-s", arg))
+                       output_option |= OUTPUT_NO_AUTHOR;
+               else if (!strcmp("-w", arg))
+                       xdl_opts |= XDF_IGNORE_WHITESPACE;
                else if (!strcmp("-S", arg) && ++i < argc)
                        revs_file = argv[i];
-               else if (!strncmp("-M", arg, 2)) {
+               else if (!prefixcmp(arg, "-M")) {
                        opt |= PICKAXE_BLAME_MOVE;
                        blame_move_score = parse_score(arg+2);
                }
-               else if (!strncmp("-C", arg, 2)) {
+               else if (!prefixcmp(arg, "-C")) {
+                       /*
+                        * -C enables copy from removed files;
+                        * -C -C enables copy from existing files, but only
+                        *       when blaming a new file;
+                        * -C -C -C enables copy from existing files for
+                        *          everybody
+                        */
+                       if (opt & PICKAXE_BLAME_COPY_HARDER)
+                               opt |= PICKAXE_BLAME_COPY_HARDEST;
                        if (opt & PICKAXE_BLAME_COPY)
                                opt |= PICKAXE_BLAME_COPY_HARDER;
                        opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE;
                        blame_copy_score = parse_score(arg+2);
                }
-               else if (!strncmp("-L", arg, 2)) {
+               else if (!prefixcmp(arg, "-L")) {
                        if (!arg[2]) {
                                if (++i >= argc)
                                        usage(blame_usage);
@@ -2299,7 +2379,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                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 = read_sha1_file(o->blob_sha1, &type,
                                              &sb.final_buf_size);
        }
        num_read_blob++;
@@ -2333,6 +2413,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                die("reading graft file %s failed: %s",
                    revs_file, strerror(errno));
 
+       read_mailmap(&mailmap, ".mailmap", NULL);
+
        assign_blame(&sb, &revs, opt);
 
        if (incremental)
@@ -2351,7 +2433,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                ent = e;
        }
 
-       if (DEBUG) {
+       if (show_stats) {
                printf("num read blob: %d\n", num_read_blob);
                printf("num get patch: %d\n", num_get_patch);
                printf("num commits: %d\n", num_commits);