git: submodule honor -c credential.* from command line
[gitweb.git] / builtin / blame.c
index b5474690153bae66727de6f4d44d6e395eddb9e4..55bf5fae9d5d6ea6c4e058e50d9353e77e09b34b 100644 (file)
@@ -6,6 +6,7 @@
  */
 
 #include "cache.h"
+#include "refs.h"
 #include "builtin.h"
 #include "blob.h"
 #include "commit.h"
 #include "userdiff.h"
 #include "line-range.h"
 #include "line-log.h"
+#include "dir.h"
+#include "progress.h"
 
-static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file");
+static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>");
 
 static const char *blame_opt_usage[] = {
        blame_usage,
        "",
-       N_("[rev-opts] are documented in git-rev-list(1)"),
+       N_("<rev-opts> are documented in git-rev-list(1)"),
        NULL
 };
 
@@ -48,8 +51,9 @@ static int incremental;
 static int xdl_opts;
 static int abbrev = -1;
 static int no_whole_file_rename;
+static int show_progress;
 
-static enum date_mode blame_date_mode = DATE_ISO8601;
+static struct date_mode blame_date_mode = { DATE_ISO8601 };
 static size_t blame_date_width;
 
 static struct string_list mailmap;
@@ -125,6 +129,11 @@ struct origin {
        char path[FLEX_ARRAY];
 };
 
+struct progress_info {
+       struct progress *progress;
+       int blamed_lines;
+};
+
 static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, long ctxlen,
                      xdl_emit_hunk_consume_func_t hunk_func, void *cb_data)
 {
@@ -457,12 +466,13 @@ static void queue_blames(struct scoreboard *sb, struct origin *porigin,
 static struct origin *make_origin(struct commit *commit, const char *path)
 {
        struct origin *o;
-       o = xcalloc(1, sizeof(*o) + strlen(path) + 1);
+       size_t pathlen = strlen(path) + 1;
+       o = xcalloc(1, sizeof(*o) + pathlen);
        o->commit = commit;
        o->refcnt = 1;
        o->next = commit->util;
        commit->util = o;
-       strcpy(o->path, path);
+       memcpy(o->path, path, pathlen); /* includes NUL */
        return o;
 }
 
@@ -503,7 +513,7 @@ static int fill_blob_sha1_and_mode(struct origin *origin)
 {
        if (!is_null_sha1(origin->blob_sha1))
                return 0;
-       if (get_tree_entry(origin->commit->object.sha1,
+       if (get_tree_entry(origin->commit->object.oid.hash,
                           origin->path,
                           origin->blob_sha1, &origin->mode))
                goto error_out;
@@ -554,11 +564,11 @@ static struct origin *find_origin(struct scoreboard *sb,
                       PATHSPEC_LITERAL_PATH, "", paths);
        diff_setup_done(&diff_opts);
 
-       if (is_null_sha1(origin->commit->object.sha1))
-               do_diff_cache(parent->tree->object.sha1, &diff_opts);
+       if (is_null_oid(&origin->commit->object.oid))
+               do_diff_cache(parent->tree->object.oid.hash, &diff_opts);
        else
-               diff_tree_sha1(parent->tree->object.sha1,
-                              origin->commit->tree->object.sha1,
+               diff_tree_sha1(parent->tree->object.oid.hash,
+                              origin->commit->tree->object.oid.hash,
                               "", &diff_opts);
        diffcore_std(&diff_opts);
 
@@ -624,11 +634,11 @@ static struct origin *find_rename(struct scoreboard *sb,
        diff_opts.single_follow = origin->path;
        diff_setup_done(&diff_opts);
 
-       if (is_null_sha1(origin->commit->object.sha1))
-               do_diff_cache(parent->tree->object.sha1, &diff_opts);
+       if (is_null_oid(&origin->commit->object.oid))
+               do_diff_cache(parent->tree->object.oid.hash, &diff_opts);
        else
-               diff_tree_sha1(parent->tree->object.sha1,
-                              origin->commit->tree->object.sha1,
+               diff_tree_sha1(parent->tree->object.oid.hash,
+                              origin->commit->tree->object.oid.hash,
                               "", &diff_opts);
        diffcore_std(&diff_opts);
 
@@ -972,7 +982,10 @@ static void pass_blame_to_parent(struct scoreboard *sb,
        fill_origin_blob(&sb->revs->diffopt, target, &file_o);
        num_get_patch++;
 
-       diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d);
+       if (diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d))
+               die("unable to generate diff (%s -> %s)",
+                   oid_to_hex(&parent->commit->object.oid),
+                   oid_to_hex(&target->commit->object.oid));
        /* The rest are the same as the parent */
        blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent);
        *d.dstq = NULL;
@@ -1118,7 +1131,9 @@ static void find_copy_in_blob(struct scoreboard *sb,
         * file_p partially may match that image.
         */
        memset(split, 0, sizeof(struct blame_entry [3]));
-       diff_hunks(file_p, &file_o, 1, handle_split_cb, &d);
+       if (diff_hunks(file_p, &file_o, 1, handle_split_cb, &d))
+               die("unable to generate diff (%s)",
+                   oid_to_hex(&parent->commit->object.oid));
        /* remainder, if any, all match the preimage */
        handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
 }
@@ -1267,11 +1282,11 @@ static void find_copy_in_parent(struct scoreboard *sb,
                && (!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);
+       if (is_null_oid(&target->commit->object.oid))
+               do_diff_cache(parent->tree->object.oid.hash, &diff_opts);
        else
-               diff_tree_sha1(parent->tree->object.sha1,
-                              target->commit->tree->object.sha1,
+               diff_tree_sha1(parent->tree->object.oid.hash,
+                              target->commit->tree->object.oid.hash,
                               "", &diff_opts);
 
        if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER))
@@ -1364,8 +1379,15 @@ static void pass_whole_blame(struct scoreboard *sb,
  */
 static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit)
 {
-       if (!reverse)
+       if (!reverse) {
+               if (revs->first_parent_only &&
+                   commit->parents &&
+                   commit->parents->next) {
+                       free_commit_list(commit->parents->next);
+                       commit->parents->next = NULL;
+               }
                return commit->parents;
+       }
        return lookup_decoration(&revs->children, &commit->object);
 }
 
@@ -1675,7 +1697,7 @@ static void get_commit_info(struct commit *commit,
        if (len)
                strbuf_add(&ret->summary, subject, len);
        else
-               strbuf_addf(&ret->summary, "(%s)", sha1_to_hex(commit->object.sha1));
+               strbuf_addf(&ret->summary, "(%s)", oid_to_hex(&commit->object.oid));
 
        unuse_commit_buffer(commit, message);
 }
@@ -1718,7 +1740,7 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat)
                printf("boundary\n");
        if (suspect->previous) {
                struct origin *prev = suspect->previous;
-               printf("previous %s ", sha1_to_hex(prev->commit->object.sha1));
+               printf("previous %s ", oid_to_hex(&prev->commit->object.oid));
                write_name_quoted(prev->path, stdout, '\n');
        }
 
@@ -1731,18 +1753,21 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat)
  * The blame_entry is found to be guilty for the range.
  * Show it in incremental output.
  */
-static void found_guilty_entry(struct blame_entry *ent)
+static void found_guilty_entry(struct blame_entry *ent,
+                          struct progress_info *pi)
 {
        if (incremental) {
                struct origin *suspect = ent->suspect;
 
                printf("%s %d %d %d\n",
-                      sha1_to_hex(suspect->commit->object.sha1),
+                      oid_to_hex(&suspect->commit->object.oid),
                       ent->s_lno + 1, ent->lno + 1, ent->num_lines);
                emit_one_suspect_detail(suspect, 0);
                write_filename_info(suspect->path);
                maybe_flush_or_die(stdout, "stdout");
        }
+       pi->blamed_lines += ent->num_lines;
+       display_progress(pi->progress, pi->blamed_lines);
 }
 
 /*
@@ -1753,6 +1778,11 @@ static void assign_blame(struct scoreboard *sb, int opt)
 {
        struct rev_info *revs = sb->revs;
        struct commit *commit = prio_queue_get(&sb->commits);
+       struct progress_info pi = { NULL, 0 };
+
+       if (show_progress)
+               pi.progress = start_progress_delay(_("Blaming lines"),
+                                                  sb->num_lines, 50, 1);
 
        while (commit) {
                struct blame_entry *ent;
@@ -1794,7 +1824,7 @@ static void assign_blame(struct scoreboard *sb, int opt)
                        suspect->guilty = 1;
                        for (;;) {
                                struct blame_entry *next = ent->next;
-                               found_guilty_entry(ent);
+                               found_guilty_entry(ent, &pi);
                                if (next) {
                                        ent = next;
                                        continue;
@@ -1810,6 +1840,8 @@ static void assign_blame(struct scoreboard *sb, int opt)
                if (DEBUG) /* sanity */
                        sanity_check_refcnt(sb);
        }
+
+       stop_progress(&pi.progress);
 }
 
 static const char *format_time(unsigned long time, const char *tz_str,
@@ -1826,7 +1858,7 @@ static const char *format_time(unsigned long time, const char *tz_str,
                size_t time_width;
                int tz;
                tz = atoi(tz_str);
-               time_str = show_date(time, tz, blame_date_mode);
+               time_str = show_date(time, tz, &blame_date_mode);
                strbuf_addstr(&time_buf, time_str);
                /*
                 * Add space paddings to time_buf to display a fixed width
@@ -1865,9 +1897,9 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
        int cnt;
        const char *cp;
        struct origin *suspect = ent->suspect;
-       char hex[41];
+       char hex[GIT_SHA1_HEXSZ + 1];
 
-       strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+       sha1_to_hex_r(hex, suspect->commit->object.oid.hash);
        printf("%s %d %d %d\n",
               hex,
               ent->s_lno + 1,
@@ -1903,11 +1935,11 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
        const char *cp;
        struct origin *suspect = ent->suspect;
        struct commit_info ci;
-       char hex[41];
+       char hex[GIT_SHA1_HEXSZ + 1];
        int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
 
        get_commit_info(suspect->commit, &ci, 1);
-       strcpy(hex, sha1_to_hex(suspect->commit->object.sha1));
+       sha1_to_hex_r(hex, suspect->commit->object.oid.hash);
 
        cp = nth_line(sb, ent->lno);
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
@@ -2062,7 +2094,7 @@ static int read_ancestry(const char *graft_file)
 
 static int update_auto_abbrev(int auto_abbrev, struct origin *suspect)
 {
-       const char *uniq = find_unique_abbrev(suspect->commit->object.sha1,
+       const char *uniq = find_unique_abbrev(suspect->commit->object.oid.hash,
                                              auto_abbrev);
        int len = strlen(uniq);
        if (auto_abbrev < len)
@@ -2085,7 +2117,6 @@ static void find_alignment(struct scoreboard *sb, int *option)
 
        for (e = sb->ent; e; e = e->next) {
                struct origin *suspect = e->suspect;
-               struct commit_info ci;
                int num;
 
                if (compute_auto_abbrev)
@@ -2096,6 +2127,7 @@ static void find_alignment(struct scoreboard *sb, int *option)
                if (longest_file < num)
                        longest_file = num;
                if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
+                       struct commit_info ci;
                        suspect->commit->object.flags |= METAINFO_SHOWN;
                        get_commit_info(suspect->commit, &ci, 1);
                        if (*option & OUTPUT_SHOW_EMAIL)
@@ -2104,6 +2136,7 @@ static void find_alignment(struct scoreboard *sb, int *option)
                                num = utf8_strwidth(ci.author.buf);
                        if (longest_author < num)
                                longest_author = num;
+                       commit_info_destroy(&ci);
                }
                num = e->s_lno + e->num_lines;
                if (longest_src_lines < num)
@@ -2113,8 +2146,6 @@ static void find_alignment(struct scoreboard *sb, int *option)
                        longest_dst_lines = num;
                if (largest_score < ent_score(sb, e))
                        largest_score = ent_score(sb, e);
-
-               commit_info_destroy(&ci);
        }
        max_orig_digits = decimal_width(longest_src_lines);
        max_digits = decimal_width(longest_dst_lines);
@@ -2139,7 +2170,7 @@ static void sanity_check_refcnt(struct scoreboard *sb)
                if (ent->suspect->refcnt <= 0) {
                        fprintf(stderr, "%s in %s has negative refcnt %d\n",
                                ent->suspect->path,
-                               sha1_to_hex(ent->suspect->commit->object.sha1),
+                               oid_to_hex(&ent->suspect->commit->object.oid),
                                ent->suspect->refcnt);
                        baa = 1;
                }
@@ -2152,16 +2183,6 @@ 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_string_in_work_tree(const char *path)
-{
-       struct stat st;
-       return !lstat(path, &st);
-}
-
 static unsigned parse_score(const char *arg)
 {
        char *end;
@@ -2186,10 +2207,18 @@ static int git_blame_config(const char *var, const char *value, void *cb)
                blank_boundary = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "blame.showemail")) {
+               int *output_option = cb;
+               if (git_config_bool(var, value))
+                       *output_option |= OUTPUT_SHOW_EMAIL;
+               else
+                       *output_option &= ~OUTPUT_SHOW_EMAIL;
+               return 0;
+       }
        if (!strcmp(var, "blame.date")) {
                if (!value)
                        return config_error_nonbool(var);
-               blame_date_mode = parse_date_format(value);
+               parse_date_format(value, &blame_date_mode);
                return 0;
        }
 
@@ -2204,7 +2233,7 @@ static void verify_working_tree_path(struct commit *work_tree, const char *path)
        struct commit_list *parents;
 
        for (parents = work_tree->parents; parents; parents = parents->next) {
-               const unsigned char *commit_sha1 = parents->item->object.sha1;
+               const unsigned char *commit_sha1 = parents->item->object.oid.hash;
                unsigned char blob_sha1[20];
                unsigned mode;
 
@@ -2228,20 +2257,19 @@ static struct commit_list **append_parent(struct commit_list **tail, const unsig
 static void append_merge_parents(struct commit_list **tail)
 {
        int merge_head;
-       const char *merge_head_file = git_path("MERGE_HEAD");
        struct strbuf line = STRBUF_INIT;
 
-       merge_head = open(merge_head_file, O_RDONLY);
+       merge_head = open(git_path_merge_head(), O_RDONLY);
        if (merge_head < 0) {
                if (errno == ENOENT)
                        return;
-               die("cannot open '%s' for reading", merge_head_file);
+               die("cannot open '%s' for reading", git_path_merge_head());
        }
 
        while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) {
                unsigned char sha1[20];
                if (line.len < 40 || get_sha1_hex(line.buf, sha1))
-                       die("unknown line in '%s': %s", merge_head_file, line.buf);
+                       die("unknown line in '%s': %s", git_path_merge_head(), line.buf);
                tail = append_parent(tail, sha1);
        }
        close(merge_head);
@@ -2299,7 +2327,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n");
        for (parent = commit->parents; parent; parent = parent->next)
                strbuf_addf(&msg, "parent %s\n",
-                           sha1_to_hex(parent->item->object.sha1));
+                           oid_to_hex(&parent->item->object.oid));
        strbuf_addf(&msg,
                    "author %s\n"
                    "committer %s\n\n"
@@ -2349,6 +2377,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
                if (strbuf_read(&buf, 0, 0) < 0)
                        die_errno("failed to read from stdin");
        }
+       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);
@@ -2380,26 +2409,18 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        ce->ce_mode = create_ce_mode(mode);
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
 
-       /*
-        * We are not going to write this out, so this does not matter
-        * right now, but someday we might optimize diff-index --cached
-        * with cache-tree information.
-        */
        cache_tree_invalidate_path(&the_index, path);
 
        return commit;
 }
 
-static const char *prepare_final(struct scoreboard *sb)
+static struct commit *find_single_final(struct rev_info *revs,
+                                       const char **name_p)
 {
        int i;
-       const char *final_commit_name = NULL;
-       struct rev_info *revs = sb->revs;
+       struct commit *found = NULL;
+       const char *name = NULL;
 
-       /*
-        * 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)
@@ -2408,17 +2429,25 @@ static const char *prepare_final(struct scoreboard *sb)
                        obj = deref_tag(obj, NULL, 0);
                if (obj->type != OBJ_COMMIT)
                        die("Non commit %s?", revs->pending.objects[i].name);
-               if (sb->final)
+               if (found)
                        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;
+                           revs->pending.objects[i].name, name);
+               found = (struct commit *)obj;
+               name = revs->pending.objects[i].name;
        }
-       return final_commit_name;
+       if (name_p)
+               *name_p = name;
+       return found;
 }
 
-static const char *prepare_initial(struct scoreboard *sb)
+static char *prepare_final(struct scoreboard *sb)
+{
+       const char *name;
+       sb->final = find_single_final(sb->revs, &name);
+       return xstrdup_or_null(name);
+}
+
+static char *prepare_initial(struct scoreboard *sb)
 {
        int i;
        const char *final_commit_name = NULL;
@@ -2445,7 +2474,7 @@ static const char *prepare_initial(struct scoreboard *sb)
        }
        if (!final_commit_name)
                die("No commit to dig down to?");
-       return final_commit_name;
+       return xstrdup(final_commit_name);
 }
 
 static int blame_copy_callback(const struct option *option, const char *arg, int unset)
@@ -2489,8 +2518,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        struct origin *o;
        struct blame_entry *ent = NULL;
        long dashdash_pos, lno;
-       const char *final_commit_name = NULL;
+       char *final_commit_name = NULL;
        enum object_type type;
+       struct commit *final_commit = NULL;
 
        static struct string_list range_list;
        static int output_option = 0, opt = 0;
@@ -2502,6 +2532,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                OPT_BOOL('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")),
                OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")),
                OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")),
+               OPT_BOOL(0, "progress", &show_progress, N_("Force progress reporting")),
                OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE),
                OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME),
                OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER),
@@ -2529,7 +2560,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        unsigned int range_i;
        long anchor;
 
-       git_config(git_blame_config, NULL);
+       git_config(git_blame_config, &output_option);
        init_revisions(&revs, NULL);
        revs.date_mode = blame_date_mode;
        DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV);
@@ -2537,6 +2568,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 
        save_commit_buffer = 0;
        dashdash_pos = 0;
+       show_progress = -1;
 
        parse_options_start(&ctx, argc, argv, prefix, options,
                            PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
@@ -2561,6 +2593,13 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES);
        argc = parse_options_end(&ctx);
 
+       if (incremental || (output_option & OUTPUT_PORCELAIN)) {
+               if (show_progress > 0)
+                       die("--progress can't be used with --incremental or porcelain formats");
+               show_progress = 0;
+       } else if (show_progress < 0)
+               show_progress = isatty(2);
+
        if (0 < abbrev)
                /* one more abbrev length is needed for the boundary commit */
                abbrev++;
@@ -2570,13 +2609,13 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 
        if (cmd_is_annotate) {
                output_option |= OUTPUT_ANNOTATE_COMPAT;
-               blame_date_mode = DATE_ISO8601;
+               blame_date_mode.type = DATE_ISO8601;
        } else {
                blame_date_mode = revs.date_mode;
        }
 
        /* The maximum width used to show the dates */
-       switch (blame_date_mode) {
+       switch (blame_date_mode.type) {
        case DATE_RFC2822:
                blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700");
                break;
@@ -2601,10 +2640,12 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                   fewer display columns. */
                blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */
                break;
-       case DATE_LOCAL:
        case DATE_NORMAL:
                blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
                break;
+       case DATE_STRFTIME:
+               blame_date_width = strlen(show_date(0, 0, &blame_date_mode)) + 1; /* add the null */
+               break;
        }
        blame_date_width -= 1; /* strip the null */
 
@@ -2656,14 +2697,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                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) */
+               if (argc == 3 && !file_exists(path)) { /* (2b) */
                        path = add_prefix(prefix, argv[1]);
                        argv[1] = argv[2];
                }
                argv[argc - 1] = "--";
 
                setup_work_tree();
-               if (!has_string_in_work_tree(path))
+               if (!file_exists(path))
                        die_errno("cannot stat path '%s'", path);
        }
 
@@ -2681,6 +2722,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        else {
                final_commit_name = prepare_initial(&sb);
                sb.commits.compare = compare_commits_by_reverse_commit_date;
+               if (revs.first_parent_only)
+                       revs.children.name = NULL;
        }
 
        if (!sb.final) {
@@ -2697,6 +2740,12 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        else if (contents_from)
                die("Cannot use --contents with final commit object name");
 
+       if (reverse && revs.first_parent_only) {
+               final_commit = find_single_final(sb.revs, NULL);
+               if (!final_commit)
+                       die("--reverse and --first-parent together require specified latest commit");
+       }
+
        /*
         * If we have bottom, this will mark the ancestors of the
         * bottom commits we would reach while traversing as
@@ -2705,7 +2754,26 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        if (prepare_revision_walk(&revs))
                die(_("revision walk setup failed"));
 
-       if (is_null_sha1(sb.final->object.sha1)) {
+       if (reverse && revs.first_parent_only) {
+               struct commit *c = final_commit;
+
+               sb.revs->children.name = "children";
+               while (c->parents &&
+                      oidcmp(&c->object.oid, &sb.final->object.oid)) {
+                       struct commit_list *l = xcalloc(1, sizeof(*l));
+
+                       l->item = c;
+                       if (add_decoration(&sb.revs->children,
+                                          &c->parents->item->object, l))
+                               die("BUG: not unique item in first-parent chain");
+                       c = c->parents->item;
+               }
+
+               if (oidcmp(&c->object.oid, &sb.final->object.oid))
+                       die("--reverse --first-parent together require range along first-parent chain");
+       }
+
+       if (is_null_oid(&sb.final->object.oid)) {
                o = sb.final->util;
                sb.final_buf = xmemdupz(o->file.ptr, o->file.size);
                sb.final_buf_size = o->file.size;
@@ -2781,10 +2849,12 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 
        read_mailmap(&mailmap, NULL);
 
+       assign_blame(&sb, opt);
+
        if (!incremental)
                setup_pager();
 
-       assign_blame(&sb, opt);
+       free(final_commit_name);
 
        if (incremental)
                return 0;