databricks-cli/libs/git/view.go

132 lines
3.5 KiB
Go

package git
import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"github.com/databricks/cli/libs/vfs"
)
// View represents a view on a directory tree that takes into account
// all applicable .gitignore files. The directory tree does NOT need
// to be the repository root.
//
// For example: with a repository root at "myrepo", a view can be
// anchored at "myrepo/someproject" and still respect the ignore
// rules defined at "myrepo/.gitignore".
//
// We use this functionality to synchronize files from a path nested
// in a repository while respecting the repository's ignore rules.
type View struct {
// repo points to the repository that contains the directory
// that this view is anchored at.
repo *Repository
// targetPath is the relative path to the directory tree that this
// view is anchored at (with respect to the repository root).
// For example: "." or "a/b".
targetPath string
}
// Ignore computes whether to ignore the specified path.
// The specified path is relative to the view's target path.
func (v *View) Ignore(relPath string) (bool, error) {
// Retain trailing slash for directory patterns.
// Needs special handling because it is removed by path cleaning.
trailingSlash := ""
if strings.HasSuffix(relPath, "/") {
trailingSlash = "/"
}
return v.repo.Ignore(path.Join(v.targetPath, relPath) + trailingSlash)
}
// IgnoreFile returns if the gitignore rules in this fileset
// apply to the specified file path.
//
// This function is provided to implement [fileset.Ignorer].
func (v *View) IgnoreFile(file string) (bool, error) {
return v.Ignore(file)
}
// IgnoreDirectory returns if the gitignore rules in this fileset
// apply to the specified directory path.
//
// A gitignore rule may apply only to directories if it uses
// a trailing slash. Therefore this function checks the gitignore
// rules for the specified directory path first without and then
// with a trailing slash.
//
// This function is provided to implement [fileset.Ignorer].
func (v *View) IgnoreDirectory(dir string) (bool, error) {
ign, err := v.Ignore(dir)
if err != nil {
return false, err
}
if ign {
return ign, nil
}
return v.Ignore(dir + "/")
}
func NewView(root vfs.Path) (*View, error) {
repo, err := NewRepository(root)
if err != nil {
return nil, err
}
// Target path must be relative to the repository root path.
target := root.Native()
prefix := repo.root.Native()
if !strings.HasPrefix(target, prefix) {
return nil, fmt.Errorf("path %q is not within repository root %q", root.Native(), prefix)
}
// Make target a relative path.
target = strings.TrimPrefix(target, prefix)
target = strings.TrimPrefix(target, string(os.PathSeparator))
target = path.Clean(filepath.ToSlash(target))
return &View{
repo: repo,
targetPath: target,
}, nil
}
func (v *View) EnsureValidGitIgnoreExists() error {
ign, err := v.IgnoreDirectory(".databricks")
if err != nil {
return err
}
// return early if .databricks is already being ignored
if ign {
return nil
}
// Create .gitignore with .databricks entry
gitIgnorePath := filepath.Join(v.repo.Root(), v.targetPath, ".gitignore")
file, err := os.OpenFile(gitIgnorePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return err
}
defer file.Close()
// Hard code .databricks ignore pattern so that we never sync it (irrespective)
// of .gitignore patterns
v.repo.addIgnoreRule(newStringIgnoreRules([]string{
".databricks",
}))
_, err = file.WriteString("\n.databricks\n")
if err != nil {
return err
}
v.repo.taintIgnoreRules()
return nil
}