summaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2023-07-29 17:02:04 +1000
committerJesse Duffield <jessedduffield@gmail.com>2023-07-30 18:35:36 +1000
commit7b302d8c298ca2eb7e08d63f993186d25252f97e (patch)
treeef50114f45816c9dc16cfa6e827cbd74fce38469 /pkg
parenta1fae4105116775b5c4a0c9c3b01fa822b29cfac (diff)
Write unit tests with the help of afero
Afero is a package that lets you mock out a filesystem with an in-memory filesystem. It allows us to easily create the files required for a given test without worrying about a cleanup step or different tests tripping on eachother when run in parallel. Later on I'll standardise on using afero over the vanilla os package
Diffstat (limited to 'pkg')
-rw-r--r--pkg/app/app.go2
-rw-r--r--pkg/commands/git.go94
-rw-r--r--pkg/commands/git_commands/common.go4
-rw-r--r--pkg/commands/git_commands/deps_test.go21
-rw-r--r--pkg/commands/git_commands/file_loader.go2
-rw-r--r--pkg/commands/git_commands/file_loader_test.go3
-rw-r--r--pkg/commands/git_commands/paths.go194
-rw-r--r--pkg/commands/git_commands/paths_test.go118
-rw-r--r--pkg/commands/git_commands/worktree_loader.go38
-rw-r--r--pkg/commands/git_commands/worktree_loader_test.go186
-rw-r--r--pkg/commands/git_test.go315
-rw-r--r--pkg/common/common.go4
-rw-r--r--pkg/utils/dummies.go5
13 files changed, 536 insertions, 450 deletions
diff --git a/pkg/app/app.go b/pkg/app/app.go
index 53e6e4411..2b7a8457d 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -11,6 +11,7 @@ import (
"github.com/go-errors/errors"
"github.com/sirupsen/logrus"
+ "github.com/spf13/afero"
"github.com/jesseduffield/generics/slices"
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
@@ -75,6 +76,7 @@ func NewCommon(config config.AppConfigurer) (*common.Common, error) {
Tr: tr,
UserConfig: userConfig,
Debug: config.GetDebug(),
+ Fs: afero.NewOsFs(),
}, nil
}
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index 510b26c11..92df3d8a2 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -2,11 +2,13 @@ package commands
import (
"os"
+ "path"
"path/filepath"
"strings"
"github.com/go-errors/errors"
"github.com/sasha-s/go-deadlock"
+ "github.com/spf13/afero"
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
@@ -39,7 +41,7 @@ type GitCommand struct {
Bisect *git_commands.BisectCommands
Worktree *git_commands.WorktreeCommands
Version *git_commands.GitVersion
- RepoPaths git_commands.RepoPaths
+ RepoPaths *git_commands.RepoPaths
Loaders Loaders
}
@@ -63,11 +65,37 @@ func NewGitCommand(
gitConfig git_config.IGitConfig,
syncMutex *deadlock.Mutex,
) (*GitCommand, error) {
- if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil {
- return nil, err
+ currentPath, err := os.Getwd()
+ if err != nil {
+ return nil, utils.WrapError(err)
+ }
+
+ // converting to forward slashes for the sake of windows (which uses backwards slashes). We want everything
+ // to have forward slashes internally
+ currentPath = filepath.ToSlash(currentPath)
+
+ gitDir := env.GetGitDirEnv()
+ if gitDir != "" {
+ // we've been given the git directory explicitly so no need to navigate to it
+ _, err := cmn.Fs.Stat(gitDir)
+ if err != nil {
+ return nil, utils.WrapError(err)
+ }
+ } else {
+ // we haven't been given the git dir explicitly so we assume it's in the current working directory as `.git/` (or an ancestor directory)
+
+ rootDirectory, err := findWorktreeRoot(cmn.Fs, currentPath)
+ if err != nil {
+ return nil, utils.WrapError(err)
+ }
+ currentPath = rootDirectory
+ err = os.Chdir(rootDirectory)
+ if err != nil {
+ return nil, utils.WrapError(err)
+ }
}
- repoPaths, err := git_commands.GetRepoPaths()
+ repoPaths, err := git_commands.GetRepoPaths(cmn.Fs, currentPath)
if err != nil {
return nil, errors.Errorf("Error getting repo paths: %v", err)
}
@@ -99,7 +127,7 @@ func NewGitCommandAux(
version *git_commands.GitVersion,
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
- repoPaths git_commands.RepoPaths,
+ repoPaths *git_commands.RepoPaths,
repo *gogit.Repository,
syncMutex *deadlock.Mutex,
) *GitCommand {
@@ -144,7 +172,7 @@ func NewGitCommandAux(
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.RebaseMode, gitCommon)
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
- worktreeLoader := git_commands.NewWorktreeLoader(gitCommon, cmd)
+ worktreeLoader := git_commands.NewWorktreeLoader(gitCommon)
stashLoader := git_commands.NewStashLoader(cmn, cmd)
tagLoader := git_commands.NewTagLoader(cmn, cmd)
@@ -183,66 +211,32 @@ func NewGitCommandAux(
}
}
-func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error {
- gitDir := env.GetGitDirEnv()
- if gitDir != "" {
- // we've been given the git directory explicitly so no need to navigate to it
- _, err := stat(gitDir)
- if err != nil {
- return utils.WrapError(err)
- }
-
- return nil
- }
-
- // we haven't been given the git dir explicitly so we assume it's in the current working directory as `.git/` (or an ancestor directory)
-
+// this returns the root of the current worktree. So if you start lazygit from within
+// a subdirectory of the worktree, it will start in the context of the root of that worktree
+func findWorktreeRoot(fs afero.Fs, currentPath string) (string, error) {
for {
- _, err := stat(".git")
+ // we don't care if .git is a directory or a file: either is okay.
+ _, err := fs.Stat(path.Join(currentPath, ".git"))
if err == nil {
- return nil
+ return currentPath, nil
}
if !os.IsNotExist(err) {
- return utils.WrapError(err)
- }
-
- if err = chdir(".."); err != nil {
- return utils.WrapError(err)
+ return "", utils.WrapError(err)
}
- currentPath, err := os.Getwd()
- if err != nil {
- return err
- }
+ currentPath = path.Dir(currentPath)
- atRoot := currentPath == filepath.Dir(currentPath)
+ atRoot := currentPath == path.Dir(currentPath)
if atRoot {
// we should never really land here: the code that creates GitCommand should
// verify we're in a git directory
- return errors.New("Must open lazygit in a git repository")
+ return "", errors.New("Must open lazygit in a git repository")
}
}
}
-func setupRepository(
- openGitRepository func(string, *gogit.PlainOpenOptions) (*gogit.Repository, error),
- options gogit.PlainOpenOptions,
- gitConfigParseErrorStr string,
- path string,
-) (*gogit.Repository, error) {
- repository, err := openGitRepository(path, &options)
- if err != nil {
- if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) {
- return nil, errors.New(gitConfigParseErrorStr)
- }
- return nil, err
- }
-
- return repository, err
-}
-
func VerifyInGitRepo(osCommand *oscommands.OSCommand) error {
return osCommand.Cmd.New(git_commands.NewGitCmd("rev-parse").Arg("--git-dir").ToArgv()).DontLog().Run()
}
diff --git a/pkg/commands/git_commands/common.go b/pkg/commands/git_commands/common.go
index b4c8bea32..cf8250863 100644
--- a/pkg/commands/git_commands/common.go
+++ b/pkg/commands/git_commands/common.go
@@ -12,7 +12,7 @@ type GitCommon struct {
version *GitVersion
cmd oscommands.ICmdObjBuilder
os *oscommands.OSCommand
- repoPaths RepoPaths
+ 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,
- repoPaths RepoPaths,
+ repoPaths *RepoPaths,
repo *gogit.Repository,
config *ConfigCommands,
syncMutex *deadlock.Mutex,
diff --git a/pkg/commands/git_commands/deps_test.go b/pkg/commands/git_commands/deps_test.go
index a960098df..bb17ad0a7 100644
--- a/pkg/commands/git_commands/deps_test.go
+++ b/pkg/commands/git_commands/deps_test.go
@@ -11,6 +11,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/utils"
+ "github.com/spf13/afero"
)
type commonDeps struct {
@@ -20,9 +21,10 @@ type commonDeps struct {
gitConfig *git_config.FakeGitConfig
getenv func(string) string
removeFile func(string) error
- dotGitDir string
common *common.Common
cmd *oscommands.CmdObjBuilder
+ fs afero.Fs
+ repoPaths *RepoPaths
}
func buildGitCommon(deps commonDeps) *GitCommon {
@@ -33,6 +35,16 @@ func buildGitCommon(deps commonDeps) *GitCommon {
gitCommon.Common = utils.NewDummyCommonWithUserConfig(deps.userConfig)
}
+ if deps.fs != nil {
+ gitCommon.Fs = deps.fs
+ }
+
+ if deps.repoPaths != nil {
+ gitCommon.repoPaths = deps.repoPaths
+ } else {
+ gitCommon.repoPaths = MockRepoPaths(".git")
+ }
+
runner := deps.runner
if runner == nil {
runner = oscommands.NewFakeRunner(nil)
@@ -81,11 +93,6 @@ func buildGitCommon(deps commonDeps) *GitCommon {
TempDir: os.TempDir(),
})
- gitCommon.dotGitDir = deps.dotGitDir
- if gitCommon.dotGitDir == "" {
- gitCommon.dotGitDir = ".git"
- }
-
return gitCommon
}
@@ -96,7 +103,7 @@ func buildRepo() *gogit.Repository {
}
func buildFileLoader(gitCommon *GitCommon) *FileLoader {
- return NewFileLoader(gitCommon.Common, gitCommon.cmd, gitCommon.config)
+ return NewFileLoader(gitCommon, gitCommon.cmd, gitCommon.config)
}
func buildSubmoduleCommands(deps commonDeps) *SubmoduleCommands {
diff --git a/pkg/commands/git_commands/file_loader.go b/pkg/commands/git_commands/file_loader.go
index 574a8a6f0..73d7fdc64 100644
--- a/pkg/commands/git_commands/file_loader.go
+++ b/pkg/commands/git_commands/file_loader.go
@@ -66,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(self.repoPaths.RepoGitDirPath())
+ worktreePaths := linkedWortkreePaths(self.Fs, self.repoPaths.RepoGitDirPath())
for _, file := range files {
for _, worktreePath := range worktreePaths {
absFilePath, err := filepath.Abs(file.Name)
diff --git a/pkg/commands/git_commands/file_loader_test.go b/pkg/commands/git_commands/file_loader_test.go
index ae149c2f2..f7efe68b8 100644
--- a/pkg/commands/git_commands/file_loader_test.go
+++ b/pkg/commands/git_commands/file_loader_test.go
@@ -5,7 +5,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
- "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/stretchr/testify/assert"
)
@@ -178,7 +177,7 @@ func TestFileGetStatusFiles(t *testing.T) {
cmd := oscommands.NewDummyCmdObjBuilder(s.runner)
loader := &FileLoader{
- Common: utils.NewDummyCommon(),
+ GitCommon: buildGitCommon(commonDeps{}),
cmd: cmd,
config: &FakeFileLoaderConfig{showUntrackedFiles: "yes"},
getFileType: func(string) string { return "file" },
diff --git a/pkg/commands/git_commands/paths.go b/pkg/commands/git_commands/paths.go
index a4f5ccb74..46fcb5d10 100644
--- a/pkg/commands/git_commands/paths.go
+++ b/pkg/commands/git_commands/paths.go
@@ -2,7 +2,7 @@ package git_commands
import (
"fmt"
- "io/fs"
+ ioFs "io/fs"
"os"
"path"
"path/filepath"
@@ -11,33 +11,10 @@ import (
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/samber/lo"
+ "github.com/spf13/afero"
)
-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 {
+type RepoPaths struct {
currentPath string
worktreePath string
worktreeGitDirPath string
@@ -46,54 +23,81 @@ type RepoDirsImpl struct {
repoName string
}
-var _ RepoPaths = &RepoDirsImpl{}
-
-func (self *RepoDirsImpl) CurrentPath() string {
+// 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.
+func (self *RepoPaths) CurrentPath() string {
return self.currentPath
}
-func (self *RepoDirsImpl) WorktreePath() string {
+// Path to the current worktree. If we're in the main worktree, this will
+// be the same as RepoPath()
+func (self *RepoPaths) WorktreePath() string {
return self.worktreePath
}
-func (self *RepoDirsImpl) WorktreeGitDirPath() 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
+func (self *RepoPaths) WorktreeGitDirPath() string {
return self.worktreeGitDirPath
}
-func (self *RepoDirsImpl) RepoPath() 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
+func (self *RepoPaths) RepoPath() string {
return self.repoPath
}
-func (self *RepoDirsImpl) RepoGitDirPath() 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.
+func (self *RepoPaths) RepoGitDirPath() string {
return self.repoGitDirPath
}
-func (self *RepoDirsImpl) RepoName() string {
+// Name of the repo. Basename of the folder containing the repo.
+func (self *RepoPaths) 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)
+// Returns the repo paths for a typical repo
+func MockRepoPaths(currentPath string) *RepoPaths {
+ return &RepoPaths{
+ currentPath: currentPath,
+ worktreePath: currentPath,
+ worktreeGitDirPath: path.Join(currentPath, ".git"),
+ repoPath: currentPath,
+ repoGitDirPath: path.Join(currentPath, ".git"),
+ repoName: "lazygit",
}
+}
- // converting to forward slashes for the sake of windows (which uses backwards slashes). We want everything
- // to have forward slashes internally
- currentPath = filepath.ToSlash(currentPath)
+func GetRepoPaths(
+ fs afero.Fs,
+ currentPath string,
+) (*RepoPaths, error) {
+ return getRepoPathsAux(afero.NewOsFs(), resolveSymlink, currentPath)
+}
+func getRepoPathsAux(
+ fs afero.Fs,
+ resolveSymlinkFn func(string) (string, error),
+ currentPath string,
+) (*RepoPaths, error) {
worktreePath := currentPath
- repoGitDirPath, repoPath, err := GetCurrentRepoGitDirPath(currentPath)
+ repoGitDirPath, repoPath, err := getCurrentRepoGitDirPath(fs, resolveSymlinkFn, currentPath)
if err != nil {
- return &RepoDirsImpl{}, errors.Errorf("failed to get repo git dir path: %v", err)
+ return nil, errors.Errorf("failed to get repo git dir path: %v", err)
}
- worktreeGitDirPath, err := worktreeGitDirPath(currentPath)
+ worktreeGitDirPath, err := worktreeGitDirPath(fs, currentPath)
if err != nil {
- return &RepoDirsImpl{}, errors.Errorf("failed to get worktree git dir path: %v", err)
+ return nil, errors.Errorf("failed to get worktree git dir path: %v", err)
}
repoName := path.Base(repoPath)
- return &RepoDirsImpl{
+ return &RepoPaths{
currentPath: currentPath,
worktreePath: worktreePath,
worktreeGitDirPath: worktreeGitDirPath,
@@ -103,52 +107,14 @@ func GetRepoPaths() (RepoPaths, error) {
}, 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 := path.Join(repoGitDirPath, "worktrees")
-
- // ensure the directory exists
- _, err := os.Stat(worktreeGitDirsPath)
- if err != nil {
- return result
- }
-
- _ = filepath.Walk(worktreeGitDirsPath, func(currPath string, info fs.FileInfo, err error) error {
- if err != nil {
- return err
- }
-
- if !info.IsDir() {
- return nil
- }
-
- gitDirPath := path.Join(currPath, "gitdir")
- gitDirBytes, err := os.ReadFile(gitDirPath)
- if err != nil {
- // ignoring error
- return nil
- }
- trimmedGitDir := strings.TrimSpace(string(gitDirBytes))
- // removing the .git part
- worktreeDir := path.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) {
+func worktreeGitDirPath(fs afero.Fs, worktreePath string) (string, error) {
// if .git is a file, we're in a linked worktree, otherwise we're in
// the main worktree
dotGitPath := path.Join(worktreePath, ".git")
- gitFileInfo, err := os.Stat(dotGitPath)
+ gitFileInfo, err := fs.Stat(dotGitPath)
if err != nil {
return "", err
}
@@ -157,12 +123,12 @@ func worktreeGitDirPath(worktreePath string) (string, error) {
return dotGitPath, nil
}
- return linkedWorktreeGitDirPath(worktreePath)
+ return linkedWorktreeGitDirPath(fs, worktreePath)
}
-func linkedWorktreeGitDirPath(worktreePath string) (string, error) {
+func linkedWorktreeGitDirPath(fs afero.Fs, worktreePath string) (string, error) {
dotGitPath := path.Join(worktreePath, ".git")
- gitFileContents, err := os.ReadFile(dotGitPath)
+ gitFileContents, err := afero.ReadFile(fs, dotGitPath)
if err != nil {
return "", err
}
@@ -180,7 +146,11 @@ func linkedWorktreeGitDirPath(worktreePath string) (string, error) {
return gitDir, nil
}
-func GetCurrentRepoGitDirPath(currentPath string) (string, string, error) {
+func getCurrentRepoGitDirPath(
+ fs afero.Fs,
+ resolveSymlinkFn func(string) (string, error),
+ currentPath string,
+) (string, string, error) {
var unresolvedGitPath string
if env.GetGitDirEnv() != "" {
unresolvedGitPath = env.GetGitDirEnv()
@@ -188,13 +158,13 @@ func GetCurrentRepoGitDirPath(currentPath string) (string, string, error) {
unresolvedGitPath = path.Join(currentPath, ".git")
}
- gitPath, err := resolveSymlink(unresolvedGitPath)
+ gitPath, err := resolveSymlinkFn(unresolvedGitPath)
if err != nil {
return "", "", err
}
// check if .git is a file or a directory
- gitFileInfo, err := os.Stat(gitPath)
+ gitFileInfo, err := fs.Stat(gitPath)
if err != nil {
return "", "", err
}
@@ -205,7 +175,7 @@ func GetCurrentRepoGitDirPath(currentPath string) (string, string, error) {
}
// either in a submodule, or worktree
- worktreeGitPath, err := linkedWorktreeGitDirPath(currentPath)
+ worktreeGitPath, err := linkedWorktreeGitDirPath(fs, currentPath)
if err != nil {
return "", "", errors.Errorf("could not find git dir for %s: %v", currentPath, err)
}
@@ -238,3 +208,41 @@ func resolveSymlink(path string) (string, error) {
return filepath.EvalSymlinks(path)
}
+
+// Returns the paths of linked worktrees
+func linkedWortkreePaths(fs afero.Fs, 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 := path.Join(repoGitDirPath, "worktrees")
+
+ // ensure the directory exists
+ _, err := fs.Stat(worktreeGitDirsPath)
+ if err != nil {
+ return result
+ }
+
+ _ = afero.Walk(fs, worktreeGitDirsPath, func(currPath string, info ioFs.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ if !info.IsDir() {
+ return nil
+ }
+
+ gitDirPath := path.Join(currPath, "gitdir")
+ gitDirBytes, err := afero.ReadFile(fs, gitDirPath)
+ if err != nil {
+ // ignoring error
+ return nil
+ }
+ trimmedGitDir := strings.TrimSpace(string(gitDirBytes))
+ // removing the .git part
+ worktreeDir := path.Dir(trimmedGitDir)
+ result = append(result, worktreeDir)
+ return nil
+ })
+
+ return result
+}
diff --git a/pkg/commands/git_commands/paths_test.go b/pkg/commands/git_commands/paths_test.go
new file mode 100644
index 000000000..52ad9bd8f
--- /dev/null
+++ b/pkg/commands/git_commands/paths_test.go
@@ -0,0 +1,118 @@
+package git_commands
+
+import (
+ "testing"
+
+ "github.com/go-errors/errors"
+ "github.com/spf13/afero"
+ "github.com/stretchr/testify/assert"
+)
+
+func mockResolveSymlinkFn(p string) (string, error) { return p, nil }
+
+type Scenario struct {
+ Name string
+ BeforeFunc func(fs afero.Fs)
+ Path string
+ Expected *RepoPaths
+ Err error
+}
+
+func TestGetRepoPathsAux(t *testing.T) {
+ scenarios := []Scenario{
+ {
+ Name: "typical case",
+ BeforeFunc: func(fs afero.Fs) {
+ // setup for main worktree
+ _ = fs.MkdirAll("/path/to/repo/.git", 0o755)
+ },
+ Path: "/path/to/repo",
+ Expected: &RepoPaths{
+ currentPath: "/path/to/repo",
+ worktreePath: "/path/to/repo",
+ worktreeGitDirPath: "/path/to/repo/.git",
+ repoPath: "/path/to/repo",
+ repoGitDirPath: "/path/to/repo/.git",
+ repoName: "repo",
+ },
+ Err: nil,
+ },
+ {
+ Name: "linked worktree",
+ BeforeFunc: func(fs afero.Fs) {
+ // setup for linked worktree
+ _ = fs.MkdirAll("/path/to/repo/.git/worktrees/worktree1", 0o755)
+ _ = afero.WriteFile(fs, "/path/to/repo/worktree1/.git", []byte("gitdir: /path/to/repo/.git/worktrees/worktree1"), 0o644)
+ },
+ Path: "/path/to/repo/worktree1",
+ Expected: &RepoPaths{
+ currentPath: "/path/to/repo/worktree1",
+ worktreePath: "/path/to/repo/worktree1",
+ worktreeGitDirPath: "/path/to/repo/.git/worktrees/worktree1",
+ repoPath: "/path/to/repo",
+ repoGitDirPath: "/path/to/repo/.git",
+ repoName: "repo",
+ },
+ Err: nil,
+ },
+ {
+ Name: "worktree .git file missing gitdir directive",
+ BeforeFunc: func(fs afero.Fs) {
+ _ = fs.MkdirAll("/path/to/repo/.git/worktrees/worktree2", 0o755)
+ _ = afero.WriteFile(fs, "/path/to/repo/worktree2/.git", []byte("blah"), 0o644)
+ },
+ Path: "/path/to/repo/worktree2",
+ Expected: nil,
+ Err: errors.New("failed to get repo git dir path: could not find git dir for /path/to/repo/worktree2: /path/to/repo/worktree2/.git 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"),
+ },
+ {
+ Name: "worktree .git file gitdir directive points to a non-existing directory",
+ BeforeFunc: func(fs afero.Fs) {
+ _ = fs.MkdirAll("/path/to/repo/.git/worktrees/worktree2", 0o755)
+ _ = afero.WriteFile(fs, "/path/to/repo/worktree2/.git", []byte("gitdir: /nonexistant"), 0o644)
+ },
+ Path: "/path/to/repo/worktree2",
+ Expected: nil,
+ Err: errors.New("failed to get repo git dir path: could not find git dir for /path/to/repo/worktree2"),
+ },
+ {
+ Name: "submodule",
+ BeforeFunc: func(fs afero.Fs) {
+ _ = fs.MkdirAll("/path/to/repo/.git/modules/submodule1", 0o755)
+ _ = afero.WriteFile(fs, "/path/to/repo/submodule1/.git", []byte("gitdir: /path/to/repo/.git/modules/submodule1"), 0o644)
+ },
+ Path: "/path/to/repo/submodule1",
+ Expected: &RepoPaths{
+ currentPath: "/path/to/repo/submodule1",
+ worktreePath: "/path/to/repo/submodule1",
+ worktreeGitDirPath: "/path/to/repo/.git/modules/submodule1",
+ repoPath: "/path/to/repo/submodule1",
+ repoGitDirPath: "/path/to/repo/.git/modules/submodule1",
+ repoName: "submodule1",
+ },
+ Err: nil,
+ },
+ }
+
+ for _, s := range scenarios {
+ s := s
+ t.Run(s.Name, func(t *testing.T) {
+ fs := afero.NewMemMapFs()
+
+ // prepare the filesystem for the scenario
+ s.BeforeFunc(fs)
+
+ // run the function with the scenario path
+ repoPaths, err := getRepoPathsAux(fs, mockResolveSymlinkFn, s.Path)
+
+ // check the error and the paths
+ if s.Err != nil {
+ assert.Error(t, err)
+ assert.EqualError(t, err, s.Err.Error())
+ } else {
+ assert.Nil(t, err)
+ assert.Equal(t, s.Expected, repoPaths)
+ }
+ })
+ }
+}
diff --git a/pkg/commands/git_commands/worktree_loader.go b/pkg/commands/git_commands/worktree_loader.go
index d81b23f4b..687e9680a 100644
--- a/pkg/commands/git_commands/worktree_loader.go
+++ b/pkg/commands/git_commands/worktree_loader.go
@@ -1,31 +1,23 @@
package git_commands
import (
- "io/fs"
- "os"
+ iofs "io/fs"
"path/filepath"
"strings"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models"
- "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
+ "github.com/spf13/afero"
)
type WorktreeLoader struct {
*GitCommon
- cmd oscommands.ICmdObjBuilder
}
-func NewWorktreeLoader(
- gitCommon *GitCommon,
- cmd oscommands.ICmdObjBuilder,
-) *WorktreeLoader {
- return &WorktreeLoader{
- GitCommon: gitCommon,
- cmd: cmd,
- }
+func NewWorktreeLoader(gitCommon *GitCommon) *WorktreeLoader {
+ return &WorktreeLoader{GitCommon: gitCommon}
}
func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
@@ -38,7 +30,9 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
return nil, err
}
- splitLines := utils.SplitLines(worktreesOutput)
+ splitLines := strings.Split(
+ utils.NormalizeLinefeeds(worktreesOutput), "\n",
+ )
var worktrees []*models.Worktree
var current *models.Worktree
@@ -64,7 +58,7 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
isPathMissing := self.pathExists(path)
var gitDir string
- gitDir, err := worktreeGitDirPath(path)
+ gitDir, err := worktreeGitDirPath(self.Fs, path)
if err != nil {
self.Log.Warnf("Could not find git dir for worktree %s: %v", path, err)
}
@@ -114,13 +108,13 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
continue
}
- rebasedBranch, ok := rebasedBranch(worktree)
+ rebasedBranch, ok := self.rebasedBranch(worktree)
if ok {
worktree.Branch = rebasedBranch
continue
}
- bisectedBranch, ok := bisectedBranch(worktree)
+ bisectedBranch, ok := self.bisectedBranch(worktree)
if ok {
worktree.Branch = bisectedBranch
continue
@@ -131,8 +125,8 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
}
func (self *WorktreeLoader) pathExists(path string) bool {
- if _, err := os.Stat(path); err != nil {
- if errors.Is(err, fs.ErrNotExist) {
+ if _, err := self.Fs.Stat(path); err != nil {
+ if errors.Is(err, iofs.ErrNotExist) {
return true
}
self.Log.Errorf("failed to check if worktree path `%s` exists\n%v", path, err)
@@ -141,9 +135,9 @@ func (self *WorktreeLoader) pathExists(path string) bool {
return false
}
-func rebasedBranch(worktree *models.Worktree) (string, bool) {
+func (self *WorktreeLoader) 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 {
+ if bytesContent, err := afero.ReadFile(self.Fs, filepath.Join(worktree.GitDir, dir, "head-name")); err == nil {
headName := strings.TrimSpace(string(bytesContent))
shortHeadName := strings.TrimPrefix(headName, "refs/heads/")
return shortHeadName, true
@@ -153,9 +147,9 @@ func rebasedBranch(worktree *models.Worktree) (string, bool) {
return "", false
}
-func bisectedB