blame: move textconv_object with related functions
[gitweb.git] / builtin / blame.c
index 5e5d30ecbc83cbbef4f1feadfd77163e3afbbf96..fbd757e58368dd76a1adeb00341b2eb7a9e66efc 100644 (file)
@@ -8,7 +8,6 @@
 #include "cache.h"
 #include "refs.h"
 #include "builtin.h"
-#include "blob.h"
 #include "commit.h"
 #include "tag.h"
 #include "tree-walk.h"
@@ -56,7 +55,7 @@ static int show_progress;
 static struct date_mode blame_date_mode = { DATE_ISO8601 };
 static size_t blame_date_width;
 
-static struct string_list mailmap;
+static struct string_list mailmap = STRING_LIST_INIT_NODUP;
 
 #ifndef DEBUG
 #define DEBUG 0
@@ -120,7 +119,7 @@ struct origin {
         */
        struct blame_entry *suspects;
        mmfile_t file;
-       unsigned char blob_sha1[20];
+       struct object_id blob_oid;
        unsigned mode;
        /* guilty gets set when shipping any suspects to the final
         * blame list instead of other commits
@@ -147,34 +146,6 @@ static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b,
        return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb);
 }
 
-/*
- * Prepare diff_filespec and convert it using diff textconv API
- * if the textconv driver exists.
- * Return 1 if the conversion succeeds, 0 otherwise.
- */
-int textconv_object(const char *path,
-                   unsigned mode,
-                   const unsigned char *sha1,
-                   int sha1_valid,
-                   char **buf,
-                   unsigned long *buf_size)
-{
-       struct diff_filespec *df;
-       struct userdiff_driver *textconv;
-
-       df = alloc_filespec(path);
-       fill_filespec(df, sha1, sha1_valid, mode);
-       textconv = get_textconv(df);
-       if (!textconv) {
-               free_filespec(df);
-               return 0;
-       }
-
-       *buf_size = fill_textconv(textconv, df, buf);
-       free_filespec(df);
-       return 1;
-}
-
 /*
  * Given an origin, prepare mmfile_t structure to be used by the
  * diff machinery
@@ -188,15 +159,16 @@ static void fill_origin_blob(struct diff_options *opt,
 
                num_read_blob++;
                if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
-                   textconv_object(o->path, o->mode, o->blob_sha1, 1, &file->ptr, &file_size))
+                   textconv_object(o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size))
                        ;
                else
-                       file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size);
+                       file->ptr = read_sha1_file(o->blob_oid.hash, &type,
+                                                  &file_size);
                file->size = file_size;
 
                if (!file->ptr)
                        die("Cannot read blob %s for path %s",
-                           sha1_to_hex(o->blob_sha1),
+                           oid_to_hex(&o->blob_oid),
                            o->path);
                o->file = *file;
        }
@@ -508,17 +480,17 @@ static struct origin *get_origin(struct scoreboard *sb,
  */
 static int fill_blob_sha1_and_mode(struct origin *origin)
 {
-       if (!is_null_sha1(origin->blob_sha1))
+       if (!is_null_oid(&origin->blob_oid))
                return 0;
        if (get_tree_entry(origin->commit->object.oid.hash,
                           origin->path,
-                          origin->blob_sha1, &origin->mode))
+                          origin->blob_oid.hash, &origin->mode))
                goto error_out;
-       if (sha1_object_info(origin->blob_sha1, NULL) != OBJ_BLOB)
+       if (sha1_object_info(origin->blob_oid.hash, NULL) != OBJ_BLOB)
                goto error_out;
        return 0;
  error_out:
-       hashclr(origin->blob_sha1);
+       oidclr(&origin->blob_oid);
        origin->mode = S_IFINVALID;
        return -1;
 }
@@ -572,7 +544,7 @@ static struct origin *find_origin(struct scoreboard *sb,
        if (!diff_queued_diff.nr) {
                /* The path is the same as parent */
                porigin = get_origin(sb, parent, origin->path);
-               hashcpy(porigin->blob_sha1, origin->blob_sha1);
+               oidcpy(&porigin->blob_oid, &origin->blob_oid);
                porigin->mode = origin->mode;
        } else {
                /*
@@ -598,7 +570,7 @@ static struct origin *find_origin(struct scoreboard *sb,
                            p->status);
                case 'M':
                        porigin = get_origin(sb, parent, origin->path);
-                       hashcpy(porigin->blob_sha1, p->one->sha1);
+                       oidcpy(&porigin->blob_oid, &p->one->oid);
                        porigin->mode = p->one->mode;
                        break;
                case 'A':
@@ -608,7 +580,7 @@ static struct origin *find_origin(struct scoreboard *sb,
                }
        }
        diff_flush(&diff_opts);
-       free_pathspec(&diff_opts.pathspec);
+       clear_pathspec(&diff_opts.pathspec);
        return porigin;
 }
 
@@ -644,21 +616,24 @@ static struct origin *find_rename(struct scoreboard *sb,
                if ((p->status == 'R' || p->status == 'C') &&
                    !strcmp(p->two->path, origin->path)) {
                        porigin = get_origin(sb, parent, p->one->path);
-                       hashcpy(porigin->blob_sha1, p->one->sha1);
+                       oidcpy(&porigin->blob_oid, &p->one->oid);
                        porigin->mode = p->one->mode;
                        break;
                }
        }
        diff_flush(&diff_opts);
-       free_pathspec(&diff_opts.pathspec);
+       clear_pathspec(&diff_opts.pathspec);
        return porigin;
 }
 
 /*
  * Append a new blame entry to a given output queue.
  */
-static void add_blame_entry(struct blame_entry ***queue, struct blame_entry *e)
+static void add_blame_entry(struct blame_entry ***queue,
+                           const struct blame_entry *src)
 {
+       struct blame_entry *e = xmalloc(sizeof(*e));
+       memcpy(e, src, sizeof(*e));
        origin_incref(e->suspect);
 
        e->next = **queue;
@@ -759,21 +734,15 @@ static void split_blame(struct blame_entry ***blamed,
                        struct blame_entry *split,
                        struct blame_entry *e)
 {
-       struct blame_entry *new_entry;
-
        if (split[0].suspect && split[2].suspect) {
                /* The first part (reuse storage for the existing entry e) */
                dup_entry(unblamed, e, &split[0]);
 
                /* The last part -- me */
-               new_entry = xmalloc(sizeof(*new_entry));
-               memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
-               add_blame_entry(unblamed, new_entry);
+               add_blame_entry(unblamed, &split[2]);
 
                /* ... and the middle part -- parent */
-               new_entry = xmalloc(sizeof(*new_entry));
-               memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
-               add_blame_entry(blamed, new_entry);
+               add_blame_entry(blamed, &split[1]);
        }
        else if (!split[0].suspect && !split[2].suspect)
                /*
@@ -784,18 +753,12 @@ static void split_blame(struct blame_entry ***blamed,
        else if (split[0].suspect) {
                /* me and then parent */
                dup_entry(unblamed, e, &split[0]);
-
-               new_entry = xmalloc(sizeof(*new_entry));
-               memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
-               add_blame_entry(blamed, new_entry);
+               add_blame_entry(blamed, &split[1]);
        }
        else {
                /* parent and then me */
                dup_entry(blamed, e, &split[1]);
-
-               new_entry = xmalloc(sizeof(*new_entry));
-               memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
-               add_blame_entry(unblamed, new_entry);
+               add_blame_entry(unblamed, &split[2]);
        }
 }
 
@@ -1308,7 +1271,7 @@ static void find_copy_in_parent(struct scoreboard *sb,
                                continue;
 
                        norigin = get_origin(sb, parent, p->one->path);
-                       hashcpy(norigin->blob_sha1, p->one->sha1);
+                       oidcpy(&norigin->blob_oid, &p->one->oid);
                        norigin->mode = p->one->mode;
                        fill_origin_blob(&sb->revs->diffopt, norigin, &file_p);
                        if (!file_p.ptr)
@@ -1342,7 +1305,7 @@ static void find_copy_in_parent(struct scoreboard *sb,
        } while (unblamed);
        target->suspects = reverse_blame(leftover, NULL);
        diff_flush(&diff_opts);
-       free_pathspec(&diff_opts.pathspec);
+       clear_pathspec(&diff_opts.pathspec);
 }
 
 /*
@@ -1458,15 +1421,14 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
                        porigin = find(sb, p, origin);
                        if (!porigin)
                                continue;
-                       if (!hashcmp(porigin->blob_sha1, origin->blob_sha1)) {
+                       if (!oidcmp(&porigin->blob_oid, &origin->blob_oid)) {
                                pass_whole_blame(sb, origin, porigin);
                                origin_decref(porigin);
                                goto finish;
                        }
                        for (j = same = 0; j < i; j++)
                                if (sg_origin[j] &&
-                                   !hashcmp(sg_origin[j]->blob_sha1,
-                                            porigin->blob_sha1)) {
+                                   !oidcmp(&sg_origin[j]->blob_oid, &porigin->blob_oid)) {
                                        same = 1;
                                        break;
                                }
@@ -1570,13 +1532,13 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
 struct commit_info {
        struct strbuf author;
        struct strbuf author_mail;
-       unsigned long author_time;
+       timestamp_t author_time;
        struct strbuf author_tz;
 
        /* filled only when asked for details */
        struct strbuf committer;
        struct strbuf committer_mail;
-       unsigned long committer_time;
+       timestamp_t committer_time;
        struct strbuf committer_tz;
 
        struct strbuf summary;
@@ -1587,7 +1549,7 @@ struct commit_info {
  */
 static void get_ac_line(const char *inbuf, const char *what,
        struct strbuf *name, struct strbuf *mail,
-       unsigned long *time, struct strbuf *tz)
+       timestamp_t *time, struct strbuf *tz)
 {
        struct ident_split ident;
        size_t len, maillen, namelen;
@@ -1700,13 +1662,23 @@ static void get_commit_info(struct commit *commit,
 }
 
 /*
+ * Write out any suspect information which depends on the path. This must be
+ * handled separately from emit_one_suspect_detail(), because a given commit
+ * may have changes in multiple paths. So this needs to appear each time
+ * we mention a new group.
+ *
  * To allow LF and other nonportable characters in pathnames,
  * they are c-style quoted as needed.
  */
-static void write_filename_info(const char *path)
+static void write_filename_info(struct origin *suspect)
 {
+       if (suspect->previous) {
+               struct origin *prev = suspect->previous;
+               printf("previous %s ", oid_to_hex(&prev->commit->object.oid));
+               write_name_quoted(prev->path, stdout, '\n');
+       }
        printf("filename ");
-       write_name_quoted(path, stdout, '\n');
+       write_name_quoted(suspect->path, stdout, '\n');
 }
 
 /*
@@ -1726,20 +1698,15 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat)
        get_commit_info(suspect->commit, &ci, 1);
        printf("author %s\n", ci.author.buf);
        printf("author-mail %s\n", ci.author_mail.buf);
-       printf("author-time %lu\n", ci.author_time);
+       printf("author-time %"PRItime"\n", ci.author_time);
        printf("author-tz %s\n", ci.author_tz.buf);
        printf("committer %s\n", ci.committer.buf);
        printf("committer-mail %s\n", ci.committer_mail.buf);
-       printf("committer-time %lu\n", ci.committer_time);
+       printf("committer-time %"PRItime"\n", ci.committer_time);
        printf("committer-tz %s\n", ci.committer_tz.buf);
        printf("summary %s\n", ci.summary.buf);
        if (suspect->commit->object.flags & UNINTERESTING)
                printf("boundary\n");
-       if (suspect->previous) {
-               struct origin *prev = suspect->previous;
-               printf("previous %s ", oid_to_hex(&prev->commit->object.oid));
-               write_name_quoted(prev->path, stdout, '\n');
-       }
 
        commit_info_destroy(&ci);
 
@@ -1760,7 +1727,7 @@ static void found_guilty_entry(struct blame_entry *ent,
                       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);
+               write_filename_info(suspect);
                maybe_flush_or_die(stdout, "stdout");
        }
        pi->blamed_lines += ent->num_lines;
@@ -1841,14 +1808,14 @@ static void assign_blame(struct scoreboard *sb, int opt)
        stop_progress(&pi.progress);
 }
 
-static const char *format_time(unsigned long time, const char *tz_str,
+static const char *format_time(timestamp_t time, const char *tz_str,
                               int show_raw_time)
 {
        static struct strbuf time_buf = STRBUF_INIT;
 
        strbuf_reset(&time_buf);
        if (show_raw_time) {
-               strbuf_addf(&time_buf, "%lu %s", time, tz_str);
+               strbuf_addf(&time_buf, "%"PRItime" %s", time, tz_str);
        }
        else {
                const char *time_str;
@@ -1884,7 +1851,7 @@ static void emit_porcelain_details(struct origin *suspect, int repeat)
 {
        if (emit_one_suspect_detail(suspect, repeat) ||
            (suspect->commit->object.flags & MORE_THAN_ONE_PATH))
-               write_filename_info(suspect->path);
+               write_filename_info(suspect);
 }
 
 static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
@@ -1894,9 +1861,9 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
        int cnt;
        const char *cp;
        struct origin *suspect = ent->suspect;
-       char hex[GIT_SHA1_HEXSZ + 1];
+       char hex[GIT_MAX_HEXSZ + 1];
 
-       sha1_to_hex_r(hex, suspect->commit->object.oid.hash);
+       oid_to_hex_r(hex, &suspect->commit->object.oid);
        printf("%s %d %d %d\n",
               hex,
               ent->s_lno + 1,
@@ -1932,16 +1899,16 @@ 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[GIT_SHA1_HEXSZ + 1];
+       char hex[GIT_MAX_HEXSZ + 1];
        int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
 
        get_commit_info(suspect->commit, &ci, 1);
-       sha1_to_hex_r(hex, suspect->commit->object.oid.hash);
+       oid_to_hex_r(hex, &suspect->commit->object.oid);
 
        cp = nth_line(sb, ent->lno);
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
                char ch;
-               int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : abbrev;
+               int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev;
 
                if (suspect->commit->object.flags & UNINTERESTING) {
                        if (blank_boundary)
@@ -2111,7 +2078,7 @@ static void find_alignment(struct scoreboard *sb, int *option)
        unsigned largest_score = 0;
        struct blame_entry *e;
        int compute_auto_abbrev = (abbrev < 0);
-       int auto_abbrev = default_abbrev;
+       int auto_abbrev = DEFAULT_ABBREV;
 
        for (e = sb->ent; e; e = e->next) {
                struct origin *suspect = e->suspect;
@@ -2220,6 +2187,8 @@ static int git_blame_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (git_diff_heuristic_config(var, value, cb) < 0)
+               return -1;
        if (userdiff_config(var, value) < 0)
                return -1;
 
@@ -2232,31 +2201,32 @@ static void verify_working_tree_path(struct commit *work_tree, const char *path)
        int pos;
 
        for (parents = work_tree->parents; parents; parents = parents->next) {
-               const unsigned char *commit_sha1 = parents->item->object.oid.hash;
-               unsigned char blob_sha1[20];
+               const struct object_id *commit_oid = &parents->item->object.oid;
+               struct object_id blob_oid;
                unsigned mode;
 
-               if (!get_tree_entry(commit_sha1, path, blob_sha1, &mode) &&
-                   sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
+               if (!get_tree_entry(commit_oid->hash, path, blob_oid.hash, &mode) &&
+                   sha1_object_info(blob_oid.hash, NULL) == OBJ_BLOB)
                        return;
        }
 
        pos = cache_name_pos(path, strlen(path));
        if (pos >= 0)
                ; /* path is in the index */
-       else if (!strcmp(active_cache[-1 - pos]->name, path))
+       else if (-1 - pos < active_nr &&
+                !strcmp(active_cache[-1 - pos]->name, path))
                ; /* path is in the index, unmerged */
        else
                die("no such path '%s' in HEAD", path);
 }
 
-static struct commit_list **append_parent(struct commit_list **tail, const unsigned char *sha1)
+static struct commit_list **append_parent(struct commit_list **tail, const struct object_id *oid)
 {
        struct commit *parent;
 
-       parent = lookup_commit_reference(sha1);
+       parent = lookup_commit_reference(oid->hash);
        if (!parent)
-               die("no such commit %s", sha1_to_hex(sha1));
+               die("no such commit %s", oid_to_hex(oid));
        return &commit_list_insert(parent, tail)->next;
 }
 
@@ -2273,10 +2243,10 @@ static void append_merge_parents(struct commit_list **tail)
        }
 
        while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) {
-               unsigned char sha1[20];
-               if (line.len < 40 || get_sha1_hex(line.buf, sha1))
+               struct object_id oid;
+               if (line.len < GIT_SHA1_HEXSZ || get_oid_hex(line.buf, &oid))
                        die("unknown line in '%s': %s", git_path_merge_head(), line.buf);
-               tail = append_parent(tail, sha1);
+               tail = append_parent(tail, &oid);
        }
        close(merge_head);
        strbuf_release(&line);
@@ -2305,7 +2275,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        struct commit *commit;
        struct origin *origin;
        struct commit_list **parent_tail, *parent;
-       unsigned char head_sha1[20];
+       struct object_id head_oid;
        struct strbuf buf = STRBUF_INIT;
        const char *ident;
        time_t now;
@@ -2321,10 +2291,10 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        commit->date = now;
        parent_tail = &commit->parents;
 
-       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL))
+       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL))
                die("no such ref: HEAD");
 
-       parent_tail = append_parent(parent_tail, head_sha1);
+       parent_tail = append_parent(parent_tail, &head_oid);
        append_merge_parents(parent_tail);
        verify_working_tree_path(commit, path);
 
@@ -2365,7 +2335,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
                switch (st.st_mode & S_IFMT) {
                case S_IFREG:
                        if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
-                           textconv_object(read_from, mode, null_sha1, 0, &buf_ptr, &buf_len))
+                           textconv_object(read_from, mode, &null_oid, 0, &buf_ptr, &buf_len))
                                strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1);
                        else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
                                die_errno("cannot open or read '%s'", read_from);
@@ -2387,7 +2357,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        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);
+       pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_oid.hash);
 
        /*
         * Read the current index, replace the path entry with
@@ -2409,7 +2379,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        }
        size = cache_entry_size(len);
        ce = xcalloc(1, size);
-       hashcpy(ce->sha1, origin->blob_sha1);
+       oidcpy(&ce->oid, &origin->blob_oid);
        memcpy(ce->name, path, len);
        ce->ce_flags = create_ce_flags(0);
        ce->ce_namelen = len;
@@ -2453,6 +2423,41 @@ static char *prepare_final(struct scoreboard *sb)
        return xstrdup_or_null(name);
 }
 
+static const char *dwim_reverse_initial(struct scoreboard *sb)
+{
+       /*
+        * DWIM "git blame --reverse ONE -- PATH" as
+        * "git blame --reverse ONE..HEAD -- PATH" but only do so
+        * when it makes sense.
+        */
+       struct object *obj;
+       struct commit *head_commit;
+       unsigned char head_sha1[20];
+
+       if (sb->revs->pending.nr != 1)
+               return NULL;
+
+       /* Is that sole rev a committish? */
+       obj = sb->revs->pending.objects[0].item;
+       obj = deref_tag(obj, NULL, 0);
+       if (obj->type != OBJ_COMMIT)
+               return NULL;
+
+       /* Do we have HEAD? */
+       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL))
+               return NULL;
+       head_commit = lookup_commit_reference_gently(head_sha1, 1);
+       if (!head_commit)
+               return NULL;
+
+       /* Turn "ONE" into "ONE..HEAD" then */
+       obj->flags |= UNINTERESTING;
+       add_pending_object(sb->revs, &head_commit->object, "HEAD");
+
+       sb->final = (struct commit *)obj;
+       return sb->revs->pending.objects[0].name;
+}
+
 static char *prepare_initial(struct scoreboard *sb)
 {
        int i;
@@ -2471,14 +2476,17 @@ static char *prepare_initial(struct scoreboard *sb)
                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?",
+                       die("More than one commit to dig up 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;
        }
+
+       if (!final_commit_name)
+               final_commit_name = dwim_reverse_initial(sb);
        if (!final_commit_name)
-               die("No commit to dig down to?");
+               die("No commit to dig up from?");
        return xstrdup(final_commit_name);
 }
 
@@ -2527,12 +2535,12 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        enum object_type type;
        struct commit *final_commit = NULL;
 
-       static struct string_list range_list;
-       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[] = {
+       struct string_list range_list = STRING_LIST_INIT_NODUP;
+       int output_option = 0, opt = 0;
+       int show_stats = 0;
+       const char *revs_file = NULL;
+       const char *contents_from = NULL;
+       const struct option options[] = {
                OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")),
                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)")),
@@ -2549,6 +2557,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR),
                OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL),
                OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE),
+
+               /*
+                * The following two options are parsed by parse_revision_opt()
+                * and are only included here to get included in the "-h"
+                * output:
+                */
+               { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb },
+
                OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL),
                OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")),
                OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")),
@@ -2595,19 +2611,22 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        }
 parse_done:
        no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES);
+       xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC;
        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");
+                       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)
+       if (0 < abbrev && abbrev < GIT_SHA1_HEXSZ)
                /* one more abbrev length is needed for the boundary commit */
                abbrev++;
+       else if (!abbrev)
+               abbrev = GIT_SHA1_HEXSZ;
 
        if (revs_file && read_ancestry(revs_file))
                die_errno("reading graft file '%s' failed", revs_file);
@@ -2726,7 +2745,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                sb.commits.compare = compare_commits_by_commit_date;
        }
        else if (contents_from)
-               die("--contents and --reverse do not blend well.");
+               die(_("--contents and --reverse do not blend well."));
        else {
                final_commit_name = prepare_initial(&sb);
                sb.commits.compare = compare_commits_by_reverse_commit_date;
@@ -2746,12 +2765,12 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                add_pending_object(&revs, &(sb.final->object), ":");
        }
        else if (contents_from)
-               die("Cannot use --contents with final commit object name");
+               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");
+                       die(_("--reverse and --first-parent together require specified latest commit"));
        }
 
        /*
@@ -2778,7 +2797,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                }
 
                if (oidcmp(&c->object.oid, &sb.final->object.oid))
-                       die("--reverse --first-parent together require range along first-parent chain");
+                       die(_("--reverse --first-parent together require range along first-parent chain"));
        }
 
        if (is_null_oid(&sb.final->object.oid)) {
@@ -2789,26 +2808,26 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        else {
                o = get_origin(&sb, sb.final, path);
                if (fill_blob_sha1_and_mode(o))
-                       die("no such path %s in %s", path, final_commit_name);
+                       die(_("no such path %s in %s"), path, final_commit_name);
 
                if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) &&
-                   textconv_object(path, o->mode, o->blob_sha1, 1, (char **) &sb.final_buf,
+                   textconv_object(path, o->mode, &o->blob_oid, 1, (char **) &sb.final_buf,
                                    &sb.final_buf_size))
                        ;
                else
-                       sb.final_buf = read_sha1_file(o->blob_sha1, &type,
+                       sb.final_buf = read_sha1_file(o->blob_oid.hash, &type,
                                                      &sb.final_buf_size);
 
                if (!sb.final_buf)
-                       die("Cannot read blob %s for path %s",
-                           sha1_to_hex(o->blob_sha1),
+                       die(_("cannot read blob %s for path %s"),
+                           oid_to_hex(&o->blob_oid),
                            path);
        }
        num_read_blob++;
        lno = prepare_lines(&sb);
 
        if (lno && !range_list.nr)
-               string_list_append(&range_list, xstrdup("1"));
+               string_list_append(&range_list, "1");
 
        anchor = 1;
        range_set_init(&ranges, range_list.nr);
@@ -2819,7 +2838,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                                    &bottom, &top, sb.path))
                        usage(blame_usage);
                if (lno < top || ((lno || bottom) && lno < bottom))
-                       die("file %s has only %lu lines", path, lno);
+                       die(Q_("file %s has only %lu line",
+                              "file %s has only %lu lines",
+                              lno), path, lno);
                if (bottom < 1)
                        bottom = 1;
                if (top < 1)