Changeset 0.27.1 (#167)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user