Changeset 0.27.1 (#167)

This commit is contained in:
2026-03-10 17:43:29 +00:00
parent 7e4f1581f2
commit 41be9d6081
16 changed files with 780 additions and 57 deletions

View File

@@ -4,6 +4,7 @@
package workspace
import (
"bufio"
"context"
"crypto/sha256"
"encoding/hex"
@@ -452,6 +453,9 @@ func (fs *FS) Tree(ctx context.Context, w *models.Workspace) ([]models.Workspace
func (fs *FS) Reconcile(ctx context.Context, w *models.Workspace) (added, removed, updated int, err error) {
root := fs.filesDir(w)
// v0.27.0: Load .gitignore patterns from workspace root
ignorePatterns := loadGitignore(root)
// Walk FS and collect all paths
fsPaths := make(map[string]os.FileInfo)
err = filepath.Walk(root, func(abs string, info os.FileInfo, walkErr error) error {
@@ -467,6 +471,15 @@ func (fs *FS) Reconcile(ctx context.Context, w *models.Workspace) (added, remove
}
// Normalize to forward slashes
rel = filepath.ToSlash(rel)
// v0.27.0: Skip gitignored paths
if matchesGitignore(rel, info.IsDir(), ignorePatterns) {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
fsPaths[rel] = info
return nil
})
@@ -624,3 +637,80 @@ func hashFile(absPath string) (string, error) {
}
return hex.EncodeToString(h.Sum(nil)), nil
}
// ── Gitignore ───────────────────────────────
// v0.27.0: Simple .gitignore parser for workspace indexing.
// Supports glob patterns, directory-only patterns (trailing /),
// negation (!), and comments (#). Does not support ** (double-star).
type gitignorePattern struct {
pattern string
negate bool
dirOnly bool
}
// loadGitignore reads .gitignore from the workspace root.
// Returns nil if the file doesn't exist.
func loadGitignore(root string) []gitignorePattern {
f, err := os.Open(filepath.Join(root, ".gitignore"))
if err != nil {
return nil
}
defer f.Close()
var patterns []gitignorePattern
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || line[0] == '#' {
continue
}
p := gitignorePattern{}
if line[0] == '!' {
p.negate = true
line = line[1:]
}
if strings.HasSuffix(line, "/") {
p.dirOnly = true
line = strings.TrimSuffix(line, "/")
}
p.pattern = line
patterns = append(patterns, p)
}
// Always ignore .git directory
patterns = append([]gitignorePattern{{pattern: ".git", dirOnly: true}}, patterns...)
return patterns
}
// matchesGitignore checks if a relative path matches any gitignore pattern.
// Last matching pattern wins (same as git semantics).
func matchesGitignore(relPath string, isDir bool, patterns []gitignorePattern) bool {
if len(patterns) == 0 {
return false
}
ignored := false
basename := path.Base(relPath)
for _, p := range patterns {
if p.dirOnly && !isDir {
continue
}
// Match against basename or full relative path
matched := false
if strings.Contains(p.pattern, "/") {
// Pattern with slash — match against full path
matched, _ = filepath.Match(p.pattern, relPath)
} else {
// Pattern without slash — match against basename
matched, _ = filepath.Match(p.pattern, basename)
if !matched {
// Also try against full path for prefix directory matches
matched, _ = filepath.Match(p.pattern, relPath)
}
}
if matched {
ignored = !p.negate
}
}
return ignored
}