mirror of https://github.com/databricks/cli.git
155 lines
3.7 KiB
Go
155 lines
3.7 KiB
Go
package git
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
ignore "github.com/sabhiram/go-gitignore"
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
type File struct {
|
|
fs.DirEntry
|
|
Absolute, Relative string
|
|
}
|
|
|
|
func (f File) Modified() (ts time.Time) {
|
|
info, err := f.Info()
|
|
if err != nil {
|
|
// return default time, beginning of epoch
|
|
return ts
|
|
}
|
|
return info.ModTime()
|
|
}
|
|
|
|
// FileSet facilitates fast recursive tracked file listing
|
|
// with respect to patterns defined in `.gitignore` file
|
|
//
|
|
// root: Root of the git repository
|
|
// ignore: List of patterns defined in `.gitignore`.
|
|
//
|
|
// We do not sync files that match this pattern
|
|
type FileSet struct {
|
|
root string
|
|
ignore *ignore.GitIgnore
|
|
}
|
|
|
|
// Retuns FileSet for the git repo located at `root`
|
|
func NewFileSet(root string) *FileSet {
|
|
syncIgnoreLines := append(getGitIgnoreLines(root), getSyncIgnoreLines()...)
|
|
return &FileSet{
|
|
root: root,
|
|
ignore: ignore.CompileIgnoreLines(syncIgnoreLines...),
|
|
}
|
|
}
|
|
|
|
func getGitIgnoreLines(root string) []string {
|
|
gitIgnoreLines := []string{}
|
|
gitIgnorePath := fmt.Sprintf("%s/.gitignore", root)
|
|
rawIgnore, err := os.ReadFile(gitIgnorePath)
|
|
|
|
if err == nil {
|
|
// add entries from .gitignore if the file exists (did read correctly)
|
|
for _, line := range strings.Split(string(rawIgnore), "\n") {
|
|
// underlying library doesn't behave well with Rule 5 of .gitignore,
|
|
// hence this workaround
|
|
gitIgnoreLines = append(gitIgnoreLines, strings.Trim(line, "/"))
|
|
}
|
|
}
|
|
return gitIgnoreLines
|
|
}
|
|
|
|
// This contains additional files/dirs to be ignored while syncing that are
|
|
// not mentioned in .gitignore
|
|
func getSyncIgnoreLines() []string {
|
|
return []string{".git"}
|
|
}
|
|
|
|
// Only call this function for a bricks project root
|
|
// since it will create a .gitignore file if missing
|
|
func (w *FileSet) EnsureValidGitIgnoreExists() error {
|
|
gitIgnoreLines := getGitIgnoreLines(w.root)
|
|
gitIgnorePath := fmt.Sprintf("%s/.gitignore", w.root)
|
|
if slices.Contains(gitIgnoreLines, ".databricks") {
|
|
return nil
|
|
}
|
|
f, err := os.OpenFile(gitIgnorePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
_, err = f.WriteString("\n.databricks\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
gitIgnoreLines = append(gitIgnoreLines, ".databricks")
|
|
w.ignore = ignore.CompileIgnoreLines(append(gitIgnoreLines, getSyncIgnoreLines()...)...)
|
|
return nil
|
|
}
|
|
|
|
// Return root for fileset.
|
|
func (w *FileSet) Root() string {
|
|
return w.root
|
|
}
|
|
|
|
// Return all tracked files for Repo
|
|
func (w *FileSet) All() ([]File, error) {
|
|
return w.RecursiveListFiles(w.root)
|
|
}
|
|
|
|
func (w *FileSet) IsGitIgnored(pattern string) bool {
|
|
return w.ignore.MatchesPath(pattern)
|
|
}
|
|
|
|
// Recursively traverses dir in a depth first manner and returns a list of all files
|
|
// that are being tracked in the FileSet (ie not being ignored for matching one of the
|
|
// patterns in w.ignore)
|
|
func (w *FileSet) RecursiveListFiles(dir string) (fileList []File, err error) {
|
|
queue, err := readDir(dir, w.root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for len(queue) > 0 {
|
|
current := queue[0]
|
|
queue = queue[1:]
|
|
if w.ignore.MatchesPath(current.Relative) {
|
|
continue
|
|
}
|
|
if !current.IsDir() {
|
|
fileList = append(fileList, current)
|
|
continue
|
|
}
|
|
children, err := readDir(current.Absolute, w.root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
queue = append(queue, children...)
|
|
}
|
|
return fileList, nil
|
|
}
|
|
|
|
func readDir(dir, root string) (queue []File, err error) {
|
|
f, err := os.Open(dir)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer f.Close()
|
|
dirs, err := f.ReadDir(-1)
|
|
if err != nil {
|
|
return
|
|
}
|
|
for _, v := range dirs {
|
|
absolute := filepath.Join(dir, v.Name())
|
|
relative, err := filepath.Rel(root, absolute)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
queue = append(queue, File{v, absolute, relative})
|
|
}
|
|
return
|
|
}
|