1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-04-04 22:34:39 +02:00

Support per-repo config files

For now we only support .git/lazygit.yml; in the future we would also like to
support ./.lazygit.yml, but that one will need a trust prompt as it could be
versioned, which adds quite a bit of complexity, so we leave that for later.

We do, however, support config files in parent directories (all the way up to
the root directory). This makes it possible to add a config file that applies to
multiple repos at once. Useful if you want to set different options for all your
work repos vs. all your open-source repos, for instance.
This commit is contained in:
Stefan Haller 2024-07-15 13:48:00 +02:00
parent d6d48f2866
commit 74ed1ac584
2 changed files with 95 additions and 25 deletions

View File

@ -15,16 +15,17 @@ import (
// AppConfig contains the base configuration fields required for lazygit.
type AppConfig struct {
debug bool `long:"debug" env:"DEBUG" default:"false"`
version string `long:"version" env:"VERSION" default:"unversioned"`
buildDate string `long:"build-date" env:"BUILD_DATE"`
name string `long:"name" env:"NAME" default:"lazygit"`
buildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
userConfig *UserConfig
userConfigFiles []*ConfigFile
userConfigDir string
tempDir string
appState *AppState
debug bool `long:"debug" env:"DEBUG" default:"false"`
version string `long:"version" env:"VERSION" default:"unversioned"`
buildDate string `long:"build-date" env:"BUILD_DATE"`
name string `long:"name" env:"NAME" default:"lazygit"`
buildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
userConfig *UserConfig
globalUserConfigFiles []*ConfigFile
userConfigFiles []*ConfigFile
userConfigDir string
tempDir string
appState *AppState
}
type AppConfigurer interface {
@ -38,6 +39,7 @@ type AppConfigurer interface {
GetUserConfig() *UserConfig
GetUserConfigPaths() []string
GetUserConfigDir() string
ReloadUserConfigForRepo(repoConfigFiles []*ConfigFile) error
GetTempDir() string
GetAppState() *AppState
@ -49,11 +51,13 @@ type ConfigFilePolicy int
const (
ConfigFilePolicyCreateIfMissing ConfigFilePolicy = iota
ConfigFilePolicyErrorIfMissing
ConfigFilePolicySkipIfMissing
)
type ConfigFile struct {
Path string
Policy ConfigFilePolicy
exists bool
}
// NewAppConfig makes a new app config
@ -108,16 +112,17 @@ func NewAppConfig(
}
appConfig := &AppConfig{
name: name,
version: version,
buildDate: date,
debug: debuggingFlag,
buildSource: buildSource,
userConfig: userConfig,
userConfigFiles: configFiles,
userConfigDir: configDir,
tempDir: tempDir,
appState: appState,
name: name,
version: version,
buildDate: date,
debug: debuggingFlag,
buildSource: buildSource,
userConfig: userConfig,
globalUserConfigFiles: configFiles,
userConfigFiles: configFiles,
userConfigDir: configDir,
tempDir: tempDir,
appState: appState,
}
return appConfig, nil
@ -141,7 +146,10 @@ func loadUserConfigWithDefaults(configFiles []*ConfigFile) (*UserConfig, error)
func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, error) {
for _, configFile := range configFiles {
path := configFile.Path
if _, err := os.Stat(path); err != nil {
_, err := os.Stat(path)
if err == nil {
configFile.exists = true
} else {
if !os.IsNotExist(err) {
return nil, err
}
@ -150,6 +158,10 @@ func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, e
case ConfigFilePolicyErrorIfMissing:
return nil, err
case ConfigFilePolicySkipIfMissing:
configFile.exists = false
continue
case ConfigFilePolicyCreateIfMissing:
file, err := os.Create(path)
if err != nil {
@ -160,6 +172,8 @@ func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, e
return nil, err
}
file.Close()
configFile.exists = true
}
}
@ -260,8 +274,8 @@ func (c *AppConfig) GetAppState() *AppState {
}
func (c *AppConfig) GetUserConfigPaths() []string {
return lo.Map(c.userConfigFiles, func(f *ConfigFile, _ int) string {
return f.Path
return lo.FilterMap(c.userConfigFiles, func(f *ConfigFile, _ int) (string, bool) {
return f.Path, f.exists
})
}
@ -269,6 +283,18 @@ func (c *AppConfig) GetUserConfigDir() string {
return c.userConfigDir
}
func (c *AppConfig) ReloadUserConfigForRepo(repoConfigFiles []*ConfigFile) error {
configFiles := append(c.globalUserConfigFiles, repoConfigFiles...)
userConfig, err := loadUserConfigWithDefaults(configFiles)
if err != nil {
return err
}
c.userConfig = userConfig
c.userConfigFiles = configFiles
return nil
}
func (c *AppConfig) GetTempDir() string {
return c.tempDir
}
@ -365,7 +391,7 @@ func loadAppState() (*AppState, error) {
// SaveGlobalUserConfig saves the UserConfig back to disk. This is only used in
// integration tests, so we are a bit sloppy with error handling.
func (c *AppConfig) SaveGlobalUserConfig() {
if len(c.userConfigFiles) != 1 {
if len(c.globalUserConfigFiles) != 1 {
panic("expected exactly one global user config file")
}
@ -374,7 +400,7 @@ func (c *AppConfig) SaveGlobalUserConfig() {
log.Fatalf("error marshalling user config: %v", err)
}
err = os.WriteFile(c.userConfigFiles[0].Path, yamlContent, 0o644)
err = os.WriteFile(c.globalUserConfigFiles[0].Path, yamlContent, 0o644)
if err != nil {
log.Fatalf("error saving user config: %v", err)
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"sync"
@ -307,6 +308,16 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context
return err
}
err = gui.Config.ReloadUserConfigForRepo(gui.getPerRepoConfigFiles())
if err != nil {
return err
}
err = gui.onUserConfigLoaded()
if err != nil {
return err
}
contextToPush := gui.resetState(startArgs)
gui.resetHelpersAndControllers()
@ -342,6 +353,39 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context
return nil
}
func (gui *Gui) getPerRepoConfigFiles() []*config.ConfigFile {
repoConfigFiles := []*config.ConfigFile{
// TODO: add filepath.Join(gui.git.RepoPaths.RepoPath(), ".lazygit.yml"),
// with trust prompt
{
Path: filepath.Join(gui.git.RepoPaths.RepoGitDirPath(), "lazygit.yml"),
Policy: config.ConfigFilePolicySkipIfMissing,
},
}
prevDir := gui.c.Git().RepoPaths.RepoPath()
dir := filepath.Dir(prevDir)
for dir != prevDir {
repoConfigFiles = utils.Prepend(repoConfigFiles, &config.ConfigFile{
Path: filepath.Join(dir, ".lazygit.yml"),
Policy: config.ConfigFilePolicySkipIfMissing,
})
prevDir = dir
dir = filepath.Dir(dir)
}
return repoConfigFiles
}
func (gui *Gui) onUserConfigLoaded() error {
userConfig := gui.Config.GetUserConfig()
gui.Common.SetUserConfig(userConfig)
gui.setColorScheme()
gui.configureViewProperties()
return nil
}
// resetState reuses the repo state from our repo state map, if the repo was
// open before; otherwise it creates a new one.
func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context {