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 }