Code

Merge branch 'jc/blame'
authorJunio C Hamano <gitster@pobox.com>
Thu, 5 Mar 2009 23:41:43 +0000 (15:41 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 5 Mar 2009 23:41:43 +0000 (15:41 -0800)
* jc/blame:
  blame: show "previous" information in --porcelain/--incremental format
  git-blame: refactor code to emit "porcelain format" output

1  2 
builtin-blame.c

diff --combined builtin-blame.c
index 114a214ed3fef40ae5cc13737d037f29d6f8acfd,e386120596efb9f0fc9ed7e2c5bd2f05f3f1df5f..971126a80d3919c07b068b15540e297f64222248
@@@ -19,7 -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";
  
@@@ -74,6 -73,7 +74,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 -115,8 +116,8 @@@ static inline struct origin *origin_inc
  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,6 -445,131 +446,6 @@@ static struct origin *find_rename(struc
        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 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;
 -      memset(&state, 0, sizeof(state));
 -      state.ret = xmalloc(sizeof(struct patch));
 -      state.ret->chunks = NULL;
 -      state.ret->num = 0;
 -
 -      xdi_diff_outf(file_p, file_o, process_u_diff, &state, &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.
@@@ -689,22 -816,6 +692,22 @@@ static void blame_chunk(struct scoreboa
        }
  }
  
 +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
@@@ -714,28 -825,26 +717,28 @@@ static int pass_blame_to_parent(struct 
                                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;
  }
  
@@@ -827,23 -936,6 +830,23 @@@ static void handle_split(struct scorebo
        }
  }
  
 +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
@@@ -858,9 -950,8 +861,9 @@@ static void find_copy_in_blob(struct sc
        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.
        }
        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);
  }
  
  /*
@@@ -1198,6 -1295,10 +1201,10 @@@ static void pass_blame(struct scoreboar
                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;
        }
@@@ -1264,12 -1365,11 +1271,12 @@@ struct commit_inf
   * 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)
                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;
        }
        *tmp = 0;
        while (*tmp != ' ')
                tmp--;
 -      *mail = tmp + 1;
 +      mailpos = tmp + 1;
        *tmp = 0;
        maillen = timepos - tmp;
 +      memcpy(mail, mailpos, maillen);
  
        if (!mailmap.nr)
                return;
         * 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,
                            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];
  
        /*
                        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;
                goto error_out;
        memcpy(summary_buf, tmp, len);
        summary_buf[len] = 0;
 +      free(reencoded);
  }
  
  /*
@@@ -1414,6 -1497,39 +1421,39 @@@ static void write_filename_info(const c
        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.
@@@ -1429,22 -1545,7 +1469,7 @@@ static void found_guilty_entry(struct b
                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");
        }
@@@ -1551,24 -1652,8 +1576,8 @@@ static void emit_porcelain(struct score
               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);
@@@ -1631,14 -1716,13 +1640,14 @@@ static void emit_other(struct scoreboar
                                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);
                }
@@@ -1769,7 -1853,7 +1778,7 @@@ static void find_alignment(struct score
                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;
                }
@@@ -1806,36 -1890,6 +1815,6 @@@ static void sanity_check_refcnt(struct 
                        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);
@@@ -2010,6 -2064,7 +1989,6 @@@ static struct commit *fake_working_tree
        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)
                                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:
                                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);
@@@ -2408,7 -2465,7 +2387,7 @@@ parse_done
                die("reading graft file %s failed: %s",
                    revs_file, strerror(errno));
  
 -      read_mailmap(&mailmap, ".mailmap", NULL);
 +      read_mailmap(&mailmap, NULL);
  
        if (!incremental)
                setup_pager();