Skip to content
Open
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
6 changes: 5 additions & 1 deletion internal/pool/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/kunchenguid/treehouse/internal/git"
Expand Down Expand Up @@ -355,7 +356,10 @@ func cwdInWorktree(cwd, worktreePath string) bool {
if err != nil {
return false
}
return rel == "." || !filepath.IsAbs(rel) && len(rel) >= 1 && rel[0] != '.'
// The cwd is inside the worktree when rel is "." (the worktree root itself)
// or any path that does not escape the root via "..". This deliberately
// accepts dot-prefixed subdirectories such as .venv/bin or .cache/tmp.
return rel == "." || (rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)))
}

func nextName(state State) string {
Expand Down
41 changes: 41 additions & 0 deletions internal/pool/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2206,6 +2206,47 @@ func TestHoldCwdProbe(t *testing.T) {
select {}
}

func TestCwdInWorktree(t *testing.T) {
worktree := t.TempDir()
worktree, err := filepath.EvalSymlinks(worktree)
if err != nil {
t.Fatal(err)
}

// Create the subdirectories the test cases cd into.
for _, sub := range []string{"src/foo", ".venv/bin", ".cache/tmp"} {
if err := os.MkdirAll(filepath.Join(worktree, sub), 0o755); err != nil {
t.Fatal(err)
}
}

sibling := filepath.Join(filepath.Dir(worktree), "sibling")
if err := os.MkdirAll(sibling, 0o755); err != nil {
t.Fatal(err)
}

tests := []struct {
name string
cwd string
want bool
}{
{"worktree root", worktree, true},
{"regular subdir", filepath.Join(worktree, "src", "foo"), true},
{"dot-prefixed subdir .venv/bin", filepath.Join(worktree, ".venv", "bin"), true},
{"dot-prefixed subdir .cache/tmp", filepath.Join(worktree, ".cache", "tmp"), true},
{"parent of worktree (rel starts with ..)", filepath.Dir(worktree), false},
{"sibling dir", sibling, false},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := cwdInWorktree(tc.cwd, worktree); got != tc.want {
t.Fatalf("cwdInWorktree(%q, %q) = %v, want %v", tc.cwd, worktree, got, tc.want)
}
})
}
}

// quoteForShell wraps a path so it survives splitting by /bin/sh or cmd.exe.
// Tests only use temp-dir paths which don't contain quotes, so simple quoting
// is sufficient.
Expand Down