# [BUG] submodule merge tries to read B's commit from A

4 messages from 2026-09-23 to 2026-10-02. Participants: Guillaume CHAUVEL, Philippe Blain, Patrick Steinhardt.
Thread: https://gitlist.dev/t/66379

## Guillaume CHAUVEL, 2026-09-23 20:20

Subject: [BUG] submodule merge tries to read B's commit from A
Message-ID: <CAP4DsUexEmm1qo6jH+Qzy+n3dQs_OCJ8yg=ReF+aVrcTrC7NeQ@mail.gmail.com>

```
I ran into two problems while merging a superproject with submodules.

One problem, involving the repository used for commit-graph lookups, was
reported in this thread:
https://lore.kernel.org/git/d3241733-d015-4646-88e0-06e56a04e77b@nutanix.com/T/#m174067937aaf76e9fa844386961b3e9e66c1e4d9

The other problem is that during a merge, Git sometimes tries to read
from submodule A a commit that exists only in submodule B. I reproduced
this with Git v2.56.0-rc2, built from source in an Ubuntu 26.04
container and an Alpine container. The reproducer below triggered the
issue in all 50 Ubuntu runs and in 43 out of 50 Alpine runs.

The merge should report a submodule conflict, not look for B's commit
in A or report A as corrupt. The script checks the OID's presence in
both submodules and prints the "BUG" line when it finds this case.

---------
#!/usr/bin/env bash

set -euo pipefail

unset $(git rev-parse --local-env-vars)
export LC_ALL=C
export GIT_CONFIG_NOSYSTEM=1
export GIT_CONFIG_GLOBAL=/dev/null
export GIT_DEFAULT_HASH=sha1
export GIT_TEMPLATE_DIR=
export GIT_AUTHOR_NAME=Reproducer
export GIT_AUTHOR_EMAIL=reproducer@example.invalid
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
export GIT_AUTHOR_DATE='2000-01-01T00:00:00 +0000'
export GIT_COMMITTER_DATE='2000-01-01T00:00:00 +0000'

tmpdir=$(mktemp -d)

for name in A B; do
mkdir "$tmpdir/source-$name"
cd "$tmpdir/source-$name"
git init -q -b main
printf '%s base\n' "$name" >file
git add file
git commit -qm "$name base"
git switch -qc branch-a
git commit --allow-empty -qm "$name branch-a"
git switch -qc branch-b main
git commit --allow-empty -qm "$name branch-b"
git switch -q main
done

mkdir "$tmpdir/super"
cd "$tmpdir/super"
git init -q -b base
git config --local protocol.file.allow always
for name in A B; do
# reproduces the bug
git -c protocol.file.allow=always submodule add -q
"file://$tmpdir/source-$name" "$name"

# does not reproduce the bug
# git  -c protocol.file.allow=always submodule add -q
"$tmpdir/source-$name" "$name"
done
git add .
git commit -qm base

git switch -qc branch-a
for name in A B; do
(cd "$name" && git switch -q -c branch-a --track origin/branch-a)
done
git add A B
git commit -qm branch-a

git switch -qc branch-b base
for name in A B; do
(cd "$name" && git switch -q -c branch-b --track origin/branch-b)
done
git add A B
git commit -qm branch-b

cd "$tmpdir"
git -c protocol.file.allow=always clone -q --no-local
"file://$tmpdir/super" clone
cd clone
git -c protocol.file.allow=always submodule update --init -q
git switch -q -c branch-a --track origin/branch-a
if merge_output=$(git merge branch-b 2>&1); then
merge_status=0
else
merge_status=$?
fi
printf 'git merge exit status: %s\n%s\n' "$merge_status" "$merge_output"

if [[ $merge_output =~ Could\ not\ read\ ([0-9a-f]{40}|[0-9a-f]{64}) ]]; then
foreign_oid=${BASH_REMATCH[1]}
if ! (cd A && git cat-file -e "$foreign_oid" 2>/dev/null) &&
(cd B && git cat-file -e "$foreign_oid" 2>/dev/null); then
printf 'BUG: OID %s belongs to B instead of A\n' "$foreign_oid"
fi
fi
---------

One run produced:

git merge exit status: 2
error: Could not read 7d549ba7e9152029e66ddca8dd23ee7da32b036f
error: could not parse commit 7d549ba7e9152029e66ddca8dd23ee7da32b036f
error: failed to merge submodule A (repository corrupt)
Merge with strategy ort failed.
BUG: OID 7d549ba7e9152029e66ddca8dd23ee7da32b036f belongs to B instead of A

An AI analysis identified a likely cause: a delta-base cache entry may
remain after its pack is closed. If a pack from another submodule reuses
the same packed_git address and base offset, Git may return stale cached
data.

```

## Philippe Blain, 2026-09-30 18:31

Subject: Re: [BUG] submodule merge tries to read B's commit from A
Message-ID: <764b8c2e-cf09-4531-94f2-268f97a889d7@gmail.com>
In-Reply-To: <CAP4DsUexEmm1qo6jH+Qzy+n3dQs_OCJ8yg=ReF+aVrcTrC7NeQ@mail.gmail.com>

```
Hi Guillaume,

Le 2026-09-23 à 16 h 20, Guillaume CHAUVEL a écrit :
> I ran into two problems while merging a superproject with submodules.
> 
> One problem, involving the repository used for commit-graph lookups, was
> reported in this thread:
> https://lore.kernel.org/git/d3241733-d015-4646-88e0-06e56a04e77b@nutanix.com/T/#m174067937aaf76e9fa844386961b3e9e66c1e4d9

FYI, the above bug was fixed in 700f7b74de (commit-reach: parse commits in 
the given repository, 2026-09-16), which is currently in 'next' but not yet
in master.

> The other problem is that during a merge, Git sometimes tries to read
> from submodule A a commit that exists only in submodule B. I reproduced
> this with Git v2.56.0-rc2, built from source in an Ubuntu 26.04
> container and an Alpine container. The reproducer below triggered the
> issue in all 50 Ubuntu runs and in 43 out of 50 Alpine runs.
> 
> The merge should report a submodule conflict, not look for B's commit
> in A or report A as corrupt. The script checks the OID's presence in
> both submodules and prints the "BUG" line when it finds this case.

Thanks for the reproducer, I confirm I see the same behaviour with v2.56.0-rc2, 
on RHEL 9. With v2.48.1, the merge results in a conflict, instead of aborting, 
although I get a spurious "hash mismatch" message, and the reason for the 
conflict ("commits not present") is wrong:

git version 2.48.1
git merge exit status: 1
error: hash mismatch 2ca9f0f330e976b992fc18633d1d267b8aad596e
Failed to merge submodule A (commits not present)
CONFLICT (submodule): Merge conflict in A
Failed to merge submodule B
CONFLICT (submodule): Merge conflict in B
Automatic merge failed; fix conflicts and then commit the result.

With 2.33.0, which I chose randomly, we get the correct behaviour:

git version 2.33.0
git merge exit status: 1
Failed to merge submodule A
CONFLICT (submodule): Merge conflict in A
Failed to merge submodule B
CONFLICT (submodule): Merge conflict in B
Automatic merge failed; fix conflicts and then commit the result.

I turned your reproducer into a bisection script (~/bisect-merge.sh) 
by tweaking the final 'if':

ˋˋˋ
if [[ $merge_output =~ Could\ not\ read\ ([0-9a-f]{40}|[0-9a-f]{64}) ]]; then
    foreign_oid=${BASH_REMATCH[1]}
    if ! (cd A && git cat-file -e "$foreign_oid" 2>/dev/null) &&
         (cd B && git cat-file -e "$foreign_oid" 2>/dev/null); then
        printf 'BUG: OID %s belongs to B instead of A\n' "$foreign_oid"
        exit 1
    fi
elif [[ $merge_output =~ hash\ mismatch ]];then
        [ ${1:-""} = MISMATCH ] && exit 1 || exit 0
else
    exit 0
fi
ˋˋˋ

and invoking it in my ~/bisect-git.sh script:

ˋˋˋ
#!/bin/bash

make clean > /dev/null
# build but keep the output on one line
if	make -j |& { while read line; do  printf "\033[K%s\r" "${line}" ; done; 
                     printf "\033[KFinished building $(cat GIT-VERSION-FILE)\n" ; }
then
	# run project specific test and report its status
	export PATH="$PWD/bin-wrappers/:$PATH"
	~/bisect-merge.sh "$@"
	status=$?
else
	# tell the caller this is untestable
	status=125
fi

# return control
echo
exit $status
ˋˋˋ

Bisecting the merge failure with:

	git bisect start v2.56.0-rc2 v2.48.1 && git bisect run ~/bisect-git.sh

finds bb5da75d61 (commit: use commit graph in lookup_commit_reference_gently(), 
2026-02-16), i.e. v2.54.0-rc0~136^2, which is the same commit from which the 
commit-graph bug mentioned above originates. I CC'ed Patrick, its author.

Bisecting the "hash mismatch" behaviour with:

	git bisect start v2.48.1 v2.33.0 && git bisect run ~/bisect-git.sh MISMATCH

finds 6f1e9394e2 (object: fix leaking packfiles when closing object store, 2024-08-08),
i.e. v2.47.0-rc0~123^2, which is also authored by Patrick.

I did not yet dig further, but I have a few additional observations:

- in contrast to the commit-graph bug, disabling the use of commit-graphs via
  'git config --global core.commitGraph false' early in the script, by moving the 'tmpdir'
  definition to the top and setting GIT_CONFIG_GLOBAL=$tmpdir/.gitconfig, does not change
  the behaviour, neither in the "repository corrupt" case, nor in the "hash mismatch" case.
- On Ubuntu 22.02 under WSL, the reproducer does not trigger the bug on v2.56.0-rc2 (on a dozen runs),
  but it does trigger it on v2.55.0. Funnily on that system with v2.56.0-rc2 I get the correct behaviour !
  (no "hash mismatch" either).
- On a Ubuntu 22.04 Docker container, I get the same behaviour as on RHEL 9.

> An AI analysis identified a likely cause: a delta-base cache entry may
> remain after its pack is closed. If a pack from another submodule reuses
> the same packed_git address and base offset, Git may return stale cached
> data.
> 

```

## Patrick Steinhardt, 2026-10-01 14:09

Subject: Re: [BUG] submodule merge tries to read B's commit from A
Message-ID: <ar5ppRMQ8NkGnbGp@pks.im>
In-Reply-To: <764b8c2e-cf09-4531-94f2-268f97a889d7@gmail.com>

```
On Wed, Sep 30, 2026 at 02:31:44PM -0400, Philippe Blain wrote:
> I did not yet dig further, but I have a few additional observations:
> 
> - in contrast to the commit-graph bug, disabling the use of commit-graphs via
>   'git config --global core.commitGraph false' early in the script, by moving the 'tmpdir'
>   definition to the top and setting GIT_CONFIG_GLOBAL=$tmpdir/.gitconfig, does not change
>   the behaviour, neither in the "repository corrupt" case, nor in the "hash mismatch" case.
> - On Ubuntu 22.02 under WSL, the reproducer does not trigger the bug on v2.56.0-rc2 (on a dozen runs),
>   but it does trigger it on v2.55.0. Funnily on that system with v2.56.0-rc2 I get the correct behaviour !
>   (no "hash mismatch" either).
> - On a Ubuntu 22.04 Docker container, I get the same behaviour as on RHEL 9.
> 
> > An AI analysis identified a likely cause: a delta-base cache entry may
> > remain after its pack is closed. If a pack from another submodule reuses
> > the same packed_git address and base offset, Git may return stale cached
> > data.

Yup, that seems to be the issue indeed. We should really be clearing
packfiles out of the delta base cache when closing packfiles, but we
don't right now. I'll investigate tomorrow.

Thanks!

Patrick

```

## Patrick Steinhardt, 2026-10-02 08:10

Subject: Re: [BUG] submodule merge tries to read B's commit from A
Message-ID: <ar9nBA2e_sEiFZ4k@pks.im>
In-Reply-To: <ar5ppRMQ8NkGnbGp@pks.im>

```
On Thu, Oct 01, 2026 at 04:09:41PM +0200, Patrick Steinhardt wrote:
> On Wed, Sep 30, 2026 at 02:31:44PM -0400, Philippe Blain wrote:
> > I did not yet dig further, but I have a few additional observations:
> > 
> > - in contrast to the commit-graph bug, disabling the use of commit-graphs via
> >   'git config --global core.commitGraph false' early in the script, by moving the 'tmpdir'
> >   definition to the top and setting GIT_CONFIG_GLOBAL=$tmpdir/.gitconfig, does not change
> >   the behaviour, neither in the "repository corrupt" case, nor in the "hash mismatch" case.
> > - On Ubuntu 22.02 under WSL, the reproducer does not trigger the bug on v2.56.0-rc2 (on a dozen runs),
> >   but it does trigger it on v2.55.0. Funnily on that system with v2.56.0-rc2 I get the correct behaviour !
> >   (no "hash mismatch" either).
> > - On a Ubuntu 22.04 Docker container, I get the same behaviour as on RHEL 9.
> > 
> > > An AI analysis identified a likely cause: a delta-base cache entry may
> > > remain after its pack is closed. If a pack from another submodule reuses
> > > the same packed_git address and base offset, Git may return stale cached
> > > data.
> 
> Yup, that seems to be the issue indeed. We should really be clearing
> packfiles out of the delta base cache when closing packfiles, but we
> don't right now. I'll investigate tomorrow.

I've sent [1] now to fix this issue. Thanks!

Patrick

[1]: <20261002-pks-packfile-stale-delta-base-cache-v1-0-7592a3e31ae0@pks.im>

```
