diff --git a/src/indevcontainer/core.py b/src/indevcontainer/core.py index 5f8fe67..90d7ddf 100644 --- a/src/indevcontainer/core.py +++ b/src/indevcontainer/core.py @@ -48,18 +48,24 @@ def resolve_worktree(target: Path) -> tuple[Path, Path] | None: # Worktrees point to /.git/worktrees/. # Submodules point to /.git/modules/ — reject those. + # This structural check works even when the recorded path is foreign + # (e.g. a container path) and therefore absent on this host. if gitdir.parent.name != "worktrees" or gitdir.parent.parent.name != ".git": return None - main_repo = gitdir.parent.parent.parent # /.git/worktrees/ - - # The gitdir may contain an absolute path from a different environment - # (e.g. a container path when running on the host). Fall back to - # walking the filesystem when the derived main_repo doesn't exist. - if not main_repo.is_dir(): - main_repo = _find_repo_root(target) - if main_repo is None: - return None + # The path recorded in a worktree's .git file may come from a different + # environment (e.g. a devcontainer's /workspaces/... path). That path can + # be missing — or even coincidentally present as an unrelated directory — + # on the current host, so it can't be trusted to locate the owning repo. + # Resolve the repo on the *local* filesystem first by walking up from the + # target, and only fall back to the recorded path when no local ancestor + # repository is found. + main_repo = _find_repo_root(target) + if main_repo is None: + gitdir_main = gitdir.parent.parent.parent # /.git/worktrees/ + main_repo = gitdir_main if gitdir_main.is_dir() else None + if main_repo is None: + return None try: rel_path = target.relative_to(main_repo) diff --git a/tests/test_core.py b/tests/test_core.py index f9b0624..408959a 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -263,6 +263,29 @@ def test_resolves_worktree_with_foreign_absolute_gitdir(self, tmp_path): assert main == main_repo assert rel == Path(".worktrees/pr-34") + def test_resolves_worktree_when_foreign_gitdir_path_exists_on_host(self, tmp_path): + """Regression: a worktree created inside a devcontainer records a path + like /workspaces//.git/worktrees/. On WSL (and similar + hosts) that path can *exist* as an unrelated directory, which used to + make resolution give up with an "external worktree" error. Resolution + must instead find the real owning repo on the local filesystem. + """ + main_repo, worktree = _make_worktree(tmp_path) + + # An unrelated tree that mimics the recorded container path and which + # actually exists on the host (the WSL /workspaces case). + foreign = tmp_path / "workspaces" / "myapp" + (foreign / ".git" / "worktrees" / "pr-34").mkdir(parents=True) + (worktree / ".git").write_text( + f"gitdir: {foreign / '.git' / 'worktrees' / 'pr-34'}\n" + ) + + result = resolve_worktree(worktree) + assert result is not None + main, rel = result + assert main == main_repo + assert rel == Path(".worktrees/pr-34") + class TestRunDcodeWorktree: def test_worktree_uses_main_repo_host_path(self, tmp_path):