Centralise logic for obtaining repo paths

There are quite a few paths you might want to get e.g. the repo's path, the worktree's path,
the repo's git dir path, the worktree's git dir path. I want these all obtained once and
then used when needed rather than having to have IO whenever we need them. This is not so
much about reducing time spent on IO as it is about not having to care about errors every time
we want a path.
This commit is contained in:
Jesse Duffield 2023-07-28 18:27:14 +10:00
parent de57cfd6ff
commit 4c5b1574f1
22 changed files with 347 additions and 250 deletions

View file

@ -19,7 +19,7 @@ func NewBisectCommands(gitCommon *GitCommon) *BisectCommands {
// This command is pretty cheap to run so we're not storing the result anywhere.
// But if it becomes problematic we can chang that.
func (self *BisectCommands) GetInfo() *BisectInfo {
return self.GetInfoForGitDir(self.dotGitDir)
return self.GetInfoForGitDir(self.repoPaths.WorktreeGitDirPath())
}
func (self *BisectCommands) GetInfoForGitDir(gitDir string) *BisectInfo {

View file

@ -47,7 +47,6 @@ type CommitLoader struct {
func NewCommitLoader(
cmn *common.Common,
cmd oscommands.ICmdObjBuilder,
dotGitDir string,
getRebaseMode func() (enums.RebaseMode, error),
gitCommon *GitCommon,
) *CommitLoader {
@ -57,7 +56,6 @@ func NewCommitLoader(
getRebaseMode: getRebaseMode,
readFile: os.ReadFile,
walkFiles: filepath.Walk,
dotGitDir: dotGitDir,
mainBranches: nil,
GitCommon: gitCommon,
}
@ -299,7 +297,7 @@ func (self *CommitLoader) getRebasingCommits(rebaseMode enums.RebaseMode) ([]*mo
func (self *CommitLoader) getNormalRebasingCommits() ([]*models.Commit, error) {
rewrittenCount := 0
bytesContent, err := self.readFile(filepath.Join(self.dotGitDir, "rebase-apply/rewritten"))
bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-apply/rewritten"))
if err == nil {
content := string(bytesContent)
rewrittenCount = len(strings.Split(content, "\n"))
@ -307,7 +305,7 @@ func (self *CommitLoader) getNormalRebasingCommits() ([]*models.Commit, error) {
// we know we're rebasing, so lets get all the files whose names have numbers
commits := []*models.Commit{}
err = self.walkFiles(filepath.Join(self.dotGitDir, "rebase-apply"), func(path string, f os.FileInfo, err error) error {
err = self.walkFiles(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-apply"), func(path string, f os.FileInfo, err error) error {
if rewrittenCount > 0 {
rewrittenCount--
return nil
@ -348,7 +346,7 @@ func (self *CommitLoader) getNormalRebasingCommits() ([]*models.Commit, error) {
// and extracts out the sha and names of commits that we still have to go
// in the rebase:
func (self *CommitLoader) getInteractiveRebasingCommits() ([]*models.Commit, error) {
bytesContent, err := self.readFile(filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo"))
bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"))
if err != nil {
self.Log.Error(fmt.Sprintf("error occurred reading git-rebase-todo: %s", err.Error()))
// we assume an error means the file doesn't exist so we just return
@ -393,7 +391,7 @@ func (self *CommitLoader) getInteractiveRebasingCommits() ([]*models.Commit, err
}
func (self *CommitLoader) getConflictedCommit(todos []todo.Todo) string {
bytesContent, err := self.readFile(filepath.Join(self.dotGitDir, "rebase-merge/done"))
bytesContent, err := self.readFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/done"))
if err != nil {
self.Log.Error(fmt.Sprintf("error occurred reading rebase-merge/done: %s", err.Error()))
return ""
@ -406,7 +404,7 @@ func (self *CommitLoader) getConflictedCommit(todos []todo.Todo) string {
}
amendFileExists := false
if _, err := os.Stat(filepath.Join(self.dotGitDir, "rebase-merge/amend")); err == nil {
if _, err := os.Stat(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/amend")); err == nil {
amendFileExists = true
}

View file

@ -12,7 +12,7 @@ type GitCommon struct {
version *GitVersion
cmd oscommands.ICmdObjBuilder
os *oscommands.OSCommand
dotGitDir string
repoPaths RepoPaths
repo *gogit.Repository
config *ConfigCommands
// mutex for doing things like push/pull/fetch
@ -24,7 +24,7 @@ func NewGitCommon(
version *GitVersion,
cmd oscommands.ICmdObjBuilder,
osCommand *oscommands.OSCommand,
dotGitDir string,
repoPaths RepoPaths,
repo *gogit.Repository,
config *ConfigCommands,
syncMutex *deadlock.Mutex,
@ -34,7 +34,7 @@ func NewGitCommon(
version: version,
cmd: cmd,
os: osCommand,
dotGitDir: dotGitDir,
repoPaths: repoPaths,
repo: repo,
config: config,
syncMutex: syncMutex,

View file

@ -7,7 +7,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
)
type FileLoaderConfig interface {
@ -15,15 +14,15 @@ type FileLoaderConfig interface {
}
type FileLoader struct {
*common.Common
*GitCommon
cmd oscommands.ICmdObjBuilder
config FileLoaderConfig
getFileType func(string) string
}
func NewFileLoader(cmn *common.Common, cmd oscommands.ICmdObjBuilder, config FileLoaderConfig) *FileLoader {
func NewFileLoader(gitCommon *GitCommon, cmd oscommands.ICmdObjBuilder, config FileLoaderConfig) *FileLoader {
return &FileLoader{
Common: cmn,
GitCommon: gitCommon,
cmd: cmd,
getFileType: oscommands.FileType,
config: config,
@ -67,7 +66,7 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
// Go through the files to see if any of these files are actually worktrees
// so that we can render them correctly
worktreePaths := linkedWortkreePaths()
worktreePaths := linkedWortkreePaths(self.repoPaths.RepoGitDirPath())
for _, file := range files {
for _, worktreePath := range worktreePaths {
absFilePath, err := filepath.Abs(file.Name)

View file

@ -79,7 +79,7 @@ func (self *PatchCommands) applyPatchFile(filepath string, opts ApplyPatchOpts)
}
func (self *PatchCommands) SaveTemporaryPatch(patch string) (string, error) {
filepath := filepath.Join(self.os.GetTempDir(), GetCurrentRepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch")
filepath := filepath.Join(self.os.GetTempDir(), self.repoPaths.RepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch")
self.Log.Infof("saving temporary patch to %s", filepath)
if err := self.os.CreateFileWithContent(filepath, patch); err != nil {
return "", err

View file

@ -0,0 +1,235 @@
package git_commands
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/samber/lo"
)
type RepoPaths interface {
// Current working directory of the program. Currently, this will always
// be the same as WorktreePath(), but in future we may support running
// lazygit from inside a subdirectory of the worktree.
CurrentPath() string
// Path to the current worktree. If we're in the main worktree, this will
// be the same as RepoPath()
WorktreePath() string
// Path of the worktree's git dir.
// If we're in the main worktree, this will be the .git dir under the RepoPath().
// If we're in a linked worktree, it will be the directory pointed at by the worktree's .git file
WorktreeGitDirPath() string
// Path of the repo. If we're in a the main worktree, this will be the same as WorktreePath()
// If we're in a bare repo, it will be the parent folder of the bare repo
RepoPath() string
// path of the git-dir for the repo.
// If this is a bare repo, it will be the location of the bare repo
// If this is a non-bare repo, it will be the location of the .git dir in
// the main worktree.
RepoGitDirPath() string
// Name of the repo. Basename of the folder containing the repo.
RepoName() string
}
type RepoDirsImpl struct {
currentPath string
worktreePath string
worktreeGitDirPath string
repoPath string
repoGitDirPath string
repoName string
}
var _ RepoPaths = &RepoDirsImpl{}
func (self *RepoDirsImpl) CurrentPath() string {
return self.currentPath
}
func (self *RepoDirsImpl) WorktreePath() string {
return self.worktreePath
}
func (self *RepoDirsImpl) WorktreeGitDirPath() string {
return self.worktreeGitDirPath
}
func (self *RepoDirsImpl) RepoPath() string {
return self.repoPath
}
func (self *RepoDirsImpl) RepoGitDirPath() string {
return self.repoGitDirPath
}
func (self *RepoDirsImpl) RepoName() string {
return self.repoName
}
func GetRepoPaths() (RepoPaths, error) {
currentPath, err := os.Getwd()
if err != nil {
return &RepoDirsImpl{}, errors.Errorf("failed to get current path: %v", err)
}
worktreePath := currentPath
repoGitDirPath, repoPath, err := GetCurrentRepoGitDirPath(currentPath)
if err != nil {
return &RepoDirsImpl{}, errors.Errorf("failed to get repo git dir path: %v", err)
}
worktreeGitDirPath, err := worktreeGitDirPath(currentPath)
if err != nil {
return &RepoDirsImpl{}, errors.Errorf("failed to get worktree git dir path: %v", err)
}
repoName := filepath.Base(repoPath)
return &RepoDirsImpl{
currentPath: currentPath,
worktreePath: worktreePath,
worktreeGitDirPath: worktreeGitDirPath,
repoPath: repoPath,
repoGitDirPath: repoGitDirPath,
repoName: repoName,
}, nil
}
// Returns the paths of linked worktrees
func linkedWortkreePaths(repoGitDirPath string) []string {
result := []string{}
// For each directory in this path we're going to cat the `gitdir` file and append its contents to our result
// That file points us to the `.git` file in the worktree.
worktreeGitDirsPath := filepath.Join(repoGitDirPath, "worktrees")
// ensure the directory exists
_, err := os.Stat(worktreeGitDirsPath)
if err != nil {
return result
}
_ = filepath.Walk(worktreeGitDirsPath, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
return nil
}
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
})
return result
}
// Returns the path of the git-dir for the worktree. For linked worktrees, the worktree has
// a .git file that points to the git-dir (which itself lives in the git-dir
// of the repo)
func worktreeGitDirPath(worktreePath string) (string, error) {
// if .git is a file, we're in a linked worktree, otherwise we're in
// the main worktree
dotGitPath := filepath.Join(worktreePath, ".git")
gitFileInfo, err := os.Stat(dotGitPath)
if err != nil {
return "", err
}
if gitFileInfo.IsDir() {
return dotGitPath, nil
}
return linkedWorktreeGitDirPath(worktreePath)
}
func linkedWorktreeGitDirPath(worktreePath string) (string, error) {
dotGitPath := filepath.Join(worktreePath, ".git")
gitFileContents, err := os.ReadFile(dotGitPath)
if err != nil {
return "", err
}
// The file will have `gitdir: /path/to/.git/worktrees/<worktree-name>`
gitDirLine := lo.Filter(strings.Split(string(gitFileContents), "\n"), func(line string, _ int) bool {
return strings.HasPrefix(line, "gitdir: ")
})
if len(gitDirLine) == 0 {
return "", errors.New(fmt.Sprintf("%s is a file which suggests we are in a submodule or a worktree but the file's contents do not contain a gitdir pointing to the actual .git directory", dotGitPath))
}
gitDir := strings.TrimPrefix(gitDirLine[0], "gitdir: ")
return gitDir, nil
}
func GetCurrentRepoGitDirPath(currentPath string) (string, string, error) {
var unresolvedGitPath string
if env.GetGitDirEnv() != "" {
unresolvedGitPath = env.GetGitDirEnv()
} else {
unresolvedGitPath = filepath.Join(currentPath, ".git")
}
gitPath, err := resolveSymlink(unresolvedGitPath)
if err != nil {
return "", "", err
}
// check if .git is a file or a directory
gitFileInfo, err := os.Stat(gitPath)
if err != nil {
return "", "", err
}
if gitFileInfo.IsDir() {
// must be in the main worktree
return gitPath, filepath.Dir(gitPath), nil
}
// either in a submodule, or worktree
worktreeGitPath, err := linkedWorktreeGitDirPath(currentPath)
if err != nil {
return "", "", errors.Errorf("could not find git dir for %s: %v", currentPath, err)
}
// confirm whether the next directory up is the worktrees/submodules directory
parent := filepath.Dir(worktreeGitPath)
if filepath.Base(parent) != "worktrees" && filepath.Base(parent) != "modules" {
return "", "", errors.Errorf("could not find git dir for %s", currentPath)
}
// if it's a submodule, we treat it as its own repo
if filepath.Base(parent) == "modules" {
return worktreeGitPath, currentPath, nil
}
gitDirPath := filepath.Dir(parent)
return gitDirPath, filepath.Dir(gitDirPath), nil
}
// takes a path containing a symlink and returns the true path
func resolveSymlink(path string) (string, error) {
l, err := os.Lstat(path)
if err != nil {
return "", err
}
if l.Mode()&os.ModeSymlink == 0 {
return path, nil
}
return filepath.EvalSymlinks(path)
}

View file

@ -243,18 +243,18 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
// EditRebaseTodo sets the action for a given rebase commit in the git-rebase-todo file
func (self *RebaseCommands) EditRebaseTodo(commit *models.Commit, action todo.TodoCommand) error {
return utils.EditRebaseTodo(
filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo"), commit.Sha, commit.Action, action, self.config.GetCoreCommentChar())
filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo"), commit.Sha, commit.Action, action, self.config.GetCoreCommentChar())
}
// MoveTodoDown moves a rebase todo item down by one position
func (self *RebaseCommands) MoveTodoDown(commit *models.Commit) error {
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
fileName := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo")
return utils.MoveTodoDown(fileName, commit.Sha, commit.Action, self.config.GetCoreCommentChar())
}
// MoveTodoDown moves a rebase todo item down by one position
func (self *RebaseCommands) MoveTodoUp(commit *models.Commit) error {
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
fileName := filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge/git-rebase-todo")
return utils.MoveTodoUp(fileName, commit.Sha, commit.Action, self.config.GetCoreCommentChar())
}

View file

@ -24,14 +24,14 @@ func NewStatusCommands(
// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase
// and "interactive" for interactive rebase
func (self *StatusCommands) RebaseMode() (enums.RebaseMode, error) {
exists, err := self.os.FileExists(filepath.Join(self.dotGitDir, "rebase-apply"))
exists, err := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-apply"))
if err != nil {
return enums.REBASE_MODE_NONE, err
}
if exists {
return enums.REBASE_MODE_NORMAL, nil
}
exists, err = self.os.FileExists(filepath.Join(self.dotGitDir, "rebase-merge"))
exists, err = self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge"))
if exists {
return enums.REBASE_MODE_INTERACTIVE, err
} else {
@ -69,5 +69,5 @@ func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
// IsInMergeState states whether we are still mid-merge
func (self *StatusCommands) IsInMergeState() (bool, error) {
return self.os.FileExists(filepath.Join(self.dotGitDir, "MERGE_HEAD"))
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "MERGE_HEAD"))
}

View file

@ -139,7 +139,9 @@ func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
self.Log.Error(err)
}
return os.RemoveAll(filepath.Join(self.dotGitDir, "modules", submodule.Path))
// We may in fact want to use the repo's git dir path but git docs say not to
// mix submodules and worktrees anyway.
return os.RemoveAll(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "modules", submodule.Path))
}
func (self *SubmoduleCommands) Add(name string, path string, url string) error {

View file

@ -1,11 +1,7 @@
package git_commands
import (
"io/fs"
"log"
"os"
"path/filepath"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
)
@ -76,89 +72,3 @@ func CheckedOutByOtherWorktree(branch *models.Branch, worktrees []*models.Worktr
return !worktree.IsCurrent
}
// 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
}

View file

@ -9,33 +9,28 @@ import (
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type WorktreeLoader struct {
*common.Common
*GitCommon
cmd oscommands.ICmdObjBuilder
}
func NewWorktreeLoader(
common *common.Common,
gitCommon *GitCommon,
cmd oscommands.ICmdObjBuilder,
) *WorktreeLoader {
return &WorktreeLoader{
Common: common,
cmd: cmd,
GitCommon: gitCommon,
cmd: cmd,
}
}
func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
currentRepoPath := GetCurrentRepoPath()
pwd, err := os.Getwd()
if err != nil {
return nil, err
}
currentRepoPath := self.repoPaths.RepoPath()
worktreePath := self.repoPaths.WorktreePath()
cmdArgs := NewGitCmd("worktree").Arg("list", "--porcelain").ToArgv()
worktreesOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
@ -48,11 +43,15 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
var worktrees []*models.Worktree
var current *models.Worktree
for _, splitLine := range splitLines {
// worktrees are defined over multiple lines and are separated by blank lines
// so if we reach a blank line we're done with the current worktree
if len(splitLine) == 0 && current != nil {
worktrees = append(worktrees, current)
current = nil
continue
}
// ignore bare repo (not sure why it's even appearing in this list: it's not a worktree)
if splitLine == "bare" {
current = nil
continue
@ -61,18 +60,13 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
if strings.HasPrefix(splitLine, "worktree ") {
path := strings.SplitN(splitLine, " ", 2)[1]
isMain := path == currentRepoPath
isCurrent := path == pwd
isCurrent := path == worktreePath
isPathMissing := self.pathExists(path)
var gitDir string
if isMain {
gitDir = filepath.Join(path, ".git")
} else {
var ok bool
gitDir, ok = LinkedWorktreeGitPath(path)
if !ok {
self.Log.Warnf("Could not find git dir for worktree %s", path)
}
gitDir, err := worktreeGitDirPath(path)
if err != nil {
self.Log.Warnf("Could not find git dir for worktree %s: %v", path, err)
}
current = &models.Worktree{
@ -120,15 +114,15 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
continue
}
rebaseBranch, ok := rebaseBranch(worktree)
rebasedBranch, ok := rebasedBranch(worktree)
if ok {
worktree.Branch = rebaseBranch
worktree.Branch = rebasedBranch
continue
}
bisectBranch, ok := bisectBranch(worktree)
bisectedBranch, ok := bisectedBranch(worktree)
if ok {
worktree.Branch = bisectBranch
worktree.Branch = bisectedBranch
continue
}
}
@ -147,7 +141,7 @@ func (self *WorktreeLoader) pathExists(path string) bool {
return false
}
func rebaseBranch(worktree *models.Worktree) (string, bool) {
func rebasedBranch(worktree *models.Worktree) (string, bool) {
for _, dir := range []string{"rebase-merge", "rebase-apply"} {
if bytesContent, err := os.ReadFile(filepath.Join(worktree.GitDir, dir, "head-name")); err == nil {
headName := strings.TrimSpace(string(bytesContent))
@ -159,7 +153,7 @@ func rebaseBranch(worktree *models.Worktree) (string, bool) {
return "", false
}
func bisectBranch(worktree *models.Worktree) (string, bool) {
func bisectedBranch(worktree *models.Worktree) (string, bool) {
bisectStartPath := filepath.Join(worktree.GitDir, "BISECT_START")
startContent, err := os.ReadFile(bisectStartPath)
if err != nil {
@ -169,27 +163,6 @@ func bisectBranch(worktree *models.Worktree) (string, bool) {
return strings.TrimSpace(string(startContent)), true
}
func LinkedWorktreeGitPath(worktreePath string) (string, bool) {
// first we get the path of the worktree, then we look at the contents of the `.git` file in that path
// then we look for the line that says `gitdir: /path/to/.git/worktrees/<worktree-name>`
// then we return that path
gitFileContents, err := os.ReadFile(filepath.Join(worktreePath, ".git"))
if err != nil {
return "", false
}
gitDirLine := lo.Filter(strings.Split(string(gitFileContents), "\n"), func(line string, _ int) bool {
return strings.HasPrefix(line, "gitdir: ")
})
if len(gitDirLine) == 0 {
return "", false
}
gitDir := strings.TrimPrefix(gitDirLine[0], "gitdir: ")
return gitDir, true
}
type pathWithIndexT struct {
path string
index int