提交 b67f6b26 编写于 作者: D Derrick Stolee 提交者: Junio C Hamano

commit-reach: properly peel tags

The can_all_from_reach_with_flag() algorithm was refactored in 4fbcca4e
"commit-reach: make can_all_from_reach... linear" but incorrectly
assumed that all objects provided were commits. During a fetch
negotiation, ok_to_give_up() in upload-pack.c may provide unpeeled tags
to the 'from' array. The current code creates a segfault.

Add a direct call to can_all_from_reach_with_flag() in 'test-tool reach'
and add a test in t6600-test-reach.sh that demonstrates this segfault.

Correct the issue by peeling tags when investigating the initial list
of objects in the 'from' array.
Signed-off-by: NJeff King <peff@peff.net>
Signed-off-by: NDerrick Stolee <dstolee@microsoft.com>
Signed-off-by: NJunio C Hamano <gitster@pobox.com>
上级 6621c838
...@@ -544,20 +544,42 @@ int can_all_from_reach_with_flag(struct object_array *from, ...@@ -544,20 +544,42 @@ int can_all_from_reach_with_flag(struct object_array *from,
{ {
struct commit **list = NULL; struct commit **list = NULL;
int i; int i;
int nr_commits;
int result = 1; int result = 1;
ALLOC_ARRAY(list, from->nr); ALLOC_ARRAY(list, from->nr);
nr_commits = 0;
for (i = 0; i < from->nr; i++) { for (i = 0; i < from->nr; i++) {
list[i] = (struct commit *)from->objects[i].item; struct object *from_one = from->objects[i].item;
if (parse_commit(list[i]) || if (!from_one || from_one->flags & assign_flag)
list[i]->generation < min_generation) continue;
return 0;
from_one = deref_tag(the_repository, from_one,
"a from object", 0);
if (!from_one || from_one->type != OBJ_COMMIT) {
/* no way to tell if this is reachable by
* looking at the ancestry chain alone, so
* leave a note to ourselves not to worry about
* this object anymore.
*/
from->objects[i].item->flags |= assign_flag;
continue;
}
list[nr_commits] = (struct commit *)from_one;
if (parse_commit(list[nr_commits]) ||
list[nr_commits]->generation < min_generation) {
result = 0;
goto cleanup;
}
nr_commits++;
} }
QSORT(list, from->nr, compare_commits_by_gen); QSORT(list, nr_commits, compare_commits_by_gen);
for (i = 0; i < from->nr; i++) { for (i = 0; i < nr_commits; i++) {
/* DFS from list[i] */ /* DFS from list[i] */
struct commit_list *stack = NULL; struct commit_list *stack = NULL;
...@@ -600,7 +622,7 @@ int can_all_from_reach_with_flag(struct object_array *from, ...@@ -600,7 +622,7 @@ int can_all_from_reach_with_flag(struct object_array *from,
} }
cleanup: cleanup:
for (i = 0; i < from->nr; i++) { for (i = 0; i < nr_commits; i++) {
clear_commit_marks(list[i], RESULT); clear_commit_marks(list[i], RESULT);
clear_commit_marks(list[i], assign_flag); clear_commit_marks(list[i], assign_flag);
} }
......
...@@ -31,6 +31,7 @@ int cmd__reach(int ac, const char **av) ...@@ -31,6 +31,7 @@ int cmd__reach(int ac, const char **av)
struct object_id oid_A, oid_B; struct object_id oid_A, oid_B;
struct commit *A, *B; struct commit *A, *B;
struct commit_list *X, *Y; struct commit_list *X, *Y;
struct object_array X_obj = OBJECT_ARRAY_INIT;
struct commit **X_array; struct commit **X_array;
int X_nr, X_alloc; int X_nr, X_alloc;
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT;
...@@ -49,7 +50,8 @@ int cmd__reach(int ac, const char **av) ...@@ -49,7 +50,8 @@ int cmd__reach(int ac, const char **av)
while (strbuf_getline(&buf, stdin) != EOF) { while (strbuf_getline(&buf, stdin) != EOF) {
struct object_id oid; struct object_id oid;
struct object *o; struct object *orig;
struct object *peeled;
struct commit *c; struct commit *c;
if (buf.len < 3) if (buf.len < 3)
continue; continue;
...@@ -57,14 +59,14 @@ int cmd__reach(int ac, const char **av) ...@@ -57,14 +59,14 @@ int cmd__reach(int ac, const char **av)
if (get_oid_committish(buf.buf + 2, &oid)) if (get_oid_committish(buf.buf + 2, &oid))
die("failed to resolve %s", buf.buf + 2); die("failed to resolve %s", buf.buf + 2);
o = parse_object(r, &oid); orig = parse_object(r, &oid);
o = deref_tag_noverify(o); peeled = deref_tag_noverify(orig);
if (!o) if (!peeled)
die("failed to load commit for input %s resulting in oid %s\n", die("failed to load commit for input %s resulting in oid %s\n",
buf.buf, oid_to_hex(&oid)); buf.buf, oid_to_hex(&oid));
c = object_as_type(r, o, OBJ_COMMIT, 0); c = object_as_type(r, peeled, OBJ_COMMIT, 0);
if (!c) if (!c)
die("failed to load commit for input %s resulting in oid %s\n", die("failed to load commit for input %s resulting in oid %s\n",
...@@ -85,6 +87,7 @@ int cmd__reach(int ac, const char **av) ...@@ -85,6 +87,7 @@ int cmd__reach(int ac, const char **av)
commit_list_insert(c, &X); commit_list_insert(c, &X);
ALLOC_GROW(X_array, X_nr + 1, X_alloc); ALLOC_GROW(X_array, X_nr + 1, X_alloc);
X_array[X_nr++] = c; X_array[X_nr++] = c;
add_object_array(orig, NULL, &X_obj);
break; break;
case 'Y': case 'Y':
...@@ -113,6 +116,15 @@ int cmd__reach(int ac, const char **av) ...@@ -113,6 +116,15 @@ int cmd__reach(int ac, const char **av)
print_sorted_commit_ids(list); print_sorted_commit_ids(list);
} else if (!strcmp(av[1], "can_all_from_reach")) { } else if (!strcmp(av[1], "can_all_from_reach")) {
printf("%s(X,Y):%d\n", av[1], can_all_from_reach(X, Y, 1)); printf("%s(X,Y):%d\n", av[1], can_all_from_reach(X, Y, 1));
} else if (!strcmp(av[1], "can_all_from_reach_with_flag")) {
struct commit_list *iter = Y;
while (iter) {
iter->item->object.flags |= 2;
iter = iter->next;
}
printf("%s(X,_,_,0,0):%d\n", av[1], can_all_from_reach_with_flag(&X_obj, 2, 4, 0, 0));
} else if (!strcmp(av[1], "commit_contains")) { } else if (!strcmp(av[1], "commit_contains")) {
struct ref_filter filter; struct ref_filter filter;
struct contains_cache cache; struct contains_cache cache;
......
...@@ -31,7 +31,8 @@ test_expect_success 'setup' ' ...@@ -31,7 +31,8 @@ test_expect_success 'setup' '
for i in $(test_seq 1 10) for i in $(test_seq 1 10)
do do
test_commit "1-$i" && test_commit "1-$i" &&
git branch -f commit-1-$i git branch -f commit-1-$i &&
git tag -a -m "1-$i" tag-1-$i commit-1-$i
done && done &&
for j in $(test_seq 1 9) for j in $(test_seq 1 9)
do do
...@@ -39,11 +40,13 @@ test_expect_success 'setup' ' ...@@ -39,11 +40,13 @@ test_expect_success 'setup' '
x=$(($j + 1)) && x=$(($j + 1)) &&
test_commit "$x-1" && test_commit "$x-1" &&
git branch -f commit-$x-1 && git branch -f commit-$x-1 &&
git tag -a -m "$x-1" tag-$x-1 commit-$x-1 &&
for i in $(test_seq 2 10) for i in $(test_seq 2 10)
do do
git merge commit-$j-$i -m "$x-$i" && git merge commit-$j-$i -m "$x-$i" &&
git branch -f commit-$x-$i git branch -f commit-$x-$i &&
git tag -a -m "$x-$i" tag-$x-$i commit-$x-$i
done done
done && done &&
git commit-graph write --reachable && git commit-graph write --reachable &&
...@@ -205,6 +208,29 @@ test_expect_success 'can_all_from_reach:miss' ' ...@@ -205,6 +208,29 @@ test_expect_success 'can_all_from_reach:miss' '
test_three_modes can_all_from_reach test_three_modes can_all_from_reach
' '
test_expect_success 'can_all_from_reach_with_flag: tags case' '
cat >input <<-\EOF &&
X:tag-2-10
X:tag-3-9
X:tag-4-8
X:commit-5-7
X:commit-6-6
X:commit-7-5
X:commit-8-4
X:commit-9-3
Y:tag-1-9
Y:tag-2-8
Y:tag-3-7
Y:commit-4-6
Y:commit-5-5
Y:commit-6-4
Y:commit-7-3
Y:commit-8-1
EOF
echo "can_all_from_reach_with_flag(X,_,_,0,0):1" >expect &&
test_three_modes can_all_from_reach_with_flag
'
test_expect_success 'commit_contains:hit' ' test_expect_success 'commit_contains:hit' '
cat >input <<-\EOF && cat >input <<-\EOF &&
A:commit-7-7 A:commit-7-7
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册