From 42c0016c008f74a7fc9c491b95366c415d0e5600 Mon Sep 17 00:00:00 2001 From: rosstaco Date: Wed, 24 Jun 2026 13:04:24 +1000 Subject: [PATCH] fix: resolve worktrees whose recorded gitdir path exists on the host A worktree created inside a devcontainer records a container-absolute path in its .git file (e.g. gitdir: /workspaces//.git/worktrees/). resolve_worktree() derived the main repo from that path and only walked the local filesystem when the derived path did not exist. On WSL (and similar hosts) that /workspaces/... path can actually exist as an unrelated directory, so the fallback was skipped, target.relative_to raised, and idc reported the worktree as an unsupported "external worktree". Locate the owning repository on the local filesystem first by walking up from the worktree, and trust the recorded gitdir path only as a fallback when no local ancestor repository is found. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/indevcontainer/core.py | 24 +++++++++++++++--------- tests/test_core.py | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 9 deletions(-) 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):