1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-08-06 11:02:41 +03:00

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
This commit is contained in:
Jesse Duffield
2023-07-29 17:02:04 +10:00
parent a1fae41051
commit 7b302d8c29
88 changed files with 55298 additions and 4243 deletions

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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" },

View File

@@ -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
}

View File

@@ -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)
}
})
}
}

View File

@@ -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 bisectedBranch(worktree *models.Worktree) (string, bool) {
func (self *WorktreeLoader) bisectedBranch(worktree *models.Worktree) (string, bool) {
bisectStartPath := filepath.Join(worktree.GitDir, "BISECT_START")
startContent, err := os.ReadFile(bisectStartPath)
startContent, err := afero.ReadFile(self.Fs, bisectStartPath)
if err != nil {
return "", false
}

View File

@@ -3,9 +3,195 @@ package git_commands
import (
"testing"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
)
func TestGetWorktrees(t *testing.T) {
type scenario struct {
testName string
repoPaths *RepoPaths
before func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs)
expectedWorktrees []*models.Worktree
expectedErr string
}
scenarios := []scenario{
{
testName: "Single worktree (main)",
repoPaths: &RepoPaths{
repoPath: "/path/to/repo",
worktreePath: "/path/to/repo",
},
before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs) {
runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"},
`worktree /path/to/repo
HEAD d85cc9d281fa6ae1665c68365fc70e75e82a042d
branch refs/heads/mybranch
`,
nil)
_ = fs.MkdirAll("/path/to/repo/.git", 0o755)
},
expectedWorktrees: []*models.Worktree{
{
IsMain: true,
IsCurrent: true,
Path: "/path/to/repo",
IsPathMissing: false,
GitDir: "/path/to/repo/.git",
Branch: "mybranch",
Name: "repo",
},
},
expectedErr: "",
},
{
testName: "Multiple worktrees (main + linked)",
repoPaths: &RepoPaths{
repoPath: "/path/to/repo",
worktreePath: "/path/to/repo",
},
before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs) {
runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"},
`worktree /path/to/repo
HEAD d85cc9d281fa6ae1665c68365fc70e75e82a042d
branch refs/heads/mybranch
worktree /path/to/repo-worktree
HEAD 775955775e79b8f5b4c4b56f82fbf657e2d5e4de
branch refs/heads/mybranch-worktree
`,
nil)
_ = fs.MkdirAll("/path/to/repo/.git", 0o755)
_ = fs.MkdirAll("/path/to/repo-worktree", 0o755)
_ = fs.MkdirAll("/path/to/repo/.git/worktrees/repo-worktree", 0o755)
_ = afero.WriteFile(fs, "/path/to/repo-worktree/.git", []byte("gitdir: /path/to/repo/.git/worktrees/repo-worktree"), 0o755)
},
expectedWorktrees: []*models.Worktree{
{
IsMain: true,
IsCurrent: true,
Path: "/path/to/repo",
IsPathMissing: false,
GitDir: "/path/to/repo/.git",
Branch: "mybranch",
Name: "repo",
},
{
IsMain: false,
IsCurrent: false,
Path: "/path/to/repo-worktree",
IsPathMissing: false,
GitDir: "/path/to/repo/.git/worktrees/repo-worktree",
Branch: "mybranch-worktree",
Name: "repo-worktree",
},
},
expectedErr: "",
},
{
testName: "Worktree missing path",
repoPaths: &RepoPaths{
repoPath: "/path/to/repo",
worktreePath: "/path/to/repo",
},
before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs) {
runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"},
`worktree /path/to/worktree
HEAD 775955775e79b8f5b4c4b56f82fbf657e2d5e4de
branch refs/heads/missingbranch
`,
nil)
_ = fs.MkdirAll("/path/to/repo/.git", 0o755)
},
expectedWorktrees: []*models.Worktree{
{
IsMain: false,
IsCurrent: false,
Path: "/path/to/worktree",
IsPathMissing: true,
GitDir: "",
Branch: "missingbranch",
Name: "worktree",
},
},
expectedErr: "",
},
{
testName: "In linked worktree",
repoPaths: &RepoPaths{
repoPath: "/path/to/repo",
worktreePath: "/path/to/repo-worktree",
},
before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs) {
runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"},
`worktree /path/to/repo
HEAD d85cc9d281fa6ae1665c68365fc70e75e82a042d
branch refs/heads/mybranch
worktree /path/to/repo-worktree
HEAD 775955775e79b8f5b4c4b56f82fbf657e2d5e4de
branch refs/heads/mybranch-worktree
`,
nil)
_ = fs.MkdirAll("/path/to/repo/.git", 0o755)
_ = fs.MkdirAll("/path/to/repo-worktree", 0o755)
_ = fs.MkdirAll("/path/to/repo/.git/worktrees/repo-worktree", 0o755)
_ = afero.WriteFile(fs, "/path/to/repo-worktree/.git", []byte("gitdir: /path/to/repo/.git/worktrees/repo-worktree"), 0o755)
},
expectedWorktrees: []*models.Worktree{
{
IsMain: false,
IsCurrent: true,
Path: "/path/to/repo-worktree",
IsPathMissing: false,
GitDir: "/path/to/repo/.git/worktrees/repo-worktree",
Branch: "mybranch-worktree",
Name: "repo-worktree",
},
{
IsMain: true,
IsCurrent: false,
Path: "/path/to/repo",
IsPathMissing: false,
GitDir: "/path/to/repo/.git",
Branch: "mybranch",
Name: "repo",
},
},
expectedErr: "",
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t)
fs := afero.NewMemMapFs()
s.before(runner, fs)
loader := &WorktreeLoader{
GitCommon: buildGitCommon(commonDeps{runner: runner, fs: fs, repoPaths: s.repoPaths}),
}
worktrees, err := loader.GetWorktrees()
if s.expectedErr != "" {
assert.EqualError(t, errors.New(s.expectedErr), err.Error())
} else {
assert.NoError(t, err)
assert.EqualValues(t, worktrees, s.expectedWorktrees)
}
})
}
}
func TestGetUniqueNamesFromPaths(t *testing.T) {
for _, scenario := range []struct {
input []string