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
14 changes: 14 additions & 0 deletions cmd/stdio.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/ory/lumen/internal/config"
"github.com/ory/lumen/internal/embedder"
"github.com/ory/lumen/internal/git"
"github.com/ory/lumen/internal/index"
"github.com/ory/lumen/internal/merkle"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -136,8 +137,21 @@ type indexerCache struct {
// path passes through a directory in merkle.SkipDirs (e.g. "testdata"). Such
// a parent index would never contain path's files, so it is not useful.
func (ic *indexerCache) findEffectiveRoot(path string) string {
// Cap the upward walk at the git repository root. This prevents lumen
// from adopting a large ancestor index (e.g. a GOPATH index) that
// happens to contain path as a subdirectory, which would cause
// EnsureFresh to scan the entire ancestor tree.
gitRoot, gitErr := git.RepoRoot(path)

candidate := filepath.Dir(path)
for {
// Do not walk above the git repo root.
if gitErr == nil {
if rel, relErr := filepath.Rel(gitRoot, candidate); relErr != nil || strings.HasPrefix(rel, "..") {
break
}
}

if !pathCrossesSkipDir(candidate, path) {
if _, ok := ic.cache[candidate]; ok {
return candidate
Expand Down
45 changes: 45 additions & 0 deletions cmd/stdio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package cmd
import (
"context"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
Expand Down Expand Up @@ -164,6 +165,50 @@ func TestIndexerCache_FindEffectiveRoot(t *testing.T) {
})
}

func TestIndexerCache_FindEffectiveRoot_GitBoundary(t *testing.T) {
// Structure: ancestor/ (has an index DB) → repo/ (git root) → subdir/
// findEffectiveRoot must not walk above the git repo root to adopt the
// ancestor index.
tmpDir := t.TempDir()
t.Setenv("XDG_DATA_HOME", tmpDir)

const model = "test-model"

// Create directory layout.
ancestor := filepath.Join(tmpDir, "ancestor")
repo := filepath.Join(ancestor, "repo")
subdir := filepath.Join(repo, "subdir")
for _, d := range []string{ancestor, repo, subdir} {
if err := os.MkdirAll(d, 0o755); err != nil {
t.Fatal(err)
}
}

// Initialise a git repository at repo/.
cmd := exec.Command("git", "init", repo)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git init failed: %v\n%s", err, out)
}

// Create a fake index DB for the ancestor directory (above the git root).
ancestorDBPath := config.DBPathForProject(ancestor, model)
if err := os.MkdirAll(filepath.Dir(ancestorDBPath), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(ancestorDBPath, []byte{}, 0o644); err != nil {
t.Fatal(err)
}

ic := &indexerCache{
cache: make(map[string]cacheEntry),
model: model,
}
root := ic.findEffectiveRoot(subdir)
if root != subdir {
t.Fatalf("expected findEffectiveRoot to stop at git boundary and return %s, got %s", subdir, root)
}
}

func TestIndexerCache_GetOrCreate_ReusesParentIndex(t *testing.T) {
tmpDir := t.TempDir()
t.Setenv("XDG_DATA_HOME", tmpDir)
Expand Down
22 changes: 22 additions & 0 deletions internal/git/worktree.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,28 @@ func InternalWorktreePaths(projectPath string) []string {
return result
}

// RepoRoot returns the absolute path of the root directory of the git
// repository containing projectPath, by running "git rev-parse --show-toplevel".
// Returns an error if git is not available or projectPath is not inside a git
// repository.
func RepoRoot(projectPath string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

cmd := exec.CommandContext(ctx, "git", "rev-parse", "--show-toplevel")
cmd.Dir = projectPath
out, err := cmd.Output()
if err != nil {
return "", err
}

root := strings.TrimSpace(string(out))
if resolved, err := filepath.EvalSymlinks(root); err == nil {
root = resolved
}
return root, nil
}

// ListWorktrees returns the absolute paths of all worktrees (including the
// main working tree) for the repository containing projectPath. Returns nil
// if git is not available or projectPath is not inside a git repository.
Expand Down
Loading