Merge branch 'of/commit-reach-repo-awareness' into seen

The can_all_from_reach() and can_all_from_reach_with_flag()
functions have been updated to accept a repository context,
preventing bugs where submodule merging incorrectly reads from the
superproject's commit-graph.

* of/commit-reach-repo-awareness:
  commit-reach: parse commits in the given repository
Junio C Hamano 2026-09-17 12:54:47 -07:00
commit bb703dd851
6 changed files with 62 additions and 21 deletions

View File

@ -658,7 +658,7 @@ int repo_is_descendant_of(struct repository *r,
struct commit_list *from_list = NULL;
int result;
commit_list_insert(commit, &from_list);
result = can_all_from_reach(from_list, with_commit, 0);
result = can_all_from_reach(r, from_list, with_commit, 0);
commit_list_free(from_list);
return result;
} else {
@ -952,7 +952,8 @@ int commit_contains(struct ref_filter *filter, struct commit *commit,
return result;
}

int can_all_from_reach_with_flag(struct object_array *from,
int can_all_from_reach_with_flag(struct repository *r,
struct object_array *from,
unsigned int with_flag,
unsigned int assign_flag,
timestamp_t min_commit_date,
@ -971,8 +972,7 @@ int can_all_from_reach_with_flag(struct object_array *from,
if (!from_one || from_one->flags & assign_flag)
continue;

from_one = deref_tag(the_repository, from_one,
"a from object", 0);
from_one = deref_tag(r, from_one, "a from object", 0);
if (!from_one || from_one->type != OBJ_COMMIT) {
/*
* no way to tell if this is reachable by
@ -985,7 +985,7 @@ int can_all_from_reach_with_flag(struct object_array *from,
}

list[nr_commits] = (struct commit *)from_one;
if (repo_parse_commit(the_repository, list[nr_commits]) ||
if (repo_parse_commit(r, list[nr_commits]) ||
commit_graph_generation(list[nr_commits]) < min_generation) {
result = 0;
goto cleanup;
@ -1020,7 +1020,7 @@ int can_all_from_reach_with_flag(struct object_array *from,
if (!(parent->item->object.flags & assign_flag)) {
parent->item->object.flags |= assign_flag;

if (repo_parse_commit(the_repository, parent->item) ||
if (repo_parse_commit(r, parent->item) ||
parent->item->date < min_commit_date ||
commit_graph_generation(parent->item) < min_generation)
continue;
@ -1054,8 +1054,8 @@ cleanup:
return result;
}

int can_all_from_reach(struct commit_list *from, struct commit_list *to,
int cutoff_by_min_date)
int can_all_from_reach(struct repository *r, struct commit_list *from,
struct commit_list *to, int cutoff_by_min_date)
{
struct object_array from_objs = OBJECT_ARRAY_INIT;
struct commit_list *from_iter = from, *to_iter = to;
@ -1066,7 +1066,7 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to,
while (from_iter) {
add_object_array(&from_iter->item->object, NULL, &from_objs);

if (!repo_parse_commit(the_repository, from_iter->item)) {
if (!repo_parse_commit(r, from_iter->item)) {
timestamp_t generation;
if (from_iter->item->date < min_commit_date)
min_commit_date = from_iter->item->date;
@ -1080,7 +1080,7 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to,
}

while (to_iter) {
if (!repo_parse_commit(the_repository, to_iter->item)) {
if (!repo_parse_commit(r, to_iter->item)) {
timestamp_t generation;
if (to_iter->item->date < min_commit_date)
min_commit_date = to_iter->item->date;
@ -1095,7 +1095,7 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to,
to_iter = to_iter->next;
}

result = can_all_from_reach_with_flag(&from_objs, PARENT2, PARENT1,
result = can_all_from_reach_with_flag(r, &from_objs, PARENT2, PARENT1,
min_commit_date, min_generation);

while (from) {

View File

@ -89,13 +89,14 @@ int commit_contains(struct ref_filter *filter, struct commit *commit,
* commits with date below 'min_commit_date' or generation below
* 'min_generation'.
*/
int can_all_from_reach_with_flag(struct object_array *from,
int can_all_from_reach_with_flag(struct repository *r,
struct object_array *from,
unsigned int with_flag,
unsigned int assign_flag,
timestamp_t min_commit_date,
timestamp_t min_generation);
int can_all_from_reach(struct commit_list *from, struct commit_list *to,
int commit_date_cutoff);
int can_all_from_reach(struct repository *r, struct commit_list *from,
struct commit_list *to, int commit_date_cutoff);


/*

View File

@ -2311,8 +2311,8 @@ void negotiate_using_fetch(const struct oid_array *negotiation_restrict_tips,
die(_("unexpected 'ready' from remote"));
else
do_check_stateless_delimiter(stateless_rpc, &reader);
if (can_all_from_reach_with_flag(&nt_object_array, COMMON,
REACH_SCRATCH, 0,
if (can_all_from_reach_with_flag(the_repository, &nt_object_array,
COMMON, REACH_SCRATCH, 0,
min_generation))
last_iteration = 1;
trace2_region_leave_printf("negotiation", "round",

View File

@ -127,7 +127,7 @@ int cmd__reach(int ac, const char **av)
print_sorted_commit_ids(list);
commit_list_free(list);
} 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(r, X, Y, 1));
} else if (!strcmp(av[1], "can_all_from_reach_with_flag")) {
struct commit_list *iter = Y;

@ -136,7 +136,8 @@ int cmd__reach(int ac, const char **av)
iter = iter->next;
}

printf("%s(X,_,_,0,0):%d\n", av[1], can_all_from_reach_with_flag(&X_obj, 2, 4, 0, 0));
printf("%s(X,_,_,0,0):%d\n", av[1],
can_all_from_reach_with_flag(r, &X_obj, 2, 4, 0, 0));
} else if (!strcmp(av[1], "commit_contains")) {
struct ref_filter filter = REF_FILTER_INIT;
struct contains_cache cache;

View File

@ -514,4 +514,43 @@ test_expect_success 'merging should fail with no merge base' '
)
'

test_expect_success 'setup for commit-graphs in superproject and submodule' '
git init commit-graph &&
(cd commit-graph &&
git init sub &&
for i in 1 2 3 4 5 6 7 8 9 10
do
test_commit -C sub "sub-$i" || return 1
done &&

git -C sub checkout --detach sub-1 &&
git add sub &&
git commit -m base &&

# Write the commit-graph of the superproject while it only
# contains a single commit. Any commit-graph position of the
# submodule is thus out of bounds for the superproject.
git commit-graph write --reachable &&

git checkout -b side &&
git -C sub checkout --detach sub-5 &&
git add sub &&
git commit -m side &&

git checkout main &&
git -C sub checkout --detach sub-10 &&
git add sub &&
git commit -m main &&

git -C sub commit-graph write --reachable)
'

test_expect_success 'merge does not mix up superproject and submodule commit-graphs' '
(cd commit-graph &&
git merge side &&
git rev-parse HEAD:sub >actual &&
git -C sub rev-parse sub-10^{commit} >expect &&
test_cmp expect actual)
'

test_done

View File

@ -532,9 +532,9 @@ static int ok_to_give_up(struct upload_pack_data *data)
if (!data->have_obj.nr)
return 0;

return can_all_from_reach_with_flag(&data->want_obj, THEY_HAVE,
COMMON_KNOWN, data->oldest_have,
min_generation);
return can_all_from_reach_with_flag(the_repository, &data->want_obj,
THEY_HAVE, COMMON_KNOWN,
data->oldest_have, min_generation);
}

static int get_common_commits(struct upload_pack_data *data,