Skip to content

Commit 9aeaab6

Browse files
committed
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 <[email protected]>
1 parent ffcabcc commit 9aeaab6

File tree

2 files changed

+70
-25
lines changed

2 files changed

+70
-25
lines changed

builtin/blame.c

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2044,17 +2044,53 @@ static int git_blame_config(const char *var, const char *value, void *cb)
20442044
return git_default_config(var, value, cb);
20452045
}
20462046

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)
20482048
{
2049-
unsigned char blob_sha1[20];
2050-
unsigned mode;
2049+
struct commit_list *parents;
20512050

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);
20582094
}
20592095

20602096
/*
@@ -2067,26 +2103,46 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
20672103
{
20682104
struct commit *commit;
20692105
struct origin *origin;
2106+
struct commit_list **parent_tail, *parent;
20702107
unsigned char head_sha1[20];
20712108
struct strbuf buf = STRBUF_INIT;
20722109
const char *ident;
20732110
time_t now;
20742111
int size, len;
20752112
struct cache_entry *ce;
20762113
unsigned mode;
2077-
2078-
verify_working_tree_path(head_sha1, path);
2114+
struct strbuf msg = STRBUF_INIT;
20792115

20802116
time(&now);
20812117
commit = xcalloc(1, sizeof(*commit));
2082-
commit->parents = xcalloc(1, sizeof(*commit->parents));
2083-
commit->parents->item = lookup_commit_reference(head_sha1);
20842118
commit->object.parsed = 1;
20852119
commit->date = now;
20862120
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);
20872129

20882130
origin = make_origin(commit, path);
20892131

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+
20902146
if (!contents_from || strcmp("-", contents_from)) {
20912147
struct stat st;
20922148
const char *read_from;
@@ -2123,7 +2179,6 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
21232179
}
21242180
else {
21252181
/* Reading from stdin */
2126-
contents_from = "standard input";
21272182
mode = 0;
21282183
if (strbuf_read(&buf, 0, 0) < 0)
21292184
die_errno("failed to read from stdin");
@@ -2167,16 +2222,6 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
21672222
*/
21682223
cache_tree_invalidate_path(active_cache_tree, path);
21692224

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);
21802225
return commit;
21812226
}
21822227

t/t8004-blame-with-conflicts.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ test_expect_success \
6767
'
6868

6969
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
7171
'
7272

7373
test_done

0 commit comments

Comments
 (0)