databricks-cli/libs/git/ignore.go

127 lines
2.8 KiB
Go

package git
import (
"errors"
"io/fs"
"strings"
"time"
"github.com/databricks/cli/libs/vfs"
ignore "github.com/sabhiram/go-gitignore"
)
// ignoreRules implements the interface for a gitignore-like file.
// It is backed by implementations for a file at a specific path (it may not exist)
// and an implementation for a set of in-memory ignore patterns.
type ignoreRules interface {
MatchesPath(path string) (bool, error)
// Taint forces checking if the underlying file needs to be reloaded.
// It checks the mtime of the file to see if has been modified after loading.
Taint()
}
// ignoreFile represents a gitignore file backed by a path.
// If the path doesn't exist (yet), it is treated as an empty file.
type ignoreFile struct {
root vfs.Path
path string
// Signal a reload of this file.
// Set this to call [os.Stat] and a potential reload
// of the file's contents next time it is used.
checkForReload bool
// Modified time for this file.
modTime time.Time
// Ignore patterns contained in this file.
patterns *ignore.GitIgnore
}
func newIgnoreFile(root vfs.Path, path string) ignoreRules {
return &ignoreFile{
root: root,
path: path,
checkForReload: true,
}
}
func (f *ignoreFile) MatchesPath(path string) (bool, error) {
if f.checkForReload {
err := f.load()
if err != nil {
return false, err
}
// Don't check again in next call.
f.checkForReload = false
}
// A file that doesn't exist doesn't have ignore patterns.
if f.patterns == nil {
return false, nil
}
return f.patterns.MatchesPath(path), nil
}
func (f *ignoreFile) Taint() {
f.checkForReload = true
}
func (f *ignoreFile) load() error {
// The file must be stat-able.
// If it doesn't exist, treat it as an empty file.
stat, err := fs.Stat(f.root, f.path)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return nil
}
return err
}
// If the underlying file has not been modified
// it does not need to be reloaded.
if stat.ModTime() == f.modTime {
return nil
}
f.modTime = stat.ModTime()
f.patterns, err = f.loadGitignore()
if err != nil {
return err
}
return nil
}
func (f *ignoreFile) loadGitignore() (*ignore.GitIgnore, error) {
data, err := fs.ReadFile(f.root, f.path)
if err != nil {
return nil, err
}
lines := strings.Split(string(data), "\n")
return ignore.CompileIgnoreLines(lines...), nil
}
// stringIgnoreRules implements the [ignoreRules] interface
// for a set of in-memory ignore patterns.
type stringIgnoreRules struct {
patterns *ignore.GitIgnore
}
func newStringIgnoreRules(patterns []string) ignoreRules {
return &stringIgnoreRules{
patterns: ignore.CompileIgnoreLines(patterns...),
}
}
func (r *stringIgnoreRules) MatchesPath(path string) (bool, error) {
return r.patterns.MatchesPath(path), nil
}
func (r *stringIgnoreRules) Taint() {
// Tainting in-memory ignore patterns is a nop.
}