|
2 | 2 |
|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
| 5 | +import os |
5 | 6 | import shutil |
| 7 | +import subprocess |
6 | 8 | import textwrap |
7 | 9 | import typing as t |
8 | 10 |
|
@@ -176,3 +178,83 @@ def test_git_bare_repo_sync_and_commit( |
176 | 178 | # Test |
177 | 179 | result = pytester.runpytest(str(first_test_filename)) |
178 | 180 | result.assert_outcomes(passed=2) |
| 181 | + |
| 182 | + |
| 183 | +@pytest.mark.skipif(not shutil.which("git"), reason="git is not available") |
| 184 | +def test_gitconfig_submodule_file_protocol( |
| 185 | + gitconfig: pathlib.Path, |
| 186 | + user_path: pathlib.Path, |
| 187 | + tmp_path: pathlib.Path, |
| 188 | + monkeypatch: pytest.MonkeyPatch, |
| 189 | +) -> None: |
| 190 | + """Test that gitconfig fixture allows file:// protocol for git submodule operations. |
| 191 | +
|
| 192 | + Git submodule operations spawn child processes that don't inherit local repo config. |
| 193 | + The child `git clone` process needs protocol.file.allow=always in global config. |
| 194 | +
|
| 195 | + Without this setting, submodule operations fail with: |
| 196 | + fatal: transport 'file' not allowed |
| 197 | +
|
| 198 | + This reproduces GitHub issue #509 where tests fail in strict build environments |
| 199 | + (like Arch Linux packaging) that don't have protocol.file.allow set globally. |
| 200 | +
|
| 201 | + See: https://github.com/vcs-python/libvcs/issues/509 |
| 202 | + """ |
| 203 | + # Isolate git config: use fixture's gitconfig via HOME, block only system config |
| 204 | + # Note: We don't block GIT_CONFIG_GLOBAL because git falls back to $HOME/.gitconfig |
| 205 | + # when GIT_CONFIG_GLOBAL is unset, which is where our fixture puts the config |
| 206 | + monkeypatch.setenv("HOME", str(user_path)) |
| 207 | + monkeypatch.setenv("GIT_CONFIG_SYSTEM", os.devnull) |
| 208 | + monkeypatch.delenv("GIT_CONFIG_GLOBAL", raising=False) |
| 209 | + |
| 210 | + # Create a source repository to use as submodule |
| 211 | + submodule_source = tmp_path / "submodule_source" |
| 212 | + submodule_source.mkdir() |
| 213 | + subprocess.run( |
| 214 | + ["git", "init"], |
| 215 | + cwd=submodule_source, |
| 216 | + check=True, |
| 217 | + capture_output=True, |
| 218 | + ) |
| 219 | + subprocess.run( |
| 220 | + ["git", "commit", "--allow-empty", "-m", "initial"], |
| 221 | + cwd=submodule_source, |
| 222 | + check=True, |
| 223 | + capture_output=True, |
| 224 | + ) |
| 225 | + |
| 226 | + # Create a main repository |
| 227 | + main_repo = tmp_path / "main_repo" |
| 228 | + main_repo.mkdir() |
| 229 | + subprocess.run( |
| 230 | + ["git", "init"], |
| 231 | + cwd=main_repo, |
| 232 | + check=True, |
| 233 | + capture_output=True, |
| 234 | + ) |
| 235 | + subprocess.run( |
| 236 | + ["git", "commit", "--allow-empty", "-m", "initial"], |
| 237 | + cwd=main_repo, |
| 238 | + check=True, |
| 239 | + capture_output=True, |
| 240 | + ) |
| 241 | + |
| 242 | + # Try to add submodule using file:// protocol |
| 243 | + # This spawns a child git clone that needs protocol.file.allow=always |
| 244 | + result = subprocess.run( |
| 245 | + ["git", "submodule", "add", str(submodule_source), "vendor/lib"], |
| 246 | + cwd=main_repo, |
| 247 | + capture_output=True, |
| 248 | + text=True, |
| 249 | + ) |
| 250 | + |
| 251 | + # Assert: submodule add should succeed (no "fatal" errors) |
| 252 | + assert "fatal" not in result.stderr.lower(), ( |
| 253 | + f"git submodule add failed with: {result.stderr}\n" |
| 254 | + 'This indicates gitconfig fixture is missing [protocol "file"] allow = always' |
| 255 | + ) |
| 256 | + assert result.returncode == 0, f"git submodule add failed: {result.stderr}" |
| 257 | + |
| 258 | + # Verify submodule was actually added |
| 259 | + gitmodules = main_repo / ".gitmodules" |
| 260 | + assert gitmodules.exists(), "Submodule should create .gitmodules file" |
0 commit comments