git-merge-octopus: use (merge-base A (merge B C D E...)) for stepwise merge
[gitweb.git] / builtin-blame.c
index 530b97f97d2661d53a39d2101eeadd86be2e6b00..8b6b09b10b8f9dcda0b7224f31c860bb803945f0 100644 (file)
 #include "quote.h"
 #include "xdiff-interface.h"
 #include "cache-tree.h"
+#include "string-list.h"
+#include "mailmap.h"
+#include "parse-options.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"
-"  -b                  Show blank SHA-1 for boundary commits (Default: off)\n"
-"  -l, --long          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"
-"  -f, --show-name     Show original filename (Default: auto)\n"
-"  -n, --show-number   Show original linenumber (Default: off)\n"
-"  -p, --porcelain     Show in a format designed for machine consumption\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"
-"  --contents file     Use <file>'s contents as the final image\n"
-"  -S revs-file        Use revisions from revs-file instead of calling git-rev-list\n";
+static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
+
+static const char *blame_opt_usage[] = {
+       blame_usage,
+       "",
+       "[rev-opts] are documented in git-rev-list(1)",
+       NULL
+};
 
 static int longest_file;
 static int longest_author;
@@ -39,9 +35,12 @@ static int max_orig_digits;
 static int max_digits;
 static int max_score_digits;
 static int show_root;
+static int reverse;
 static int blank_boundary;
 static int incremental;
 static int cmd_is_annotate;
+static int xdl_opts = XDF_NEED_MINIMAL;
+static struct string_list mailmap;
 
 #ifndef DEBUG
 #define DEBUG 0
@@ -55,6 +54,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
@@ -84,18 +84,21 @@ struct origin {
  * Given an origin, prepare mmfile_t structure to be used by the
  * diff machinery
  */
-static char *fill_origin_blob(struct origin *o, mmfile_t *file)
+static void 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)));
+               if (!file->ptr)
+                       die("Cannot read blob %s for path %s",
+                           sha1_to_hex(o->blob_sha1),
+                           o->path);
                o->file = *file;
        }
        else
                *file = o->file;
-       return file->ptr;
 }
 
 /*
@@ -112,13 +115,19 @@ static inline struct origin *origin_incref(struct origin *o)
 static void origin_decref(struct origin *o)
 {
        if (o && --o->refcnt <= 0) {
-               if (o->file.ptr)
-                       free(o->file.ptr);
-               memset(o, 0, sizeof(*o));
+               free(o->file.ptr);
                free(o);
        }
 }
 
+static void drop_origin_blob(struct origin *o)
+{
+       if (o->file.ptr) {
+               free(o->file.ptr);
+               o->file.ptr = NULL;
+       }
+}
+
 /*
  * Each group of lines is described by a blame_entry; it can be split
  * as we pass blame to the parents.  They form a linked list in the
@@ -144,6 +153,10 @@ struct blame_entry {
         */
        char guilty;
 
+       /* true if the entry has been scanned for copies in the current parent
+        */
+       char scanned;
+
        /* the line number of the first line of this group in the
         * suspect's file; internally all line numbers are 0 based.
         */
@@ -161,7 +174,7 @@ struct blame_entry {
 struct scoreboard {
        /* the final commit (i.e. where we started digging from) */
        struct commit *final;
-
+       struct rev_info *revs;
        const char *path;
 
        /*
@@ -180,16 +193,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 +214,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 +275,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 +282,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:
@@ -327,7 +337,7 @@ static struct origin *find_origin(struct scoreboard *sb,
         * same and diff-tree is fairly efficient about this.
         */
        diff_setup(&diff_opts);
-       diff_opts.recursive = 1;
+       DIFF_OPT_SET(&diff_opts, RECURSIVE);
        diff_opts.detect_rename = 0;
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        paths[0] = origin->path;
@@ -372,6 +382,7 @@ static struct origin *find_origin(struct scoreboard *sb,
                }
        }
        diff_flush(&diff_opts);
+       diff_tree_release_paths(&diff_opts);
        if (porigin) {
                /*
                 * Create a freestanding copy that is not part of
@@ -401,7 +412,7 @@ static struct origin *find_rename(struct scoreboard *sb,
        const char *paths[2];
 
        diff_setup(&diff_opts);
-       diff_opts.recursive = 1;
+       DIFF_OPT_SET(&diff_opts, RECURSIVE);
        diff_opts.detect_rename = DIFF_DETECT_RENAME;
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diff_opts.single_follow = origin->path;
@@ -428,6 +439,7 @@ static struct origin *find_rename(struct scoreboard *sb,
                }
        }
        diff_flush(&diff_opts);
+       diff_tree_release_paths(&diff_opts);
        return porigin;
 }
 
@@ -513,9 +525,9 @@ 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;
+       memset(&xecfg, 0, sizeof(xecfg));
        xecfg.ctxlen = context;
-       xecfg.flags = 0;
        ecb.outf = xdiff_outf;
        ecb.priv = &state;
        memset(&state, 0, sizeof(state));
@@ -524,7 +536,7 @@ static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o,
        state.ret->chunks = NULL;
        state.ret->num = 0;
 
-       xdl_diff(file_p, file_o, &xpp, &xecfg, &ecb);
+       xdi_diff(file_p, file_o, &xpp, &xecfg, &ecb);
 
        if (state.ret->num) {
                struct chunk *chunk;
@@ -589,7 +601,7 @@ static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
 
 /*
  * src typically is on-stack; we want to copy the information in it to
- * an malloced blame_entry that is already on the linked list of the
+ * a malloced blame_entry that is already on the linked list of the
  * scoreboard.  The origin of dst loses a refcnt while the origin of src
  * gains one.
  */
@@ -777,7 +789,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 +809,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 +905,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 +970,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 +1012,8 @@ 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) ||
+                           ent_score(sb, e) < blame_move_score)
                                continue;
                        find_copy_in_blob(sb, e, parent, split, &file_p);
                        if (split[1].suspect &&
@@ -997,6 +1038,7 @@ struct blame_list {
  */
 static struct blame_list *setup_blame_list(struct scoreboard *sb,
                                           struct origin *target,
+                                          int min_score,
                                           int *num_ents_p)
 {
        struct blame_entry *e;
@@ -1004,18 +1046,32 @@ 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->scanned && !e->guilty &&
+                   same_suspect(e->suspect, target) &&
+                   min_score < ent_score(sb, e))
                        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->scanned && !e->guilty &&
+                           same_suspect(e->suspect, target) &&
+                           min_score < ent_score(sb, e))
                                blame_list[i++].ent = e;
        }
        *num_ents_p = num_ents;
        return blame_list;
 }
 
+/*
+ * Reset the scanned status on all entries.
+ */
+static void reset_scanned_flag(struct scoreboard *sb)
+{
+       struct blame_entry *e;
+       for (e = sb->ent; e; e = e->next)
+               e->scanned = 0;
+}
+
 /*
  * For lines target is suspected for, see if we can find code movement
  * across file boundary from the parent commit.  porigin is the path
@@ -1034,12 +1090,12 @@ static int find_copy_in_parent(struct scoreboard *sb,
        struct blame_list *blame_list;
        int num_ents;
 
-       blame_list = setup_blame_list(sb, target, &num_ents);
+       blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
        if (!blame_list)
                return 1; /* nothing remains for this target */
 
        diff_setup(&diff_opts);
-       diff_opts.recursive = 1;
+       DIFF_OPT_SET(&diff_opts, RECURSIVE);
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
 
        paths[0] = NULL;
@@ -1054,9 +1110,10 @@ 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)))
-               diff_opts.find_copies_harder = 1;
+       if ((opt & PICKAXE_BLAME_COPY_HARDEST)
+           || ((opt & PICKAXE_BLAME_COPY_HARDER)
+               && (!porigin || strcmp(target->path, porigin->path))))
+               DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
 
        if (is_null_sha1(target->commit->object.sha1))
                do_diff_cache(parent->tree->object.sha1, &diff_opts);
@@ -1065,7 +1122,7 @@ static int find_copy_in_parent(struct scoreboard *sb,
                               target->commit->tree->object.sha1,
                               "", &diff_opts);
 
-       if (!diff_opts.find_copies_harder)
+       if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER))
                diffcore_std(&diff_opts);
 
        retval = 0;
@@ -1107,20 +1164,23 @@ static int find_copy_in_parent(struct scoreboard *sb,
                                split_blame(sb, split, blame_list[j].ent);
                                made_progress = 1;
                        }
+                       else
+                               blame_list[j].ent->scanned = 1;
                        decref_split(split);
                }
                free(blame_list);
 
                if (!made_progress)
                        break;
-               blame_list = setup_blame_list(sb, target, &num_ents);
+               blame_list = setup_blame_list(sb, target, blame_copy_score, &num_ents);
                if (!blame_list) {
                        retval = 1;
                        break;
                }
        }
+       reset_scanned_flag(sb);
        diff_flush(&diff_opts);
-
+       diff_tree_release_paths(&diff_opts);
        return retval;
 }
 
@@ -1139,7 +1199,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);
@@ -1147,18 +1207,48 @@ static void pass_whole_blame(struct scoreboard *sb,
        }
 }
 
-#define MAXPARENT 16
+/*
+ * We pass blame from the current commit to its parents.  We keep saying
+ * "parent" (and "porigin"), but what we mean is to find scapegoat to
+ * exonerate ourselves.
+ */
+static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
+{
+       if (!reverse)
+               return commit->parents;
+       return lookup_decoration(&revs->children, &commit->object);
+}
+
+static int num_scapegoats(struct rev_info *revs, struct commit *commit)
+{
+       int cnt;
+       struct commit_list *l = first_scapegoat(revs, commit);
+       for (cnt = 0; l; l = l->next)
+               cnt++;
+       return cnt;
+}
+
+#define MAXSG 16
 
 static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
 {
-       int i, pass;
+       struct rev_info *revs = sb->revs;
+       int i, pass, num_sg;
        struct commit *commit = origin->commit;
-       struct commit_list *parent;
-       struct origin *parent_origin[MAXPARENT], *porigin;
-
-       memset(parent_origin, 0, sizeof(parent_origin));
+       struct commit_list *sg;
+       struct origin *sg_buf[MAXSG];
+       struct origin *porigin, **sg_origin = sg_buf;
+
+       num_sg = num_scapegoats(revs, commit);
+       if (!num_sg)
+               goto finish;
+       else if (num_sg < ARRAY_SIZE(sg_buf))
+               memset(sg_buf, 0, sizeof(sg_buf));
+       else
+               sg_origin = xcalloc(num_sg, sizeof(*sg_origin));
 
-       /* The first pass looks for unrenamed path to optimize for
+       /*
+        * The first pass looks for unrenamed path to optimize for
         * common cases, then we look for renames in the second pass.
         */
        for (pass = 0; pass < 2; pass++) {
@@ -1166,13 +1256,13 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
                                       struct commit *, struct origin *);
                find = pass ? find_rename : find_origin;
 
-               for (i = 0, parent = commit->parents;
-                    i < MAXPARENT && parent;
-                    parent = parent->next, i++) {
-                       struct commit *p = parent->item;
+               for (i = 0, sg = first_scapegoat(revs, commit);
+                    i < num_sg && sg;
+                    sg = sg->next, i++) {
+                       struct commit *p = sg->item;
                        int j, same;
 
-                       if (parent_origin[i])
+                       if (sg_origin[i])
                                continue;
                        if (parse_commit(p))
                                continue;
@@ -1185,24 +1275,24 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
                                goto finish;
                        }
                        for (j = same = 0; j < i; j++)
-                               if (parent_origin[j] &&
-                                   !hashcmp(parent_origin[j]->blob_sha1,
+                               if (sg_origin[j] &&
+                                   !hashcmp(sg_origin[j]->blob_sha1,
                                             porigin->blob_sha1)) {
                                        same = 1;
                                        break;
                                }
                        if (!same)
-                               parent_origin[i] = porigin;
+                               sg_origin[i] = porigin;
                        else
                                origin_decref(porigin);
                }
        }
 
        num_commits++;
-       for (i = 0, parent = commit->parents;
-            i < MAXPARENT && parent;
-            parent = parent->next, i++) {
-               struct origin *porigin = parent_origin[i];
+       for (i = 0, sg = first_scapegoat(revs, commit);
+            i < num_sg && sg;
+            sg = sg->next, i++) {
+               struct origin *porigin = sg_origin[i];
                if (!porigin)
                        continue;
                if (pass_blame_to_parent(sb, origin, porigin))
@@ -1213,10 +1303,10 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
         * Optionally find moves in parents' files.
         */
        if (opt & PICKAXE_BLAME_MOVE)
-               for (i = 0, parent = commit->parents;
-                    i < MAXPARENT && parent;
-                    parent = parent->next, i++) {
-                       struct origin *porigin = parent_origin[i];
+               for (i = 0, sg = first_scapegoat(revs, commit);
+                    i < num_sg && sg;
+                    sg = sg->next, i++) {
+                       struct origin *porigin = sg_origin[i];
                        if (!porigin)
                                continue;
                        if (find_move_in_parent(sb, origin, porigin))
@@ -1227,18 +1317,25 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
         * Optionally find copies from parents' files.
         */
        if (opt & PICKAXE_BLAME_COPY)
-               for (i = 0, parent = commit->parents;
-                    i < MAXPARENT && parent;
-                    parent = parent->next, i++) {
-                       struct origin *porigin = parent_origin[i];
-                       if (find_copy_in_parent(sb, origin, parent->item,
+               for (i = 0, sg = first_scapegoat(revs, commit);
+                    i < num_sg && sg;
+                    sg = sg->next, i++) {
+                       struct origin *porigin = sg_origin[i];
+                       if (find_copy_in_parent(sb, origin, sg->item,
                                                porigin, opt))
                                goto finish;
                }
 
  finish:
-       for (i = 0; i < MAXPARENT; i++)
-               origin_decref(parent_origin[i]);
+       for (i = 0; i < num_sg; i++) {
+               if (sg_origin[i]) {
+                       drop_origin_blob(sg_origin[i]);
+                       origin_decref(sg_origin[i]);
+               }
+       }
+       drop_origin_blob(origin);
+       if (sg_buf != sg_origin)
+               free(sg_origin);
 }
 
 /*
@@ -1246,29 +1343,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 +1379,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 +1391,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 +1444,13 @@ 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);
+               if (!commit->buffer)
+                       die("Cannot read commit %s",
+                           sha1_to_hex(commit->object.sha1));
        }
        ret->author = author_buf;
        get_ac_line(commit->buffer, "\nauthor ",
@@ -1365,8 +1490,7 @@ static void get_commit_info(struct commit *commit,
 static void write_filename_info(const char *path)
 {
        printf("filename ");
-       write_name_quoted(NULL, 0, path, 1, stdout);
-       putchar('\n');
+       write_name_quoted(path, stdout, '\n');
 }
 
 /*
@@ -1401,6 +1525,7 @@ static void found_guilty_entry(struct blame_entry *ent)
                                printf("boundary\n");
                }
                write_filename_info(suspect->path);
+               maybe_flush_or_die(stdout, "stdout");
        }
 }
 
@@ -1409,8 +1534,10 @@ static void found_guilty_entry(struct blame_entry *ent)
  * is still unknown, pick one blame_entry, and allow its current
  * suspect to pass blames to its parents.
  */
-static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
+static void assign_blame(struct scoreboard *sb, int opt)
 {
+       struct rev_info *revs = sb->revs;
+
        while (1) {
                struct blame_entry *ent;
                struct commit *commit;
@@ -1431,8 +1558,9 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
                commit = suspect->commit;
                if (!commit->object.parsed)
                        parse_commit(commit);
-               if (!(commit->object.flags & UNINTERESTING) &&
-                   !(revs->max_age != -1 && commit->date < revs->max_age))
+               if (reverse ||
+                   (!(commit->object.flags & UNINTERESTING) &&
+                    !(revs->max_age != -1 && commit->date < revs->max_age)))
                        pass_blame(sb, suspect, opt);
                else {
                        commit->object.flags |= UNINTERESTING;
@@ -1445,7 +1573,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 +1614,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 +1709,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 +1816,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;
 }
 
 /*
@@ -1792,7 +1926,7 @@ static void sanity_check_refcnt(struct scoreboard *sb)
  * Used for the command line parsing; check if the path exists
  * in the working tree.
  */
-static int has_path_in_work_tree(const char *path)
+static int has_string_in_work_tree(const char *path)
 {
        struct stat st;
        return !lstat(path, &st);
@@ -1809,9 +1943,7 @@ static unsigned parse_score(const char *arg)
 
 static const char *add_prefix(const char *prefix, const char *path)
 {
-       if (!prefix || !prefix[0])
-               return path;
-       return prefix_path(prefix, strlen(prefix), path);
+       return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
 }
 
 /*
@@ -1911,7 +2043,7 @@ static void prepare_blame_range(struct scoreboard *sb,
                usage(blame_usage);
 }
 
-static int git_blame_config(const char *var, const char *value)
+static int git_blame_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "blame.showroot")) {
                show_root = git_config_bool(var, value);
@@ -1921,19 +2053,21 @@ static int git_blame_config(const char *var, const char *value)
                blank_boundary = git_config_bool(var, value);
                return 0;
        }
-       return git_default_config(var, value);
+       return git_default_config(var, value, cb);
 }
 
+/*
+ * Prepare a dummy commit that represents the work tree (or staged) item.
+ * Note that annotating work tree item never works in the reverse.
+ */
 static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)
 {
        struct commit *commit;
        struct origin *origin;
        unsigned char head_sha1[20];
-       char *buf;
+       struct strbuf buf;
        const char *ident;
-       int fd;
        time_t now;
-       unsigned long fin_size;
        int size, len;
        struct cache_entry *ce;
        unsigned mode;
@@ -1951,9 +2085,11 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
 
        origin = make_origin(commit, path);
 
+       strbuf_init(&buf, 0);
        if (!contents_from || strcmp("-", contents_from)) {
                struct stat st;
                const char *read_from;
+               unsigned long fin_size;
 
                if (contents_from) {
                        if (stat(contents_from, &st) < 0)
@@ -1965,20 +2101,17 @@ 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;
-               buf = xmalloc(fin_size+1);
+               fin_size = xsize_t(st.st_size);
                mode = canon_mode(st.st_mode);
                switch (st.st_mode & S_IFMT) {
                case S_IFREG:
-                       fd = open(read_from, O_RDONLY);
-                       if (fd < 0)
-                               die("cannot open %s", read_from);
-                       if (read_in_full(fd, buf, fin_size) != fin_size)
-                               die("cannot read %s", read_from);
+                       if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
+                               die("cannot open or read %s", read_from);
                        break;
                case S_IFLNK:
-                       if (readlink(read_from, buf, fin_size+1) != fin_size)
+                       if (readlink(read_from, buf.buf, buf.alloc) != fin_size)
                                die("cannot readlink %s", read_from);
+                       buf.len = fin_size;
                        break;
                default:
                        die("unsupported file type %s", read_from);
@@ -1987,26 +2120,14 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
        else {
                /* Reading from stdin */
                contents_from = "standard input";
-               buf = NULL;
-               fin_size = 0;
                mode = 0;
-               while (1) {
-                       ssize_t cnt = 8192;
-                       buf = xrealloc(buf, fin_size + cnt);
-                       cnt = xread(0, buf + fin_size, cnt);
-                       if (cnt < 0)
-                               die("read error %s from stdin",
-                                   strerror(errno));
-                       if (!cnt)
-                               break;
-                       fin_size += cnt;
-               }
-               buf = xrealloc(buf, fin_size + 1);
+               if (strbuf_read(&buf, 0, 0) < 0)
+                       die("read error %s from stdin", strerror(errno));
        }
-       buf[fin_size] = 0;
-       origin->file.ptr = buf;
-       origin->file.size = fin_size;
-       pretend_sha1_file(buf, fin_size, blob_type, origin->blob_sha1);
+       convert_to_git(path, buf.buf, buf.len, &buf, 0);
+       origin->file.ptr = buf.buf;
+       origin->file.size = buf.len;
+       pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_sha1);
        commit->util = origin;
 
        /*
@@ -2022,7 +2143,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
        if (!mode) {
                int pos = cache_name_pos(path, len);
                if (0 <= pos)
-                       mode = ntohl(active_cache[pos]->ce_mode);
+                       mode = active_cache[pos]->ce_mode;
                else
                        /* Let's not bother reading from HEAD tree */
                        mode = S_IFREG | 0644;
@@ -2044,7 +2165,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"
@@ -2055,6 +2176,108 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
        return commit;
 }
 
+static const char *prepare_final(struct scoreboard *sb)
+{
+       int i;
+       const char *final_commit_name = NULL;
+       struct rev_info *revs = sb->revs;
+
+       /*
+        * There must be one and only one positive commit in the
+        * revs->pending array.
+        */
+       for (i = 0; i < revs->pending.nr; i++) {
+               struct object *obj = revs->pending.objects[i].item;
+               if (obj->flags & UNINTERESTING)
+                       continue;
+               while (obj->type == OBJ_TAG)
+                       obj = deref_tag(obj, NULL, 0);
+               if (obj->type != OBJ_COMMIT)
+                       die("Non commit %s?", revs->pending.objects[i].name);
+               if (sb->final)
+                       die("More than one commit to dig from %s and %s?",
+                           revs->pending.objects[i].name,
+                           final_commit_name);
+               sb->final = (struct commit *) obj;
+               final_commit_name = revs->pending.objects[i].name;
+       }
+       return final_commit_name;
+}
+
+static const char *prepare_initial(struct scoreboard *sb)
+{
+       int i;
+       const char *final_commit_name = NULL;
+       struct rev_info *revs = sb->revs;
+
+       /*
+        * There must be one and only one negative commit, and it must be
+        * the boundary.
+        */
+       for (i = 0; i < revs->pending.nr; i++) {
+               struct object *obj = revs->pending.objects[i].item;
+               if (!(obj->flags & UNINTERESTING))
+                       continue;
+               while (obj->type == OBJ_TAG)
+                       obj = deref_tag(obj, NULL, 0);
+               if (obj->type != OBJ_COMMIT)
+                       die("Non commit %s?", revs->pending.objects[i].name);
+               if (sb->final)
+                       die("More than one commit to dig down to %s and %s?",
+                           revs->pending.objects[i].name,
+                           final_commit_name);
+               sb->final = (struct commit *) obj;
+               final_commit_name = revs->pending.objects[i].name;
+       }
+       if (!final_commit_name)
+               die("No commit to dig down to?");
+       return final_commit_name;
+}
+
+static int blame_copy_callback(const struct option *option, const char *arg, int unset)
+{
+       int *opt = option->value;
+
+       /*
+        * -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;
+
+       if (arg)
+               blame_copy_score = parse_score(arg);
+       return 0;
+}
+
+static int blame_move_callback(const struct option *option, const char *arg, int unset)
+{
+       int *opt = option->value;
+
+       *opt |= PICKAXE_BLAME_MOVE;
+
+       if (arg)
+               blame_move_score = parse_score(arg);
+       return 0;
+}
+
+static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
+{
+       const char **bottomtop = option->value;
+       if (!arg)
+               return -1;
+       if (*bottomtop)
+               die("More than one '-L n,m' option given");
+       *bottomtop = arg;
+       return 0;
+}
+
 int cmd_blame(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
@@ -2062,92 +2285,66 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        struct scoreboard sb;
        struct origin *o;
        struct blame_entry *ent;
-       int i, seen_dashdash, unk, opt;
-       long bottom, top, lno;
-       int output_option = 0;
-       int show_stats = 0;
-       const char *revs_file = NULL;
+       long dashdash_pos, bottom, top, lno;
        const char *final_commit_name = NULL;
-       char type[10];
-       const char *bottomtop = NULL;
-       const char *contents_from = NULL;
+       enum object_type type;
+
+       static const char *bottomtop = NULL;
+       static int output_option = 0, opt = 0;
+       static int show_stats = 0;
+       static const char *revs_file = NULL;
+       static const char *contents_from = NULL;
+       static const struct option options[] = {
+               OPT_BOOLEAN(0, "incremental", &incremental, "Show blame entries as we find them, incrementally"),
+               OPT_BOOLEAN('b', NULL, &blank_boundary, "Show blank SHA-1 for boundary commits (Default: off)"),
+               OPT_BOOLEAN(0, "root", &show_root, "Do not treat root commits as boundaries (Default: off)"),
+               OPT_BOOLEAN(0, "show-stats", &show_stats, "Show work cost statistics"),
+               OPT_BIT(0, "score-debug", &output_option, "Show output score for blame entries", OUTPUT_SHOW_SCORE),
+               OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME),
+               OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER),
+               OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN),
+               OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT),
+               OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
+               OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
+               OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
+               OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
+               OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
+               OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
+               { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },
+               { OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback },
+               OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback),
+               OPT_END()
+       };
+
+       struct parse_opt_ctx_t ctx;
 
        cmd_is_annotate = !strcmp(argv[0], "annotate");
 
-       git_config(git_blame_config);
+       git_config(git_blame_config, NULL);
+       init_revisions(&revs, NULL);
        save_commit_buffer = 0;
-
-       opt = 0;
-       seen_dashdash = 0;
-       for (unk = i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (*arg != '-')
-                       break;
-               else if (!strcmp("-b", arg))
-                       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) && ++i < argc)
-                       revs_file = argv[i];
-               else if (!prefixcmp(arg, "-M")) {
-                       opt |= PICKAXE_BLAME_MOVE;
-                       blame_move_score = parse_score(arg+2);
-               }
-               else if (!prefixcmp(arg, "-C")) {
-                       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 (!prefixcmp(arg, "-L")) {
-                       if (!arg[2]) {
-                               if (++i >= argc)
-                                       usage(blame_usage);
-                               arg = argv[i];
-                       }
-                       else
-                               arg += 2;
-                       if (bottomtop)
-                               die("More than one '-L n,m' option given");
-                       bottomtop = arg;
+       dashdash_pos = 0;
+
+       parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH |
+                           PARSE_OPT_KEEP_ARGV0);
+       for (;;) {
+               switch (parse_options_step(&ctx, options, blame_opt_usage)) {
+               case PARSE_OPT_HELP:
+                       exit(129);
+               case PARSE_OPT_DONE:
+                       if (ctx.argv[0])
+                               dashdash_pos = ctx.cpidx;
+                       goto parse_done;
                }
-               else if (!strcmp("--contents", arg)) {
-                       if (++i >= argc)
-                               usage(blame_usage);
-                       contents_from = argv[i];
-               }
-               else if (!strcmp("--incremental", arg))
-                       incremental = 1;
-               else if (!strcmp("--score-debug", arg))
-                       output_option |= OUTPUT_SHOW_SCORE;
-               else if (!strcmp("-f", arg) ||
-                        !strcmp("--show-name", arg))
-                       output_option |= OUTPUT_SHOW_NAME;
-               else if (!strcmp("-n", arg) ||
-                        !strcmp("--show-number", arg))
-                       output_option |= OUTPUT_SHOW_NUMBER;
-               else if (!strcmp("-p", arg) ||
-                        !strcmp("--porcelain", arg))
-                       output_option |= OUTPUT_PORCELAIN;
-               else if (!strcmp("--", arg)) {
-                       seen_dashdash = 1;
-                       i++;
-                       break;
+
+               if (!strcmp(ctx.argv[0], "--reverse")) {
+                       ctx.argv[0] = "--children";
+                       reverse = 1;
                }
-               else
-                       argv[unk++] = arg;
+               parse_revision_opt(&revs, &ctx, options, blame_opt_usage);
        }
-
-       if (!incremental)
-               setup_pager();
+parse_done:
+       argc = parse_options_end(&ctx);
 
        if (!blame_move_score)
                blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
@@ -2161,114 +2358,59 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
         *
         * The remaining are:
         *
-        * (1) if seen_dashdash, its either
-        *     "-options -- <path>" or
-        *     "-options -- <path> <rev>".
-        *     but the latter is allowed only if there is no
-        *     options that we passed to revision machinery.
+        * (1) if dashdash_pos != 0, its either
+        *     "blame [revisions] -- <path>" or
+        *     "blame -- <path> <rev>"
         *
-        * (2) otherwise, we may have "--" somewhere later and
-        *     might be looking at the first one of multiple 'rev'
-        *     parameters (e.g. " master ^next ^maint -- path").
-        *     See if there is a dashdash first, and give the
-        *     arguments before that to revision machinery.
-        *     After that there must be one 'path'.
+        * (2) otherwise, its one of the two:
+        *     "blame [revisions] <path>"
+        *     "blame <path> <rev>"
         *
-        * (3) otherwise, its one of the three:
-        *     "-options <path> <rev>"
-        *     "-options <rev> <path>"
-        *     "-options <path>"
-        *     but again the first one is allowed only if
-        *     there is no options that we passed to revision
-        *     machinery.
+        * Note that we must strip out <path> from the arguments: we do not
+        * want the path pruning but we may want "bottom" processing.
         */
-
-       if (seen_dashdash) {
-               /* (1) */
-               if (argc <= i)
-                       usage(blame_usage);
-               path = add_prefix(prefix, argv[i]);
-               if (i + 1 == argc - 1) {
-                       if (unk != 1)
-                               usage(blame_usage);
-                       argv[unk++] = argv[i + 1];
+       if (dashdash_pos) {
+               switch (argc - dashdash_pos - 1) {
+               case 2: /* (1b) */
+                       if (argc != 4)
+                               usage_with_options(blame_opt_usage, options);
+                       /* reorder for the new way: <rev> -- <path> */
+                       argv[1] = argv[3];
+                       argv[3] = argv[2];
+                       argv[2] = "--";
+                       /* FALLTHROUGH */
+               case 1: /* (1a) */
+                       path = add_prefix(prefix, argv[--argc]);
+                       argv[argc] = NULL;
+                       break;
+               default:
+                       usage_with_options(blame_opt_usage, options);
                }
-               else if (i + 1 != argc)
-                       /* garbage at end */
-                       usage(blame_usage);
-       }
-       else {
-               int j;
-               for (j = i; !seen_dashdash && j < argc; j++)
-                       if (!strcmp(argv[j], "--"))
-                               seen_dashdash = j;
-               if (seen_dashdash) {
-                       /* (2) */
-                       if (seen_dashdash + 1 != argc - 1)
-                               usage(blame_usage);
-                       path = add_prefix(prefix, argv[seen_dashdash + 1]);
-                       for (j = i; j < seen_dashdash; j++)
-                               argv[unk++] = argv[j];
+       } else {
+               if (argc < 2)
+                       usage_with_options(blame_opt_usage, options);
+               path = add_prefix(prefix, argv[argc - 1]);
+               if (argc == 3 && !has_string_in_work_tree(path)) { /* (2b) */
+                       path = add_prefix(prefix, argv[1]);
+                       argv[1] = argv[2];
                }
-               else {
-                       /* (3) */
-                       if (argc <= i)
-                               usage(blame_usage);
-                       path = add_prefix(prefix, argv[i]);
-                       if (i + 1 == argc - 1) {
-                               final_commit_name = argv[i + 1];
-
-                               /* if (unk == 1) we could be getting
-                                * old-style
-                                */
-                               if (unk == 1 && !has_path_in_work_tree(path)) {
-                                       path = add_prefix(prefix, argv[i + 1]);
-                                       final_commit_name = argv[i];
-                               }
-                       }
-                       else if (i != argc - 1)
-                               usage(blame_usage); /* garbage at end */
+               argv[argc - 1] = "--";
 
-                       if (!has_path_in_work_tree(path))
-                               die("cannot stat path %s: %s",
-                                   path, strerror(errno));
-               }
+               setup_work_tree();
+               if (!has_string_in_work_tree(path))
+                       die("cannot stat path %s: %s", path, strerror(errno));
        }
 
-       if (final_commit_name)
-               argv[unk++] = final_commit_name;
-
-       /*
-        * Now we got rev and path.  We do not want the path pruning
-        * but we may want "bottom" processing.
-        */
-       argv[unk++] = "--"; /* terminate the rev name */
-       argv[unk] = NULL;
-
-       init_revisions(&revs, NULL);
-       setup_revisions(unk, argv, &revs, NULL);
+       setup_revisions(argc, argv, &revs, NULL);
        memset(&sb, 0, sizeof(sb));
 
-       /*
-        * There must be one and only one positive commit in the
-        * revs->pending array.
-        */
-       for (i = 0; i < revs.pending.nr; i++) {
-               struct object *obj = revs.pending.objects[i].item;
-               if (obj->flags & UNINTERESTING)
-                       continue;
-               while (obj->type == OBJ_TAG)
-                       obj = deref_tag(obj, NULL, 0);
-               if (obj->type != OBJ_COMMIT)
-                       die("Non commit %s?",
-                           revs.pending.objects[i].name);
-               if (sb.final)
-                       die("More than one commit to dig from %s and %s?",
-                           revs.pending.objects[i].name,
-                           final_commit_name);
-               sb.final = (struct commit *) obj;
-               final_commit_name = revs.pending.objects[i].name;
-       }
+       sb.revs = &revs;
+       if (!reverse)
+               final_commit_name = prepare_final(&sb);
+       else if (contents_from)
+               die("--contents and --children do not blend well.");
+       else
+               final_commit_name = prepare_initial(&sb);
 
        if (!sb.final) {
                /*
@@ -2276,6 +2418,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                 * do not default to HEAD, but use the working tree
                 * or "--contents".
                 */
+               setup_work_tree();
                sb.final = fake_working_tree_commit(path, contents_from);
                add_pending_object(&revs, &(sb.final->object), ":");
        }
@@ -2287,7 +2430,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
         * bottom commits we would reach while traversing as
         * uninteresting.
         */
-       prepare_revision_walk(&revs);
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
 
        if (is_null_sha1(sb.final->object.sha1)) {
                char *buf;
@@ -2302,8 +2446,12 @@ 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);
+               if (!sb.final_buf)
+                       die("Cannot read blob %s for path %s",
+                           sha1_to_hex(o->blob_sha1),
+                           path);
        }
        num_read_blob++;
        lno = prepare_lines(&sb);
@@ -2336,7 +2484,12 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                die("reading graft file %s failed: %s",
                    revs_file, strerror(errno));
 
-       assign_blame(&sb, &revs, opt);
+       read_mailmap(&mailmap, ".mailmap", NULL);
+
+       if (!incremental)
+               setup_pager();
+
+       assign_blame(&sb, opt);
 
        if (incremental)
                return 0;