Merge branch 'jc/maint-blame-no-such-path'
authorJunio C Hamano <gitster@pobox.com>
Mon, 17 Sep 2012 22:52:32 +0000 (15:52 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 17 Sep 2012 22:52:32 +0000 (15:52 -0700)
"git blame MAKEFILE" run in a history that has "Makefile" but not
"MAKEFILE" should say "No such file MAKEFILE in HEAD", but got
confused on a case insensitive filesystem and failed to do so.

Even during a conflicted merge, "git blame $path" always meant to
blame uncommitted changes to the "working tree" version; make it
more useful by showing cleanly merged parts as coming from the other
branch that is being merged.

* jc/maint-blame-no-such-path:
blame: allow "blame file" in the middle of a conflicted merge
blame $path: avoid getting fooled by case insensitive filesystems

1  2 
builtin/blame.c
diff --combined builtin/blame.c
index 0e102bf2c2aa26bd0dcbcb1788341eaa7ac2c037,449880e802dc1c6ee72a482deed8aca67b4f3ca8..c27ef21c2326afd29a68146d23c734c7ea708556
  #include "utf8.h"
  #include "userdiff.h"
  
 -static char blame_usage[] = "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,
        "",
 -      "[rev-opts] are documented in git-rev-list(1)",
 +      N_("[rev-opts] are documented in git-rev-list(1)"),
        NULL
  };
  
@@@ -88,20 -88,6 +88,20 @@@ struct origin 
        char path[FLEX_ARRAY];
  };
  
 +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)
 +{
 +      xpparam_t xpp = {0};
 +      xdemitconf_t xecfg = {0};
 +      xdemitcb_t ecb = {NULL};
 +
 +      xpp.flags = xdl_opts;
 +      xecfg.ctxlen = ctxlen;
 +      xecfg.hunk_func = hunk_func;
 +      ecb.priv = cb_data;
 +      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.
  int textconv_object(const char *path,
                    unsigned mode,
                    const unsigned char *sha1,
 +                  int sha1_valid,
                    char **buf,
                    unsigned long *buf_size)
  {
        struct userdiff_driver *textconv;
  
        df = alloc_filespec(path);
 -      fill_filespec(df, sha1, mode);
 +      fill_filespec(df, sha1, sha1_valid, mode);
        textconv = get_textconv(df);
        if (!textconv) {
                free_filespec(df);
@@@ -143,7 -128,7 +143,7 @@@ static void fill_origin_blob(struct dif
  
                num_read_blob++;
                if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
 -                  textconv_object(o->path, o->mode, o->blob_sha1, &file->ptr, &file_size))
 +                  textconv_object(o->path, o->mode, o->blob_sha1, 1, &file->ptr, &file_size))
                        ;
                else
                        file->ptr = read_sha1_file(o->blob_sha1, &type, &file_size);
@@@ -407,7 -392,8 +407,7 @@@ static struct origin *find_origin(struc
        paths[1] = NULL;
  
        diff_tree_setup_paths(paths, &diff_opts);
 -      if (diff_setup_done(&diff_opts) < 0)
 -              die("diff-setup");
 +      diff_setup_done(&diff_opts);
  
        if (is_null_sha1(origin->commit->object.sha1))
                do_diff_cache(parent->tree->object.sha1, &diff_opts);
@@@ -493,7 -479,8 +493,7 @@@ static struct origin *find_rename(struc
        diff_opts.single_follow = origin->path;
        paths[0] = NULL;
        diff_tree_setup_paths(paths, &diff_opts);
 -      if (diff_setup_done(&diff_opts) < 0)
 -              die("diff-setup");
 +      diff_setup_done(&diff_opts);
  
        if (is_null_sha1(origin->commit->object.sha1))
                do_diff_cache(parent->tree->object.sha1, &diff_opts);
@@@ -772,14 -759,12 +772,14 @@@ struct blame_chunk_cb_data 
        long tlno;
  };
  
 -static void blame_chunk_cb(void *data, long same, long p_next, long t_next)
 +static int blame_chunk_cb(long start_a, long count_a,
 +                        long start_b, long count_b, void *data)
  {
        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;
 +      blame_chunk(d->sb, d->tlno, d->plno, start_b, d->target, d->parent);
 +      d->plno = start_a + count_a;
 +      d->tlno = start_b + count_b;
 +      return 0;
  }
  
  /*
@@@ -794,7 -779,8 +794,7 @@@ static int pass_blame_to_parent(struct 
        int last_in_target;
        mmfile_t file_p, file_o;
        struct blame_chunk_cb_data d;
 -      xpparam_t xpp;
 -      xdemitconf_t xecfg;
 +
        memset(&d, 0, sizeof(d));
        d.sb = sb; d.target = target; d.parent = parent;
        last_in_target = find_last_in_target(sb, target);
        fill_origin_blob(&sb->revs->diffopt, target, &file_o);
        num_get_patch++;
  
 -      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);
 +      diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d);
        /* The rest (i.e. anything after tlno) are the same as the parent */
        blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent);
  
@@@ -909,15 -899,12 +909,15 @@@ struct handle_split_cb_data 
        long tlno;
  };
  
 -static void handle_split_cb(void *data, long same, long p_next, long t_next)
 +static int handle_split_cb(long start_a, long count_a,
 +                         long start_b, long count_b, void *data)
  {
        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;
 +      handle_split(d->sb, d->ent, d->tlno, d->plno, start_b, d->parent,
 +                   d->split);
 +      d->plno = start_a + count_a;
 +      d->tlno = start_b + count_b;
 +      return 0;
  }
  
  /*
@@@ -935,7 -922,8 +935,7 @@@ static void find_copy_in_blob(struct sc
        int cnt;
        mmfile_t file_o;
        struct handle_split_cb_data d;
 -      xpparam_t xpp;
 -      xdemitconf_t xecfg;
 +
        memset(&d, 0, sizeof(d));
        d.sb = sb; d.ent = ent; d.parent = parent; d.split = split;
        /*
         * 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]));
 -      xdi_diff_hunks(file_p, &file_o, handle_split_cb, &d, &xpp, &xecfg);
 +      diff_hunks(file_p, &file_o, 1, handle_split_cb, &d);
        /* remainder, if any, all match the preimage */
        handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
  }
@@@ -1073,7 -1065,8 +1073,7 @@@ static int find_copy_in_parent(struct s
  
        paths[0] = NULL;
        diff_tree_setup_paths(paths, &diff_opts);
 -      if (diff_setup_done(&diff_opts) < 0)
 -              die("diff-setup");
 +      diff_setup_done(&diff_opts);
  
        /* Try "find copies harder" on new path if requested;
         * we do not want to use diffcore_rename() actually to
@@@ -1835,16 -1828,6 +1835,16 @@@ static int read_ancestry(const char *gr
        return 0;
  }
  
 +static int update_auto_abbrev(int auto_abbrev, struct origin *suspect)
 +{
 +      const char *uniq = find_unique_abbrev(suspect->commit->object.sha1,
 +                                            auto_abbrev);
 +      int len = strlen(uniq);
 +      if (auto_abbrev < len)
 +              return len;
 +      return auto_abbrev;
 +}
 +
  /*
   * How many columns do we need to show line numbers, authors,
   * and filenames?
@@@ -1855,16 -1838,12 +1855,16 @@@ static void find_alignment(struct score
        int longest_dst_lines = 0;
        unsigned largest_score = 0;
        struct blame_entry *e;
 +      int compute_auto_abbrev = (abbrev < 0);
 +      int auto_abbrev = default_abbrev;
  
        for (e = sb->ent; e; e = e->next) {
                struct origin *suspect = e->suspect;
                struct commit_info ci;
                int num;
  
 +              if (compute_auto_abbrev)
 +                      auto_abbrev = update_auto_abbrev(auto_abbrev, suspect);
                if (strcmp(suspect->path, sb->path))
                        *option |= OUTPUT_SHOW_NAME;
                num = strlen(suspect->path);
        max_orig_digits = decimal_width(longest_src_lines);
        max_digits = decimal_width(longest_dst_lines);
        max_score_digits = decimal_width(largest_score);
 +
 +      if (compute_auto_abbrev)
 +              /* one more abbrev length is needed for the boundary commit */
 +              abbrev = auto_abbrev + 1;
  }
  
  /*
@@@ -2069,6 -2044,55 +2069,55 @@@ static int git_blame_config(const char 
        return git_default_config(var, value, cb);
  }
  
+ 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;
+               unsigned char blob_sha1[20];
+               unsigned mode;
+               if (!get_tree_entry(commit_sha1, path, blob_sha1, &mode) &&
+                   sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
+                       return;
+       }
+       die("no such path '%s' in HEAD", path);
+ }
+ static struct commit_list **append_parent(struct commit_list **tail, const unsigned char *sha1)
+ {
+       struct commit *parent;
+       parent = lookup_commit_reference(sha1);
+       if (!parent)
+               die("no such commit %s", sha1_to_hex(sha1));
+       return &commit_list_insert(parent, tail)->next;
+ }
+ 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);
+       if (merge_head < 0) {
+               if (errno == ENOENT)
+                       return;
+               die("cannot open '%s' for reading", merge_head_file);
+       }
+       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);
+               tail = append_parent(tail, sha1);
+       }
+       close(merge_head);
+       strbuf_release(&line);
+ }
  /*
   * Prepare a dummy commit that represents the work tree (or staged) item.
   * Note that annotating work tree item never works in the reverse.
@@@ -2079,6 -2103,7 +2128,7 @@@ static struct commit *fake_working_tree
  {
        struct commit *commit;
        struct origin *origin;
+       struct commit_list **parent_tail, *parent;
        unsigned char head_sha1[20];
        struct strbuf buf = STRBUF_INIT;
        const char *ident;
        int size, len;
        struct cache_entry *ce;
        unsigned mode;
-       if (get_sha1("HEAD", head_sha1))
-               die("No such ref: HEAD");
+       struct strbuf msg = STRBUF_INIT;
  
        time(&now);
        commit = xcalloc(1, sizeof(*commit));
-       commit->parents = xcalloc(1, sizeof(*commit->parents));
-       commit->parents->item = lookup_commit_reference(head_sha1);
        commit->object.parsed = 1;
        commit->date = now;
        commit->object.type = OBJ_COMMIT;
+       parent_tail = &commit->parents;
+       if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL))
+               die("no such ref: HEAD");
+       parent_tail = append_parent(parent_tail, head_sha1);
+       append_merge_parents(parent_tail);
+       verify_working_tree_path(commit, path);
  
        origin = make_origin(commit, path);
  
+       ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
+       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));
+       strbuf_addf(&msg,
+                   "author %s\n"
+                   "committer %s\n\n"
+                   "Version of %s from %s\n",
+                   ident, ident, path,
+                   (!contents_from ? path :
+                    (!strcmp(contents_from, "-") ? "standard input" : contents_from)));
+       commit->buffer = strbuf_detach(&msg, NULL);
        if (!contents_from || strcmp("-", contents_from)) {
                struct stat st;
                const char *read_from;
                switch (st.st_mode & S_IFMT) {
                case S_IFREG:
                        if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
 -                          textconv_object(read_from, mode, null_sha1, &buf_ptr, &buf_len))
 +                          textconv_object(read_from, mode, null_sha1, 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);
        }
        else {
                /* Reading from stdin */
-               contents_from = "standard input";
                mode = 0;
                if (strbuf_read(&buf, 0, 0) < 0)
                        die_errno("failed to read from stdin");
        ce = xcalloc(1, size);
        hashcpy(ce->sha1, origin->blob_sha1);
        memcpy(ce->name, path, len);
 -      ce->ce_flags = create_ce_flags(len, 0);
 +      ce->ce_flags = create_ce_flags(0);
 +      ce->ce_namelen = len;
        ce->ce_mode = create_ce_mode(mode);
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
  
         */
        cache_tree_invalidate_path(active_cache_tree, path);
  
-       commit->buffer = xmalloc(400);
-       ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
-       snprintf(commit->buffer, 400,
-               "tree 0000000000000000000000000000000000000000\n"
-               "parent %s\n"
-               "author %s\n"
-               "committer %s\n\n"
-               "Version of %s from %s\n",
-               sha1_to_hex(head_sha1),
-               ident, ident, path, contents_from ? contents_from : path);
        return commit;
  }
  
@@@ -2313,27 -2344,27 +2370,27 @@@ int cmd_blame(int argc, const char **ar
        static const char *revs_file = NULL;
        static const char *contents_from = NULL;
        static const struct option options[] = {
 -              OPT_BOOLEAN(0, "incremental", &incremental, "Show blame entries as we find them, incrementally"),
 -              OPT_BOOLEAN('b', NULL, &blank_boundary, "Show blank SHA-1 for boundary commits (Default: off)"),
 -              OPT_BOOLEAN(0, "root", &show_root, "Do not treat root commits as boundaries (Default: off)"),
 -              OPT_BOOLEAN(0, "show-stats", &show_stats, "Show work cost statistics"),
 -              OPT_BIT(0, "score-debug", &output_option, "Show output score for blame entries", OUTPUT_SHOW_SCORE),
 -              OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME),
 -              OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER),
 -              OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN),
 -              OPT_BIT(0, "line-porcelain", &output_option, "Show porcelain format with per-line commit information", OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
 -              OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT),
 -              OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
 -              OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
 -              OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
 -              OPT_BIT('e', "show-email", &output_option, "Show author email instead of name (Default: off)", OUTPUT_SHOW_EMAIL),
 -              OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
 -              OPT_BIT(0, "minimal", &xdl_opts, "Spend extra cycles to find better match", XDF_NEED_MINIMAL),
 -              OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
 -              OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
 -              { OPTION_CALLBACK, 'C', NULL, &opt, "score", "Find line copies within and across files", PARSE_OPT_OPTARG, blame_copy_callback },
 -              { OPTION_CALLBACK, 'M', NULL, &opt, "score", "Find line movements within and across files", PARSE_OPT_OPTARG, blame_move_callback },
 -              OPT_CALLBACK('L', NULL, &bottomtop, "n,m", "Process only line range n,m, counting from 1", blame_bottomtop_callback),
 +              OPT_BOOLEAN(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")),
 +              OPT_BOOLEAN('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")),
 +              OPT_BOOLEAN(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")),
 +              OPT_BOOLEAN(0, "show-stats", &show_stats, N_("Show work cost statistics")),
 +              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),
 +              OPT_BIT('p', "porcelain", &output_option, N_("Show in a format designed for machine consumption"), OUTPUT_PORCELAIN),
 +              OPT_BIT(0, "line-porcelain", &output_option, N_("Show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
 +              OPT_BIT('c', NULL, &output_option, N_("Use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT),
 +              OPT_BIT('t', NULL, &output_option, N_("Show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP),
 +              OPT_BIT('l', NULL, &output_option, N_("Show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME),
 +              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),
 +              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")),
 +              { OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback },
 +              { OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback },
 +              OPT_CALLBACK('L', NULL, &bottomtop, N_("n,m"), N_("Process only line range n,m, counting from 1"), blame_bottomtop_callback),
                OPT__ABBREV(&abbrev),
                OPT_END()
        };
  parse_done:
        argc = parse_options_end(&ctx);
  
 -      if (abbrev == -1)
 -              abbrev = default_abbrev;
 -      /* one more abbrev length is needed for the boundary commit */
 -      abbrev++;
 +      if (0 < abbrev)
 +              /* one more abbrev length is needed for the boundary commit */
 +              abbrev++;
  
        if (revs_file && read_ancestry(revs_file))
                die_errno("reading graft file '%s' failed", revs_file);
                        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, (char **) &sb.final_buf,
 +                  textconv_object(path, o->mode, o->blob_sha1, 1, (char **) &sb.final_buf,
                                    &sb.final_buf_size))
                        ;
                else