Skip to content

Commit fca7a70

Browse files
EliahKaganclaude
andcommitted
Add a test that fixture directories are usable by git
Many tests rely on git accepting their fixture directories -- the GitPython working tree and the gitdb and smmap submodules used by the test suite as submodule fixtures (gitdb providing the direct submodule fixture and smmap, nested inside gitdb, providing the nested-submodule fixture). When git rejects one for "dubious ownership," typically because a CI workflow's `safe.directory` list is missing an entry, three submodule-related tests fail in opaque ways. The exact failure type depends on which side of a race wins in the flush to the cached `git cat-file --batch-check` subprocess: when Python wins, `ValueError` reaches the test directly; when git wins, `BrokenPipeError` is caught internally as `IOError` and `iter_items` returns early. Each test then fails on the empty-or-short result in its own way: `test_docs::Tutorials::test_submodules` indexes the empty list with `[0]` and raises `IndexError`; `test_repo::TestRepo::test_submodules` and `test_submodule::TestSubmodule::test_root_module` see a length-1 (not length-2) submodule list from their recursive traversals and fail their length assertion with `AssertionError`. `test/test_fixture_health.py` runs `git rev-parse --show-toplevel` in each fixture directory and asserts both that git is willing to operate there and that it reports the directory as its own toplevel. The failure message names `safe.directory` and ownership as the likely causes, so a misconfigured environment is recognizable directly from the test output rather than only inferable from the cascade of downstream failures. This commit adds the test only. On the Cygwin CI workflow as it stands, the test fails for `[gitdb]` and passes for `[repo_root]` and `[smmap]`. The asymmetry between the two submodules is incidental to NTFS ownership inherited from `actions/checkout`'s clone of the main tree; commit 4 (the fix) explains the mechanism in detail. The forthcoming commits reproduce the bug across a temporarily introduced large CI matrix and apply the fix. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7b83f7a commit fca7a70

1 file changed

Lines changed: 91 additions & 0 deletions

File tree

test/test_fixture_health.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# This module is part of GitPython and is released under the
2+
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
3+
4+
"""Verify that fixture directories are usable by git.
5+
6+
If a directory the test suite relies on is rejected by git for "dubious
7+
ownership" -- because the directory's owner doesn't match the running user
8+
and there is no ``safe.directory`` entry overriding the check -- three
9+
submodule-related tests fail in confusing ways. The checks here name the
10+
root cause clearly so a misconfigured environment is recognizable from the
11+
test output.
12+
13+
The rejection is most often a CI-workflow issue (the workflow's
14+
``safe.directory`` list doesn't cover the path); on a developer's own
15+
clone, it usually reflects an ownership mismatch (sudo clone, restored
16+
backup, container mount, networked filesystem) rather than a config gap.
17+
18+
These tests do not exercise GitPython's production code. They verify the
19+
conditions under which production code is exercised are valid.
20+
21+
A check is skipped, rather than failed, if a fixture directory is missing or
22+
has no ``.git`` marker, since that condition is more naturally diagnosed as
23+
"``init-tests-after-clone.sh`` hasn't been run" than as a problem with
24+
``safe.directory``.
25+
"""
26+
27+
import subprocess
28+
from pathlib import Path
29+
30+
import pytest
31+
32+
REPO_ROOT = Path(__file__).resolve().parent.parent
33+
34+
# Directories git must trust for the test suite to operate normally. The
35+
# current set is the GitPython working tree plus the working trees of its
36+
# gitdb submodule and the smmap submodule nested inside gitdb. New entries
37+
# should be added here whenever the test suite gains a dependency on git
38+
# accepting another directory.
39+
FIXTURE_DIRS = [
40+
pytest.param(REPO_ROOT, id="repo_root"),
41+
pytest.param(REPO_ROOT / "git" / "ext" / "gitdb", id="gitdb"),
42+
pytest.param(
43+
REPO_ROOT / "git" / "ext" / "gitdb" / "gitdb" / "ext" / "smmap",
44+
id="smmap",
45+
),
46+
]
47+
48+
49+
@pytest.mark.parametrize("fixture_dir", FIXTURE_DIRS)
50+
def test_fixture_dir_is_trusted_by_git(fixture_dir: Path) -> None:
51+
"""git accepts ``fixture_dir`` as its own repository owned by a trusted user.
52+
53+
Run ``git -C <fixture_dir> rev-parse --show-toplevel`` and assert it
54+
succeeds and reports ``fixture_dir`` itself as the toplevel. Failure
55+
typically means the directory's on-disk ownership doesn't match the
56+
running user and the CI workflow's ``safe.directory`` list is missing
57+
an entry that would override the check.
58+
"""
59+
if not fixture_dir.exists():
60+
pytest.skip(f"{fixture_dir} not present (run ./init-tests-after-clone.sh from the repo root)")
61+
if not (fixture_dir / ".git").exists():
62+
pytest.skip(
63+
f"{fixture_dir} has no .git marker "
64+
"(submodule not initialized; run ./init-tests-after-clone.sh "
65+
"from the repo root)"
66+
)
67+
try:
68+
result = subprocess.run(
69+
["git", "-C", str(fixture_dir), "rev-parse", "--show-toplevel"],
70+
capture_output=True,
71+
text=True,
72+
check=False,
73+
)
74+
except FileNotFoundError:
75+
pytest.skip("git is not installed or not on PATH")
76+
assert result.returncode == 0, (
77+
f"git refuses to operate in {fixture_dir}.\n"
78+
f"stderr: {result.stderr.strip()}\n"
79+
"The directory's owner doesn't match the running user and no "
80+
"`safe.directory` entry overrides the check. On CI, the "
81+
"workflow's `safe.directory` list typically needs an entry for "
82+
"this path. Locally, this is unexpected and usually indicates "
83+
"an ownership issue worth investigating."
84+
)
85+
reported = Path(result.stdout.strip())
86+
assert reported.samefile(fixture_dir), (
87+
f"git reports the toplevel as {reported}, "
88+
f"not as {fixture_dir} itself. "
89+
"This usually means the directory is not an initialized git "
90+
"repository (its `.git` marker may be stale or pointing elsewhere)."
91+
)

0 commit comments

Comments
 (0)