blame: allow "blame file" in the middle of a conflicted merge

"git blame file" has always meant "find the origin of each line of the file in the history leading to HEAD, oh by the way, blame the lines that are modified locally to the working tree". This teaches "git blame" that during a conflicted merge, some uncommitted changes may have come from the other history that is being merged. The verify_working_tree_path() function introduced in the previous patch to notice a typo in the filename (primarily on case insensitive filesystems) has been updated to allow a filename that does not exist in HEAD (i.e. the tip of our history) as long as it exists one of the commits being merged, so that a "we deleted, the other side modified" case tracks the history of the file in the history of the other side. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Sep 11, 2012 at 14:30 UTC 9aeaab6811dce596b4f6141d76f5300359bfd009
2 files changed +70 -25
builtin/blame.c
+69 -24
@@ -2044,17 +2044,53 @@ static int git_blame_config(const char *var, const char *value, void *cb)
2044 return git_default_config(var, value, cb);
2045 }
2046
2047 -static void verify_working_tree_path(unsigned char *head_sha1, const char *path)
2047 +static void verify_working_tree_path(struct commit *work_tree, const char *path)
2048 {
2049 - unsigned char blob_sha1[20];
2050 - unsigned mode;
2049 + struct commit_list *parents;
2050
2052 - if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL))
2053 - die("no such ref: HEAD");
2054 - if (get_tree_entry(head_sha1, path, blob_sha1, &mode))
2055 - die("no such path '%s' in HEAD", path);
2056 - if (sha1_object_info(blob_sha1, NULL) != OBJ_BLOB)
2057 - die("path '%s' in HEAD is not a blob", path);
2051 + for (parents = work_tree->parents; parents; parents = parents->next) {
2052 + const unsigned char *commit_sha1 = parents->item->object.sha1;
2053 + unsigned char blob_sha1[20];
2054 + unsigned mode;
2055 +
2056 + if (!get_tree_entry(commit_sha1, path, blob_sha1, &mode) &&
2057 + sha1_object_info(blob_sha1, NULL) == OBJ_BLOB)
2058 + return;
2059 + }
2060 + die("no such path '%s' in HEAD", path);
2061 +}
2062 +
2063 +static struct commit_list **append_parent(struct commit_list **tail, const unsigned char *sha1)
2064 +{
2065 + struct commit *parent;
2066 +
2067 + parent = lookup_commit_reference(sha1);
2068 + if (!parent)
2069 + die("no such commit %s", sha1_to_hex(sha1));
2070 + return &commit_list_insert(parent, tail)->next;
2071 +}
2072 +
2073 +static void append_merge_parents(struct commit_list **tail)
2074 +{
2075 + int merge_head;
2076 + const char *merge_head_file = git_path("MERGE_HEAD");
2077 + struct strbuf line = STRBUF_INIT;
2078 +
2079 + merge_head = open(merge_head_file, O_RDONLY);
2080 + if (merge_head < 0) {
2081 + if (errno == ENOENT)
2082 + return;
2083 + die("cannot open '%s' for reading", merge_head_file);
2084 + }
2085 +
2086 + while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) {
2087 + unsigned char sha1[20];
2088 + if (line.len < 40 || get_sha1_hex(line.buf, sha1))
2089 + die("unknown line in '%s': %s", merge_head_file, line.buf);
2090 + tail = append_parent(tail, sha1);
2091 + }
2092 + close(merge_head);
2093 + strbuf_release(&line);
2094 }
2095
2096 /*
@@ -2067,6 +2103,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
2103 {
2104 struct commit *commit;
2105 struct origin *origin;
2106 + struct commit_list **parent_tail, *parent;
2107 unsigned char head_sha1[20];
2108 struct strbuf buf = STRBUF_INIT;
2109 const char *ident;
@@ -2074,19 +2111,38 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
2111 int size, len;
2112 struct cache_entry *ce;
2113 unsigned mode;
2077 -
2078 - verify_working_tree_path(head_sha1, path);
2114 + struct strbuf msg = STRBUF_INIT;
2115
2116 time(&now);
2117 commit = xcalloc(1, sizeof(*commit));
2082 - commit->parents = xcalloc(1, sizeof(*commit->parents));
2083 - commit->parents->item = lookup_commit_reference(head_sha1);
2118 commit->object.parsed = 1;
2119 commit->date = now;
2120 commit->object.type = OBJ_COMMIT;
2121 + parent_tail = &commit->parents;
2122 +
2123 + if (!resolve_ref_unsafe("HEAD", head_sha1, 1, NULL))
2124 + die("no such ref: HEAD");
2125 +
2126 + parent_tail = append_parent(parent_tail, head_sha1);
2127 + append_merge_parents(parent_tail);
2128 + verify_working_tree_path(commit, path);
2129
2130 origin = make_origin(commit, path);
2131
2132 + ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
2133 + strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n");
2134 + for (parent = commit->parents; parent; parent = parent->next)
2135 + strbuf_addf(&msg, "parent %s\n",
2136 + sha1_to_hex(parent->item->object.sha1));
2137 + strbuf_addf(&msg,
2138 + "author %s\n"
2139 + "committer %s\n\n"
2140 + "Version of %s from %s\n",
2141 + ident, ident, path,
2142 + (!contents_from ? path :
2143 + (!strcmp(contents_from, "-") ? "standard input" : contents_from)));
2144 + commit->buffer = strbuf_detach(&msg, NULL);
2145 +
2146 if (!contents_from || strcmp("-", contents_from)) {
2147 struct stat st;
2148 const char *read_from;
@@ -2123,7 +2179,6 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
2179 }
2180 else {
2181 /* Reading from stdin */
2126 - contents_from = "standard input";
2182 mode = 0;
2183 if (strbuf_read(&buf, 0, 0) < 0)
2184 die_errno("failed to read from stdin");
@@ -2167,16 +2222,6 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
2222 */
2223 cache_tree_invalidate_path(active_cache_tree, path);
2224
2170 - commit->buffer = xmalloc(400);
2171 - ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
2172 - snprintf(commit->buffer, 400,
2173 - "tree 0000000000000000000000000000000000000000\n"
2174 - "parent %s\n"
2175 - "author %s\n"
2176 - "committer %s\n\n"
2177 - "Version of %s from %s\n",
2178 - sha1_to_hex(head_sha1),
2179 - ident, ident, path, contents_from ? contents_from : path);
2225 return commit;
2226 }
2227
t/t8004-blame-with-conflicts.sh
+1 -1
@@ -67,7 +67,7 @@ test_expect_success \
67 '
68
69 test_expect_success 'blame does not crash with conflicted file in stages 1,3' '
70 - test_must_fail git blame file1
70 + git blame file1
71 '
72
73 test_done