2018-08-12 11:31:27 +02:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
2019-03-16 02:31:09 +02:00
|
|
|
"bufio"
|
2019-03-02 11:32:16 +02:00
|
|
|
"fmt"
|
2021-10-23 00:52:19 +02:00
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2022-05-07 07:23:08 +02:00
|
|
|
"github.com/go-errors/errors"
|
|
|
|
|
2022-03-20 01:19:14 +02:00
|
|
|
"github.com/jesseduffield/generics/slices"
|
2018-08-12 11:31:27 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
2021-10-23 00:52:19 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
|
2020-09-29 11:10:57 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
2021-12-29 02:37:15 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/common"
|
2018-08-12 11:31:27 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/config"
|
2022-05-07 07:23:08 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/constants"
|
2020-09-27 08:17:26 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/env"
|
2018-08-13 12:26:02 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui"
|
2022-06-09 11:52:08 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
2018-08-14 15:26:25 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/i18n"
|
2018-08-19 15:28:29 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/updates"
|
2018-08-12 11:31:27 +02:00
|
|
|
)
|
|
|
|
|
2022-05-07 07:23:08 +02:00
|
|
|
// App is the struct that's instantiated from within main.go and it manages
|
|
|
|
// bootstrapping and running the application.
|
2018-08-12 11:31:27 +02:00
|
|
|
type App struct {
|
2021-12-29 02:37:15 +02:00
|
|
|
*common.Common
|
2022-05-07 07:23:08 +02:00
|
|
|
closers []io.Closer
|
|
|
|
Config config.AppConfigurer
|
|
|
|
OSCommand *oscommands.OSCommand
|
|
|
|
Gui *gui.Gui
|
|
|
|
Updater *updates.Updater // may only need this on the Gui
|
2019-07-07 20:15:11 +02:00
|
|
|
}
|
|
|
|
|
2022-06-09 11:52:08 +02:00
|
|
|
func Run(config config.AppConfigurer, common *common.Common, startArgs types.StartArgs) {
|
2022-05-07 07:23:08 +02:00
|
|
|
app, err := NewApp(config, common)
|
2018-08-26 07:46:18 +02:00
|
|
|
|
2022-05-07 07:23:08 +02:00
|
|
|
if err == nil {
|
2022-06-09 11:52:08 +02:00
|
|
|
err = app.Run(startArgs)
|
2019-03-16 01:37:31 +02:00
|
|
|
}
|
|
|
|
|
2018-08-13 13:16:21 +02:00
|
|
|
if err != nil {
|
2022-05-07 07:23:08 +02:00
|
|
|
if errorMessage, known := knownError(common.Tr, err); known {
|
|
|
|
log.Fatal(errorMessage)
|
|
|
|
}
|
|
|
|
newErr := errors.Wrap(err, 0)
|
|
|
|
stackTrace := newErr.ErrorStack()
|
|
|
|
app.Log.Error(stackTrace)
|
2018-08-12 11:31:27 +02:00
|
|
|
|
2022-05-18 13:33:35 +02:00
|
|
|
log.Fatalf("%s: %s\n\n%s", common.Tr.ErrorOccurred, constants.Links.Issues, stackTrace)
|
2018-08-26 07:46:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-07 07:23:08 +02:00
|
|
|
func NewCommon(config config.AppConfigurer) (*common.Common, error) {
|
2021-12-29 03:03:35 +02:00
|
|
|
userConfig := config.GetUserConfig()
|
|
|
|
|
2018-08-12 11:31:27 +02:00
|
|
|
var err error
|
2021-12-29 02:37:15 +02:00
|
|
|
log := newLogger(config)
|
2021-12-29 03:03:35 +02:00
|
|
|
tr, err := i18n.NewTranslationSetFromConfig(log, userConfig.Gui.Language)
|
2021-08-27 09:00:53 +02:00
|
|
|
if err != nil {
|
2022-05-07 07:23:08 +02:00
|
|
|
return nil, err
|
2021-08-27 09:00:53 +02:00
|
|
|
}
|
2018-08-14 14:12:07 +02:00
|
|
|
|
2022-05-07 07:23:08 +02:00
|
|
|
return &common.Common{
|
2021-12-29 02:37:15 +02:00
|
|
|
Log: log,
|
|
|
|
Tr: tr,
|
2021-12-29 03:03:35 +02:00
|
|
|
UserConfig: userConfig,
|
2021-12-29 02:37:15 +02:00
|
|
|
Debug: config.GetDebug(),
|
2022-05-07 07:23:08 +02:00
|
|
|
}, nil
|
|
|
|
}
|
2021-12-29 02:37:15 +02:00
|
|
|
|
2022-05-07 07:23:08 +02:00
|
|
|
// NewApp bootstrap a new application
|
|
|
|
func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
|
|
|
|
app := &App{
|
|
|
|
closers: []io.Closer{},
|
|
|
|
Config: config,
|
|
|
|
Common: common,
|
2019-02-18 12:29:43 +02:00
|
|
|
}
|
|
|
|
|
2022-05-07 07:23:08 +02:00
|
|
|
app.OSCommand = oscommands.NewOSCommand(common, config, oscommands.GetPlatform(), oscommands.NewNullGuiIO(app.Log))
|
2019-02-18 12:29:43 +02:00
|
|
|
|
2022-05-07 07:23:08 +02:00
|
|
|
var err error
|
|
|
|
app.Updater, err = updates.NewUpdater(common, config, app.OSCommand)
|
2018-08-12 11:31:27 +02:00
|
|
|
if err != nil {
|
2018-08-18 11:43:58 +02:00
|
|
|
return app, err
|
2018-08-12 11:31:27 +02:00
|
|
|
}
|
2019-03-16 02:31:09 +02:00
|
|
|
|
2022-03-15 15:12:26 +02:00
|
|
|
dirName, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return app, err
|
|
|
|
}
|
|
|
|
|
2020-08-16 14:49:37 +02:00
|
|
|
showRecentRepos, err := app.setupRepo()
|
|
|
|
if err != nil {
|
2019-03-16 02:31:09 +02:00
|
|
|
return app, err
|
|
|
|
}
|
|
|
|
|
2022-01-02 01:34:33 +02:00
|
|
|
gitConfig := git_config.NewStdCachedGitConfig(app.Log)
|
2020-09-27 07:36:04 +02:00
|
|
|
|
2022-05-07 07:23:08 +02:00
|
|
|
app.Gui, err = gui.NewGui(common, config, gitConfig, app.Updater, showRecentRepos, dirName)
|
2018-08-13 12:26:02 +02:00
|
|
|
if err != nil {
|
2018-08-18 11:43:58 +02:00
|
|
|
return app, err
|
2018-08-13 12:26:02 +02:00
|
|
|
}
|
2018-08-12 11:31:27 +02:00
|
|
|
return app, nil
|
|
|
|
}
|
|
|
|
|
2020-08-27 14:19:03 +02:00
|
|
|
func (app *App) validateGitVersion() error {
|
2021-12-29 05:33:38 +02:00
|
|
|
output, err := app.OSCommand.Cmd.New("git --version").RunWithOutput()
|
2020-08-27 14:19:03 +02:00
|
|
|
// if we get an error anywhere here we'll show the same status
|
2020-10-04 02:00:48 +02:00
|
|
|
minVersionError := errors.New(app.Tr.MinGitVersionError)
|
2020-08-27 14:19:03 +02:00
|
|
|
if err != nil {
|
|
|
|
return minVersionError
|
|
|
|
}
|
2020-09-18 13:00:03 +02:00
|
|
|
|
|
|
|
if isGitVersionValid(output) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return minVersionError
|
|
|
|
}
|
|
|
|
|
|
|
|
func isGitVersionValid(versionStr string) bool {
|
|
|
|
// output should be something like: 'git version 2.23.0 (blah)'
|
|
|
|
re := regexp.MustCompile(`[^\d]+([\d\.]+)`)
|
|
|
|
matches := re.FindStringSubmatch(versionStr)
|
|
|
|
|
|
|
|
if len(matches) == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
gitVersion := matches[1]
|
2020-08-27 14:19:03 +02:00
|
|
|
majorVersion, err := strconv.Atoi(gitVersion[0:1])
|
|
|
|
if err != nil {
|
2020-09-18 13:00:03 +02:00
|
|
|
return false
|
2020-08-27 14:19:03 +02:00
|
|
|
}
|
|
|
|
if majorVersion < 2 {
|
2020-09-18 13:00:03 +02:00
|
|
|
return false
|
2020-08-27 14:19:03 +02:00
|
|
|
}
|
|
|
|
|
2020-09-18 13:00:03 +02:00
|
|
|
return true
|
2020-08-27 14:19:03 +02:00
|
|
|
}
|
|
|
|
|
2022-06-09 15:57:54 +02:00
|
|
|
func isDirectoryAGitRepository(dir string) (bool, error) {
|
|
|
|
info, err := os.Stat(filepath.Join(dir, ".git"))
|
2022-06-17 21:20:37 +02:00
|
|
|
return info != nil, err
|
2022-06-09 15:57:54 +02:00
|
|
|
}
|
|
|
|
|
2022-07-30 00:55:34 +02:00
|
|
|
func isBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
|
|
|
|
res, err := osCommand.Cmd.New("git rev-parse --is-bare-repository").DontLog().RunWithOutput()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// The command returns output with a newline, so we need to strip
|
|
|
|
return strconv.ParseBool(strings.TrimSpace(res))
|
|
|
|
}
|
|
|
|
|
2020-08-16 14:49:37 +02:00
|
|
|
func (app *App) setupRepo() (bool, error) {
|
2020-08-27 14:19:03 +02:00
|
|
|
if err := app.validateGitVersion(); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2020-09-27 08:17:26 +02:00
|
|
|
if env.GetGitDirEnv() != "" {
|
2022-01-08 05:10:01 +02:00
|
|
|
// we've been given the git dir directly. We'll verify this dir when initializing our Git object
|
2020-09-27 07:36:04 +02:00
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2019-03-16 02:31:09 +02:00
|
|
|
// if we are not in a git repo, we ask if we want to `git init`
|
2021-03-30 13:17:42 +02:00
|
|
|
if err := commands.VerifyInGitRepo(app.OSCommand); err != nil {
|
2020-05-28 06:20:13 +02:00
|
|
|
cwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
2020-08-16 14:49:37 +02:00
|
|
|
return false, err
|
2019-03-16 02:31:09 +02:00
|
|
|
}
|
2022-07-30 00:55:34 +02:00
|
|
|
|
2022-06-09 15:57:54 +02:00
|
|
|
if isRepo, err := isDirectoryAGitRepository(cwd); isRepo {
|
|
|
|
return false, err
|
2020-05-28 06:20:13 +02:00
|
|
|
}
|
|
|
|
|
2020-11-24 13:21:11 +02:00
|
|
|
shouldInitRepo := true
|
2021-12-29 03:03:35 +02:00
|
|
|
notARepository := app.UserConfig.NotARepository
|
2022-06-30 13:53:58 +02:00
|
|
|
initialBranch := ""
|
2020-11-24 14:02:31 +02:00
|
|
|
if notARepository == "prompt" {
|
2020-11-24 13:21:11 +02:00
|
|
|
// Offer to initialize a new repository in current directory.
|
|
|
|
fmt.Print(app.Tr.CreateRepo)
|
|
|
|
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
|
2022-05-28 16:52:49 +02:00
|
|
|
if strings.Trim(response, " \r\n") != "y" {
|
2020-11-24 13:21:11 +02:00
|
|
|
shouldInitRepo = false
|
2022-06-30 13:53:58 +02:00
|
|
|
} else {
|
|
|
|
// Ask for the initial branch name
|
|
|
|
fmt.Print(app.Tr.InitialBranch)
|
|
|
|
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
|
|
|
|
if trimmedResponse := strings.Trim(response, " \r\n"); len(trimmedResponse) > 0 {
|
|
|
|
initialBranch += "--initial-branch=" + trimmedResponse
|
|
|
|
}
|
2020-11-24 13:21:11 +02:00
|
|
|
}
|
2020-11-24 14:02:31 +02:00
|
|
|
} else if notARepository == "skip" {
|
2020-11-24 13:21:11 +02:00
|
|
|
shouldInitRepo = false
|
|
|
|
}
|
|
|
|
|
|
|
|
if !shouldInitRepo {
|
2020-08-16 14:49:37 +02:00
|
|
|
// check if we have a recent repo we can open
|
2022-06-09 15:57:54 +02:00
|
|
|
for _, repoDir := range app.Config.GetAppState().RecentRepos {
|
|
|
|
if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo {
|
|
|
|
if err := os.Chdir(repoDir); err == nil {
|
2020-08-16 14:49:37 +02:00
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-08 16:01:30 +02:00
|
|
|
fmt.Println(app.Tr.NoRecentRepositories)
|
2019-03-16 02:31:09 +02:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
2022-06-30 13:53:58 +02:00
|
|
|
if err := app.OSCommand.Cmd.New("git init " + initialBranch).Run(); err != nil {
|
2020-08-16 14:49:37 +02:00
|
|
|
return false, err
|
2019-03-16 02:31:09 +02:00
|
|
|
}
|
|
|
|
}
|
2021-03-30 13:17:42 +02:00
|
|
|
|
2022-07-30 00:55:34 +02:00
|
|
|
// Run this afterward so that the previous repo creation steps can run without this interfering
|
|
|
|
if isBare, err := isBareRepo(app.OSCommand); isBare {
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2022-08-01 20:39:39 +02:00
|
|
|
fmt.Print(app.Tr.BareRepo)
|
|
|
|
|
|
|
|
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
|
|
|
|
shouldOpenRecent := strings.Trim(response, " \r\n") == "y"
|
|
|
|
|
|
|
|
if shouldOpenRecent {
|
|
|
|
for _, repoDir := range app.Config.GetAppState().RecentRepos {
|
|
|
|
if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo {
|
|
|
|
if err := os.Chdir(repoDir); err == nil {
|
|
|
|
return true, nil
|
2022-08-01 18:05:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-01 18:41:20 +02:00
|
|
|
|
2022-08-01 20:39:39 +02:00
|
|
|
fmt.Println(app.Tr.NoRecentRepositories)
|
|
|
|
os.Exit(1)
|
2022-07-30 00:55:34 +02:00
|
|
|
}
|
2022-08-01 20:39:39 +02:00
|
|
|
|
|
|
|
os.Exit(0)
|
2022-07-30 00:55:34 +02:00
|
|
|
}
|
|
|
|
|
2020-08-16 14:49:37 +02:00
|
|
|
return false, nil
|
2019-03-16 02:31:09 +02:00
|
|
|
}
|
|
|
|
|
2022-06-09 11:52:08 +02:00
|
|
|
func (app *App) Run(startArgs types.StartArgs) error {
|
|
|
|
err := app.Gui.RunAndHandleError(startArgs)
|
2019-07-07 20:15:11 +02:00
|
|
|
return err
|
2019-02-18 10:42:23 +02:00
|
|
|
}
|
|
|
|
|
2018-08-12 11:31:27 +02:00
|
|
|
// Close closes any resources
|
|
|
|
func (app *App) Close() error {
|
2022-03-20 01:19:14 +02:00
|
|
|
return slices.TryForEach(app.closers, func(closer io.Closer) error {
|
|
|
|
return closer.Close()
|
|
|
|
})
|
2018-08-12 11:31:27 +02:00
|
|
|
}
|