1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-12 11:15:00 +02:00
lazygit/pkg/commands/git.go

280 lines
8.9 KiB
Go
Raw Normal View History

package commands
import (
"os"
"path/filepath"
2018-08-12 11:50:55 +02:00
"strings"
2019-02-18 12:29:43 +02:00
"github.com/go-errors/errors"
"github.com/sasha-s/go-deadlock"
2020-10-06 11:50:54 +02:00
gogit "github.com/jesseduffield/go-git/v5"
2022-01-08 05:00:36 +02:00
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
2021-10-23 00:52:19 +02:00
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
2020-08-15 03:18:40 +02:00
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/env"
2018-08-12 13:04:47 +02:00
"github.com/jesseduffield/lazygit/pkg/utils"
)
2022-01-02 01:34:33 +02:00
// GitCommand is our main git interface
type GitCommand struct {
2022-01-08 05:00:36 +02:00
Branch *git_commands.BranchCommands
Commit *git_commands.CommitCommands
Config *git_commands.ConfigCommands
Custom *git_commands.CustomCommands
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
2022-01-19 09:32:27 +02:00
Bisect *git_commands.BisectCommands
2022-01-07 11:36:11 +02:00
Loaders Loaders
2022-01-02 01:34:33 +02:00
}
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
}
2021-10-23 00:52:19 +02:00
func NewGitCommand(
cmn *common.Common,
2022-12-26 16:43:08 +02:00
version *git_commands.GitVersion,
2021-10-23 00:52:19 +02:00
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
syncMutex *deadlock.Mutex,
2021-10-23 00:52:19 +02:00
) (*GitCommand, error) {
2020-09-27 07:36:04 +02:00
if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil {
return nil, err
}
2022-04-07 18:45:08 +02:00
repo, err := setupRepository(gogit.PlainOpenWithOptions, gogit.PlainOpenOptions{DetectDotGit: false, EnableDotGitCommonDir: true}, cmn.Tr.GitconfigParseErr)
2022-01-02 01:34:33 +02:00
if err != nil {
2020-09-27 07:36:04 +02:00
return nil, err
}
2022-09-13 12:11:03 +02:00
dotGitDir, err := findDotGitDir(os.Stat, os.ReadFile)
2019-05-12 09:04:32 +02:00
if err != nil {
return nil, err
}
2022-01-02 01:34:33 +02:00
return NewGitCommandAux(
cmn,
2022-12-26 16:43:08 +02:00
version,
2022-01-02 01:34:33 +02:00
osCommand,
gitConfig,
dotGitDir,
repo,
syncMutex,
2022-01-02 01:34:33 +02:00
), nil
}
2021-12-29 05:33:38 +02:00
2022-01-02 01:34:33 +02:00
func NewGitCommandAux(
cmn *common.Common,
2022-12-26 16:43:08 +02:00
version *git_commands.GitVersion,
2022-01-02 01:34:33 +02:00
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
dotGitDir string,
repo *gogit.Repository,
syncMutex *deadlock.Mutex,
2022-01-02 01:34:33 +02:00
) *GitCommand {
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
2019-11-05 09:10:47 +02:00
2022-01-07 05:45:18 +02: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.
// common ones are: cmn, osCommand, dotGitDir, configCommands
2022-01-08 05:44:07 +02:00
configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo)
fileLoader := git_commands.NewFileLoader(cmn, cmd, configCommands)
2022-12-26 16:43:08 +02:00
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, dotGitDir, repo, configCommands, syncMutex)
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)
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)
2022-01-02 01:34:33 +02:00
// TODO: have patch manager take workingTreeCommands in its entirety
patchManager := patch.NewPatchManager(cmn.Log, workingTreeCommands.ApplyPatch,
func(from string, to string, reverse bool, filename string, plain bool) (string, error) {
// TODO: make patch manager 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, patchManager)
2022-01-19 09:32:27 +02:00
bisectCommands := git_commands.NewBisectCommands(gitCommon)
2022-01-02 01:34:33 +02:00
branchLoader := git_commands.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchInfo, configCommands)
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchInfo, statusCommands.RebaseMode)
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
stashLoader := git_commands.NewStashLoader(cmn, cmd)
tagLoader := git_commands.NewTagLoader(cmn, cmd)
2022-01-02 01:34:33 +02:00
return &GitCommand{
Branch: branchCommands,
Commit: commitCommands,
Config: configCommands,
2022-01-07 11:36:11 +02:00
Custom: customCommands,
File: fileCommands,
Flow: flowCommands,
2022-01-02 01:34:33 +02:00
Patch: patchCommands,
2022-01-07 11:36:11 +02:00
Rebase: rebaseCommands,
2022-01-02 01:34:33 +02:00
Remote: remoteCommands,
2022-01-07 11:36:11 +02:00
Stash: stashCommands,
Status: statusCommands,
Submodule: submoduleCommands,
2022-01-02 01:34:33 +02:00
Sync: syncCommands,
2022-01-07 11:36:11 +02:00
Tag: tagCommands,
2022-01-19 09:32:27 +02:00
Bisect: bisectCommands,
2022-01-07 11:36:11 +02:00
WorkingTree: workingTreeCommands,
2022-01-02 01:34:33 +02:00
Loaders: Loaders{
BranchLoader: branchLoader,
CommitFileLoader: commitFileLoader,
CommitLoader: commitLoader,
FileLoader: fileLoader,
ReflogCommitLoader: reflogCommitLoader,
RemoteLoader: remoteLoader,
StashLoader: stashLoader,
TagLoader: tagLoader,
2022-01-02 01:34:33 +02:00
},
}
}
2020-09-29 12:03:39 +02: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 00:47:14 +02:00
}
2018-08-12 11:50:55 +02:00
2020-09-29 12:03:39 +02:00
return nil
2018-11-29 18:57:28 +02:00
}
2020-09-29 12:03:39 +02: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 13:18:38 +02:00
2020-09-29 12:03:39 +02:00
for {
_, err := stat(".git")
2020-08-11 13:18:38 +02:00
2020-09-29 12:03:39 +02:00
if err == nil {
return nil
2018-11-25 14:15:36 +02:00
}
2018-08-12 11:50:55 +02:00
2020-09-29 12:03:39 +02:00
if !os.IsNotExist(err) {
return utils.WrapError(err)
2020-03-26 11:29:35 +02:00
}
2020-09-29 12:03:39 +02:00
if err = chdir(".."); err != nil {
return utils.WrapError(err)
2019-02-20 10:47:01 +02:00
}
2021-03-30 13:17:42 +02: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 10:47:01 +02:00
}
2018-08-12 12:22:20 +02:00
}
2020-09-29 12:03:39 +02: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 09:52:17 +02:00
2020-09-29 12:03:39 +02:00
if l.Mode()&os.ModeSymlink == 0 {
return path, nil
2020-08-07 09:52:17 +02:00
}
2020-09-29 12:03:39 +02:00
return filepath.EvalSymlinks(path)
2020-08-07 09:52:17 +02:00
}
2022-04-07 18:45:08 +02:00
func setupRepository(openGitRepository func(string, *gogit.PlainOpenOptions) (*gogit.Repository, error), options gogit.PlainOpenOptions, gitConfigParseErrorStr string) (*gogit.Repository, error) {
2020-09-29 12:03:39 +02:00
unresolvedPath := env.GetGitDirEnv()
if unresolvedPath == "" {
var err error
unresolvedPath, err = os.Getwd()
2020-08-07 09:52:17 +02:00
if err != nil {
2020-09-29 12:03:39 +02:00
return nil, err
}
}
2020-09-29 12:03:39 +02:00
path, err := resolvePath(unresolvedPath)
2019-02-18 12:29:43 +02:00
if err != nil {
return nil, err
2019-02-18 12:29:43 +02:00
}
2022-04-07 18:45:08 +02:00
repository, err := openGitRepository(path, &options)
2019-02-24 04:51:52 +02:00
if err != nil {
2020-09-29 12:03:39 +02:00
if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) {
2020-10-04 02:00:48 +02:00
return nil, errors.New(gitConfigParseErrorStr)
}
2020-09-29 12:03:39 +02:00
return nil, err
}
2020-09-29 12:03:39 +02:00
return repository, err
}
2020-09-29 12:03:39 +02: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 12:03:39 +02:00
f, err := stat(".git")
if err != nil {
2020-09-29 12:03:39 +02:00
return "", err
}
2020-09-29 12:03:39 +02:00
if f.IsDir() {
return ".git", nil
}
2020-09-29 12:03:39 +02:00
fileBytes, err := readFile(".git")
if err != nil {
2020-09-29 12:03:39 +02:00
return "", err
}
2020-09-29 12:03:39 +02:00
fileContent := string(fileBytes)
if !strings.HasPrefix(fileContent, "gitdir: ") {
2022-04-07 18:45:08 +02:00
return "", errors.New(".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")
}
2020-09-29 12:03:39 +02:00
return strings.TrimSpace(strings.TrimPrefix(fileContent, "gitdir: ")), nil
}
2021-03-30 13:17:42 +02:00
func VerifyInGitRepo(osCommand *oscommands.OSCommand) error {
2022-01-05 02:57:32 +02:00
return osCommand.Cmd.New("git rev-parse --git-dir").DontLog().Run()
2021-10-20 13:21:16 +02:00
}