[PATCH v2 2/2] odb: ignore refs of shallow and grafted alternates
- From
- Igor Wiedler <iwiedler@gitlab.com>
- Date
- Oct 14, 2026, 18:11 UTC
- Message-ID
- <20261014181134.56220-3-iwiedler@gitlab.com>
- In-Reply-To
- <20261014103507.35317-1-iwiedler@gitlab.com>
odb_for_each_alternate_ref() reports the refs of every alternate object store that is a repository. Its three callers use them as commits whose history can be assumed to be present: "rev-list --alternate-refs" (and through it the connectivity check after a fetch), fetch-pack, which sends them as "have"s so that the server does not resend what the alternate has, and receive-pack, which advertises them as ".have".
That assumption does not hold for a shallow repository, whose refs point at commits with no history behind them below its shallow boundary, nor for a grafted one. A repository borrowing objects from such an alternate does not see the alternate's shallow file, so the history that is missing there is missing here as well. The fetch then succeeds, the connectivity check stops at the alternate's refs, and a later "git log" fails with "Could not read <oid>".
"git clone --reference" already rejects a shallow or grafted reference repository for this reason, but nothing prevents such a repository from being added via objects/info/alternates or GIT_ALTERNATE_OBJECT_DIRECTORIES, or from being made shallow after other repositories started borrowing from it.
Skip the refs of an alternate that has a "shallow" file or an "info/grafts" file, the same checks clone uses. Its objects are still available through the object store. Only its refs are no longer used as a boundary.
Partial clones have the same problem for the objects they lack, but detecting one requires reading the alternate's config, so this patch does not handle them.
Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Igor Wiedler <iwiedler@gitlab.com> --- Documentation/rev-list-options.adoc | 7 ++++--- odb.c | 11 +++++++++++ t/t5618-alternate-refs.sh | 13 +++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-)
diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index bd9f345690..982a53c694 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc @@ -223,9 +223,10 @@ explicitly. Pretend as if all objects mentioned as ref tips of alternate repositories were listed on the command line. An alternate repository is any repository whose object directory is specified - in `objects/info/alternates`. The set of included objects may - be modified by `core.alternateRefsCommand`, etc. See - linkgit:git-config[1]. + in `objects/info/alternates`. The refs of shallow and grafted + alternate repositories are ignored, as the history behind them + may be missing. The set of included objects may be modified by + `core.alternateRefsCommand`, etc. See linkgit:git-config[1]. `--single-worktree`:: By default, all working trees will be examined by the diff --git a/odb.c b/odb.c index 0200e26f21..e6bb7ce6e6 100644 --- a/odb.c +++ b/odb.c @@ -458,6 +458,17 @@ static int refs_from_alternate_cb(struct odb_source *alternate, goto out; strbuf_setlen(&path, base_len); + /* A shallow or grafted repository may lack the history behind its refs. */ + strbuf_addstr(&path, "/shallow"); + if (!access(path.buf, F_OK)) + goto out; + strbuf_setlen(&path, base_len); + + strbuf_addstr(&path, "/info/grafts"); + if (!access(path.buf, F_OK)) + goto out; + strbuf_setlen(&path, base_len); + read_alternate_refs(alternate->odb->repo, path.buf, cb->fn, cb->payload); out: diff --git a/t/t5618-alternate-refs.sh b/t/t5618-alternate-refs.sh index 4cc39af0f0..9c3862709a 100755 --- a/t/t5618-alternate-refs.sh +++ b/t/t5618-alternate-refs.sh @@ -70,4 +70,17 @@ test_expect_success 'connectivity check of a deepening fetch excludes alternate --alternate-refs --quiet <deepen.trace ' +test_expect_success 'refs of a shallow alternate are ignored' ' + git clone --depth=1 "file://$TRASH_DIRECTORY" "$TRASH_DIRECTORY/shallow" && + test_commit -C "$TRASH_DIRECTORY/shallow" shallow-only && + git -C "$TRASH_DIRECTORY/shallow" rev-parse HEAD >shallow-tip && + cp .git/objects/info/alternates alternates.bak && + test_when_finished "mv alternates.bak .git/objects/info/alternates" && + echo "$TRASH_DIRECTORY/shallow/.git/objects" >>.git/objects/info/alternates && + git cat-file -e $(cat shallow-tip) && + git rev-list --remotes=origin >expect && + git rev-list --alternate-refs >actual && + test_cmp expect actual +' + test_done
-- 2.56.0