Code

docbook: change css style
[git.git] / builtin-blame.c
index 0c241a9ec803a95ff85cf98f2714c74a6503b976..83141fc84e22ad4852f255fa1ad453cc125a3255 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Pickaxe
+ * Blame
  *
  * Copyright (c) 2006, Junio C Hamano
  */
@@ -19,6 +19,7 @@
 #include "string-list.h"
 #include "mailmap.h"
 #include "parse-options.h"
+#include "utf8.h"
 
 static char blame_usage[] = "git blame [options] [rev-opts] [rev] [--] file";
 
@@ -38,8 +39,11 @@ 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 enum date_mode blame_date_mode = DATE_ISO8601;
+static size_t blame_date_width;
+
 static struct string_list mailmap;
 
 #ifndef DEBUG
@@ -74,6 +78,7 @@ static unsigned blame_copy_score;
  */
 struct origin {
        int refcnt;
+       struct origin *previous;
        struct commit *commit;
        mmfile_t file;
        unsigned char blob_sha1[20];
@@ -115,6 +120,8 @@ static inline struct origin *origin_incref(struct origin *o)
 static void origin_decref(struct origin *o)
 {
        if (o && --o->refcnt <= 0) {
+               if (o->previous)
+                       origin_decref(o->previous);
                free(o->file.ptr);
                free(o);
        }
@@ -443,135 +450,6 @@ static struct origin *find_rename(struct scoreboard *sb,
        return porigin;
 }
 
-/*
- * Parsing of patch chunks...
- */
-struct chunk {
-       /* line number in postimage; up to but not including this
-        * line is the same as preimage
-        */
-       int same;
-
-       /* preimage line number after this chunk */
-       int p_next;
-
-       /* postimage line number after this chunk */
-       int t_next;
-};
-
-struct patch {
-       struct chunk *chunks;
-       int num;
-};
-
-struct blame_diff_state {
-       struct xdiff_emit_state xm;
-       struct patch *ret;
-       unsigned hunk_post_context;
-       unsigned hunk_in_pre_context : 1;
-};
-
-static void process_u_diff(void *state_, char *line, unsigned long len)
-{
-       struct blame_diff_state *state = state_;
-       struct chunk *chunk;
-       int off1, off2, len1, len2, num;
-
-       num = state->ret->num;
-       if (len < 4 || line[0] != '@' || line[1] != '@') {
-               if (state->hunk_in_pre_context && line[0] == ' ')
-                       state->ret->chunks[num - 1].same++;
-               else {
-                       state->hunk_in_pre_context = 0;
-                       if (line[0] == ' ')
-                               state->hunk_post_context++;
-                       else
-                               state->hunk_post_context = 0;
-               }
-               return;
-       }
-
-       if (num && state->hunk_post_context) {
-               chunk = &state->ret->chunks[num - 1];
-               chunk->p_next -= state->hunk_post_context;
-               chunk->t_next -= state->hunk_post_context;
-       }
-       state->ret->num = ++num;
-       state->ret->chunks = xrealloc(state->ret->chunks,
-                                     sizeof(struct chunk) * num);
-       chunk = &state->ret->chunks[num - 1];
-       if (parse_hunk_header(line, len, &off1, &len1, &off2, &len2)) {
-               state->ret->num--;
-               return;
-       }
-
-       /* Line numbers in patch output are one based. */
-       off1--;
-       off2--;
-
-       chunk->same = len2 ? off2 : (off2 + 1);
-
-       chunk->p_next = off1 + (len1 ? len1 : 1);
-       chunk->t_next = chunk->same + len2;
-       state->hunk_in_pre_context = 1;
-       state->hunk_post_context = 0;
-}
-
-static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o,
-                                   int context)
-{
-       struct blame_diff_state state;
-       xpparam_t xpp;
-       xdemitconf_t xecfg;
-       xdemitcb_t ecb;
-
-       xpp.flags = xdl_opts;
-       memset(&xecfg, 0, sizeof(xecfg));
-       xecfg.ctxlen = context;
-       ecb.outf = xdiff_outf;
-       ecb.priv = &state;
-       memset(&state, 0, sizeof(state));
-       state.xm.consume = process_u_diff;
-       state.ret = xmalloc(sizeof(struct patch));
-       state.ret->chunks = NULL;
-       state.ret->num = 0;
-
-       xdi_diff(file_p, file_o, &xpp, &xecfg, &ecb);
-
-       if (state.ret->num) {
-               struct chunk *chunk;
-               chunk = &state.ret->chunks[state.ret->num - 1];
-               chunk->p_next -= state.hunk_post_context;
-               chunk->t_next -= state.hunk_post_context;
-       }
-       return state.ret;
-}
-
-/*
- * Run diff between two origins and grab the patch output, so that
- * we can pass blame for lines origin is currently suspected for
- * to its parent.
- */
-static struct patch *get_patch(struct origin *parent, struct origin *origin)
-{
-       mmfile_t file_p, file_o;
-       struct patch *patch;
-
-       fill_origin_blob(parent, &file_p);
-       fill_origin_blob(origin, &file_o);
-       if (!file_p.ptr || !file_o.ptr)
-               return NULL;
-       patch = compare_buffer(&file_p, &file_o, 0);
-       num_get_patch++;
-       return patch;
-}
-
-static void free_patch(struct patch *p)
-{
-       free(p->chunks);
-       free(p);
-}
-
 /*
  * Link in a new blame entry to the scoreboard.  Entries that cover the
  * same line range have been removed from the scoreboard previously.
@@ -818,6 +696,22 @@ static void blame_chunk(struct scoreboard *sb,
        }
 }
 
+struct blame_chunk_cb_data {
+       struct scoreboard *sb;
+       struct origin *target;
+       struct origin *parent;
+       long plno;
+       long tlno;
+};
+
+static void blame_chunk_cb(void *data, long same, long p_next, long t_next)
+{
+       struct blame_chunk_cb_data *d = data;
+       blame_chunk(d->sb, d->tlno, d->plno, same, d->target, d->parent);
+       d->plno = p_next;
+       d->tlno = t_next;
+}
+
 /*
  * We are looking at the origin 'target' and aiming to pass blame
  * for the lines it is suspected to its parent.  Run diff to find
@@ -827,26 +721,28 @@ static int pass_blame_to_parent(struct scoreboard *sb,
                                struct origin *target,
                                struct origin *parent)
 {
-       int i, last_in_target, plno, tlno;
-       struct patch *patch;
+       int last_in_target;
+       mmfile_t file_p, file_o;
+       struct blame_chunk_cb_data d = { sb, target, parent, 0, 0 };
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
 
        last_in_target = find_last_in_target(sb, target);
        if (last_in_target < 0)
                return 1; /* nothing remains for this target */
 
-       patch = get_patch(parent, target);
-       plno = tlno = 0;
-       for (i = 0; i < patch->num; i++) {
-               struct chunk *chunk = &patch->chunks[i];
+       fill_origin_blob(parent, &file_p);
+       fill_origin_blob(target, &file_o);
+       num_get_patch++;
 
-               blame_chunk(sb, tlno, plno, chunk->same, target, parent);
-               plno = chunk->p_next;
-               tlno = chunk->t_next;
-       }
+       memset(&xpp, 0, sizeof(xpp));
+       xpp.flags = xdl_opts;
+       memset(&xecfg, 0, sizeof(xecfg));
+       xecfg.ctxlen = 0;
+       xdi_diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, &xpp, &xecfg);
        /* The rest (i.e. anything after tlno) are the same as the parent */
-       blame_chunk(sb, tlno, plno, last_in_target, target, parent);
+       blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent);
 
-       free_patch(patch);
        return 0;
 }
 
@@ -938,6 +834,23 @@ static void handle_split(struct scoreboard *sb,
        }
 }
 
+struct handle_split_cb_data {
+       struct scoreboard *sb;
+       struct blame_entry *ent;
+       struct origin *parent;
+       struct blame_entry *split;
+       long plno;
+       long tlno;
+};
+
+static void handle_split_cb(void *data, long same, long p_next, long t_next)
+{
+       struct handle_split_cb_data *d = data;
+       handle_split(d->sb, d->ent, d->tlno, d->plno, same, d->parent, d->split);
+       d->plno = p_next;
+       d->tlno = t_next;
+}
+
 /*
  * 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
@@ -952,8 +865,9 @@ static void find_copy_in_blob(struct scoreboard *sb,
        const char *cp;
        int cnt;
        mmfile_t file_o;
-       struct patch *patch;
-       int i, plno, tlno;
+       struct handle_split_cb_data d = { sb, ent, parent, split, 0, 0 };
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
 
        /*
         * Prepare mmfile that contains only the lines in ent.
@@ -968,24 +882,18 @@ static void find_copy_in_blob(struct scoreboard *sb,
        }
        file_o.size = cp - file_o.ptr;
 
-       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(&xpp, 0, sizeof(xpp));
+       xpp.flags = xdl_opts;
+       memset(&xecfg, 0, sizeof(xecfg));
+       xecfg.ctxlen = 1;
        memset(split, 0, sizeof(struct blame_entry [3]));
-       plno = tlno = 0;
-       for (i = 0; i < patch->num; i++) {
-               struct chunk *chunk = &patch->chunks[i];
-
-               handle_split(sb, ent, tlno, plno, chunk->same, parent, split);
-               plno = chunk->p_next;
-               tlno = chunk->t_next;
-       }
+       xdi_diff_hunks(file_p, &file_o, handle_split_cb, &d, &xpp, &xecfg);
        /* remainder, if any, all match the preimage */
-       handle_split(sb, ent, tlno, plno, ent->num_lines, parent, split);
-       free_patch(patch);
+       handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
 }
 
 /*
@@ -1137,6 +1045,8 @@ static int find_copy_in_parent(struct scoreboard *sb,
 
                        if (!DIFF_FILE_VALID(p->one))
                                continue; /* does not exist in parent */
+                       if (S_ISGITLINK(p->one->mode))
+                               continue; /* ignore git links */
                        if (porigin && !strcmp(p->one->path, porigin->path))
                                /* find_move already dealt with this path */
                                continue;
@@ -1295,6 +1205,10 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
                struct origin *porigin = sg_origin[i];
                if (!porigin)
                        continue;
+               if (!origin->previous) {
+                       origin_incref(porigin);
+                       origin->previous = porigin;
+               }
                if (pass_blame_to_parent(sb, origin, porigin))
                        goto finish;
        }
@@ -1361,11 +1275,12 @@ struct commit_info
  * Parse author/committer line in the commit object buffer
  */
 static void get_ac_line(const char *inbuf, const char *what,
-                       int bufsz, char *person, const char **mail,
+                       int person_len, char *person,
+                       int mail_len, char *mail,
                        unsigned long *time, const char **tz)
 {
        int len, tzlen, maillen;
-       char *tmp, *endp, *timepos;
+       char *tmp, *endp, *timepos, *mailpos;
 
        tmp = strstr(inbuf, what);
        if (!tmp)
@@ -1376,10 +1291,11 @@ static void get_ac_line(const char *inbuf, const char *what,
                len = strlen(tmp);
        else
                len = endp - tmp;
-       if (bufsz <= len) {
+       if (person_len <= len) {
        error_out:
                /* Ugh */
-               *mail = *tz = "(unknown)";
+               *tz = "(unknown)";
+               strcpy(mail, *tz);
                *time = 0;
                return;
        }
@@ -1402,9 +1318,10 @@ static void get_ac_line(const char *inbuf, const char *what,
        *tmp = 0;
        while (*tmp != ' ')
                tmp--;
-       *mail = tmp + 1;
+       mailpos = tmp + 1;
        *tmp = 0;
        maillen = timepos - tmp;
+       memcpy(mail, mailpos, maillen);
 
        if (!mailmap.nr)
                return;
@@ -1413,20 +1330,23 @@ static void get_ac_line(const char *inbuf, const char *what,
         * mailmap expansion may make the name longer.
         * make room by pushing stuff down.
         */
-       tmp = person + bufsz - (tzlen + 1);
+       tmp = person + person_len - (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
+        * Now, convert both name and e-mail using mailmap
         */
-       map_email(&mailmap, tmp + 1, person, tmp-person-1);
+       if(map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
+               /* Add a trailing '>' to email, since map_user returns plain emails
+                  Note: It already has '<', since we replace from mail+1 */
+               mailpos = memchr(mail, '\0', mail_len);
+               if (mailpos && mailpos-mail < mail_len - 1) {
+                       *mailpos = '>';
+                       *(mailpos+1) = '\0';
+               }
+       }
 }
 
 static void get_commit_info(struct commit *commit,
@@ -1434,9 +1354,11 @@ static void get_commit_info(struct commit *commit,
                            int detailed)
 {
        int len;
-       char *tmp, *endp;
-       static char author_buf[1024];
-       static char committer_buf[1024];
+       char *tmp, *endp, *reencoded, *message;
+       static char author_name[1024];
+       static char author_mail[1024];
+       static char committer_name[1024];
+       static char committer_mail[1024];
        static char summary_buf[1024];
 
        /*
@@ -1452,24 +1374,33 @@ static void get_commit_info(struct commit *commit,
                        die("Cannot read commit %s",
                            sha1_to_hex(commit->object.sha1));
        }
-       ret->author = author_buf;
-       get_ac_line(commit->buffer, "\nauthor ",
-                   sizeof(author_buf), author_buf, &ret->author_mail,
+       reencoded = reencode_commit_message(commit, NULL);
+       message   = reencoded ? reencoded : commit->buffer;
+       ret->author = author_name;
+       ret->author_mail = author_mail;
+       get_ac_line(message, "\nauthor ",
+                   sizeof(author_name), author_name,
+                   sizeof(author_mail), author_mail,
                    &ret->author_time, &ret->author_tz);
 
-       if (!detailed)
+       if (!detailed) {
+               free(reencoded);
                return;
+       }
 
-       ret->committer = committer_buf;
-       get_ac_line(commit->buffer, "\ncommitter ",
-                   sizeof(committer_buf), committer_buf, &ret->committer_mail,
+       ret->committer = committer_name;
+       ret->committer_mail = committer_mail;
+       get_ac_line(message, "\ncommitter ",
+                   sizeof(committer_name), committer_name,
+                   sizeof(committer_mail), committer_mail,
                    &ret->committer_time, &ret->committer_tz);
 
        ret->summary = summary_buf;
-       tmp = strstr(commit->buffer, "\n\n");
+       tmp = strstr(message, "\n\n");
        if (!tmp) {
        error_out:
                sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
+               free(reencoded);
                return;
        }
        tmp += 2;
@@ -1481,6 +1412,7 @@ static void get_commit_info(struct commit *commit,
                goto error_out;
        memcpy(summary_buf, tmp, len);
        summary_buf[len] = 0;
+       free(reencoded);
 }
 
 /*
@@ -1493,6 +1425,39 @@ static void write_filename_info(const char *path)
        write_name_quoted(path, stdout, '\n');
 }
 
+/*
+ * Porcelain/Incremental format wants to show a lot of details per
+ * commit.  Instead of repeating this every line, emit it only once,
+ * the first time each commit appears in the output.
+ */
+static int emit_one_suspect_detail(struct origin *suspect)
+{
+       struct commit_info ci;
+
+       if (suspect->commit->object.flags & METAINFO_SHOWN)
+               return 0;
+
+       suspect->commit->object.flags |= METAINFO_SHOWN;
+       get_commit_info(suspect->commit, &ci, 1);
+       printf("author %s\n", ci.author);
+       printf("author-mail %s\n", ci.author_mail);
+       printf("author-time %lu\n", ci.author_time);
+       printf("author-tz %s\n", ci.author_tz);
+       printf("committer %s\n", ci.committer);
+       printf("committer-mail %s\n", ci.committer_mail);
+       printf("committer-time %lu\n", ci.committer_time);
+       printf("committer-tz %s\n", ci.committer_tz);
+       printf("summary %s\n", ci.summary);
+       if (suspect->commit->object.flags & UNINTERESTING)
+               printf("boundary\n");
+       if (suspect->previous) {
+               struct origin *prev = suspect->previous;
+               printf("previous %s ", sha1_to_hex(prev->commit->object.sha1));
+               write_name_quoted(prev->path, stdout, '\n');
+       }
+       return 1;
+}
+
 /*
  * The blame_entry is found to be guilty for the range.  Mark it
  * as such, and show it in incremental output.
@@ -1508,22 +1473,7 @@ static void found_guilty_entry(struct blame_entry *ent)
                printf("%s %d %d %d\n",
                       sha1_to_hex(suspect->commit->object.sha1),
                       ent->s_lno + 1, ent->lno + 1, ent->num_lines);
-               if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
-                       struct commit_info ci;
-                       suspect->commit->object.flags |= METAINFO_SHOWN;
-                       get_commit_info(suspect->commit, &ci, 1);
-                       printf("author %s\n", ci.author);
-                       printf("author-mail %s\n", ci.author_mail);
-                       printf("author-time %lu\n", ci.author_time);
-                       printf("author-tz %s\n", ci.author_tz);
-                       printf("committer %s\n", ci.committer);
-                       printf("committer-mail %s\n", ci.committer_mail);
-                       printf("committer-time %lu\n", ci.committer_time);
-                       printf("committer-tz %s\n", ci.committer_tz);
-                       printf("summary %s\n", ci.summary);
-                       if (suspect->commit->object.flags & UNINTERESTING)
-                               printf("boundary\n");
-               }
+               emit_one_suspect_detail(suspect);
                write_filename_info(suspect->path);
                maybe_flush_or_die(stdout, "stdout");
        }
@@ -1586,24 +1536,20 @@ static const char *format_time(unsigned long time, const char *tz_str,
                               int show_raw_time)
 {
        static char time_buf[128];
-       time_t t = time;
-       int minutes, tz;
-       struct tm *tm;
+       const char *time_str;
+       int time_len;
+       int tz;
 
        if (show_raw_time) {
                sprintf(time_buf, "%lu %s", time, tz_str);
-               return time_buf;
        }
-
-       tz = atoi(tz_str);
-       minutes = tz < 0 ? -tz : tz;
-       minutes = (minutes / 100)*60 + (minutes % 100);
-       minutes = tz < 0 ? -minutes : minutes;
-       t = time + minutes * 60;
-       tm = gmtime(&t);
-
-       strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
-       strcat(time_buf, tz_str);
+       else {
+               tz = atoi(tz_str);
+               time_str = show_date(time, tz, blame_date_mode);
+               time_len = strlen(time_str);
+               memcpy(time_buf, time_str, time_len);
+               memset(time_buf + time_len, ' ', blame_date_width - time_len);
+       }
        return time_buf;
 }
 
@@ -1630,24 +1576,8 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
               ent->s_lno + 1,
               ent->lno + 1,
               ent->num_lines);
-       if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
-               struct commit_info ci;
-               suspect->commit->object.flags |= METAINFO_SHOWN;
-               get_commit_info(suspect->commit, &ci, 1);
-               printf("author %s\n", ci.author);
-               printf("author-mail %s\n", ci.author_mail);
-               printf("author-time %lu\n", ci.author_time);
-               printf("author-tz %s\n", ci.author_tz);
-               printf("committer %s\n", ci.committer);
-               printf("committer-mail %s\n", ci.committer_mail);
-               printf("committer-time %lu\n", ci.committer_time);
-               printf("committer-tz %s\n", ci.committer_tz);
-               write_filename_info(suspect->path);
-               printf("summary %s\n", ci.summary);
-               if (suspect->commit->object.flags & UNINTERESTING)
-                       printf("boundary\n");
-       }
-       else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH)
+       if (emit_one_suspect_detail(suspect) ||
+           (suspect->commit->object.flags & MORE_THAN_ONE_PATH))
                write_filename_info(suspect->path);
 
        cp = nth_line(sb, ent->lno);
@@ -1686,7 +1616,7 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
                if (suspect->commit->object.flags & UNINTERESTING) {
                        if (blank_boundary)
                                memset(hex, ' ', length);
-                       else if (!cmd_is_annotate) {
+                       else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) {
                                length--;
                                putchar('^');
                        }
@@ -1710,13 +1640,14 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
                                printf(" %*d", max_orig_digits,
                                       ent->s_lno + 1 + cnt);
 
-                       if (!(opt & OUTPUT_NO_AUTHOR))
-                               printf(" (%-*.*s %10s",
-                                      longest_author, longest_author,
-                                      ci.author,
+                       if (!(opt & OUTPUT_NO_AUTHOR)) {
+                               int pad = longest_author - utf8_strwidth(ci.author);
+                               printf(" (%s%*s %10s",
+                                      ci.author, pad, "",
                                       format_time(ci.author_time,
                                                   ci.author_tz,
                                                   show_raw_time));
+                       }
                        printf(" %*d) ",
                               max_digits, ent->lno + 1 + cnt);
                }
@@ -1791,7 +1722,7 @@ static int prepare_lines(struct scoreboard *sb)
 
 /*
  * Add phony grafts for use with -S; this is primarily to
- * support git-cvsserver that wants to give a linear history
+ * support git's cvsserver that wants to give a linear history
  * to its clients.
  */
 static int read_ancestry(const char *graft_file)
@@ -1847,7 +1778,7 @@ static void find_alignment(struct scoreboard *sb, int *option)
                if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
                        suspect->commit->object.flags |= METAINFO_SHOWN;
                        get_commit_info(suspect->commit, &ci, 1);
-                       num = strlen(ci.author);
+                       num = utf8_strwidth(ci.author);
                        if (longest_author < num)
                                longest_author = num;
                }
@@ -1884,36 +1815,6 @@ static void sanity_check_refcnt(struct scoreboard *sb)
                        baa = 1;
                }
        }
-       for (ent = sb->ent; ent; ent = ent->next) {
-               /* Mark the ones that haven't been checked */
-               if (0 < ent->suspect->refcnt)
-                       ent->suspect->refcnt = -ent->suspect->refcnt;
-       }
-       for (ent = sb->ent; ent; ent = ent->next) {
-               /*
-                * ... then pick each and see if they have the the
-                * correct refcnt.
-                */
-               int found;
-               struct blame_entry *e;
-               struct origin *suspect = ent->suspect;
-
-               if (0 < suspect->refcnt)
-                       continue;
-               suspect->refcnt = -suspect->refcnt; /* Unmark */
-               for (found = 0, e = sb->ent; e; e = e->next) {
-                       if (e->suspect != suspect)
-                               continue;
-                       found++;
-               }
-               if (suspect->refcnt != found) {
-                       fprintf(stderr, "%s in %s has refcnt %d, not %d\n",
-                               ent->suspect->path,
-                               sha1_to_hex(ent->suspect->commit->object.sha1),
-                               ent->suspect->refcnt, found);
-                       baa = 2;
-               }
-       }
        if (baa) {
                int opt = 0160;
                find_alignment(sb, &opt);
@@ -2053,6 +1954,12 @@ 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.date")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               blame_date_mode = parse_date_format(value);
+               return 0;
+       }
        return git_default_config(var, value, cb);
 }
 
@@ -2065,7 +1972,7 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
        struct commit *commit;
        struct origin *origin;
        unsigned char head_sha1[20];
-       struct strbuf buf;
+       struct strbuf buf = STRBUF_INIT;
        const char *ident;
        time_t now;
        int size, len;
@@ -2085,11 +1992,9 @@ 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)
@@ -2101,7 +2006,6 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
                                die("Cannot lstat %s", path);
                        read_from = path;
                }
-               fin_size = xsize_t(st.st_size);
                mode = canon_mode(st.st_mode);
                switch (st.st_mode & S_IFMT) {
                case S_IFREG:
@@ -2109,9 +2013,8 @@ static struct commit *fake_working_tree_commit(const char *path, const char *con
                                die("cannot open or read %s", read_from);
                        break;
                case S_IFLNK:
-                       if (readlink(read_from, buf.buf, buf.alloc) != fin_size)
+                       if (strbuf_readlink(&buf, read_from, st.st_size) < 0)
                                die("cannot readlink %s", read_from);
-                       buf.len = fin_size;
                        break;
                default:
                        die("unsupported file type %s", read_from);
@@ -2317,11 +2220,12 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        };
 
        struct parse_opt_ctx_t ctx;
-
-       cmd_is_annotate = !strcmp(argv[0], "annotate");
+       int cmd_is_annotate = !strcmp(argv[0], "annotate");
 
        git_config(git_blame_config, NULL);
        init_revisions(&revs, NULL);
+       revs.date_mode = blame_date_mode;
+
        save_commit_buffer = 0;
        dashdash_pos = 0;
 
@@ -2350,6 +2254,36 @@ parse_done:
                die("reading graft file %s failed: %s",
                    revs_file, strerror(errno));
 
+       if (cmd_is_annotate) {
+               output_option |= OUTPUT_ANNOTATE_COMPAT;
+               blame_date_mode = DATE_ISO8601;
+       } else {
+               blame_date_mode = revs.date_mode;
+       }
+
+       /* The maximum width used to show the dates */
+       switch (blame_date_mode) {
+       case DATE_RFC2822:
+               blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700");
+               break;
+       case DATE_ISO8601:
+               blame_date_width = sizeof("2006-10-19 16:00:04 -0700");
+               break;
+       case DATE_RAW:
+               blame_date_width = sizeof("1161298804 -0700");
+               break;
+       case DATE_SHORT:
+               blame_date_width = sizeof("2006-10-19");
+               break;
+       case DATE_RELATIVE:
+               /* "normal" is used as the fallback for "relative" */
+       case DATE_LOCAL:
+       case DATE_NORMAL:
+               blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
+               break;
+       }
+       blame_date_width -= 1; /* strip the null */
+
        if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER))
                opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE |
                        PICKAXE_BLAME_COPY_HARDER);
@@ -2488,7 +2422,7 @@ parse_done:
        sb.ent = ent;
        sb.path = path;
 
-       read_mailmap(&mailmap, ".mailmap", NULL);
+       read_mailmap(&mailmap, NULL);
 
        if (!incremental)
                setup_pager();