databricks-cli/libs/git/worktree.go

124 lines
3.1 KiB
Go

package git
import (
"bufio"
"errors"
"fmt"
"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 "", fmt.Errorf(`expected %q to contain a line with "gitdir: [...]"`, filepath.Join(root.Native(), GitDirectoryName))
}
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("file is empty")
}
return strings.TrimSpace(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, fmt.Errorf(`expected "commondir" file in worktree git folder at %q: %w`, gitDir.Native(), 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
}