From 0627ad47ced77800abfe82bfa4fb8382de35404a Mon Sep 17 00:00:00 2001 From: "Wolfe David (XC-CT/PRM-NA)" Date: Tue, 19 May 2026 11:50:10 -0400 Subject: [PATCH] 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: " 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: https://github.com/ansible/molecule/issues/4142 --- src/molecule/util.py | 21 ++++++++++++------- tests/unit/test_util.py | 46 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/molecule/util.py b/src/molecule/util.py index 3084874b5e..75def0d2ff 100644 --- a/src/molecule/util.py +++ b/src/molecule/util.py @@ -440,23 +440,30 @@ def _filter_platforms( def _is_valid_vcs_dir(path: Path, name: str) -> bool: - """Check if a VCS directory is a genuine repository root. + """Check if a VCS entry marks a genuine repository root. Args: path: Parent directory to check. - name: VCS directory name (e.g. ".git", ".hg", ".svn"). + name: VCS entry name (e.g. ".git", ".hg", ".svn"). Returns: - Whether the VCS directory is a genuine repository root. + Whether the VCS entry marks a genuine repository root. """ - vcs_dir = path / name - if not vcs_dir.is_dir(): + vcs_entry = path / name + # In a git worktree (or a submodule) the ".git" entry is a file + # containing a "gitdir: " pointer rather than a directory. + if name == ".git" and vcs_entry.is_file(): + try: + return vcs_entry.read_text(encoding="utf-8").startswith("gitdir:") + except OSError: + return False + if not vcs_entry.is_dir(): return False # A real .git directory always contains a HEAD file. - # Bare repos, worktrees, and regular repos all have it. + # Bare repos and regular repos all have it. # Spurious .git dirs (e.g. created by GitKraken) do not. if name == ".git": - return (vcs_dir / "HEAD").exists() + return (vcs_entry / "HEAD").exists() return True diff --git a/tests/unit/test_util.py b/tests/unit/test_util.py index 48f5d5d7e9..8ab4393c03 100644 --- a/tests/unit/test_util.py +++ b/tests/unit/test_util.py @@ -969,6 +969,52 @@ def test_find_vcs_root_skips_fake_git_dir(tmp_path: Path) -> None: assert result == str(repo) +def test_find_vcs_root_in_git_worktree(tmp_path: Path) -> None: + """Ensure find_vcs_root recognizes a worktree where .git is a file. + + In a git worktree the ".git" entry is a file containing a + "gitdir: " pointer rather than a directory. + + Args: + tmp_path: pytest fixture for temporary directory. + """ + # Worktree root: .git is a file pointing at the main repo's gitdir. + worktree = tmp_path / "worktree" + worktree.mkdir() + git_file = worktree / ".git" + git_file.write_text(f"gitdir: {tmp_path / 'main' / '.git' / 'worktrees' / 'wt'}\n") + + subdir = worktree / "infra" + subdir.mkdir() + + util.find_vcs_root.cache_clear() + result = util.find_vcs_root(location=str(subdir)) + assert result == str(worktree) + + +def test_find_vcs_root_skips_bogus_git_file(tmp_path: Path) -> None: + """Ensure find_vcs_root ignores a .git file without a gitdir pointer. + + Args: + tmp_path: pytest fixture for temporary directory. + """ + # Real repo at top level. + repo = tmp_path / "repo" + repo.mkdir() + git_dir = repo / ".git" + git_dir.mkdir() + (git_dir / "HEAD").write_text("ref: refs/heads/main\n") + + # Subdirectory with a spurious .git file that is not a worktree pointer. + subdir = repo / "infra" + subdir.mkdir() + (subdir / ".git").write_text("not a gitdir pointer\n") + + util.find_vcs_root.cache_clear() + result = util.find_vcs_root(location=str(subdir)) + assert result == str(repo) + + @pytest.mark.parametrize( ("input_value", "expected"), (