Skip to content

Commit 0627ad4

Browse files
Wolfe David (XC-CT/PRM-NA)Wolfe David (XC-CT/PRM-NA)
authored andcommitted
fix: recognize git worktree root in find_vcs_root()
In a git worktree (or a submodule) the ".git" entry is a file containing a "gitdir: <path>" pointer rather than a directory. `_is_valid_vcs_dir()` only accepted directories, so `find_vcs_root()` walked past the worktree root and failed to locate the project root, breaking `molecule.yml` discovery for users working inside a worktree. Treat a ".git" file as a valid VCS root marker when it contains a "gitdir:" pointer, while still rejecting unrelated stray ".git" files. Refs: #4142
1 parent 5e8051d commit 0627ad4

2 files changed

Lines changed: 60 additions & 7 deletions

File tree

src/molecule/util.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -440,23 +440,30 @@ def _filter_platforms(
440440

441441

442442
def _is_valid_vcs_dir(path: Path, name: str) -> bool:
443-
"""Check if a VCS directory is a genuine repository root.
443+
"""Check if a VCS entry marks a genuine repository root.
444444
445445
Args:
446446
path: Parent directory to check.
447-
name: VCS directory name (e.g. ".git", ".hg", ".svn").
447+
name: VCS entry name (e.g. ".git", ".hg", ".svn").
448448
449449
Returns:
450-
Whether the VCS directory is a genuine repository root.
450+
Whether the VCS entry marks a genuine repository root.
451451
"""
452-
vcs_dir = path / name
453-
if not vcs_dir.is_dir():
452+
vcs_entry = path / name
453+
# In a git worktree (or a submodule) the ".git" entry is a file
454+
# containing a "gitdir: <path>" pointer rather than a directory.
455+
if name == ".git" and vcs_entry.is_file():
456+
try:
457+
return vcs_entry.read_text(encoding="utf-8").startswith("gitdir:")
458+
except OSError:
459+
return False
460+
if not vcs_entry.is_dir():
454461
return False
455462
# A real .git directory always contains a HEAD file.
456-
# Bare repos, worktrees, and regular repos all have it.
463+
# Bare repos and regular repos all have it.
457464
# Spurious .git dirs (e.g. created by GitKraken) do not.
458465
if name == ".git":
459-
return (vcs_dir / "HEAD").exists()
466+
return (vcs_entry / "HEAD").exists()
460467
return True
461468

462469

tests/unit/test_util.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,52 @@ def test_find_vcs_root_skips_fake_git_dir(tmp_path: Path) -> None:
969969
assert result == str(repo)
970970

971971

972+
def test_find_vcs_root_in_git_worktree(tmp_path: Path) -> None:
973+
"""Ensure find_vcs_root recognizes a worktree where .git is a file.
974+
975+
In a git worktree the ".git" entry is a file containing a
976+
"gitdir: <path>" pointer rather than a directory.
977+
978+
Args:
979+
tmp_path: pytest fixture for temporary directory.
980+
"""
981+
# Worktree root: .git is a file pointing at the main repo's gitdir.
982+
worktree = tmp_path / "worktree"
983+
worktree.mkdir()
984+
git_file = worktree / ".git"
985+
git_file.write_text(f"gitdir: {tmp_path / 'main' / '.git' / 'worktrees' / 'wt'}\n")
986+
987+
subdir = worktree / "infra"
988+
subdir.mkdir()
989+
990+
util.find_vcs_root.cache_clear()
991+
result = util.find_vcs_root(location=str(subdir))
992+
assert result == str(worktree)
993+
994+
995+
def test_find_vcs_root_skips_bogus_git_file(tmp_path: Path) -> None:
996+
"""Ensure find_vcs_root ignores a .git file without a gitdir pointer.
997+
998+
Args:
999+
tmp_path: pytest fixture for temporary directory.
1000+
"""
1001+
# Real repo at top level.
1002+
repo = tmp_path / "repo"
1003+
repo.mkdir()
1004+
git_dir = repo / ".git"
1005+
git_dir.mkdir()
1006+
(git_dir / "HEAD").write_text("ref: refs/heads/main\n")
1007+
1008+
# Subdirectory with a spurious .git file that is not a worktree pointer.
1009+
subdir = repo / "infra"
1010+
subdir.mkdir()
1011+
(subdir / ".git").write_text("not a gitdir pointer\n")
1012+
1013+
util.find_vcs_root.cache_clear()
1014+
result = util.find_vcs_root(location=str(subdir))
1015+
assert result == str(repo)
1016+
1017+
9721018
@pytest.mark.parametrize(
9731019
("input_value", "expected"),
9741020
(

0 commit comments

Comments
 (0)