Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions src/indevcontainer/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,24 @@ def resolve_worktree(target: Path) -> tuple[Path, Path] | None:

# Worktrees point to <repo>/.git/worktrees/<name>.
# Submodules point to <repo>/.git/modules/<name> — 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 # <repo>/.git/worktrees/<n> → <repo>

# 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 # <repo>/.git/worktrees/<n> → <repo>
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)
Expand Down
23 changes: 23 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/<repo>/.git/worktrees/<name>. 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):
Expand Down
Loading