mirror of https://github.com/databricks/cli.git
Work on worktree support
This commit is contained in:
parent
74a1c22a3a
commit
c6fc519fc0
|
@ -1,7 +1,6 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
|
@ -24,8 +23,18 @@ type Repository struct {
|
|||
// directory where we process .gitignore files.
|
||||
real bool
|
||||
|
||||
// root is the absolute path to the repository root.
|
||||
root vfs.Path
|
||||
// rootDir is the path to the root of the repository checkout.
|
||||
// This can be either the main repository checkout or a worktree.
|
||||
rootDir vfs.Path
|
||||
|
||||
// gitDir is the equivalent of $GIT_DIR and points to the
|
||||
// `.git` directory of a repository or a worktree.
|
||||
gitDir vfs.Path
|
||||
|
||||
// gitCommonDir is the equivalent of $GIT_COMMON_DIR and points to the
|
||||
// `.git` directory of the main working tree (common between worktrees).
|
||||
// This is equivalent to [gitDir] if this is the main working tree.
|
||||
gitCommonDir vfs.Path
|
||||
|
||||
// ignore contains a list of ignore patterns indexed by the
|
||||
// path prefix relative to the repository root.
|
||||
|
@ -45,12 +54,11 @@ type Repository struct {
|
|||
|
||||
// Root returns the absolute path to the repository root.
|
||||
func (r *Repository) Root() string {
|
||||
return r.root.Native()
|
||||
return r.rootDir.Native()
|
||||
}
|
||||
|
||||
func (r *Repository) CurrentBranch() (string, error) {
|
||||
// load .git/HEAD
|
||||
ref, err := LoadReferenceFile(r.root, path.Join(GitDirectoryName, "HEAD"))
|
||||
ref, err := LoadReferenceFile(r.gitDir, "HEAD")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -66,8 +74,7 @@ func (r *Repository) CurrentBranch() (string, error) {
|
|||
}
|
||||
|
||||
func (r *Repository) LatestCommit() (string, error) {
|
||||
// load .git/HEAD
|
||||
ref, err := LoadReferenceFile(r.root, path.Join(GitDirectoryName, "HEAD"))
|
||||
ref, err := LoadReferenceFile(r.gitDir, "HEAD")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -81,12 +88,12 @@ func (r *Repository) LatestCommit() (string, error) {
|
|||
return ref.Content, nil
|
||||
}
|
||||
|
||||
// read reference from .git/HEAD
|
||||
// Read reference from $GIT_DIR/HEAD
|
||||
branchHeadPath, err := ref.ResolvePath()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
branchHeadRef, err := LoadReferenceFile(r.root, path.Join(GitDirectoryName, branchHeadPath))
|
||||
branchHeadRef, err := LoadReferenceFile(r.gitCommonDir, branchHeadPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -120,54 +127,13 @@ func (r *Repository) OriginUrl() string {
|
|||
return parsedUrl.String()
|
||||
}
|
||||
|
||||
func readGitDir(root vfs.Path) (string, error) {
|
||||
file, err := root.Open(".git")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error opening file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "gitdir: ") {
|
||||
// Extract the path after "gitdir: "
|
||||
return strings.TrimSpace(strings.TrimPrefix(line, "gitdir: ")), nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return "", fmt.Errorf("error reading file: %w", err)
|
||||
}
|
||||
|
||||
return "", errors.New("gitdir line not found")
|
||||
}
|
||||
|
||||
func (r *Repository) resolveGitRoot() vfs.Path {
|
||||
fileInfo, err := r.root.Stat(".git")
|
||||
if err != nil {
|
||||
return r.root
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
return r.root
|
||||
}
|
||||
gitDir, err := readGitDir(r.root)
|
||||
if err != nil {
|
||||
return r.root
|
||||
}
|
||||
return vfs.MustNew(gitDir)
|
||||
}
|
||||
|
||||
// loadConfig loads and combines user specific and repository specific configuration files.
|
||||
func (r *Repository) loadConfig() error {
|
||||
config, err := globalGitConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load user specific gitconfig: %w", err)
|
||||
}
|
||||
root := r.resolveGitRoot()
|
||||
if root == nil {
|
||||
return fmt.Errorf("unable to resolve git root")
|
||||
}
|
||||
err = config.loadFile(root, ".git/config")
|
||||
err = config.loadFile(r.gitCommonDir, "config")
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to load repository specific gitconfig: %w", err)
|
||||
}
|
||||
|
@ -175,12 +141,6 @@ func (r *Repository) loadConfig() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// newIgnoreFile constructs a new [ignoreRules] implementation backed by
|
||||
// a file using the specified path relative to the repository root.
|
||||
func (r *Repository) newIgnoreFile(relativeIgnoreFilePath string) ignoreRules {
|
||||
return newIgnoreFile(r.resolveGitRoot(), relativeIgnoreFilePath)
|
||||
}
|
||||
|
||||
// getIgnoreRules returns a slice of [ignoreRules] that apply
|
||||
// for the specified prefix. The prefix must be cleaned by the caller.
|
||||
// It lazily initializes an entry for the specified prefix if it
|
||||
|
@ -191,7 +151,7 @@ func (r *Repository) getIgnoreRules(prefix string) []ignoreRules {
|
|||
return fs
|
||||
}
|
||||
|
||||
r.ignore[prefix] = append(r.ignore[prefix], r.newIgnoreFile(path.Join(prefix, gitIgnoreFileName)))
|
||||
r.ignore[prefix] = append(r.ignore[prefix], newIgnoreFile(r.rootDir, path.Join(prefix, gitIgnoreFileName)))
|
||||
return r.ignore[prefix]
|
||||
}
|
||||
|
||||
|
@ -247,7 +207,7 @@ func (r *Repository) Ignore(relPath string) (bool, error) {
|
|||
|
||||
func NewRepository(path vfs.Path) (*Repository, error) {
|
||||
real := true
|
||||
rootPath, err := vfs.FindLeafInTree(path, GitDirectoryName)
|
||||
rootDir, err := vfs.FindLeafInTree(path, GitDirectoryName)
|
||||
if err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, err
|
||||
|
@ -255,12 +215,21 @@ func NewRepository(path vfs.Path) (*Repository, error) {
|
|||
// Cannot find `.git` directory.
|
||||
// Treat the specified path as a potential repository root.
|
||||
real = false
|
||||
rootPath = path
|
||||
rootDir = path
|
||||
}
|
||||
|
||||
// Derive $GIT_DIR and $GIT_COMMON_DIR paths if this is a real repository.
|
||||
// If it isn't a real repository, they'll point to the (non-existent) `.git` directory.
|
||||
gitDir, gitCommonDir, err := resolveGitDirs(rootDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo := &Repository{
|
||||
real: real,
|
||||
root: rootPath,
|
||||
rootDir: rootDir,
|
||||
gitDir: gitDir,
|
||||
gitCommonDir: gitCommonDir,
|
||||
ignore: make(map[string][]ignoreRules),
|
||||
}
|
||||
|
||||
|
@ -295,9 +264,9 @@ func NewRepository(path vfs.Path) (*Repository, error) {
|
|||
".git",
|
||||
}),
|
||||
// Load repository-wide excludes file.
|
||||
repo.newIgnoreFile(".git/info/excludes"),
|
||||
newIgnoreFile(repo.gitCommonDir, "info/excludes"),
|
||||
// Load root gitignore file.
|
||||
repo.newIgnoreFile(".gitignore"),
|
||||
newIgnoreFile(repo.rootDir, ".gitignore"),
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
|
|
|
@ -80,7 +80,7 @@ func NewView(root vfs.Path) (*View, error) {
|
|||
|
||||
// Target path must be relative to the repository root path.
|
||||
target := root.Native()
|
||||
prefix := repo.root.Native()
|
||||
prefix := repo.rootDir.Native()
|
||||
if !strings.HasPrefix(target, prefix) {
|
||||
return nil, fmt.Errorf("path %q is not within repository root %q", root.Native(), prefix)
|
||||
}
|
||||
|
@ -107,17 +107,6 @@ func (v *View) EnsureValidGitIgnoreExists() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Hard code .databricks ignore pattern so that we never sync it (irrespective)
|
||||
// of .gitignore patterns
|
||||
v.repo.addIgnoreRule(newStringIgnoreRules([]string{
|
||||
".databricks",
|
||||
}))
|
||||
|
||||
// Bail if we are in a worktree
|
||||
if v.repo.Root() != v.repo.resolveGitRoot().Native() {
|
||||
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)
|
||||
|
@ -126,6 +115,12 @@ func (v *View) EnsureValidGitIgnoreExists() error {
|
|||
}
|
||||
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
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/databricks/cli/libs/vfs"
|
||||
)
|
||||
|
||||
func readLines(root vfs.Path, name string) ([]string, error) {
|
||||
file, err := root.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
var lines []string
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
|
||||
return lines, scanner.Err()
|
||||
}
|
||||
|
||||
// readGitDir reads the value of the `.git` file in a worktree.
|
||||
func readGitDir(root vfs.Path) (string, error) {
|
||||
lines, err := readLines(root, GitDirectoryName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var gitDir string
|
||||
for _, line := range lines {
|
||||
parts := strings.SplitN(line, ": ", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
if parts[0] == "gitdir" {
|
||||
gitDir = strings.TrimSpace(parts[1])
|
||||
}
|
||||
}
|
||||
|
||||
if gitDir == "" {
|
||||
return "", errors.New("gitdir line not found")
|
||||
}
|
||||
|
||||
return gitDir, nil
|
||||
}
|
||||
|
||||
// readGitCommonDir reads the value of the `commondir` file in the `.git` directory of a worktree.
|
||||
// This file typically contains "../.." to point to $GIT_COMMON_DIR.
|
||||
func readGitCommonDir(gitDir vfs.Path) (string, error) {
|
||||
lines, err := readLines(gitDir, "commondir")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(lines) == 0 {
|
||||
return "", errors.New("commondir file not found")
|
||||
}
|
||||
|
||||
return lines[0], nil
|
||||
}
|
||||
|
||||
// resolveGitDirs resolves the paths for $GIT_DIR and $GIT_COMMON_DIR.
|
||||
// The path argument is the root of the checkout where (supposedly) a `.git` file or directory exists.
|
||||
func resolveGitDirs(root vfs.Path) (vfs.Path, vfs.Path, error) {
|
||||
fileInfo, err := root.Stat(GitDirectoryName)
|
||||
if err != nil {
|
||||
// If the `.git` file or directory does not exist, then this is not a git repository.
|
||||
// Return paths that we know don't exist, so we do not need to perform nil checks in the caller.
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
gitDir := vfs.MustNew(filepath.Join(root.Native(), GitDirectoryName))
|
||||
return gitDir, gitDir, nil
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// If the path is a directory, then it is the main working tree.
|
||||
// Both $GIT_DIR and $GIT_COMMON_DIR point to the same directory.
|
||||
if fileInfo.IsDir() {
|
||||
gitDir := vfs.MustNew(filepath.Join(root.Native(), GitDirectoryName))
|
||||
return gitDir, gitDir, nil
|
||||
}
|
||||
|
||||
// If the path is not a directory, then it is a worktree.
|
||||
// Read value for $GIT_DIR.
|
||||
gitDirValue, err := readGitDir(root)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Resolve $GIT_DIR.
|
||||
var gitDir vfs.Path
|
||||
if filepath.IsAbs(gitDirValue) {
|
||||
gitDir = vfs.MustNew(gitDirValue)
|
||||
} else {
|
||||
gitDir = vfs.MustNew(filepath.Join(root.Native(), gitDirValue))
|
||||
}
|
||||
|
||||
// Read value for $GIT_COMMON_DIR.
|
||||
gitCommonDirValue, err := readGitCommonDir(gitDir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Resolve $GIT_COMMON_DIR.
|
||||
var gitCommonDir vfs.Path
|
||||
if filepath.IsAbs(gitCommonDirValue) {
|
||||
gitCommonDir = vfs.MustNew(gitCommonDirValue)
|
||||
} else {
|
||||
gitCommonDir = vfs.MustNew(filepath.Join(gitDir.Native(), gitCommonDirValue))
|
||||
}
|
||||
|
||||
return gitDir, gitCommonDir, nil
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package git
|
Loading…
Reference in New Issue