mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-05-22 10:15:43 +02:00
05b27c390d
This way we don't have to update the text and all translations every time we bump the version. Remove the year from the error text, it's cumbersome to update and I don't find it very important to have in the message. Also remove the invitation to file an issue; I don't find it very likely that we are going to relax the minimum git requirement again.
291 lines
7.2 KiB
Go
291 lines
7.2 KiB
Go
package app
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/go-errors/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/spf13/afero"
|
|
|
|
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
|
"github.com/jesseduffield/lazygit/pkg/common"
|
|
"github.com/jesseduffield/lazygit/pkg/config"
|
|
"github.com/jesseduffield/lazygit/pkg/constants"
|
|
"github.com/jesseduffield/lazygit/pkg/env"
|
|
"github.com/jesseduffield/lazygit/pkg/gui"
|
|
"github.com/jesseduffield/lazygit/pkg/i18n"
|
|
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
|
"github.com/jesseduffield/lazygit/pkg/logs"
|
|
"github.com/jesseduffield/lazygit/pkg/updates"
|
|
)
|
|
|
|
// App is the struct that's instantiated from within main.go and it manages
|
|
// bootstrapping and running the application.
|
|
type App struct {
|
|
*common.Common
|
|
closers []io.Closer
|
|
Config config.AppConfigurer
|
|
OSCommand *oscommands.OSCommand
|
|
Gui *gui.Gui
|
|
}
|
|
|
|
func Run(
|
|
config config.AppConfigurer,
|
|
common *common.Common,
|
|
startArgs appTypes.StartArgs,
|
|
) {
|
|
app, err := NewApp(config, startArgs.IntegrationTest, common)
|
|
|
|
if err == nil {
|
|
err = app.Run(startArgs)
|
|
}
|
|
|
|
if err != nil {
|
|
if errorMessage, known := knownError(common.Tr, err); known {
|
|
log.Fatal(errorMessage)
|
|
}
|
|
newErr := errors.Wrap(err, 0)
|
|
stackTrace := newErr.ErrorStack()
|
|
app.Log.Error(stackTrace)
|
|
|
|
log.Fatalf("%s: %s\n\n%s", common.Tr.ErrorOccurred, constants.Links.Issues, stackTrace)
|
|
}
|
|
}
|
|
|
|
func NewCommon(config config.AppConfigurer) (*common.Common, error) {
|
|
userConfig := config.GetUserConfig()
|
|
appState := config.GetAppState()
|
|
log := newLogger(config)
|
|
// Initialize with English for the time being; the real translation set for
|
|
// the configured language will be read after reading the user config
|
|
tr := i18n.EnglishTranslationSet()
|
|
|
|
cmn := &common.Common{
|
|
Log: log,
|
|
Tr: tr,
|
|
AppState: appState,
|
|
Debug: config.GetDebug(),
|
|
Fs: afero.NewOsFs(),
|
|
}
|
|
cmn.SetUserConfig(userConfig)
|
|
return cmn, nil
|
|
}
|
|
|
|
func newLogger(cfg config.AppConfigurer) *logrus.Entry {
|
|
if cfg.GetDebug() {
|
|
logPath, err := config.LogPath()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return logs.NewDevelopmentLogger(logPath)
|
|
}
|
|
|
|
return logs.NewProductionLogger()
|
|
}
|
|
|
|
// NewApp bootstrap a new application
|
|
func NewApp(config config.AppConfigurer, test integrationTypes.IntegrationTest, common *common.Common) (*App, error) {
|
|
app := &App{
|
|
closers: []io.Closer{},
|
|
Config: config,
|
|
Common: common,
|
|
}
|
|
|
|
app.OSCommand = oscommands.NewOSCommand(common, config, oscommands.GetPlatform(), oscommands.NewNullGuiIO(app.Log))
|
|
|
|
updater, err := updates.NewUpdater(common, config, app.OSCommand)
|
|
if err != nil {
|
|
return app, err
|
|
}
|
|
|
|
dirName, err := os.Getwd()
|
|
if err != nil {
|
|
return app, err
|
|
}
|
|
|
|
gitVersion, err := app.validateGitVersion()
|
|
if err != nil {
|
|
return app, err
|
|
}
|
|
|
|
// If we're not in a repo, GetRepoPaths will return an error. The error is moot for us
|
|
// at this stage, since we'll try to init a new repo in setupRepo(), below
|
|
repoPaths, err := git_commands.GetRepoPaths(app.OSCommand.Cmd, gitVersion)
|
|
if err != nil {
|
|
common.Log.Infof("Error getting repo paths: %v", err)
|
|
}
|
|
|
|
showRecentRepos, err := app.setupRepo(repoPaths)
|
|
if err != nil {
|
|
return app, err
|
|
}
|
|
|
|
// used for testing purposes
|
|
if os.Getenv("SHOW_RECENT_REPOS") == "true" {
|
|
showRecentRepos = true
|
|
}
|
|
|
|
app.Gui, err = gui.NewGui(common, config, gitVersion, updater, showRecentRepos, dirName, test)
|
|
if err != nil {
|
|
return app, err
|
|
}
|
|
return app, nil
|
|
}
|
|
|
|
const minGitVersionStr = "2.32.0"
|
|
|
|
func minGitVersionErrorMessage(tr *i18n.TranslationSet) string {
|
|
return fmt.Sprintf(tr.MinGitVersionError, minGitVersionStr)
|
|
}
|
|
|
|
func (app *App) validateGitVersion() (*git_commands.GitVersion, error) {
|
|
version, err := git_commands.GetGitVersion(app.OSCommand)
|
|
// if we get an error anywhere here we'll show the same status
|
|
minVersionError := errors.New(minGitVersionErrorMessage(app.Tr))
|
|
if err != nil {
|
|
return nil, minVersionError
|
|
}
|
|
|
|
minRequiredVersion, _ := git_commands.ParseGitVersion(minGitVersionStr)
|
|
if version.IsOlderThanVersion(minRequiredVersion) {
|
|
return nil, minVersionError
|
|
}
|
|
|
|
return version, nil
|
|
}
|
|
|
|
func isDirectoryAGitRepository(dir string) (bool, error) {
|
|
info, err := os.Stat(filepath.Join(dir, ".git"))
|
|
return info != nil, err
|
|
}
|
|
|
|
func openRecentRepo(app *App) bool {
|
|
for _, repoDir := range app.Config.GetAppState().RecentRepos {
|
|
if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo {
|
|
if err := os.Chdir(repoDir); err == nil {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (app *App) setupRepo(
|
|
repoPaths *git_commands.RepoPaths,
|
|
) (bool, error) {
|
|
if env.GetGitDirEnv() != "" {
|
|
// we've been given the git dir directly. Skip setup
|
|
return false, nil
|
|
}
|
|
|
|
// if we are not in a git repo, we ask if we want to `git init`
|
|
if repoPaths == nil {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if isRepo, err := isDirectoryAGitRepository(cwd); isRepo {
|
|
return false, err
|
|
}
|
|
|
|
var shouldInitRepo bool
|
|
initialBranchArg := ""
|
|
switch app.UserConfig().NotARepository {
|
|
case "prompt":
|
|
// Offer to initialize a new repository in current directory.
|
|
fmt.Print(app.Tr.CreateRepo)
|
|
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
|
|
shouldInitRepo = (strings.Trim(response, " \r\n") == "y")
|
|
if shouldInitRepo {
|
|
// 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 {
|
|
initialBranchArg += "--initial-branch=" + trimmedResponse
|
|
}
|
|
}
|
|
case "create":
|
|
shouldInitRepo = true
|
|
case "skip":
|
|
shouldInitRepo = false
|
|
case "quit":
|
|
fmt.Fprintln(os.Stderr, app.Tr.NotARepository)
|
|
os.Exit(1)
|
|
default:
|
|
fmt.Fprintln(os.Stderr, app.Tr.IncorrectNotARepository)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if shouldInitRepo {
|
|
args := []string{"git", "init"}
|
|
if initialBranchArg != "" {
|
|
args = append(args, initialBranchArg)
|
|
}
|
|
if err := app.OSCommand.Cmd.New(args).Run(); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if we have a recent repo we can open
|
|
for _, repoDir := range app.Config.GetAppState().RecentRepos {
|
|
if isRepo, _ := isDirectoryAGitRepository(repoDir); isRepo {
|
|
if err := os.Chdir(repoDir); err == nil {
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Fprintln(os.Stderr, app.Tr.NoRecentRepositories)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Run this afterward so that the previous repo creation steps can run without this interfering
|
|
if repoPaths.IsBareRepo() {
|
|
|
|
fmt.Print(app.Tr.BareRepo)
|
|
|
|
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
|
|
|
|
if shouldOpenRecent := strings.Trim(response, " \r\n") == "y"; !shouldOpenRecent {
|
|
os.Exit(0)
|
|
}
|
|
|
|
if didOpenRepo := openRecentRepo(app); didOpenRepo {
|
|
return true, nil
|
|
}
|
|
|
|
fmt.Println(app.Tr.NoRecentRepositories)
|
|
os.Exit(1)
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (app *App) Run(startArgs appTypes.StartArgs) error {
|
|
err := app.Gui.RunAndHandleError(startArgs)
|
|
return err
|
|
}
|
|
|
|
// Close closes any resources
|
|
func (app *App) Close() error {
|
|
for _, closer := range app.closers {
|
|
if err := closer.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|