mirror of https://github.com/databricks/cli.git
127 lines
2.8 KiB
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.
|
|
}
|