mirror of
https://github.com/jesseduffield/lazygit.git
synced 2024-12-02 09:21:40 +02:00
7b302d8c29
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
243 lines
8.4 KiB
Go
243 lines
8.4 KiB
Go
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"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
|
"github.com/jesseduffield/lazygit/pkg/common"
|
|
"github.com/jesseduffield/lazygit/pkg/env"
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
)
|
|
|
|
// GitCommand is our main git interface
|
|
type GitCommand struct {
|
|
Branch *git_commands.BranchCommands
|
|
Commit *git_commands.CommitCommands
|
|
Config *git_commands.ConfigCommands
|
|
Custom *git_commands.CustomCommands
|
|
Diff *git_commands.DiffCommands
|
|
File *git_commands.FileCommands
|
|
Flow *git_commands.FlowCommands
|
|
Patch *git_commands.PatchCommands
|
|
Rebase *git_commands.RebaseCommands
|
|
Remote *git_commands.RemoteCommands
|
|
Stash *git_commands.StashCommands
|
|
Status *git_commands.StatusCommands
|
|
Submodule *git_commands.SubmoduleCommands
|
|
Sync *git_commands.SyncCommands
|
|
Tag *git_commands.TagCommands
|
|
WorkingTree *git_commands.WorkingTreeCommands
|
|
Bisect *git_commands.BisectCommands
|
|
Worktree *git_commands.WorktreeCommands
|
|
Version *git_commands.GitVersion
|
|
RepoPaths *git_commands.RepoPaths
|
|
|
|
Loaders Loaders
|
|
}
|
|
|
|
type Loaders struct {
|
|
BranchLoader *git_commands.BranchLoader
|
|
CommitFileLoader *git_commands.CommitFileLoader
|
|
CommitLoader *git_commands.CommitLoader
|
|
FileLoader *git_commands.FileLoader
|
|
ReflogCommitLoader *git_commands.ReflogCommitLoader
|
|
RemoteLoader *git_commands.RemoteLoader
|
|
StashLoader *git_commands.StashLoader
|
|
TagLoader *git_commands.TagLoader
|
|
Worktrees *git_commands.WorktreeLoader
|
|
}
|
|
|
|
func NewGitCommand(
|
|
cmn *common.Common,
|
|
version *git_commands.GitVersion,
|
|
osCommand *oscommands.OSCommand,
|
|
gitConfig git_config.IGitConfig,
|
|
syncMutex *deadlock.Mutex,
|
|
) (*GitCommand, error) {
|
|
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(cmn.Fs, currentPath)
|
|
if err != nil {
|
|
return nil, errors.Errorf("Error getting repo paths: %v", err)
|
|
}
|
|
|
|
repository, err := gogit.PlainOpenWithOptions(
|
|
repoPaths.WorktreeGitDirPath(),
|
|
&gogit.PlainOpenOptions{DetectDotGit: false, EnableDotGitCommonDir: true},
|
|
)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) {
|
|
return nil, errors.New(cmn.Tr.GitconfigParseErr)
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return NewGitCommandAux(
|
|
cmn,
|
|
version,
|
|
osCommand,
|
|
gitConfig,
|
|
repoPaths,
|
|
repository,
|
|
syncMutex,
|
|
), nil
|
|
}
|
|
|
|
func NewGitCommandAux(
|
|
cmn *common.Common,
|
|
version *git_commands.GitVersion,
|
|
osCommand *oscommands.OSCommand,
|
|
gitConfig git_config.IGitConfig,
|
|
repoPaths *git_commands.RepoPaths,
|
|
repo *gogit.Repository,
|
|
syncMutex *deadlock.Mutex,
|
|
) *GitCommand {
|
|
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
|
|
|
|
// here we're doing a bunch of dependency injection for each of our commands structs.
|
|
// This is admittedly messy, but allows us to test each command struct in isolation,
|
|
// and allows for better namespacing when compared to having every method living
|
|
// on the one struct.
|
|
// common ones are: cmn, osCommand, dotGitDir, configCommands
|
|
configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo)
|
|
|
|
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands, syncMutex)
|
|
|
|
fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands)
|
|
statusCommands := git_commands.NewStatusCommands(gitCommon)
|
|
flowCommands := git_commands.NewFlowCommands(gitCommon)
|
|
remoteCommands := git_commands.NewRemoteCommands(gitCommon)
|
|
branchCommands := git_commands.NewBranchCommands(gitCommon)
|
|
syncCommands := git_commands.NewSyncCommands(gitCommon)
|
|
tagCommands := git_commands.NewTagCommands(gitCommon)
|
|
commitCommands := git_commands.NewCommitCommands(gitCommon)
|
|
customCommands := git_commands.NewCustomCommands(gitCommon)
|
|
diffCommands := git_commands.NewDiffCommands(gitCommon)
|
|
fileCommands := git_commands.NewFileCommands(gitCommon)
|
|
submoduleCommands := git_commands.NewSubmoduleCommands(gitCommon)
|
|
workingTreeCommands := git_commands.NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader)
|
|
rebaseCommands := git_commands.NewRebaseCommands(gitCommon, commitCommands, workingTreeCommands)
|
|
stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands)
|
|
patchBuilder := patch.NewPatchBuilder(cmn.Log,
|
|
func(from string, to string, reverse bool, filename string, plain bool) (string, error) {
|
|
// TODO: make patch builder take Gui.IgnoreWhitespaceInDiffView into
|
|
// account. For now we just pass false.
|
|
return workingTreeCommands.ShowFileDiff(from, to, reverse, filename, plain, false)
|
|
})
|
|
patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder)
|
|
bisectCommands := git_commands.NewBisectCommands(gitCommon)
|
|
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
|
|
|
|
branchLoader := git_commands.NewBranchLoader(cmn, cmd, branchCommands.CurrentBranchInfo, configCommands)
|
|
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
|
|
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)
|
|
stashLoader := git_commands.NewStashLoader(cmn, cmd)
|
|
tagLoader := git_commands.NewTagLoader(cmn, cmd)
|
|
|
|
return &GitCommand{
|
|
Branch: branchCommands,
|
|
Commit: commitCommands,
|
|
Config: configCommands,
|
|
Custom: customCommands,
|
|
Diff: diffCommands,
|
|
File: fileCommands,
|
|
Flow: flowCommands,
|
|
Patch: patchCommands,
|
|
Rebase: rebaseCommands,
|
|
Remote: remoteCommands,
|
|
Stash: stashCommands,
|
|
Status: statusCommands,
|
|
Submodule: submoduleCommands,
|
|
Sync: syncCommands,
|
|
Tag: tagCommands,
|
|
Bisect: bisectCommands,
|
|
WorkingTree: workingTreeCommands,
|
|
Worktree: worktreeCommands,
|
|
Version: version,
|
|
Loaders: Loaders{
|
|
BranchLoader: branchLoader,
|
|
CommitFileLoader: commitFileLoader,
|
|
CommitLoader: commitLoader,
|
|
FileLoader: fileLoader,
|
|
ReflogCommitLoader: reflogCommitLoader,
|
|
RemoteLoader: remoteLoader,
|
|
Worktrees: worktreeLoader,
|
|
StashLoader: stashLoader,
|
|
TagLoader: tagLoader,
|
|
},
|
|
RepoPaths: repoPaths,
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
// 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 currentPath, nil
|
|
}
|
|
|
|
if !os.IsNotExist(err) {
|
|
return "", utils.WrapError(err)
|
|
}
|
|
|
|
currentPath = path.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")
|
|
}
|
|
}
|
|
}
|
|
|
|
func VerifyInGitRepo(osCommand *oscommands.OSCommand) error {
|
|
return osCommand.Cmd.New(git_commands.NewGitCmd("rev-parse").Arg("--git-dir").ToArgv()).DontLog().Run()
|
|
}
|