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

340 lines
8.0 KiB
Go
Raw Normal View History

package app
import (
"bufio"
"errors"
"fmt"
2021-10-23 00:52:19 +02:00
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/aybabtme/humanlog"
"github.com/jesseduffield/lazygit/pkg/commands"
2021-10-23 00:52:19 +02:00
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/env"
2018-08-13 12:26:02 +02:00
"github.com/jesseduffield/lazygit/pkg/gui"
"github.com/jesseduffield/lazygit/pkg/i18n"
2018-08-19 15:28:29 +02:00
"github.com/jesseduffield/lazygit/pkg/updates"
2018-08-25 07:55:49 +02:00
"github.com/sirupsen/logrus"
)
// App struct
type App struct {
*common.Common
closers []io.Closer
Config config.AppConfigurer
OSCommand *oscommands.OSCommand
Gui *gui.Gui
Updater *updates.Updater // may only need this on the Gui
ClientContext string
2018-08-13 13:16:21 +02:00
}
type errorMapping struct {
originalError string
newError string
}
2022-01-08 06:46:35 +02:00
func newProductionLogger() *logrus.Logger {
2018-08-13 13:16:21 +02:00
log := logrus.New()
2018-08-26 07:46:18 +02:00
log.Out = ioutil.Discard
2019-03-16 01:37:31 +02:00
log.SetLevel(logrus.ErrorLevel)
2018-08-26 07:46:18 +02:00
return log
}
2019-03-16 01:37:31 +02:00
func getLogLevel() logrus.Level {
strLevel := os.Getenv("LOG_LEVEL")
level, err := logrus.ParseLevel(strLevel)
if err != nil {
return logrus.DebugLevel
}
return level
}
2022-01-08 06:46:35 +02:00
func newDevelopmentLogger() *logrus.Logger {
2020-10-03 06:54:55 +02:00
logger := logrus.New()
logger.SetLevel(getLogLevel())
logPath, err := config.LogPath()
if err != nil {
log.Fatal(err)
}
file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
2018-08-13 13:16:21 +02:00
if err != nil {
2022-01-09 05:09:15 +02:00
log.Fatalf("Unable to log to log file: %v", err)
2018-08-13 13:16:21 +02:00
}
2020-10-03 06:54:55 +02:00
logger.SetOutput(file)
return logger
}
2018-08-26 07:46:18 +02:00
func newLogger(config config.AppConfigurer) *logrus.Entry {
var log *logrus.Logger
2019-02-18 12:29:43 +02:00
if config.GetDebug() || os.Getenv("DEBUG") == "TRUE" {
2022-01-08 06:46:35 +02:00
log = newDevelopmentLogger()
2018-08-26 07:46:18 +02:00
} else {
2022-01-08 06:46:35 +02:00
log = newProductionLogger()
2018-08-26 07:46:18 +02:00
}
2018-12-07 09:52:31 +02:00
// highly recommended: tail -f development.log | humanlog
// https://github.com/aybabtme/humanlog
log.Formatter = &logrus.JSONFormatter{}
2018-08-26 07:46:18 +02:00
return log.WithFields(logrus.Fields{
"debug": config.GetDebug(),
"version": config.GetVersion(),
"commit": config.GetCommit(),
"buildDate": config.GetBuildDate(),
})
}
// NewApp bootstrap a new application
2020-03-29 01:11:15 +02:00
func NewApp(config config.AppConfigurer, filterPath string) (*App, error) {
2021-12-29 03:03:35 +02:00
userConfig := config.GetUserConfig()
app := &App{
closers: []io.Closer{},
Config: config,
}
var err error
log := newLogger(config)
2021-12-29 03:03:35 +02:00
tr, err := i18n.NewTranslationSetFromConfig(log, userConfig.Gui.Language)
if err != nil {
return app, err
}
app.Common = &common.Common{
Log: log,
Tr: tr,
2021-12-29 03:03:35 +02:00
UserConfig: userConfig,
Debug: config.GetDebug(),
}
2019-02-18 12:29:43 +02:00
// if we are being called in 'demon' mode, we can just return here
app.ClientContext = os.Getenv("LAZYGIT_CLIENT_COMMAND")
if app.ClientContext != "" {
2019-02-18 12:29:43 +02:00
return app, nil
}
2022-01-02 01:34:33 +02:00
app.OSCommand = oscommands.NewOSCommand(app.Common, oscommands.GetPlatform(), oscommands.NewNullGuiIO(log))
2019-02-18 12:29:43 +02:00
2021-12-29 03:03:35 +02:00
app.Updater, err = updates.NewUpdater(app.Common, config, app.OSCommand)
if err != nil {
2018-08-18 11:43:58 +02:00
return app, err
}
showRecentRepos, err := app.setupRepo()
if err != nil {
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-01-02 01:34:33 +02:00
app.Gui, err = gui.NewGui(app.Common, config, gitConfig, app.Updater, filterPath, showRecentRepos)
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
}
return app, nil
}
func (app *App) validateGitVersion() error {
2021-12-29 05:33:38 +02:00
output, err := app.OSCommand.Cmd.New("git --version").RunWithOutput()
// 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)
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]
majorVersion, err := strconv.Atoi(gitVersion[0:1])
if err != nil {
2020-09-18 13:00:03 +02:00
return false
}
if majorVersion < 2 {
2020-09-18 13:00:03 +02:00
return false
}
2020-09-18 13:00:03 +02:00
return true
}
func (app *App) setupRepo() (bool, error) {
if err := app.validateGitVersion(); err != nil {
return false, err
}
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
}
// 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 {
return false, err
}
2020-05-28 06:20:13 +02:00
info, _ := os.Stat(filepath.Join(cwd, ".git"))
if info != nil && info.IsDir() {
return false, err // Current directory appears to be a git repository.
2020-05-28 06:20:13 +02:00
}
shouldInitRepo := true
2021-12-29 03:03:35 +02:00
notARepository := app.UserConfig.NotARepository
if notARepository == "prompt" {
// Offer to initialize a new repository in current directory.
fmt.Print(app.Tr.CreateRepo)
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
if strings.Trim(response, " \n") != "y" {
shouldInitRepo = false
}
} else if notARepository == "skip" {
shouldInitRepo = false
}
if !shouldInitRepo {
// check if we have a recent repo we can open
recentRepos := app.Config.GetAppState().RecentRepos
if len(recentRepos) > 0 {
var err error
// try opening each repo in turn, in case any have been deleted
for _, repoDir := range recentRepos {
if err = os.Chdir(repoDir); err == nil {
return true, nil
}
}
return false, err
}
os.Exit(1)
}
2021-12-29 05:33:38 +02:00
if err := app.OSCommand.Cmd.New("git init").Run(); err != nil {
return false, err
}
}
2021-03-30 13:17:42 +02:00
return false, nil
}
func (app *App) Run() error {
if app.ClientContext == "INTERACTIVE_REBASE" {
2019-02-18 12:29:43 +02:00
return app.Rebase()
}
if app.ClientContext == "EXIT_IMMEDIATELY" {
os.Exit(0)
}
err := app.Gui.RunAndHandleError()
return err
}
2020-09-27 07:36:04 +02:00
func gitDir() string {
dir := env.GetGitDirEnv()
2020-09-27 07:36:04 +02:00
if dir == "" {
return ".git"
}
return dir
}
// Rebase contains logic for when we've been run in demon mode, meaning we've
// given lazygit as a command for git to call e.g. to edit a file
2019-02-18 12:29:43 +02:00
func (app *App) Rebase() error {
app.Log.Info("Lazygit invoked as interactive rebase demon")
app.Log.Info("args: ", os.Args)
if strings.HasSuffix(os.Args[1], "git-rebase-todo") {
2019-03-02 04:22:02 +02:00
if err := ioutil.WriteFile(os.Args[1], []byte(os.Getenv("LAZYGIT_REBASE_TODO")), 0644); err != nil {
return err
}
2020-09-27 07:36:04 +02:00
} else if strings.HasSuffix(os.Args[1], filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test
// if we are rebasing and squashing, we'll see a COMMIT_EDITMSG
// but in this case we don't need to edit it, so we'll just return
} else {
app.Log.Info("Lazygit demon did not match on any use cases")
}
2019-02-18 12:29:43 +02:00
return nil
}
// Close closes any resources
func (app *App) Close() error {
for _, closer := range app.closers {
err := closer.Close()
if err != nil {
return err
}
}
return nil
}
// KnownError takes an error and tells us whether it's an error that we know about where we can print a nicely formatted version of it rather than panicking with a stack trace
func (app *App) KnownError(err error) (string, bool) {
errorMessage := err.Error()
2020-10-04 02:00:48 +02:00
knownErrorMessages := []string{app.Tr.MinGitVersionError}
for _, message := range knownErrorMessages {
if errorMessage == message {
return message, true
}
}
mappings := []errorMapping{
{
originalError: "fatal: not a git repository",
2020-10-04 02:00:48 +02:00
newError: app.Tr.NotARepository,
},
}
for _, mapping := range mappings {
if strings.Contains(errorMessage, mapping.originalError) {
return mapping.newError, true
}
}
return "", false
}
func TailLogs() {
logFilePath, err := config.LogPath()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Tailing log file %s\n\n", logFilePath)
opts := humanlog.DefaultOptions
opts.Truncates = false
_, err = os.Stat(logFilePath)
if err != nil {
if os.IsNotExist(err) {
log.Fatal("Log file does not exist. Run `lazygit --debug` first to create the log file")
}
log.Fatal(err)
}
TailLogsForPlatform(logFilePath, opts)
}