1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-01-20 05:19:24 +02:00

269 lines
7.2 KiB
Go
Raw Normal View History

package commands
import (
"io/ioutil"
"os"
"path/filepath"
2018-08-12 19:50:55 +10:00
"strings"
2019-02-18 21:29:43 +11:00
"github.com/go-errors/errors"
2020-10-06 20:50:54 +11:00
gogit "github.com/jesseduffield/go-git/v5"
2021-10-23 09:52:19 +11:00
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
2020-08-15 11:18:40 +10:00
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/env"
2018-08-12 21:04:47 +10:00
"github.com/jesseduffield/lazygit/pkg/utils"
)
2022-01-02 10:34:33 +11:00
// GitCommand is our main git interface
type GitCommand struct {
*common.Common
Repo *gogit.Repository
Loaders Loaders
Cmd oscommands.ICmdObjBuilder
Submodule *SubmoduleCommands
Tag *TagCommands
WorkingTree *WorkingTreeCommands
File *FileCommands
Branch *BranchCommands
Commit *CommitCommands
Rebase *RebaseCommands
Stash *StashCommands
Status *StatusCommands
Config *ConfigCommands
Patch *PatchCommands
Remote *RemoteCommands
Sync *SyncCommands
}
type Loaders struct {
Commits *loaders.CommitLoader
Branches *loaders.BranchLoader
Files *loaders.FileLoader
CommitFiles *loaders.CommitFileLoader
Remotes *loaders.RemoteLoader
ReflogCommits *loaders.ReflogCommitLoader
Stash *loaders.StashLoader
Tags *loaders.TagLoader
}
2021-10-23 09:52:19 +11:00
func NewGitCommand(
cmn *common.Common,
2021-10-23 09:52:19 +11:00
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
) (*GitCommand, error) {
2020-09-27 15:36:04 +10:00
if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil {
return nil, err
}
2022-01-02 10:34:33 +11:00
repo, err := setupRepository(gogit.PlainOpen, cmn.Tr.GitconfigParseErr)
if err != nil {
2020-09-27 15:36:04 +10:00
return nil, err
}
2019-05-12 17:04:32 +10:00
dotGitDir, err := findDotGitDir(os.Stat, ioutil.ReadFile)
if err != nil {
return nil, err
}
2022-01-02 10:34:33 +11:00
return NewGitCommandAux(
cmn,
osCommand,
gitConfig,
dotGitDir,
repo,
), nil
}
2021-12-29 14:33:38 +11:00
2022-01-02 10:34:33 +11:00
func NewGitCommandAux(
cmn *common.Common,
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
dotGitDir string,
repo *gogit.Repository,
) *GitCommand {
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
2019-11-05 18:10:47 +11:00
2022-01-07 14:45:18 +11:00
// 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.
2022-01-02 10:34:33 +11:00
configCommands := NewConfigCommands(cmn, gitConfig)
statusCommands := NewStatusCommands(cmn, osCommand, repo, dotGitDir)
fileLoader := loaders.NewFileLoader(cmn, cmd, configCommands)
remoteCommands := NewRemoteCommands(cmn, cmd)
branchCommands := NewBranchCommands(cmn, cmd)
syncCommands := NewSyncCommands(cmn, cmd)
tagCommands := NewTagCommands(cmn, cmd)
commitCommands := NewCommitCommands(cmn, cmd)
fileCommands := NewFileCommands(cmn, cmd, configCommands, osCommand)
submoduleCommands := NewSubmoduleCommands(cmn, cmd, dotGitDir)
workingTreeCommands := NewWorkingTreeCommands(cmn, cmd, submoduleCommands, osCommand, fileLoader)
rebaseCommands := NewRebaseCommands(
cmn,
cmd,
osCommand,
commitCommands,
workingTreeCommands,
configCommands,
dotGitDir,
)
stashCommands := NewStashCommands(cmn, cmd, osCommand, fileLoader, workingTreeCommands)
// TODO: have patch manager take workingTreeCommands in its entirety
patchManager := patch.NewPatchManager(cmn.Log, workingTreeCommands.ApplyPatch, workingTreeCommands.ShowFileDiff)
patchCommands := NewPatchCommands(cmn, cmd, rebaseCommands, commitCommands, configCommands, statusCommands, patchManager)
return &GitCommand{
2022-01-07 15:01:07 +11:00
Common: cmn,
2022-01-02 10:34:33 +11:00
Repo: repo,
Cmd: cmd,
Submodule: submoduleCommands,
Tag: tagCommands,
WorkingTree: workingTreeCommands,
File: fileCommands,
Branch: branchCommands,
Commit: commitCommands,
Rebase: rebaseCommands,
Config: configCommands,
Stash: stashCommands,
Status: statusCommands,
Patch: patchCommands,
Remote: remoteCommands,
Sync: syncCommands,
Loaders: Loaders{
Commits: loaders.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchName, statusCommands.RebaseMode),
Branches: loaders.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchName),
Files: fileLoader,
CommitFiles: loaders.NewCommitFileLoader(cmn, cmd),
Remotes: loaders.NewRemoteLoader(cmn, cmd, repo.Remotes),
ReflogCommits: loaders.NewReflogCommitLoader(cmn, cmd),
Stash: loaders.NewStashLoader(cmn, cmd),
Tags: loaders.NewTagLoader(cmn, cmd),
},
}
}
2020-09-29 20:03:39 +10:00
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)
2020-09-29 08:47:14 +10:00
}
2018-08-12 19:50:55 +10:00
2020-09-29 20:03:39 +10:00
return nil
2018-11-29 17:57:28 +01:00
}
2020-09-29 20:03:39 +10:00
// 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)
2020-08-11 21:18:38 +10:00
2020-09-29 20:03:39 +10:00
for {
_, err := stat(".git")
2020-08-11 21:18:38 +10:00
2020-09-29 20:03:39 +10:00
if err == nil {
return nil
2018-11-25 13:15:36 +01:00
}
2018-08-12 19:50:55 +10:00
2020-09-29 20:03:39 +10:00
if !os.IsNotExist(err) {
return utils.WrapError(err)
2020-03-26 20:29:35 +11:00
}
2020-09-29 20:03:39 +10:00
if err = chdir(".."); err != nil {
return utils.WrapError(err)
2019-02-20 19:47:01 +11:00
}
2021-03-30 22:17:42 +11:00
currentPath, err := os.Getwd()
if err != nil {
return err
}
atRoot := currentPath == filepath.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")
}
2019-02-20 19:47:01 +11:00
}
2018-08-12 20:22:20 +10:00
}
2020-09-29 20:03:39 +10:00
// resolvePath takes a path containing a symlink and returns the true path
func resolvePath(path string) (string, error) {
l, err := os.Lstat(path)
if err != nil {
return "", err
}
2020-08-07 17:52:17 +10:00
2020-09-29 20:03:39 +10:00
if l.Mode()&os.ModeSymlink == 0 {
return path, nil
2020-08-07 17:52:17 +10:00
}
2020-09-29 20:03:39 +10:00
return filepath.EvalSymlinks(path)
2020-08-07 17:52:17 +10:00
}
2020-10-04 11:00:48 +11:00
func setupRepository(openGitRepository func(string) (*gogit.Repository, error), gitConfigParseErrorStr string) (*gogit.Repository, error) {
2020-09-29 20:03:39 +10:00
unresolvedPath := env.GetGitDirEnv()
if unresolvedPath == "" {
var err error
unresolvedPath, err = os.Getwd()
2020-08-07 17:52:17 +10:00
if err != nil {
2020-09-29 20:03:39 +10:00
return nil, err
}
}
2020-09-29 20:03:39 +10:00
path, err := resolvePath(unresolvedPath)
2019-02-18 21:29:43 +11:00
if err != nil {
return nil, err
2019-02-18 21:29:43 +11:00
}
2020-09-29 20:03:39 +10:00
repository, err := openGitRepository(path)
2019-02-24 13:51:52 +11:00
if err != nil {
2020-09-29 20:03:39 +10:00
if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) {
2020-10-04 11:00:48 +11:00
return nil, errors.New(gitConfigParseErrorStr)
}
2020-09-29 20:03:39 +10:00
return nil, err
}
2020-09-29 20:03:39 +10:00
return repository, err
}
2020-09-29 20:03:39 +10:00
func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filename string) ([]byte, error)) (string, error) {
if env.GetGitDirEnv() != "" {
return env.GetGitDirEnv(), nil
}
2020-09-29 20:03:39 +10:00
f, err := stat(".git")
if err != nil {
2020-09-29 20:03:39 +10:00
return "", err
}
2020-09-29 20:03:39 +10:00
if f.IsDir() {
return ".git", nil
}
2020-09-29 20:03:39 +10:00
fileBytes, err := readFile(".git")
if err != nil {
2020-09-29 20:03:39 +10:00
return "", err
}
2020-09-29 20:03:39 +10:00
fileContent := string(fileBytes)
if !strings.HasPrefix(fileContent, "gitdir: ") {
return "", errors.New(".git is a file which suggests we are in a submodule but the file's contents do not contain a gitdir pointing to the actual .git directory")
}
2020-09-29 20:03:39 +10:00
return strings.TrimSpace(strings.TrimPrefix(fileContent, "gitdir: ")), nil
}
2021-03-30 22:17:42 +11:00
func VerifyInGitRepo(osCommand *oscommands.OSCommand) error {
2022-01-05 11:57:32 +11:00
return osCommand.Cmd.New("git rev-parse --git-dir").DontLog().Run()
2021-10-20 22:21:16 +11:00
}