mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-30 03:23:08 +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:
@ -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)
|
||||
}
|
||||
|
||||
repoPaths, err := git_commands.GetRepoPaths()
|
||||
// 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(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)
|
||||
return "", utils.WrapError(err)
|
||||
}
|
||||
|
||||
if err = chdir(".."); err != nil {
|
||||
return utils.WrapError(err)
|
||||
}
|
||||
currentPath = path.Dir(currentPath)
|
||||
|
||||
currentPath, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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" },
|
||||
|
@ -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
|
||||
}
|
||||
|
118
pkg/commands/git_commands/paths_test.go
Normal file
118
pkg/commands/git_commands/paths_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1,305 +1,74 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type fileInfoMock struct {
|
||||
name string
|
||||
size int64
|
||||
fileMode os.FileMode
|
||||
fileModTime time.Time
|
||||
isDir bool
|
||||
sys interface{}
|
||||
}
|
||||
|
||||
// Name is a function.
|
||||
func (f fileInfoMock) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
// Size is a function.
|
||||
func (f fileInfoMock) Size() int64 {
|
||||
return f.size
|
||||
}
|
||||
|
||||
// Mode is a function.
|
||||
func (f fileInfoMock) Mode() os.FileMode {
|
||||
return f.fileMode
|
||||
}
|
||||
|
||||
// ModTime is a function.
|
||||
func (f fileInfoMock) ModTime() time.Time {
|
||||
return f.fileModTime
|
||||
}
|
||||
|
||||
// IsDir is a function.
|
||||
func (f fileInfoMock) IsDir() bool {
|
||||
return f.isDir
|
||||
}
|
||||
|
||||
// Sys is a function.
|
||||
func (f fileInfoMock) Sys() interface{} {
|
||||
return f.sys
|
||||
}
|
||||
|
||||
// TestNavigateToRepoRootDirectory is a function.
|
||||
func TestNavigateToRepoRootDirectory(t *testing.T) {
|
||||
func TestFindWorktreeRoot(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
stat func(string) (os.FileInfo, error)
|
||||
chdir func(string) error
|
||||
test func(error)
|
||||
testName string
|
||||
currentPath string
|
||||
before func(fs afero.Fs)
|
||||
expectedPath string
|
||||
expectedErr string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Navigate to git repository",
|
||||
func(string) (os.FileInfo, error) {
|
||||
return fileInfoMock{isDir: true}, nil
|
||||
},
|
||||
func(string) error {
|
||||
return nil
|
||||
},
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
testName: "at root of worktree",
|
||||
currentPath: "/path/to/repo",
|
||||
before: func(fs afero.Fs) {
|
||||
_ = fs.MkdirAll("/path/to/repo/.git", 0o755)
|
||||
},
|
||||
expectedPath: "/path/to/repo",
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
"An error occurred when getting path information",
|
||||
func(string) (os.FileInfo, error) {
|
||||
return nil, fmt.Errorf("An error occurred")
|
||||
},
|
||||
func(string) error {
|
||||
return nil
|
||||
},
|
||||
func(err error) {
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, "An error occurred")
|
||||
testName: "inside worktree",
|
||||
currentPath: "/path/to/repo/subdir",
|
||||
before: func(fs afero.Fs) {
|
||||
_ = fs.MkdirAll("/path/to/repo/.git", 0o755)
|
||||
_ = fs.MkdirAll("/path/to/repo/subdir", 0o755)
|
||||
},
|
||||
expectedPath: "/path/to/repo",
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
"An error occurred when trying to move one path backward",
|
||||
func(string) (os.FileInfo, error) {
|
||||
return nil, os.ErrNotExist
|
||||
},
|
||||
func(string) error {
|
||||
return fmt.Errorf("An error occurred")
|
||||
},
|
||||
func(err error) {
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, "An error occurred")
|
||||
testName: "not in a git repo",
|
||||
currentPath: "/path/to/dir",
|
||||
before: func(fs afero.Fs) {},
|
||||
expectedPath: "",
|
||||
expectedErr: "Must open lazygit in a git repository",
|
||||
},
|
||||
{
|
||||
testName: "In linked worktree",
|
||||
currentPath: "/path/to/worktree",
|
||||
before: func(fs afero.Fs) {
|
||||
_ = fs.MkdirAll("/path/to/worktree", 0o755)
|
||||
_ = afero.WriteFile(fs, "/path/to/worktree/.git", []byte("blah"), 0o755)
|
||||
},
|
||||
expectedPath: "/path/to/worktree",
|
||||
expectedErr: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
s.test(navigateToRepoRootDirectory(s.stat, s.chdir))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSetupRepository is a function.
|
||||
func TestSetupRepository(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
openGitRepository func(string, *gogit.PlainOpenOptions) (*gogit.Repository, error)
|
||||
errorStr string
|
||||
options gogit.PlainOpenOptions
|
||||
test func(*gogit.Repository, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"A gitconfig parsing error occurred",
|
||||
func(string, *gogit.PlainOpenOptions) (*gogit.Repository, error) {
|
||||
return nil, fmt.Errorf(`unquoted '\' must be followed by new line`)
|
||||
},
|
||||
"error translated",
|
||||
gogit.PlainOpenOptions{},
|
||||
func(r *gogit.Repository, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, "error translated")
|
||||
},
|
||||
},
|
||||
{
|
||||
"A gogit error occurred",
|
||||
func(string, *gogit.PlainOpenOptions) (*gogit.Repository, error) {
|
||||
return nil, fmt.Errorf("Error from inside gogit")
|
||||
},
|
||||
"",
|
||||
gogit.PlainOpenOptions{},
|
||||
func(r *gogit.Repository, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, "Error from inside gogit")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Setup done properly",
|
||||
func(string, *gogit.PlainOpenOptions) (*gogit.Repository, error) {
|
||||
assert.NoError(t, os.RemoveAll("/tmp/lazygit-test"))
|
||||
r, err := gogit.PlainInit("/tmp/lazygit-test", false)
|
||||
assert.NoError(t, err)
|
||||
return r, nil
|
||||
},
|
||||
"",
|
||||
gogit.PlainOpenOptions{},
|
||||
func(r *gogit.Repository, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, r)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
s.test(setupRepository(s.openGitRepository, s.options, s.errorStr))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewGitCommand is a function.
|
||||
func TestNewGitCommand(t *testing.T) {
|
||||
actual, err := os.Getwd()
|
||||
assert.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
assert.NoError(t, os.Chdir(actual))
|
||||
}()
|
||||
|
||||
type scenario struct {
|
||||
testName string
|
||||
setup func()
|
||||
test func(*GitCommand, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"An error occurred, folder doesn't contains a git repository",
|
||||
func() {
|
||||
assert.NoError(t, os.Chdir("/tmp"))
|
||||
},
|
||||
func(gitCmd *GitCommand, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Regexp(t, `Must open lazygit in a git repository`, err.Error())
|
||||
},
|
||||
},
|
||||
{
|
||||
"New GitCommand object created",
|
||||
func() {
|
||||
assert.NoError(t, os.RemoveAll("/tmp/lazygit-test"))
|
||||
_, err := gogit.PlainInit("/tmp/lazygit-test", false)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.Chdir("/tmp/lazygit-test"))
|
||||
},
|
||||
func(gitCmd *GitCommand, err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
s.setup()
|
||||
s.test(
|
||||
NewGitCommand(utils.NewDummyCommon(),
|
||||
&git_commands.GitVersion{},
|
||||
oscommands.NewDummyOSCommand(),
|
||||
git_config.NewFakeGitConfig(nil),
|
||||
&deadlock.Mutex{},
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindDotGitDir(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
stat func(string) (os.FileInfo, error)
|
||||
readFile func(filename string) ([]byte, error)
|
||||
test func(string, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
".git is a directory",
|
||||
func(dotGit string) (os.FileInfo, error) {
|
||||
assert.Equal(t, ".git", dotGit)
|
||||
return os.Stat("testdata/a_dir")
|
||||
},
|
||||
func(dotGit string) ([]byte, error) {
|
||||
assert.Fail(t, "readFile should not be called if .git is a directory")
|
||||
return nil, nil
|
||||
},
|
||||
func(gitDir string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ".git", gitDir)
|
||||
},
|
||||
},
|
||||
{
|
||||
".git is a file",
|
||||
func(dotGit string) (os.FileInfo, error) {
|
||||
assert.Equal(t, ".git", dotGit)
|
||||
return os.Stat("testdata/a_file")
|
||||
},
|
||||
func(dotGit string) ([]byte, error) {
|
||||
assert.Equal(t, ".git", dotGit)
|
||||
return []byte("gitdir: blah\n"), nil
|
||||
},
|
||||
func(gitDir string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "blah", gitDir)
|
||||
},
|
||||
},
|
||||
{
|
||||
"os.Stat returns an error",
|
||||
func(dotGit string) (os.FileInfo, error) {
|
||||
assert.Equal(t, ".git", dotGit)
|
||||
return nil, errors.New("error")
|
||||
},
|
||||
func(dotGit string) ([]byte, error) {
|
||||
assert.Fail(t, "readFile should not be called os.Stat returns an error")
|
||||
return nil, nil
|
||||
},
|
||||
func(gitDir string, err error) {
|
||||
assert.Error(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"readFile returns an error",
|
||||
func(dotGit string) (os.FileInfo, error) {
|
||||
assert.Equal(t, ".git", dotGit)
|
||||
return os.Stat("testdata/a_file")
|
||||
},
|
||||
func(dotGit string) ([]byte, error) {
|
||||
return nil, errors.New("error")
|
||||
},
|
||||
func(gitDir string, err error) {
|
||||
assert.Error(t, err)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
s.test(findDotGitDir(s.stat, s.readFile))
|
||||
fs := afero.NewMemMapFs()
|
||||
s.before(fs)
|
||||
|
||||
root, err := findWorktreeRoot(fs, s.currentPath)
|
||||
if s.expectedErr != "" {
|
||||
assert.EqualError(t, errors.New(s.expectedErr), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, s.expectedPath, root)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user