Skip to content

Commit beadcb8

Browse files
committed
fix(submodule): check transport policy before replacing a remote
`RootModule.update` fetches a temporary remote when `.gitmodules` changes a submodule URL. That fetch still treated the URL as user-provided, even after ordinary submodule clone/fetch gained Git's non-user transport environment. It could therefore access a denied local repository and replace `origin` before the subsequent ordinary fetch finally rejected the transport. Port PR #2304's separate changed-URL regression and apply the existing `_transport_env()` helper to that initial fetch. The test compares denial with real `git submodule sync` / `update --remote`, and checks unchanged HEAD and original-remote preservation against a rejected Git fetch. Its location-dependent initial repositories use the shared scripted fixtures. Both source regressions failed on the original CLI and Gix backends. The changed-URL test also failed independently after the ordinary update fix, because `origin` had already changed. After this fix, 17 focused transport, root-update, and no-fetch cases pass on each backend under the isolated runner, using Git 2.54.0 and GixPython 0.2.0. Ruff lint/format, shellcheck, and `git diff --check` pass. The fetch remains an explicit CLI operation.
1 parent 9b4fe22 commit beadcb8

3 files changed

Lines changed: 43 additions & 3 deletions

File tree

‎git/objects/submodule/root.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,8 @@ def update( # type: ignore[override]
306306
else:
307307
assert nn not in [r.name for r in rmts]
308308
smr = smm.create_remote(nn, sm_url)
309-
smr.fetch(progress=progress)
309+
with smm.git.custom_environment(**sm._transport_env()):
310+
smr.fetch(progress=progress)
310311

311312
# If we have a tracking branch, it should be available
312313
# in the new remote as well.

‎test/test_submodule.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,34 @@ def test_update_applies_git_transport_policy_to_gitmodules_url(monkeypatch):
165165
assert module.head.commit.hexsha == baseline_module.git.rev_parse("HEAD") == source.head.commit.hexsha
166166

167167

168+
@patch_git_config("protocol.file.allow", "user")
169+
def test_root_update_applies_git_transport_policy_to_changed_url(monkeypatch):
170+
monkeypatch.setenv("GIT_PROTOCOL_FROM_USER", "1")
171+
# The submodule URLs point to this private source and replacement.
172+
with writable_fixture("submodule-transport.sh", "changed-url", execute=True) as fixture:
173+
root = fixture.path().resolve()
174+
with git.Repo(root / "source") as source, git.Repo(root / "parent") as parent, git.Repo(
175+
root / "baseline"
176+
) as baseline, git.Repo(root / "baseline" / "module") as baseline_module:
177+
submodule = parent.submodules[0]
178+
previous = (root / "previous").read_text().strip()
179+
# A rejected fetch preserves the original remote and checked-out commit.
180+
with baseline_module.git.custom_environment(GIT_PROTOCOL_FROM_USER="0"):
181+
with pytest.raises(GitCommandError, match="transport 'file' not allowed"):
182+
baseline_module.git.fetch(str(root / "replacement"))
183+
assert Path(baseline_module.remotes.origin.url).samefile(source.working_tree_dir)
184+
assert baseline_module.git.rev_parse("HEAD") == source.head.commit.hexsha
185+
baseline.git.submodule("sync")
186+
with pytest.raises(GitCommandError, match="transport 'file' not allowed"):
187+
baseline.git.submodule("update", "--remote")
188+
189+
with pytest.raises(GitCommandError, match="transport 'file' not allowed"):
190+
RootModule(parent).update(previous_commit=previous)
191+
with submodule.module() as module:
192+
assert Path(module.remotes.origin.url).samefile(source.working_tree_dir)
193+
assert module.head.commit.hexsha == baseline_module.git.rev_parse("HEAD") == source.head.commit.hexsha
194+
195+
168196
@pytest.mark.parametrize(
169197
"scenario,name",
170198
[

‎tests/fixtures/submodule-transport.sh‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,16 @@ git -C source commit --allow-empty -qm initial
66
git init -q -b master parent
77
git -C parent -c protocol.file.allow=always submodule add "$root/source" module
88
git -C parent commit -qm 'Add submodule'
9-
git clone -q parent clone
10-
git clone -q parent baseline
9+
if test "$1" = update; then
10+
git clone -q parent clone
11+
git clone -q parent baseline
12+
else
13+
git clone -q source replacement
14+
git -C parent rev-parse HEAD >previous
15+
git -C parent config -f .gitmodules submodule.module.url "$root/replacement"
16+
git -C parent add .gitmodules
17+
git -C parent commit -qm 'Replace submodule URL'
18+
git clone -q parent baseline
19+
git -C baseline -c protocol.file.allow=always submodule update --init
20+
git -C baseline/module remote set-url origin "$root/source"
21+
fi

0 commit comments

Comments
 (0)