mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-10-08 22:52:12 +02:00
In 3a9dbf7341
we created a global /tmp/lazygit/ folder that contains the temp
directories of each running instance, to avoid polluting /tmp with multiple
folders. The problem with that approach was that the folder was created with 700
permissions, so if multiple users were using lazygit on the same machine (e.g. a
server), all users except the first one would get fatal errors on startup.
Fix this by creating temp folders containing the user's uid.
330 lines
9.5 KiB
Go
330 lines
9.5 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
_ "net/http/pprof"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"path/filepath"
|
|
"runtime"
|
|
"runtime/debug"
|
|
"strings"
|
|
|
|
"github.com/integrii/flaggy"
|
|
"github.com/jesseduffield/lazygit/pkg/app/daemon"
|
|
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
|
|
"github.com/jesseduffield/lazygit/pkg/config"
|
|
"github.com/jesseduffield/lazygit/pkg/env"
|
|
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
|
"github.com/jesseduffield/lazygit/pkg/logs/tail"
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
"github.com/samber/lo"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type cliArgs struct {
|
|
RepoPath string
|
|
FilterPath string
|
|
GitArg string
|
|
UseConfigDir string
|
|
WorkTree string
|
|
GitDir string
|
|
CustomConfigFile string
|
|
ScreenMode string
|
|
PrintVersionInfo bool
|
|
Debug bool
|
|
TailLogs bool
|
|
Profile bool
|
|
PrintDefaultConfig bool
|
|
PrintConfigDir bool
|
|
}
|
|
|
|
type BuildInfo struct {
|
|
Commit string
|
|
Date string
|
|
Version string
|
|
BuildSource string
|
|
}
|
|
|
|
func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTest) {
|
|
cliArgs := parseCliArgsAndEnvVars()
|
|
mergeBuildInfo(buildInfo)
|
|
|
|
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)
|
|
}
|
|
|
|
if isRepo, err := isDirectoryAGitRepository(absRepoPath); err != nil || !isRepo {
|
|
log.Fatal(absRepoPath + " is not a valid git repository.")
|
|
}
|
|
|
|
cliArgs.GitDir = filepath.Join(absRepoPath, ".git")
|
|
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)
|
|
}
|
|
}
|
|
|
|
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 {
|
|
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)
|
|
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 {
|
|
logPath, err := config.LogPath()
|
|
if err != nil {
|
|
log.Fatal(err.Error())
|
|
}
|
|
|
|
tail.TailLogs(logPath)
|
|
os.Exit(0)
|
|
}
|
|
|
|
tempDir, err := os.MkdirTemp(getTempDirBase(), "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())
|
|
}
|
|
|
|
if integrationTest != nil {
|
|
integrationTest.SetupConfig(appConfig)
|
|
// Set this to true so that integration tests don't have to explicitly deal with the hunk
|
|
// staging hint:
|
|
appConfig.GetAppState().DidShowHunkStagingHint = true
|
|
|
|
// Preserve the changes that the test setup just made to the config, so
|
|
// they don't get lost when we reload the config while running the test
|
|
// (which happens when switching between repos, going in and out of
|
|
// submodules, etc).
|
|
appConfig.SaveGlobalUserConfig()
|
|
}
|
|
|
|
common, err := NewCommon(appConfig)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if daemon.InDaemonMode() {
|
|
daemon.Handle(common)
|
|
return
|
|
}
|
|
|
|
if cliArgs.Profile {
|
|
go func() {
|
|
if err := http.ListenAndServe("localhost:6060", nil); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
parsedGitArg := parseGitArg(cliArgs.GitArg)
|
|
|
|
Run(appConfig, common, appTypes.NewStartArgs(cliArgs.FilterPath, parsedGitArg, cliArgs.ScreenMode, integrationTest))
|
|
}
|
|
|
|
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)")
|
|
|
|
profile := false
|
|
flaggy.Bool(&profile, "", "profile", "Start the profiler and serve it on http port 6060. See CONTRIBUTING.md for more info.")
|
|
|
|
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 := os.Getenv("GIT_WORK_TREE")
|
|
flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument")
|
|
|
|
gitDir := os.Getenv("GIT_DIR")
|
|
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)")
|
|
|
|
screenMode := ""
|
|
flaggy.String(&screenMode, "sm", "screen-mode", "The initial screen-mode, which determines the size of the focused panel. Valid options: 'normal' (default), 'half', 'full'")
|
|
|
|
flaggy.Parse()
|
|
|
|
if os.Getenv("DEBUG") == "TRUE" {
|
|
debug = true
|
|
}
|
|
|
|
return &cliArgs{
|
|
RepoPath: repoPath,
|
|
FilterPath: filterPath,
|
|
GitArg: gitArg,
|
|
PrintVersionInfo: printVersionInfo,
|
|
Debug: debug,
|
|
TailLogs: tailLogs,
|
|
Profile: profile,
|
|
PrintDefaultConfig: printDefaultConfig,
|
|
PrintConfigDir: printConfigDir,
|
|
UseConfigDir: useConfigDir,
|
|
WorkTree: workTree,
|
|
GitDir: gitDir,
|
|
CustomConfigFile: customConfigFile,
|
|
ScreenMode: screenMode,
|
|
}
|
|
}
|
|
|
|
func parseGitArg(gitArg string) appTypes.GitArg {
|
|
typedArg := appTypes.GitArg(gitArg)
|
|
|
|
// using switch so that linter catches when a new git arg value is defined but not handled here
|
|
switch typedArg {
|
|
case appTypes.GitArgNone, appTypes.GitArgStatus, appTypes.GitArgBranch, appTypes.GitArgLog, appTypes.GitArgStash:
|
|
return typedArg
|
|
}
|
|
|
|
permittedValues := []string{
|
|
string(appTypes.GitArgStatus),
|
|
string(appTypes.GitArgBranch),
|
|
string(appTypes.GitArgLog),
|
|
string(appTypes.GitArgStash),
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
// 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.ShortHash(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
|
|
}
|
|
}
|
|
|
|
func getGitVersionInfo() string {
|
|
cmd := exec.Command("git", "--version")
|
|
stdout, _ := cmd.Output()
|
|
gitVersion := strings.Trim(strings.TrimPrefix(string(stdout), "git version "), " \r\n")
|
|
return gitVersion
|
|
}
|
|
|
|
func getTempDirBase() string {
|
|
tempDir := os.TempDir()
|
|
|
|
user, err := user.Current()
|
|
if err != nil || user.Uid == "" {
|
|
return tempDir
|
|
}
|
|
|
|
tmpDirBase := filepath.Join(tempDir, "lazygit-"+user.Uid)
|
|
if err := os.MkdirAll(tmpDirBase, 0o700); err != nil {
|
|
return tempDir
|
|
}
|
|
|
|
return tmpDirBase
|
|
}
|