Skip to content

Commit 2ec4150

Browse files
peffgitster
authored andcommitted
score_trees(): fix iteration over trees with missing entries
In score_trees(), we walk over two sorted trees to find which entries are missing or have different content between the two. So if we have two trees with these entries: one two --- --- a a b c c d we'd expect the loop to: - compare "a" to "a" - compare "b" to "c"; because these are sorted lists, we know that the second tree does not have "b" - compare "c" to "c" - compare "d" to end-of-list; we know that the first tree does not have "d" And prior to d8febde (match-trees: simplify score_trees() using tree_entry(), 2013-03-24) that worked. But after that commit, we mistakenly increment the tree pointers for every loop iteration, even when we've processed the entry for only one side. As a result, we end up doing this: - compare "a" to "a" - compare "b" to "c"; we know that we do not have "b", but we still increment both tree pointers; at this point we're out of sync and all further comparisons are wrong - compare "c" to "d" and mistakenly claim that the second tree does not have "c" - exit the loop, mistakenly not realizing that the first tree does not have "d" So contrary to the claim in d8febde, we really do need to manually use update_tree_entry(), because advancing the tree pointer depends on the entry comparison. That means we must stop using tree_entry() to access each entry, since it auto-advances the pointer. Instead: - we'll use tree_desc.size directly to know if there's anything left to look at (which is what tree_entry() was doing under the hood) - rather than do an extra struct assignment to "e1" and "e2", we can just access the "entry" field of tree_desc directly That makes us a little more intimate with the tree_desc code, but that's not uncommon for its callers. The included test shows off the bug by adding a new entry "bar.t", which sorts early in the tree and de-syncs the comparison for "foo.t", which comes after. Reported-by: George Shammas <[email protected]> Helped-by: René Scharfe <[email protected]> Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent a42a58d commit 2ec4150

File tree

2 files changed

+54
-17
lines changed

2 files changed

+54
-17
lines changed

match-trees.c

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -82,34 +82,43 @@ static int score_trees(const struct object_id *hash1, const struct object_id *ha
8282
int score = 0;
8383

8484
for (;;) {
85-
struct name_entry e1, e2;
86-
int got_entry_from_one = tree_entry(&one, &e1);
87-
int got_entry_from_two = tree_entry(&two, &e2);
8885
int cmp;
8986

90-
if (got_entry_from_one && got_entry_from_two)
91-
cmp = base_name_entries_compare(&e1, &e2);
92-
else if (got_entry_from_one)
87+
if (one.size && two.size)
88+
cmp = base_name_entries_compare(&one.entry, &two.entry);
89+
else if (one.size)
9390
/* two lacks this entry */
9491
cmp = -1;
95-
else if (got_entry_from_two)
92+
else if (two.size)
9693
/* two has more entries */
9794
cmp = 1;
9895
else
9996
break;
10097

101-
if (cmp < 0)
98+
if (cmp < 0) {
10299
/* path1 does not appear in two */
103-
score += score_missing(e1.mode, e1.path);
104-
else if (cmp > 0)
100+
score += score_missing(one.entry.mode, one.entry.path);
101+
update_tree_entry(&one);
102+
} else if (cmp > 0) {
105103
/* path2 does not appear in one */
106-
score += score_missing(e2.mode, e2.path);
107-
else if (oidcmp(e1.oid, e2.oid))
108-
/* they are different */
109-
score += score_differs(e1.mode, e2.mode, e1.path);
110-
else
111-
/* same subtree or blob */
112-
score += score_matches(e1.mode, e2.mode, e1.path);
104+
score += score_missing(two.entry.mode, two.entry.path);
105+
update_tree_entry(&two);
106+
} else {
107+
/* path appears in both */
108+
if (oidcmp(one.entry.oid, two.entry.oid)) {
109+
/* they are different */
110+
score += score_differs(one.entry.mode,
111+
two.entry.mode,
112+
one.entry.path);
113+
} else {
114+
/* same subtree or blob */
115+
score += score_matches(one.entry.mode,
116+
two.entry.mode,
117+
one.entry.path);
118+
}
119+
update_tree_entry(&one);
120+
update_tree_entry(&two);
121+
}
113122
}
114123
free(one_buf);
115124
free(two_buf);

t/t6029-merge-subtree.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,34 @@ test_expect_success 'subtree available and works like recursive' '
2929
3030
'
3131

32+
test_expect_success 'setup branch sub' '
33+
git checkout --orphan sub &&
34+
git rm -rf . &&
35+
test_commit foo
36+
'
37+
38+
test_expect_success 'setup branch main' '
39+
git checkout -b main master &&
40+
git merge -s ours --no-commit --allow-unrelated-histories sub &&
41+
git read-tree --prefix=dir/ -u sub &&
42+
git commit -m "initial merge of sub into main" &&
43+
test_path_is_file dir/foo.t &&
44+
test_path_is_file hello
45+
'
46+
47+
test_expect_success 'update branch sub' '
48+
git checkout sub &&
49+
test_commit bar
50+
'
51+
52+
test_expect_success 'update branch main' '
53+
git checkout main &&
54+
git merge -s subtree sub -m "second merge of sub into main" &&
55+
test_path_is_file dir/bar.t &&
56+
test_path_is_file dir/foo.t &&
57+
test_path_is_file hello
58+
'
59+
3260
test_expect_success 'setup' '
3361
mkdir git-gui &&
3462
cd git-gui &&

0 commit comments

Comments
 (0)