1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-12 11:15:00 +02:00
lazygit/pkg/commands/git_commands/worktree.go
2023-07-30 18:35:23 +10:00

196 lines
4.4 KiB
Go

package git_commands
import (
"errors"
"io/fs"
"log"
"os"
"path/filepath"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
)
type WorktreeCommands struct {
*GitCommon
}
func NewWorktreeCommands(gitCommon *GitCommon) *WorktreeCommands {
return &WorktreeCommands{
GitCommon: gitCommon,
}
}
type NewWorktreeOpts struct {
// required. The path of the new worktree.
Path string
// required. The base branch/ref.
Base string
// if true, ends up with a detached head
Detach bool
// optional. if empty, and if detach is false, we will checkout the base
Branch string
}
func (self *WorktreeCommands) New(opts NewWorktreeOpts) error {
if opts.Detach && opts.Branch != "" {
panic("cannot specify branch when detaching")
}
cmdArgs := NewGitCmd("worktree").Arg("add").
ArgIf(opts.Detach, "--detach").
ArgIf(opts.Branch != "", "-b", opts.Branch).
Arg(opts.Path, opts.Base)
return self.cmd.New(cmdArgs.ToArgv()).Run()
}
func (self *WorktreeCommands) Delete(worktreePath string, force bool) error {
cmdArgs := NewGitCmd("worktree").Arg("remove").ArgIf(force, "-f").Arg(worktreePath).ToArgv()
return self.cmd.New(cmdArgs).Run()
}
func (self *WorktreeCommands) Detach(worktreePath string) error {
cmdArgs := NewGitCmd("checkout").Arg("--detach").GitDir(filepath.Join(worktreePath, ".git")).ToArgv()
return self.cmd.New(cmdArgs).Run()
}
func (self *WorktreeCommands) IsCurrentWorktree(path string) bool {
return IsCurrentWorktree(path)
}
func IsCurrentWorktree(path string) bool {
pwd, err := os.Getwd()
if err != nil {
return false
}
return EqualPath(pwd, path)
}
func (self *WorktreeCommands) IsWorktreePathMissing(path string) bool {
if _, err := os.Stat(path); err != nil {
if errors.Is(err, fs.ErrNotExist) {
return true
}
self.Log.Errorf("failed to check if worktree path `%s` exists\n%v", path, err)
return false
}
return false
}
// checks if two paths are equal
// TODO: support relative paths
func EqualPath(a string, b string) bool {
return a == b
}
func WorktreeForBranch(branch *models.Branch, worktrees []*models.Worktree) (*models.Worktree, bool) {
for _, worktree := range worktrees {
if worktree.Branch == branch.Name {
return worktree, true
}
}
return nil, false
}
func CheckedOutByOtherWorktree(branch *models.Branch, worktrees []*models.Worktree) bool {
worktree, ok := WorktreeForBranch(branch, worktrees)
if !ok {
return false
}
return !IsCurrentWorktree(worktree.Path)
}
// If in a non-bare repo, this returns the path of the main worktree
// TODO: see if this works with a bare repo.
func GetCurrentRepoPath() string {
pwd, err := os.Getwd()
if err != nil {
log.Fatalln(err.Error())
}
// check if .git is a file or a directory
gitPath := filepath.Join(pwd, ".git")
gitFileInfo, err := os.Stat(gitPath)
if err != nil {
// fallback
return currentPath()
}
if gitFileInfo.IsDir() {
// must be in the main worktree
return currentPath()
}
// either in a submodule, a worktree, or a bare repo
worktreeGitPath, ok := LinkedWorktreeGitPath(pwd)
if !ok {
// fallback
return currentPath()
}
// confirm whether the next directory up is the 'worktrees' directory
parent := filepath.Dir(worktreeGitPath)
if filepath.Base(parent) != "worktrees" {
// fallback
return currentPath()
}
// now we just jump up two more directories to get the repo name
return filepath.Dir(filepath.Dir(parent))
}
func GetCurrentRepoName() string {
return filepath.Base(GetCurrentRepoPath())
}
func currentPath() string {
pwd, err := os.Getwd()
if err != nil {
log.Fatalln(err.Error())
}
return pwd
}
func linkedWortkreePaths() []string {
// first we need to get the repo dir
repoPath := GetCurrentRepoPath()
result := []string{}
worktreePath := filepath.Join(repoPath, ".git", "worktrees")
// for each directory in this path we're going to cat the `gitdir` file and append its contents to our result
// ensure the directory exists
_, err := os.Stat(worktreePath)
if err != nil {
return result
}
err = filepath.Walk(worktreePath, func(path string, info fs.FileInfo, err error) error {
if info.IsDir() {
gitDirPath := filepath.Join(path, "gitdir")
gitDirBytes, err := os.ReadFile(gitDirPath)
if err != nil {
// ignoring error
return nil
}
trimmedGitDir := strings.TrimSpace(string(gitDirBytes))
// removing the .git part
worktreeDir := filepath.Dir(trimmedGitDir)
result = append(result, worktreeDir)
}
return nil
})
if err != nil {
return result
}
return result
}