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

254 lines
6.7 KiB
Go
Raw Normal View History

package commands
import (
"io/ioutil"
"os"
"path/filepath"
2018-08-12 11:50:55 +02:00
"strings"
"time"
2019-02-18 12:29:43 +02:00
"github.com/go-errors/errors"
2020-10-06 11:50:54 +02:00
gogit "github.com/jesseduffield/go-git/v5"
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"
2019-02-18 12:29:43 +02:00
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/i18n"
2018-08-12 13:04:47 +02:00
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus"
)
// this takes something like:
// * (HEAD detached at 264fc6f5)
// remotes
// and returns '264fc6f5' as the second match
const CurrentBranchNameRegex = `(?m)^\*.*?([^ ]*?)\)?$`
// GitCommand is our main git interface
type GitCommand struct {
Log *logrus.Entry
OSCommand *oscommands.OSCommand
Repo *gogit.Repository
2020-10-04 02:00:48 +02:00
Tr *i18n.TranslationSet
Config config.AppConfigurer
DotGitDir string
onSuccessfulContinue func() error
2020-08-15 03:18:40 +02:00
PatchManager *patch.PatchManager
2021-10-23 00:52:19 +02:00
GitConfig git_config.IGitConfig
// Push to current determines whether the user has configured to push to the remote branch of the same name as the current or not
PushToCurrent bool
}
// NewGitCommand it runs git commands
2021-10-23 00:52:19 +02:00
func NewGitCommand(
log *logrus.Entry,
osCommand *oscommands.OSCommand,
tr *i18n.TranslationSet,
config config.AppConfigurer,
gitConfig git_config.IGitConfig,
) (*GitCommand, error) {
var repo *gogit.Repository
2021-10-23 00:52:19 +02:00
pushToCurrent := gitConfig.Get("push.default") == "current"
2020-09-27 07:36:04 +02:00
if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil {
return nil, err
}
2021-10-23 00:52:19 +02:00
var err error
2020-10-04 02:00:48 +02:00
if repo, err = setupRepository(gogit.PlainOpen, tr.GitconfigParseErr); err != nil {
2020-09-27 07:36:04 +02:00
return nil, err
}
2019-05-12 09:04:32 +02:00
dotGitDir, err := findDotGitDir(os.Stat, ioutil.ReadFile)
if err != nil {
return nil, err
}
2019-11-05 09:10:47 +02:00
gitCommand := &GitCommand{
2021-10-23 00:52:19 +02:00
Log: log,
OSCommand: osCommand,
Tr: tr,
Repo: repo,
Config: config,
DotGitDir: dotGitDir,
PushToCurrent: pushToCurrent,
GitConfig: gitConfig,
2019-11-05 09:10:47 +02:00
}
gitCommand.PatchManager = patch.NewPatchManager(log, gitCommand.ApplyPatch, gitCommand.ShowFileDiff)
2019-11-05 09:10:47 +02:00
return gitCommand, nil
}
func (c *GitCommand) WithSpan(span string) *GitCommand {
2021-04-10 08:25:45 +02:00
// sometimes .WithSpan(span) will be called where span actually is empty, in
// which case we don't need to log anything so we can just return early here
// with the original struct
if span == "" {
return c
}
newGitCommand := &GitCommand{}
*newGitCommand = *c
newGitCommand.OSCommand = c.OSCommand.WithSpan(span)
// NOTE: unlike the other things here which create shallow clones, this will
// actually update the PatchManager on the original struct to have the new span.
// This means each time we call ApplyPatch in PatchManager, we need to ensure
// we've called .WithSpan() ahead of time with the new span value
newGitCommand.PatchManager.ApplyPatch = newGitCommand.ApplyPatch
return newGitCommand
}
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
}
2020-10-04 02:00:48 +02:00
func setupRepository(openGitRepository func(string) (*gogit.Repository, error), 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
}
2020-09-29 12:03:39 +02:00
repository, err := openGitRepository(path)
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: ") {
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 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 {
return osCommand.RunCommand("git rev-parse --git-dir")
}
func (c *GitCommand) RunCommand(formatString string, formatArgs ...interface{}) error {
_, err := c.RunCommandWithOutput(formatString, formatArgs...)
return err
}
func (c *GitCommand) RunCommandWithOutput(formatString string, formatArgs ...interface{}) (string, error) {
// TODO: have this retry logic in other places we run the command
waitTime := 50 * time.Millisecond
retryCount := 5
attempt := 0
for {
output, err := c.OSCommand.RunCommandWithOutput(formatString, formatArgs...)
if err != nil {
// if we have an error based on the index lock, we should wait a bit and then retry
if strings.Contains(output, ".git/index.lock") {
c.Log.Error(output)
c.Log.Info("index.lock prevented command from running. Retrying command after a small wait")
attempt++
time.Sleep(waitTime)
if attempt < retryCount {
continue
}
}
}
return output, err
}
}
2021-10-20 13:21:16 +02:00
func (c *GitCommand) NewCmdObjFromStr(cmdStr string) oscommands.ICmdObj {
return c.OSCommand.NewCmdObjFromStr(cmdStr).AddEnvVars("GIT_OPTIONAL_LOCKS=0")
}