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
25 changes: 25 additions & 0 deletions pkg/filesystem/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,11 @@ func discoveryBoundary(cfg *config.Config) func(string) bool {
if _, registered := cfg.NameForWorkspacePath(root); registered {
return false
}
// A linked worktree is a working copy of the user's own repo, not a dependency, wherever
// a tool chose to put it (Claude Code, for one, nests them under .claude/worktrees).
if isLinkedWorktree(root) {
return false
}
parent, found := cfg.WorkspaceForPath(root)
if !found {
return false
Expand All @@ -213,6 +218,26 @@ func discoveryBoundary(cfg *config.Config) func(string) bool {
}
}

// isLinkedWorktree reports whether dir is the root of a `git worktree add` checkout. Its .git is
// a file pointing at a gitdir that holds a commondir file; a submodule also has a .git file, but
// its gitdir has no commondir, so a vendored submodule stays behind the boundary.
func isLinkedWorktree(dir string) bool {
data, err := os.ReadFile(filepath.Clean(filepath.Join(dir, ".git")))
if err != nil {
return false
}
gitDir, ok := strings.CutPrefix(strings.TrimSpace(string(data)), "gitdir:")
if !ok {
return false
}
gitDir = strings.TrimSpace(gitDir)
if !filepath.IsAbs(gitDir) {
gitDir = filepath.Join(dir, gitDir)
}
info, err := os.Stat(filepath.Join(gitDir, "commondir"))
return err == nil && !info.IsDir()
}

// loadResolved builds a result for a registered workspace, creating its flow.yaml if the user
// registered a path that does not have one yet.
func loadResolved(name, path string, src WorkspaceSource) (*ResolvedWorkspace, error) {
Expand Down
27 changes: 27 additions & 0 deletions pkg/filesystem/resolve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,33 @@ var _ = Describe("ResolveWorkspace", func() {
Expect(res.Path).To(Equal(parent))
})

It("discovers a linked worktree nested under a boundary directory", func() {
parent := mkWorkspace("repo")
worktree := mkWorkspace("repo/.claude/worktrees/feature")
gitDir := mkDir("repo/.git/worktrees/feature")
Expect(os.WriteFile(filepath.Join(gitDir, "commondir"), []byte("../..\n"), 0600)).To(Succeed())
Expect(os.WriteFile(filepath.Join(worktree, ".git"), []byte("gitdir: "+gitDir+"\n"), 0600)).To(Succeed())
cfg := dynamic(map[string]string{"repo": parent}, "repo")
res, err := filesystem.ResolveWorkspace(cfg, filesystem.ResolveOptions{
Dir: mkDir("repo/.claude/worktrees/feature/cmd"),
})
Expect(err).NotTo(HaveOccurred())
Expect(res.Path).To(Equal(worktree))
Expect(res.Registered).To(BeFalse())
Expect(res.Source).To(Equal(filesystem.SourceDiscovered))
})

It("walks past a vendored submodule inside a registered workspace", func() {
parent := mkWorkspace("repo")
sub := mkWorkspace("repo/vendor/dep")
mkDir("repo/.git/modules/dep")
Expect(os.WriteFile(filepath.Join(sub, ".git"), []byte("gitdir: ../../.git/modules/dep\n"), 0600)).To(Succeed())
cfg := dynamic(map[string]string{"repo": parent}, "repo")
res, err := filesystem.ResolveWorkspace(cfg, filesystem.ResolveOptions{Dir: sub})
Expect(err).NotTo(HaveOccurred())
Expect(res.Path).To(Equal(parent))
})

It("still discovers a standalone clone under a directory named external", func() {
// The boundary only applies inside a registered workspace; an unrelated path that
// happens to contain "external" is a legitimate root.
Expand Down
Loading