2022-08-09 12:27:44 +02:00
package app
import (
"bytes"
"fmt"
"log"
"os"
2023-07-30 11:33:20 +02:00
"os/exec"
2022-08-09 12:27:44 +02:00
"path/filepath"
"runtime"
2022-08-13 04:56:04 +02:00
"runtime/debug"
2022-08-09 12:27:44 +02:00
"strings"
2022-08-13 04:56:04 +02:00
"github.com/integrii/flaggy"
2022-08-09 12:27:44 +02:00
"github.com/jesseduffield/lazygit/pkg/app/daemon"
2022-08-09 13:27:12 +02:00
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
2022-08-09 12:27:44 +02:00
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/env"
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
2023-05-25 10:18:35 +02:00
"github.com/jesseduffield/lazygit/pkg/logs/tail"
2022-08-13 04:56:04 +02:00
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
2022-08-09 12:27:44 +02:00
"gopkg.in/yaml.v3"
)
2022-08-13 04:56:04 +02:00
type cliArgs struct {
2022-08-09 12:27:44 +02:00
RepoPath string
FilterPath string
GitArg string
PrintVersionInfo bool
Debug bool
TailLogs bool
PrintDefaultConfig bool
PrintConfigDir bool
UseConfigDir string
WorkTree string
GitDir string
CustomConfigFile string
}
type BuildInfo struct {
Commit string
Date string
Version string
BuildSource string
}
2022-08-13 04:56:04 +02:00
func Start ( buildInfo * BuildInfo , integrationTest integrationTypes . IntegrationTest ) {
cliArgs := parseCliArgsAndEnvVars ( )
mergeBuildInfo ( buildInfo )
2022-08-09 12:27:44 +02:00
if cliArgs . RepoPath != "" {
if cliArgs . WorkTree != "" || cliArgs . GitDir != "" {
log . Fatal ( "--path option is incompatible with the --work-tree and --git-dir options" )
}
absRepoPath , err := filepath . Abs ( cliArgs . RepoPath )
if err != nil {
log . Fatal ( err )
}
2022-09-12 17:18:42 +02:00
if isRepo , err := isDirectoryAGitRepository ( absRepoPath ) ; err != nil || ! isRepo {
log . Fatal ( absRepoPath + " is not a valid git repository." )
}
2022-08-09 12:27:44 +02:00
cliArgs . GitDir = filepath . Join ( absRepoPath , ".git" )
2023-08-07 14:08:12 +02:00
err = os . Chdir ( absRepoPath )
if err != nil {
log . Fatalf ( "Failed to change directory to %s: %v" , absRepoPath , err )
}
} else if cliArgs . WorkTree != "" {
env . SetWorkTreeEnv ( cliArgs . WorkTree )
if err := os . Chdir ( cliArgs . WorkTree ) ; err != nil {
log . Fatalf ( "Failed to change directory to %s: %v" , cliArgs . WorkTree , err )
}
2022-08-09 12:27:44 +02:00
}
if cliArgs . CustomConfigFile != "" {
os . Setenv ( "LG_CONFIG_FILE" , cliArgs . CustomConfigFile )
}
if cliArgs . UseConfigDir != "" {
os . Setenv ( "CONFIG_DIR" , cliArgs . UseConfigDir )
}
if cliArgs . GitDir != "" {
env . SetGitDirEnv ( cliArgs . GitDir )
}
if cliArgs . PrintVersionInfo {
2022-11-12 12:30:24 +02:00
gitVersion := getGitVersionInfo ( )
fmt . Printf ( "commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s, git version=%s\n" , buildInfo . Commit , buildInfo . Date , buildInfo . BuildSource , buildInfo . Version , runtime . GOOS , runtime . GOARCH , gitVersion )
2022-08-09 12:27:44 +02:00
os . Exit ( 0 )
}
if cliArgs . PrintDefaultConfig {
var buf bytes . Buffer
encoder := yaml . NewEncoder ( & buf )
err := encoder . Encode ( config . GetDefaultConfig ( ) )
if err != nil {
log . Fatal ( err . Error ( ) )
}
fmt . Printf ( "%s\n" , buf . String ( ) )
os . Exit ( 0 )
}
if cliArgs . PrintConfigDir {
fmt . Printf ( "%s\n" , config . ConfigDir ( ) )
os . Exit ( 0 )
}
if cliArgs . TailLogs {
2023-05-25 10:18:35 +02:00
logPath , err := config . LogPath ( )
if err != nil {
log . Fatal ( err . Error ( ) )
}
tail . TailLogs ( logPath )
2022-08-09 12:27:44 +02:00
os . Exit ( 0 )
}
tempDir , err := os . MkdirTemp ( "" , "lazygit-*" )
if err != nil {
log . Fatal ( err . Error ( ) )
}
defer os . RemoveAll ( tempDir )
appConfig , err := config . NewAppConfig ( "lazygit" , buildInfo . Version , buildInfo . Commit , buildInfo . Date , buildInfo . BuildSource , cliArgs . Debug , tempDir )
if err != nil {
log . Fatal ( err . Error ( ) )
}
2022-08-09 13:27:12 +02:00
if integrationTest != nil {
integrationTest . SetupConfig ( appConfig )
2022-08-09 12:27:44 +02:00
}
common , err := NewCommon ( appConfig )
if err != nil {
log . Fatal ( err )
}
if daemon . InDaemonMode ( ) {
daemon . Handle ( common )
return
}
parsedGitArg := parseGitArg ( cliArgs . GitArg )
2022-08-09 13:27:12 +02:00
Run ( appConfig , common , appTypes . NewStartArgs ( cliArgs . FilterPath , parsedGitArg , integrationTest ) )
2022-08-09 12:27:44 +02:00
}
2022-08-13 04:56:04 +02:00
func parseCliArgsAndEnvVars ( ) * cliArgs {
flaggy . DefaultParser . ShowVersionWithVersionFlag = false
repoPath := ""
flaggy . String ( & repoPath , "p" , "path" , "Path of git repo. (equivalent to --work-tree=<path> --git-dir=<path>/.git/)" )
filterPath := ""
flaggy . String ( & filterPath , "f" , "filter" , "Path to filter on in `git log -- <path>`. When in filter mode, the commits, reflog, and stash are filtered based on the given path, and some operations are restricted" )
gitArg := ""
flaggy . AddPositionalValue ( & gitArg , "git-arg" , 1 , false , "Panel to focus upon opening lazygit. Accepted values (based on git terminology): status, branch, log, stash. Ignored if --filter arg is passed." )
printVersionInfo := false
flaggy . Bool ( & printVersionInfo , "v" , "version" , "Print the current version" )
debug := false
flaggy . Bool ( & debug , "d" , "debug" , "Run in debug mode with logging (see --logs flag below). Use the LOG_LEVEL env var to set the log level (debug/info/warn/error)" )
tailLogs := false
flaggy . Bool ( & tailLogs , "l" , "logs" , "Tail lazygit logs (intended to be used when `lazygit --debug` is called in a separate terminal tab)" )
printDefaultConfig := false
flaggy . Bool ( & printDefaultConfig , "c" , "config" , "Print the default config" )
printConfigDir := false
flaggy . Bool ( & printConfigDir , "cd" , "print-config-dir" , "Print the config directory" )
useConfigDir := ""
flaggy . String ( & useConfigDir , "ucd" , "use-config-dir" , "override default config directory with provided directory" )
workTree := ""
flaggy . String ( & workTree , "w" , "work-tree" , "equivalent of the --work-tree git argument" )
gitDir := ""
flaggy . String ( & gitDir , "g" , "git-dir" , "equivalent of the --git-dir git argument" )
customConfigFile := ""
flaggy . String ( & customConfigFile , "ucf" , "use-config-file" , "Comma separated list to custom config file(s)" )
flaggy . Parse ( )
if os . Getenv ( "DEBUG" ) == "TRUE" {
debug = true
}
return & cliArgs {
RepoPath : repoPath ,
FilterPath : filterPath ,
GitArg : gitArg ,
PrintVersionInfo : printVersionInfo ,
Debug : debug ,
TailLogs : tailLogs ,
PrintDefaultConfig : printDefaultConfig ,
PrintConfigDir : printConfigDir ,
UseConfigDir : useConfigDir ,
WorkTree : workTree ,
GitDir : gitDir ,
CustomConfigFile : customConfigFile ,
}
}
2022-08-09 13:27:12 +02:00
func parseGitArg ( gitArg string ) appTypes . GitArg {
typedArg := appTypes . GitArg ( gitArg )
2022-08-09 12:27:44 +02:00
// using switch so that linter catches when a new git arg value is defined but not handled here
switch typedArg {
2022-08-09 13:27:12 +02:00
case appTypes . GitArgNone , appTypes . GitArgStatus , appTypes . GitArgBranch , appTypes . GitArgLog , appTypes . GitArgStash :
2022-08-09 12:27:44 +02:00
return typedArg
}
permittedValues := [ ] string {
2022-08-09 13:27:12 +02:00
string ( appTypes . GitArgStatus ) ,
string ( appTypes . GitArgBranch ) ,
string ( appTypes . GitArgLog ) ,
string ( appTypes . GitArgStash ) ,
2022-08-09 12:27:44 +02:00
}
log . Fatalf ( "Invalid git arg value: '%s'. Must be one of the following values: %s. e.g. 'lazygit status'. See 'lazygit --help'." ,
gitArg ,
strings . Join ( permittedValues , ", " ) ,
)
panic ( "unreachable" )
}
2022-08-13 04:56:04 +02:00
// the buildInfo struct we get passed in is based on what's baked into the lazygit
// binary via the LDFLAGS argument. Some lazygit distributions will make use of these
// arguments and some will not. Go recently started baking in build info
// into the binary by default e.g. the git commit hash. So in this function
// we merge the two together, giving priority to the stuff set by LDFLAGS.
// Note: this mutates the argument passed in
func mergeBuildInfo ( buildInfo * BuildInfo ) {
// if the version has already been set by build flags then we'll honour that.
// chances are it's something like v0.31.0 which is more informative than a
// commit hash.
if buildInfo . Version != "" {
return
}
buildInfo . Version = "unversioned"
goBuildInfo , ok := debug . ReadBuildInfo ( )
if ! ok {
return
}
revision , ok := lo . Find ( goBuildInfo . Settings , func ( setting debug . BuildSetting ) bool {
return setting . Key == "vcs.revision"
} )
if ok {
buildInfo . Commit = revision . Value
// if lazygit was built from source we'll show the version as the
// abbreviated commit hash
buildInfo . Version = utils . ShortSha ( revision . Value )
}
// if version hasn't been set we assume that neither has the date
time , ok := lo . Find ( goBuildInfo . Settings , func ( setting debug . BuildSetting ) bool {
return setting . Key == "vcs.time"
} )
if ok {
buildInfo . Date = time . Value
}
}
2022-11-13 15:57:12 +02:00
2022-11-12 12:30:24 +02:00
func getGitVersionInfo ( ) string {
2023-07-30 11:33:20 +02:00
cmd := exec . Command ( "git" , "--version" )
2022-11-12 12:30:24 +02:00
stdout , _ := cmd . Output ( )
gitVersion := strings . Trim ( strings . TrimPrefix ( string ( stdout ) , "git version " ) , " \r\n" )
return gitVersion
}