1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-04-25 12:24:47 +02:00

Per-repo config files (and reloading of edited config files) (#3787)

- **PR Description**

Support per-repo user 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.

In addition, we support re-loading edited config files. This makes it
much easier to experiment with config settings, especially the ones that
affect the layout or color scheme, because you see the effect
immediately without having to restart lazygit.
This commit is contained in:
Stefan Haller 2024-08-18 10:28:33 +02:00 committed by GitHub
commit aa55995924
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
149 changed files with 791 additions and 452 deletions

View File

@ -6,6 +6,7 @@
* [ ] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [ ] Tests have been added/updated (see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) for the integration test guide)
* [ ] Text is internationalised (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be hot-reloaded (see [here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [ ] You've read through your own file changes for silly mistakes etc

View File

@ -1,6 +1,6 @@
# User Config
Default path for the config file:
Default path for the global config file:
- Linux: `~/.config/lazygit/config.yml`
- MacOS: `~/Library/Application\ Support/lazygit/config.yml`
@ -16,6 +16,8 @@ If you want to change the config directory:
- MacOS: `export XDG_CONFIG_HOME="$HOME/.config"`
In addition to the global config file you can create repo-specific config files in `<repo>/.git/lazygit.yml`. Settings in these files override settings in the global config file. In addition, files called `.lazygit.yml` in any of the parent directories of a repo will also be loaded; this can be useful if you have settings that you want to apply to a group of repositories.
JSON schema is available for `config.yml` so that IntelliSense in Visual Studio Code (completion and error checking) is automatically enabled when the [YAML Red Hat][yaml] extension is installed. However, note that automatic schema detection only works if your config file is in one of the standard paths mentioned above. If you override the path to the file, you can still make IntelliSense work by adding
```yaml

View File

@ -12,7 +12,7 @@
* `pkg/commands/models`: Contains model structs that represent commits, branches, files, etc.
* `pkg/commands/patch`: Contains code for parsing and working with git patches
* `pkg/common`: Contains the `Common` struct which holds common dependencies like the logger, i18n, and the user config. Most structs in the code will have a field named `c` which holds a common struct (or a derivative of the common struct).
* `pkg/config`: Contains code relating to the Lazygit user config. Specifically `pkg/config/user_config/go` defines the user config struct and its default values.
* `pkg/config`: Contains code relating to the Lazygit user config. Specifically `pkg/config/user_config/go` defines the user config struct and its default values. See [below](#using-userconfig) for some important information about using it.
* `pkg/constants`: Contains some constant strings (e.g. links to docs)
* `pkg/env`: Contains code relating to setting/getting environment variables
* `pkg/i18n`: Contains internationalised strings
@ -86,6 +86,12 @@ The event loop is managed in the `MainLoop` function of `vendor/github.com/jesse
Often, as part of handling a keypress, we'll want to run some code asynchronously so that it doesn't block the UI thread. For this we'll typically run `self.c.OnWorker(myFunc)`. If the worker wants to then do something on the UI thread again it can call `self.c.OnUIThread(myOtherFunc)`.
## Using UserConfig
The UserConfig struct is loaded from lazygit's global config file (and possibly repo-specific config files). It can be re-loaded while lazygit is running, e.g. when the user edits one of the config files. In this case we should make sure that any new or changed config values take effect immediately. The easiest way to achieve this is what we do in most controllers or helpers: these have a pointer to the `common.Common` struct, which contains the UserConfig, and access it from there. Since the UserConfig instance in `common.Common` is updated whenever we reload the config, the code can be sure that it always uses an up-to-date value, and there's nothing else to do.
If that's not possible for some reason, see if you can add code to `Gui.onUserConfigLoaded` to update things from the new config; there are some examples in that function to use as a guide. If that's too hard to do too, add the config to the list in `Gui.checkForChangedConfigsThatDontAutoReload` so that the user is asked to quit and restart lazygit.
## Legacy code structure
Before we had controllers and contexts, all the code lived directly in the gui package under a gui God Struct. This was fairly bloated and so we split things out to have a better separation of concerns. Nonetheless, it's a big effort to migrate all the code so we still have some logic in the gui struct that ought to live somewhere else. Likewise, we have some keybindings defined in `pkg/gui/keybindings.go` that ought to live on a controller (all keybindings used to be defined in that one file).

2
go.mod
View File

@ -16,7 +16,7 @@ require (
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
github.com/jesseduffield/gocui v0.3.1-0.20240817084901-ea75eca94702
github.com/jesseduffield/gocui v0.3.1-0.20240818082312-49cc572a9ffa
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e

4
go.sum
View File

@ -188,8 +188,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
github.com/jesseduffield/gocui v0.3.1-0.20240817084901-ea75eca94702 h1:9fkowh/FchykVmSXXcNS3XsJ2HWW3MeTlPwQmG1liJM=
github.com/jesseduffield/gocui v0.3.1-0.20240817084901-ea75eca94702/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
github.com/jesseduffield/gocui v0.3.1-0.20240818082312-49cc572a9ffa h1:XZX6Rf60E3IuF1K+fvxjIr29f4p9kNY83mveGoJ5Uuo=
github.com/jesseduffield/gocui v0.3.1-0.20240818082312-49cc572a9ffa/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=

View File

@ -63,22 +63,20 @@ func Run(
func NewCommon(config config.AppConfigurer) (*common.Common, error) {
userConfig := config.GetUserConfig()
appState := config.GetAppState()
var err error
log := newLogger(config)
tr, err := i18n.NewTranslationSetFromConfig(log, userConfig.Gui.Language)
if err != nil {
return nil, err
}
// 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()
return &common.Common{
Log: log,
Tr: tr,
UserConfig: userConfig,
AppState: appState,
Debug: config.GetDebug(),
Fs: afero.NewOsFs(),
}, nil
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 {
@ -195,7 +193,7 @@ func (app *App) setupRepo(
var shouldInitRepo bool
initialBranchArg := ""
switch app.UserConfig.NotARepository {
switch app.UserConfig().NotARepository {
case "prompt":
// Offer to initialize a new repository in current directory.
fmt.Print(app.Tr.CreateRepo)

View File

@ -136,6 +136,12 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
if integrationTest != nil {
integrationTest.SetupConfig(appConfig)
// 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)

View File

@ -63,6 +63,11 @@ func generateAtDir(cheatsheetDir string) {
if err != nil {
log.Fatal(err)
}
tr, err := i18n.NewTranslationSetFromConfig(common.Log, lang)
if err != nil {
log.Fatal(err)
}
common.Tr = tr
mApp, _ := app.NewApp(mConfig, nil, common)
path := cheatsheetDir + "/Keybindings_" + lang + ".md"
file, err := os.Create(path)

View File

@ -145,7 +145,7 @@ func (self *BranchCommands) GetGraph(branchName string) (string, error) {
}
func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj {
branchLogCmdTemplate := self.UserConfig.Git.BranchLogCmd
branchLogCmdTemplate := self.UserConfig().Git.BranchLogCmd
templateValues := map[string]string{
"branchName": self.cmd.Quote(branchName),
}
@ -236,7 +236,7 @@ func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
}
cmdArgs := NewGitCmd("merge").
Arg("--no-edit").
Arg(strings.Fields(self.UserConfig.Git.Merging.Args)...).
Arg(strings.Fields(self.UserConfig().Git.Merging.Args)...).
ArgIf(opts.FastForwardOnly, "--ff-only").
ArgIf(opts.Squash, "--squash", "--ff").
Arg(branchName).
@ -248,9 +248,9 @@ func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj {
// Only choose between non-empty, non-identical commands
candidates := lo.Uniq(lo.WithoutEmpty(append([]string{
self.UserConfig.Git.AllBranchesLogCmd,
self.UserConfig().Git.AllBranchesLogCmd,
},
self.UserConfig.Git.AllBranchesLogCmds...,
self.UserConfig().Git.AllBranchesLogCmds...,
)))
n := len(candidates)

View File

@ -140,7 +140,7 @@ func (self *BranchLoader) Load(reflogCommits []*models.Commit,
}
}
if loadBehindCounts && self.UserConfig.Gui.ShowDivergenceFromBaseBranch != "none" {
if loadBehindCounts && self.UserConfig().Gui.ShowDivergenceFromBaseBranch != "none" {
onWorker(func() error {
return self.GetBehindBaseBranchValuesForAllBranches(branches, mainBranches, renderFunc)
})

View File

@ -88,7 +88,7 @@ func (self *CommitCommands) ResetToCommit(hash string, strength string, envVars
func (self *CommitCommands) CommitCmdObj(summary string, description string) oscommands.ICmdObj {
messageArgs := self.commitMessageArgs(summary, description)
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix
skipHookPrefix := self.UserConfig().Git.SkipHookPrefix
cmdArgs := NewGitCmd("commit").
ArgIf(skipHookPrefix != "" && strings.HasPrefix(summary, skipHookPrefix), "--no-verify").
@ -148,7 +148,7 @@ func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj {
}
func (self *CommitCommands) signoffFlag() string {
if self.UserConfig.Git.Commit.SignOff {
if self.UserConfig().Git.Commit.SignOff {
return "--signoff"
} else {
return ""
@ -258,13 +258,13 @@ func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
func (self *CommitCommands) ShowCmdObj(hash string, filterPath string) oscommands.ICmdObj {
contextSize := self.AppState.DiffContextSize
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
cmdArgs := NewGitCmd("show").
Config("diff.noprefix=false").
ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd).
ArgIfElse(extDiffCmd != "", "--ext-diff", "--no-ext-diff").
Arg("--submodule").
Arg("--color="+self.UserConfig.Git.Paging.ColorArg).
Arg("--color="+self.UserConfig().Git.Paging.ColorArg).
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg("--stat").
Arg("--decorate").

View File

@ -322,9 +322,9 @@ func TestGetCommits(t *testing.T) {
},
}
common.UserConfig.Git.MainBranches = scenario.mainBranches
common.UserConfig().Git.MainBranches = scenario.mainBranches
opts := scenario.opts
opts.MainBranches = NewMainBranches(scenario.mainBranches, cmd)
opts.MainBranches = NewMainBranches(common, cmd)
commits, err := builder.GetCommits(opts)
assert.Equal(t, scenario.expectedCommits, commits)

View File

@ -43,7 +43,7 @@ func (self *ConfigCommands) ConfiguredPager() string {
}
func (self *ConfigCommands) GetPager(width int) string {
useConfig := self.UserConfig.Git.Paging.UseConfig
useConfig := self.UserConfig().Git.Paging.UseConfig
if useConfig {
pager := self.ConfiguredPager()
return strings.Split(pager, "| less")[0]
@ -53,14 +53,14 @@ func (self *ConfigCommands) GetPager(width int) string {
"columnWidth": strconv.Itoa(width/2 - 6),
}
pagerTemplate := string(self.UserConfig.Git.Paging.Pager)
pagerTemplate := string(self.UserConfig().Git.Paging.Pager)
return utils.ResolvePlaceholderString(pagerTemplate, templateValues)
}
// UsingGpg tells us whether the user has gpg enabled so that we can know
// whether we need to run a subprocess to allow them to enter their password
func (self *ConfigCommands) UsingGpg() bool {
overrideGpg := self.UserConfig.Git.OverrideGpg
overrideGpg := self.UserConfig().Git.OverrideGpg
if overrideGpg {
return false
}

View File

@ -58,9 +58,9 @@ func buildGitCommon(deps commonDeps) *GitCommon {
}
gitCommon.cmd = cmd
gitCommon.Common.UserConfig = deps.userConfig
if gitCommon.Common.UserConfig == nil {
gitCommon.Common.UserConfig = config.GetDefaultConfig()
gitCommon.Common.SetUserConfig(deps.userConfig)
if gitCommon.Common.UserConfig() == nil {
gitCommon.Common.SetUserConfig(config.GetDefaultConfig())
}
gitCommon.version = deps.gitVersion

View File

@ -17,7 +17,7 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
}
func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != ""
return self.cmd.New(
@ -26,7 +26,7 @@ func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
Arg("--submodule").
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
Arg(diffArgs...).
Dir(self.repoPaths.worktreePath).
ToArgv(),

View File

@ -31,7 +31,7 @@ func (self *FileCommands) Cat(fileName string) (string, error) {
}
func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (string, error) {
editor := self.UserConfig.OS.EditCommand
editor := self.UserConfig().OS.EditCommand
if editor == "" {
editor = self.config.GetCoreEditor()
@ -60,7 +60,7 @@ func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (
"line": strconv.Itoa(lineNumber),
}
editCmdTemplate := self.UserConfig.OS.EditCommandTemplate
editCmdTemplate := self.UserConfig().OS.EditCommandTemplate
if len(editCmdTemplate) == 0 {
switch editor {
case "emacs", "nano", "vi", "vim", "nvim":
@ -78,7 +78,7 @@ func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (
func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) {
// Legacy support for old config; to be removed at some point
if self.UserConfig.OS.Edit == "" && self.UserConfig.OS.EditCommandTemplate != "" {
if self.UserConfig().OS.Edit == "" && self.UserConfig().OS.EditCommandTemplate != "" {
// If multiple files are selected, we'll simply edit just the first one.
// It's not worth fixing this for the legacy support.
if cmdStr, err := self.GetEditCmdStrLegacy(filenames[0], 1); err == nil {
@ -86,7 +86,7 @@ func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) {
}
}
template, suspend := config.GetEditTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
template, suspend := config.GetEditTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
quotedFilenames := lo.Map(filenames, func(filename string, _ int) string { return self.cmd.Quote(filename) })
templateValues := map[string]string{
@ -99,13 +99,13 @@ func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) {
func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (string, bool) {
// Legacy support for old config; to be removed at some point
if self.UserConfig.OS.EditAtLine == "" && self.UserConfig.OS.EditCommandTemplate != "" {
if self.UserConfig().OS.EditAtLine == "" && self.UserConfig().OS.EditCommandTemplate != "" {
if cmdStr, err := self.GetEditCmdStrLegacy(filename, lineNumber); err == nil {
return cmdStr, true
}
}
template, suspend := config.GetEditAtLineTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
template, suspend := config.GetEditAtLineTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
templateValues := map[string]string{
"filename": self.cmd.Quote(filename),
@ -118,13 +118,13 @@ func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (
func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber int) string {
// Legacy support for old config; to be removed at some point
if self.UserConfig.OS.EditAtLineAndWait == "" && self.UserConfig.OS.EditCommandTemplate != "" {
if self.UserConfig().OS.EditAtLineAndWait == "" && self.UserConfig().OS.EditCommandTemplate != "" {
if cmdStr, err := self.GetEditCmdStrLegacy(filename, lineNumber); err == nil {
return cmdStr
}
}
template := config.GetEditAtLineAndWaitTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
template := config.GetEditAtLineAndWaitTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
templateValues := map[string]string{
"filename": self.cmd.Quote(filename),
@ -136,7 +136,7 @@ func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber
}
func (self *FileCommands) GetOpenDirInEditorCmdStr(path string) (string, bool) {
template, suspend := config.GetOpenDirInEditorTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
template, suspend := config.GetOpenDirInEditorTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
templateValues := map[string]string{
"dir": self.cmd.Quote(path),

View File

@ -5,32 +5,34 @@ import (
"sync"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/sasha-s/go-deadlock"
)
type MainBranches struct {
// List of main branches configured by the user. Just the bare names.
configuredMainBranches []string
// Which of these actually exist in the repository. Full ref names, and it
// could be either "refs/heads/..." or "refs/remotes/origin/..." depending
// on which one exists for a given bare name.
c *common.Common
// Which of the configured main branches actually exist in the repository. Full
// ref names, and it could be either "refs/heads/..." or "refs/remotes/origin/..."
// depending on which one exists for a given bare name.
existingMainBranches []string
previousMainBranches []string
cmd oscommands.ICmdObjBuilder
mutex *deadlock.Mutex
}
func NewMainBranches(
configuredMainBranches []string,
cmn *common.Common,
cmd oscommands.ICmdObjBuilder,
) *MainBranches {
return &MainBranches{
configuredMainBranches: configuredMainBranches,
existingMainBranches: nil,
cmd: cmd,
mutex: &deadlock.Mutex{},
c: cmn,
existingMainBranches: nil,
cmd: cmd,
mutex: &deadlock.Mutex{},
}
}
@ -40,8 +42,11 @@ func (self *MainBranches) Get() []string {
self.mutex.Lock()
defer self.mutex.Unlock()
if self.existingMainBranches == nil {
self.existingMainBranches = self.determineMainBranches()
configuredMainBranches := self.c.UserConfig().Git.MainBranches
if self.existingMainBranches == nil || !utils.EqualSlices(self.previousMainBranches, configuredMainBranches) {
self.existingMainBranches = self.determineMainBranches(configuredMainBranches)
self.previousMainBranches = configuredMainBranches
}
return self.existingMainBranches
@ -71,13 +76,13 @@ func (self *MainBranches) GetMergeBase(refName string) string {
return ignoringWarnings(output)
}
func (self *MainBranches) determineMainBranches() []string {
func (self *MainBranches) determineMainBranches(configuredMainBranches []string) []string {
var existingBranches []string
var wg sync.WaitGroup
existingBranches = make([]string, len(self.configuredMainBranches))
existingBranches = make([]string, len(configuredMainBranches))
for i, branchName := range self.configuredMainBranches {
for i, branchName := range configuredMainBranches {
wg.Add(1)
go utils.Safe(func() {
defer wg.Done()

View File

@ -3,7 +3,6 @@ package git_commands
import (
ioFs "io/fs"
"os"
"path"
"path/filepath"
"strings"
@ -64,9 +63,9 @@ func (self *RepoPaths) IsBareRepo() bool {
func MockRepoPaths(currentPath string) *RepoPaths {
return &RepoPaths{
worktreePath: currentPath,
worktreeGitDirPath: path.Join(currentPath, ".git"),
worktreeGitDirPath: filepath.Join(currentPath, ".git"),
repoPath: currentPath,
repoGitDirPath: path.Join(currentPath, ".git"),
repoGitDirPath: filepath.Join(currentPath, ".git"),
repoName: "lazygit",
isBareRepo: false,
}
@ -116,9 +115,9 @@ func GetRepoPathsForDir(
if isSubmodule {
repoPath = worktreePath
} else {
repoPath = path.Dir(repoGitDirPath)
repoPath = filepath.Dir(repoGitDirPath)
}
repoName := path.Base(repoPath)
repoName := filepath.Base(repoPath)
return &RepoPaths{
worktreePath: worktreePath,
@ -154,7 +153,7 @@ func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string {
result := []string{}
// For each directory in this path we're going to cat the `gitdir` file and append its contents to our result
// That file points us to the `.git` file in the worktree.
worktreeGitDirsPath := path.Join(repoGitDirPath, "worktrees")
worktreeGitDirsPath := filepath.Join(repoGitDirPath, "worktrees")
// ensure the directory exists
_, err := fs.Stat(worktreeGitDirsPath)
@ -171,7 +170,7 @@ func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string {
return nil
}
gitDirPath := path.Join(currPath, "gitdir")
gitDirPath := filepath.Join(currPath, "gitdir")
gitDirBytes, err := afero.ReadFile(fs, gitDirPath)
if err != nil {
// ignoring error
@ -179,7 +178,7 @@ func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string {
}
trimmedGitDir := strings.TrimSpace(string(gitDirBytes))
// removing the .git part
worktreeDir := path.Dir(trimmedGitDir)
worktreeDir := filepath.Dir(trimmedGitDir)
result = append(result, worktreeDir)
return nil
})

View File

@ -2,11 +2,13 @@ package git_commands
import (
"fmt"
"runtime"
"strings"
"testing"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
)
@ -29,7 +31,17 @@ func TestGetRepoPaths(t *testing.T) {
Name: "typical case",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
// setup for main worktree
expectedOutput := []string{
mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{
// --show-toplevel
`C:\path\to\repo`,
// --git-dir
`C:\path\to\repo\.git`,
// --git-common-dir
`C:\path\to\repo\.git`,
// --is-bare-repository
"false",
// --show-superproject-working-tree
}, []string{
// --show-toplevel
"/path/to/repo",
// --git-dir
@ -39,28 +51,45 @@ func TestGetRepoPaths(t *testing.T) {
// --is-bare-repository
"false",
// --show-superproject-working-tree
}
})
runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
strings.Join(expectedOutput, "\n"),
strings.Join(mockOutput, "\n"),
nil)
},
Path: "/path/to/repo",
Expected: &RepoPaths{
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
worktreePath: `C:\path\to\repo`,
worktreeGitDirPath: `C:\path\to\repo\.git`,
repoPath: `C:\path\to\repo`,
repoGitDirPath: `C:\path\to\repo\.git`,
repoName: `repo`,
isBareRepo: false,
}, &RepoPaths{
worktreePath: "/path/to/repo",
worktreeGitDirPath: "/path/to/repo/.git",
repoPath: "/path/to/repo",
repoGitDirPath: "/path/to/repo/.git",
repoName: "repo",
isBareRepo: false,
},
}),
Err: nil,
},
{
Name: "bare repo",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
// setup for main worktree
expectedOutput := []string{
mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{
// --show-toplevel
`C:\path\to\repo`,
// --git-dir
`C:\path\to\bare_repo\bare.git`,
// --git-common-dir
`C:\path\to\bare_repo\bare.git`,
// --is-bare-repository
`true`,
// --show-superproject-working-tree
}, []string{
// --show-toplevel
"/path/to/repo",
// --git-dir
@ -70,27 +99,45 @@ func TestGetRepoPaths(t *testing.T) {
// --is-bare-repository
"true",
// --show-superproject-working-tree
}
})
runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
strings.Join(expectedOutput, "\n"),
strings.Join(mockOutput, "\n"),
nil)
},
Path: "/path/to/repo",
Expected: &RepoPaths{
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
worktreePath: `C:\path\to\repo`,
worktreeGitDirPath: `C:\path\to\bare_repo\bare.git`,
repoPath: `C:\path\to\bare_repo`,
repoGitDirPath: `C:\path\to\bare_repo\bare.git`,
repoName: `bare_repo`,
isBareRepo: true,
}, &RepoPaths{
worktreePath: "/path/to/repo",
worktreeGitDirPath: "/path/to/bare_repo/bare.git",
repoPath: "/path/to/bare_repo",
repoGitDirPath: "/path/to/bare_repo/bare.git",
repoName: "bare_repo",
isBareRepo: true,
},
}),
Err: nil,
},
{
Name: "submodule",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
expectedOutput := []string{
mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{
// --show-toplevel
`C:\path\to\repo\submodule1`,
// --git-dir
`C:\path\to\repo\.git\modules\submodule1`,
// --git-common-dir
`C:\path\to\repo\.git\modules\submodule1`,
// --is-bare-repository
`false`,
// --show-superproject-working-tree
`C:\path\to\repo`,
}, []string{
// --show-toplevel
"/path/to/repo/submodule1",
// --git-dir
@ -101,21 +148,28 @@ func TestGetRepoPaths(t *testing.T) {
"false",
// --show-superproject-working-tree
"/path/to/repo",
}
})
runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
strings.Join(expectedOutput, "\n"),
strings.Join(mockOutput, "\n"),
nil)
},
Path: "/path/to/repo/submodule1",
Expected: &RepoPaths{
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
worktreePath: `C:\path\to\repo\submodule1`,
worktreeGitDirPath: `C:\path\to\repo\.git\modules\submodule1`,
repoPath: `C:\path\to\repo\submodule1`,
repoGitDirPath: `C:\path\to\repo\.git\modules\submodule1`,
repoName: `submodule1`,
isBareRepo: false,
}, &RepoPaths{
worktreePath: "/path/to/repo/submodule1",
worktreeGitDirPath: "/path/to/repo/.git/modules/submodule1",
repoPath: "/path/to/repo/submodule1",
repoGitDirPath: "/path/to/repo/.git/modules/submodule1",
repoName: "submodule1",
isBareRepo: false,
},
}),
Err: nil,
},
{

View File

@ -84,7 +84,7 @@ func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
cmdArgs := NewGitCmd("stash").Arg("show").
Arg("-p").
Arg("--stat").
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
Arg(fmt.Sprintf("--unified=%d", self.AppState.DiffContextSize)).
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
Arg(fmt.Sprintf("--find-renames=%d%%", self.AppState.RenameSimilarityThreshold)).

View File

@ -60,7 +60,7 @@ func (self *SyncCommands) fetchCommandBuilder(fetchAll bool) *GitCommandBuilder
}
func (self *SyncCommands) FetchCmdObj(task gocui.Task) oscommands.ICmdObj {
cmdArgs := self.fetchCommandBuilder(self.UserConfig.Git.FetchAll).ToArgv()
cmdArgs := self.fetchCommandBuilder(self.UserConfig().Git.FetchAll).ToArgv()
cmdObj := self.cmd.New(cmdArgs)
cmdObj.PromptOnCredentialRequest(task)
@ -72,7 +72,7 @@ func (self *SyncCommands) Fetch(task gocui.Task) error {
}
func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
cmdArgs := self.fetchCommandBuilder(self.UserConfig.Git.FetchAll).ToArgv()
cmdArgs := self.fetchCommandBuilder(self.UserConfig().Git.FetchAll).ToArgv()
cmdObj := self.cmd.New(cmdArgs)
cmdObj.DontLog().FailOnCredentialRequest()

View File

@ -133,7 +133,7 @@ func TestSyncFetch(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
instance := buildSyncCommands(commonDeps{})
instance.UserConfig.Git.FetchAll = s.fetchAllConfig
instance.UserConfig().Git.FetchAll = s.fetchAllConfig
task := gocui.NewFakeTask()
s.test(instance.FetchCmdObj(task))
})
@ -171,7 +171,7 @@ func TestSyncFetchBackground(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
instance := buildSyncCommands(commonDeps{})
instance.UserConfig.Git.FetchAll = s.fetchAllConfig
instance.UserConfig().Git.FetchAll = s.fetchAllConfig
s.test(instance.FetchBackgroundCmdObj())
})
}

View File

@ -3,7 +3,7 @@ package git_commands
import (
"fmt"
"os"
"path"
"path/filepath"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models"
@ -233,7 +233,7 @@ func (self *WorkingTreeCommands) Ignore(filename string) error {
// Exclude adds a file to the .git/info/exclude for the repo
func (self *WorkingTreeCommands) Exclude(filename string) error {
excludeFile := path.Join(self.repoPaths.repoGitDirPath, "info", "exclude")
excludeFile := filepath.Join(self.repoPaths.repoGitDirPath, "info", "exclude")
return self.os.AppendLineToFile(excludeFile, filename)
}
@ -245,7 +245,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool,
}
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool) oscommands.ICmdObj {
colorArg := self.UserConfig.Git.Paging.ColorArg
colorArg := self.UserConfig().Git.Paging.ColorArg
if plain {
colorArg = "never"
}
@ -253,7 +253,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
contextSize := self.AppState.DiffContextSize
prevPath := node.GetPreviousPath()
noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile()
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != "" && !plain
cmdArgs := NewGitCmd("diff").
@ -285,12 +285,12 @@ func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bo
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) oscommands.ICmdObj {
contextSize := self.AppState.DiffContextSize
colorArg := self.UserConfig.Git.Paging.ColorArg
colorArg := self.UserConfig().Git.Paging.ColorArg
if plain {
colorArg = "never"
}
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != "" && !plain
cmdArgs := NewGitCmd("diff").

View File

@ -80,10 +80,10 @@ func FileType(path string) string {
}
func (c *OSCommand) OpenFile(filename string) error {
commandTemplate := c.UserConfig.OS.Open
commandTemplate := c.UserConfig().OS.Open
if commandTemplate == "" {
// Legacy support
commandTemplate = c.UserConfig.OS.OpenCommand
commandTemplate = c.UserConfig().OS.OpenCommand
}
if commandTemplate == "" {
commandTemplate = config.GetPlatformDefaultConfig().Open
@ -96,10 +96,10 @@ func (c *OSCommand) OpenFile(filename string) error {
}
func (c *OSCommand) OpenLink(link string) error {
commandTemplate := c.UserConfig.OS.OpenLink
commandTemplate := c.UserConfig().OS.OpenLink
if commandTemplate == "" {
// Legacy support
commandTemplate = c.UserConfig.OS.OpenLinkCommand
commandTemplate = c.UserConfig().OS.OpenLinkCommand
}
if commandTemplate == "" {
commandTemplate = config.GetPlatformDefaultConfig().OpenLink
@ -294,8 +294,8 @@ func (c *OSCommand) CopyToClipboard(str string) error {
},
)
c.LogCommand(msg, false)
if c.UserConfig.OS.CopyToClipboardCmd != "" {
cmdStr := utils.ResolvePlaceholderString(c.UserConfig.OS.CopyToClipboardCmd, map[string]string{
if c.UserConfig().OS.CopyToClipboardCmd != "" {
cmdStr := utils.ResolvePlaceholderString(c.UserConfig().OS.CopyToClipboardCmd, map[string]string{
"text": c.Cmd.Quote(str),
})
return c.Cmd.NewShell(cmdStr).Run()
@ -307,8 +307,8 @@ func (c *OSCommand) CopyToClipboard(str string) error {
func (c *OSCommand) PasteFromClipboard() (string, error) {
var s string
var err error
if c.UserConfig.OS.CopyToClipboardCmd != "" {
cmdStr := c.UserConfig.OS.ReadFromClipboardCmd
if c.UserConfig().OS.CopyToClipboardCmd != "" {
cmdStr := c.UserConfig().OS.ReadFromClipboardCmd
s, err = c.Cmd.NewShell(cmdStr).RunWithOutput()
} else {
s, err = clipboard.ReadAll()

View File

@ -75,7 +75,7 @@ func TestOSCommandOpenFileDarwin(t *testing.T) {
for _, s := range scenarios {
oSCmd := NewDummyOSCommandWithRunner(s.runner)
oSCmd.Platform.OS = "darwin"
oSCmd.UserConfig.OS.Open = "open {{filename}}"
oSCmd.UserConfig().OS.Open = "open {{filename}}"
s.test(oSCmd.OpenFile(s.filename))
}
@ -135,7 +135,7 @@ func TestOSCommandOpenFileLinux(t *testing.T) {
for _, s := range scenarios {
oSCmd := NewDummyOSCommandWithRunner(s.runner)
oSCmd.Platform.OS = "linux"
oSCmd.UserConfig.OS.Open = `xdg-open {{filename}} > /dev/null`
oSCmd.UserConfig().OS.Open = `xdg-open {{filename}} > /dev/null`
s.test(oSCmd.OpenFile(s.filename))
}

View File

@ -71,7 +71,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
}
oSCmd.Platform = platform
oSCmd.Cmd.platform = platform
oSCmd.UserConfig.OS.OpenCommand = `start "" {{filename}}`
oSCmd.UserConfig().OS.OpenCommand = `start "" {{filename}}`
s.test(oSCmd.OpenFile(s.filename))
}

View File

@ -1,6 +1,8 @@
package common
import (
"sync/atomic"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/sirupsen/logrus"
@ -11,10 +13,18 @@ import (
type Common struct {
Log *logrus.Entry
Tr *i18n.TranslationSet
UserConfig *config.UserConfig
userConfig atomic.Pointer[config.UserConfig]
AppState *config.AppState
Debug bool
// for interacting with the filesystem. We use afero rather than the default
// `os` package for the sake of mocking the filesystem in tests
Fs afero.Fs
}
func (c *Common) UserConfig() *config.UserConfig {
return c.userConfig.Load()
}
func (c *Common) SetUserConfig(userConfig *config.UserConfig) {
c.userConfig.Store(userConfig)
}

View File

@ -2,29 +2,31 @@ package config
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/adrg/xdg"
"github.com/jesseduffield/lazygit/pkg/utils/yaml_utils"
"github.com/samber/lo"
"gopkg.in/yaml.v3"
)
// 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
UserConfigPaths []string
DeafultConfFiles bool
UserConfigDir string
TempDir string
AppState *AppState
IsNewRepo bool
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,13 +40,29 @@ type AppConfigurer interface {
GetUserConfig() *UserConfig
GetUserConfigPaths() []string
GetUserConfigDir() string
ReloadUserConfig() error
ReloadUserConfigForRepo(repoConfigFiles []*ConfigFile) error
ReloadChangedUserConfigFiles() (error, bool)
GetTempDir() string
GetAppState() *AppState
SaveAppState() error
}
type ConfigFilePolicy int
const (
ConfigFilePolicyCreateIfMissing ConfigFilePolicy = iota
ConfigFilePolicyErrorIfMissing
ConfigFilePolicySkipIfMissing
)
type ConfigFile struct {
Path string
Policy ConfigFilePolicy
modDate time.Time
exists bool
}
// NewAppConfig makes a new app config
func NewAppConfig(
name string,
@ -60,17 +78,22 @@ func NewAppConfig(
return nil, err
}
var userConfigPaths []string
var configFiles []*ConfigFile
customConfigFiles := os.Getenv("LG_CONFIG_FILE")
if customConfigFiles != "" {
// Load user defined config files
userConfigPaths = strings.Split(customConfigFiles, ",")
userConfigPaths := strings.Split(customConfigFiles, ",")
configFiles = lo.Map(userConfigPaths, func(path string, _ int) *ConfigFile {
return &ConfigFile{Path: path, Policy: ConfigFilePolicyErrorIfMissing}
})
} else {
// Load default config files
userConfigPaths = []string{filepath.Join(configDir, ConfigFilename)}
path := filepath.Join(configDir, ConfigFilename)
configFile := &ConfigFile{Path: path, Policy: ConfigFilePolicyCreateIfMissing}
configFiles = []*ConfigFile{configFile}
}
userConfig, err := loadUserConfigWithDefaults(userConfigPaths)
userConfig, err := loadUserConfigWithDefaults(configFiles)
if err != nil {
return nil, err
}
@ -92,26 +115,22 @@ func NewAppConfig(
}
appConfig := &AppConfig{
Name: name,
Version: version,
BuildDate: date,
Debug: debuggingFlag,
BuildSource: buildSource,
UserConfig: userConfig,
UserConfigPaths: userConfigPaths,
UserConfigDir: configDir,
TempDir: tempDir,
AppState: appState,
IsNewRepo: false,
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
}
func isCustomConfigFile(path string) bool {
return path != filepath.Join(ConfigDir(), ConfigFilename)
}
func ConfigDir() string {
_, filePath := findConfigFile("config.yml")
@ -123,32 +142,48 @@ func findOrCreateConfigDir() (string, error) {
return folder, os.MkdirAll(folder, 0o755)
}
func loadUserConfigWithDefaults(configFiles []string) (*UserConfig, error) {
func loadUserConfigWithDefaults(configFiles []*ConfigFile) (*UserConfig, error) {
return loadUserConfig(configFiles, GetDefaultConfig())
}
func loadUserConfig(configFiles []string, base *UserConfig) (*UserConfig, error) {
for _, path := range configFiles {
if _, err := os.Stat(path); err != nil {
func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, error) {
for _, configFile := range configFiles {
path := configFile.Path
statInfo, err := os.Stat(path)
if err == nil {
configFile.exists = true
configFile.modDate = statInfo.ModTime()
} else {
if !os.IsNotExist(err) {
return nil, err
}
// if use has supplied their own custom config file path(s), we assume
// the files have already been created, so we won't go and create them here.
if isCustomConfigFile(path) {
switch configFile.Policy {
case ConfigFilePolicyErrorIfMissing:
return nil, err
}
file, err := os.Create(path)
if err != nil {
if os.IsPermission(err) {
// apparently when people have read-only permissions they prefer us to fail silently
continue
case ConfigFilePolicySkipIfMissing:
configFile.exists = false
continue
case ConfigFilePolicyCreateIfMissing:
file, err := os.Create(path)
if err != nil {
if os.IsPermission(err) {
// apparently when people have read-only permissions they prefer us to fail silently
continue
}
return nil, err
}
return nil, err
file.Close()
configFile.exists = true
statInfo, err := os.Stat(configFile.Path)
if err != nil {
return nil, err
}
configFile.modDate = statInfo.ModTime()
}
file.Close()
}
content, err := os.ReadFile(path)
@ -220,53 +255,81 @@ func changeNullKeybindingsToDisabled(changedContent []byte) ([]byte, error) {
}
func (c *AppConfig) GetDebug() bool {
return c.Debug
return c.debug
}
func (c *AppConfig) GetVersion() string {
return c.Version
return c.version
}
func (c *AppConfig) GetName() string {
return c.Name
return c.name
}
// GetBuildSource returns the source of the build. For builds from goreleaser
// this will be binaryBuild
func (c *AppConfig) GetBuildSource() string {
return c.BuildSource
return c.buildSource
}
// GetUserConfig returns the user config
func (c *AppConfig) GetUserConfig() *UserConfig {
return c.UserConfig
return c.userConfig
}
// GetAppState returns the app state
func (c *AppConfig) GetAppState() *AppState {
return c.AppState
return c.appState
}
func (c *AppConfig) GetUserConfigPaths() []string {
return c.UserConfigPaths
return lo.FilterMap(c.userConfigFiles, func(f *ConfigFile, _ int) (string, bool) {
return f.Path, f.exists
})
}
func (c *AppConfig) GetUserConfigDir() string {
return c.UserConfigDir
return c.userConfigDir
}
func (c *AppConfig) ReloadUserConfig() error {
userConfig, err := loadUserConfigWithDefaults(c.UserConfigPaths)
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.userConfig = userConfig
c.userConfigFiles = configFiles
return nil
}
func (c *AppConfig) ReloadChangedUserConfigFiles() (error, bool) {
fileHasChanged := func(f *ConfigFile) bool {
info, err := os.Stat(f.Path)
if err != nil && !os.IsNotExist(err) {
// If we can't stat the file, assume it hasn't changed
return false
}
exists := err == nil
return exists != f.exists || (exists && info.ModTime() != f.modDate)
}
if lo.NoneBy(c.userConfigFiles, fileHasChanged) {
return nil, false
}
userConfig, err := loadUserConfigWithDefaults(c.userConfigFiles)
if err != nil {
return err, false
}
c.userConfig = userConfig
return nil, true
}
func (c *AppConfig) GetTempDir() string {
return c.TempDir
return c.tempDir
}
// findConfigFile looks for a possibly existing config file.
@ -305,14 +368,9 @@ func stateFilePath(filename string) (string, error) {
return xdg.StateFile(filepath.Join("lazygit", filename))
}
// ConfigFilename returns the filename of the default config file
func (c *AppConfig) ConfigFilename() string {
return filepath.Join(c.UserConfigDir, ConfigFilename)
}
// SaveAppState marshalls the AppState struct and writes it to the disk
func (c *AppConfig) SaveAppState() error {
marshalledAppState, err := yaml.Marshal(c.AppState)
marshalledAppState, err := yaml.Marshal(c.appState)
if err != nil {
return err
}
@ -363,6 +421,24 @@ func loadAppState() (*AppState, error) {
return appState, nil
}
// 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.globalUserConfigFiles) != 1 {
panic("expected exactly one global user config file")
}
yamlContent, err := yaml.Marshal(c.userConfig)
if err != nil {
log.Fatalf("error marshalling user config: %v", err)
}
err = os.WriteFile(c.globalUserConfigFiles[0].Path, yamlContent, 0o644)
if err != nil {
log.Fatalf("error saving user config: %v", err)
}
}
// AppState stores data between runs of the app like when the last update check
// was performed and which other repos have been checked out
type AppState struct {

View File

@ -7,12 +7,12 @@ import (
// NewDummyAppConfig creates a new dummy AppConfig for testing
func NewDummyAppConfig() *AppConfig {
appConfig := &AppConfig{
Name: "lazygit",
Version: "unversioned",
Debug: false,
UserConfig: GetDefaultConfig(),
AppState: &AppState{},
name: "lazygit",
version: "unversioned",
debug: false,
userConfig: GetDefaultConfig(),
appState: &AppState{},
}
_ = yaml.Unmarshal([]byte{}, appConfig.AppState)
_ = yaml.Unmarshal([]byte{}, appConfig.appState)
return appConfig
}

View File

@ -25,7 +25,7 @@ func (self *BackgroundRoutineMgr) PauseBackgroundRefreshes(pause bool) {
}
func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
userConfig := self.gui.UserConfig
userConfig := self.gui.UserConfig()
if userConfig.Git.AutoFetch {
fetchInterval := userConfig.Refresher.FetchInterval
@ -77,7 +77,7 @@ func (self *BackgroundRoutineMgr) startBackgroundFetch() {
self.gui.waitForIntro.Wait()
isNew := self.gui.IsNewRepo
userConfig := self.gui.UserConfig
userConfig := self.gui.UserConfig()
if !isNew {
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second)
}

View File

@ -55,11 +55,11 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
func (gui *Gui) printCommandLogHeader() {
introStr := fmt.Sprintf(
gui.c.Tr.CommandLogHeader,
keybindings.Label(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
keybindings.Label(gui.c.UserConfig().Keybinding.Universal.ExtrasMenu),
)
fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr))
if gui.c.UserConfig.Gui.ShowRandomTip {
if gui.c.UserConfig().Gui.ShowRandomTip {
fmt.Fprintf(
gui.Views.Extras,
"%s: %s",
@ -70,7 +70,7 @@ func (gui *Gui) printCommandLogHeader() {
}
func (gui *Gui) getRandomTip() string {
config := gui.c.UserConfig.Keybinding
config := gui.c.UserConfig().Keybinding
formattedKey := func(key string) string {
return keybindings.Label(key)

View File

@ -32,7 +32,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
c.Modes().Diffing.Ref,
c.Views().Branches.Width(),
c.Tr,
c.UserConfig,
c.UserConfig(),
c.Model().Worktrees,
)
}

View File

@ -28,7 +28,7 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
viewModel := filetree.NewCommitFileTreeViewModel(
func() []*models.CommitFile { return c.Model().CommitFiles },
c.Log,
c.UserConfig.Gui.ShowFileTree,
c.UserConfig().Gui.ShowFileTree,
)
getDisplayStrings := func(_ int, _ int) [][]string {
@ -36,7 +36,7 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
return [][]string{{style.FgRed.Sprint("(none)")}}
}
showFileIcons := icons.IsIconEnabled() && c.UserConfig.Gui.ShowFileIcons
showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons
lines := presentation.RenderCommitFileTree(viewModel, c.Git().Patch.PatchBuilder, showFileIcons)
return lo.Map(lines, func(line string, _ int) []string {
return []string{line}

View File

@ -113,19 +113,19 @@ func (self *CommitMessageContext) SetPanelState(
self.c.Views().CommitDescription.Subtitle = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionSubTitle,
map[string]string{
"togglePanelKeyBinding": keybindings.Label(self.c.UserConfig.Keybinding.Universal.TogglePanel),
"commitMenuKeybinding": keybindings.Label(self.c.UserConfig.Keybinding.CommitMessage.CommitMenu),
"togglePanelKeyBinding": keybindings.Label(self.c.UserConfig().Keybinding.Universal.TogglePanel),
"commitMenuKeybinding": keybindings.Label(self.c.UserConfig().Keybinding.CommitMessage.CommitMenu),
})
self.c.Views().CommitDescription.Visible = true
}
func (self *CommitMessageContext) RenderCommitLength() {
if !self.c.UserConfig.Gui.CommitLength.Show {
return
if self.c.UserConfig().Gui.CommitLength.Show {
self.c.Views().CommitMessage.Subtitle = getBufferLength(self.c.Views().CommitMessage)
} else {
self.c.Views().CommitMessage.Subtitle = ""
}
self.c.Views().CommitMessage.Subtitle = getBufferLength(self.c.Views().CommitMessage)
}
func getBufferLength(view *gocui.View) string {

View File

@ -54,10 +54,10 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
c.Modes().CherryPicking.SelectedHashSet(),
c.Modes().Diffing.Ref,
c.Modes().MarkedBaseCommit.GetHash(),
c.UserConfig.Gui.TimeFormat,
c.UserConfig.Gui.ShortTimeFormat,
c.UserConfig().Gui.TimeFormat,
c.UserConfig().Gui.ShortTimeFormat,
time.Now(),
c.UserConfig.Git.ParseEmoji,
c.UserConfig().Git.ParseEmoji,
selectedCommitHash,
startIdx,
endIdx,
@ -110,7 +110,7 @@ func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *ContextCommon
self := &LocalCommitsViewModel{
ListViewModel: NewListViewModel(getModel),
limitCommits: true,
showWholeGitGraph: c.UserConfig.Git.Log.ShowWholeGraph,
showWholeGitGraph: c.UserConfig().Git.Log.ShowWholeGraph,
}
return self

View File

@ -139,7 +139,7 @@ func (self *MenuViewModel) GetNonModelItems() []*NonModelItem {
// Don't display section headers when we are filtering, and the filter mode
// is fuzzy. The reason is that filtering changes the order of the items
// (they are sorted by best match), so all the sections would be messed up.
if self.FilteredListViewModel.IsFiltering() && self.c.UserConfig.Gui.UseFuzzySearch() {
if self.FilteredListViewModel.IsFiltering() && self.c.UserConfig().Gui.UseFuzzySearch() {
return result
}

View File

@ -33,9 +33,9 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
c.Modes().CherryPicking.SelectedHashSet(),
c.Modes().Diffing.Ref,
time.Now(),
c.UserConfig.Gui.TimeFormat,
c.UserConfig.Gui.ShortTimeFormat,
c.UserConfig.Git.ParseEmoji,
c.UserConfig().Gui.TimeFormat,
c.UserConfig().Gui.ShortTimeFormat,
c.UserConfig().Git.ParseEmoji,
)
}

View File

@ -26,7 +26,7 @@ func NewRemotesContext(c *ContextCommon) *RemotesContext {
getDisplayStrings := func(_ int, _ int) [][]string {
return presentation.GetRemoteListDisplayStrings(
viewModel.GetItems(), c.Modes().Diffing.Ref, c.State().GetItemOperation, c.Tr, c.UserConfig)
viewModel.GetItems(), c.Modes().Diffing.Ref, c.State().GetItemOperation, c.Tr, c.UserConfig())
}
return &RemotesContext{

View File

@ -51,7 +51,7 @@ func (self *SearchTrait) onSelectItemWrapper(innerFunc func(int) error) func(int
}
func (self *SearchTrait) RenderSearchStatus(index int, total int) {
keybindingConfig := self.c.UserConfig.Keybinding
keybindingConfig := self.c.UserConfig().Keybinding
if total == 0 {
self.c.SetViewContent(

View File

@ -68,10 +68,10 @@ func NewSubCommitsContext(
c.Modes().CherryPicking.SelectedHashSet(),
c.Modes().Diffing.Ref,
"",
c.UserConfig.Gui.TimeFormat,
c.UserConfig.Gui.ShortTimeFormat,
c.UserConfig().Gui.TimeFormat,
c.UserConfig().Gui.ShortTimeFormat,
time.Now(),
c.UserConfig.Git.ParseEmoji,
c.UserConfig().Git.ParseEmoji,
selectedCommitHash,
startIdx,
endIdx,

View File

@ -30,7 +30,7 @@ func NewTagsContext(
return presentation.GetTagListDisplayStrings(
viewModel.GetItems(),
c.State().GetItemOperation,
c.Modes().Diffing.Ref, c.Tr, c.UserConfig)
c.Modes().Diffing.Ref, c.Tr, c.UserConfig())
}
return &TagsContext{

View File

@ -25,11 +25,11 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
viewModel := filetree.NewFileTreeViewModel(
func() []*models.File { return c.Model().Files },
c.Log,
c.UserConfig.Gui.ShowFileTree,
c.UserConfig().Gui.ShowFileTree,
)
getDisplayStrings := func(_ int, _ int) [][]string {
showFileIcons := icons.IsIconEnabled() && c.UserConfig.Gui.ShowFileIcons
showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons
lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons)
return lo.Map(lines, func(line string, _ int) []string {
return []string{line}

View File

@ -311,8 +311,8 @@ func (self *BasicCommitsController) canCopyCommits(selectedCommits []*models.Com
func (self *BasicCommitsController) handleOldCherryPickKey() error {
msg := utils.ResolvePlaceholderString(self.c.Tr.OldCherryPickKeyWarning,
map[string]string{
"copy": keybindings.Label(self.c.UserConfig.Keybinding.Commits.CherryPickCopy),
"paste": keybindings.Label(self.c.UserConfig.Keybinding.Commits.PasteCommits),
"copy": keybindings.Label(self.c.UserConfig().Keybinding.Commits.CherryPickCopy),
"paste": keybindings.Label(self.c.UserConfig().Keybinding.Commits.PasteCommits),
})
return errors.New(msg)

View File

@ -118,8 +118,8 @@ func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, e
}
return false, errors.New(self.c.Tr.CommitWithoutMessageErr)
}
if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage {
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth)
if self.c.UserConfig().Git.Commit.AutoWrapCommitMessage {
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig().Git.Commit.AutoWrapWidth)
}
self.c.Helpers().Commits.UpdateCommitPanelView(commitMessage)
return true, nil

View File

@ -46,7 +46,7 @@ func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) [
// We assume that whenever things are deletable, they
// are also editable, so we show both keybindings
subtitle = fmt.Sprintf(self.c.Tr.SuggestionsSubtitle,
self.c.UserConfig.Keybinding.Universal.Remove, self.c.UserConfig.Keybinding.Universal.Edit)
self.c.UserConfig().Keybinding.Universal.Remove, self.c.UserConfig().Keybinding.Universal.Edit)
}
self.c.Views().Suggestions.Subtitle = subtitle
return self.c.Context().Replace(self.c.Contexts().Suggestions)

View File

@ -258,7 +258,7 @@ func (self *FilesController) GetOnRenderToMain() func() error {
pair = self.c.MainViewPairs().Staging
}
split := self.c.UserConfig.Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges())
split := self.c.UserConfig().Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges())
mainShowsStaged := !split && node.GetHasStagedChanges()
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged)
@ -1083,7 +1083,7 @@ func (self *FilesController) remove(selectedNodes []*filetree.FileNode) error {
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}})
},
Key: self.c.KeybindingsOpts().GetKey(self.c.UserConfig.Keybinding.Files.ConfirmDiscard),
Key: self.c.KeybindingsOpts().GetKey(self.c.UserConfig().Keybinding.Files.ConfirmDiscard),
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.DiscardAllTooltip,
map[string]string{

View File

@ -81,16 +81,16 @@ func (self *AppStatusHelper) HasStatus() bool {
}
func (self *AppStatusHelper) GetStatusString() string {
appStatus, _ := self.statusMgr().GetStatusString(self.c.UserConfig)
appStatus, _ := self.statusMgr().GetStatusString(self.c.UserConfig())
return appStatus
}
func (self *AppStatusHelper) renderAppStatus() {
self.c.OnWorker(func(_ gocui.Task) error {
ticker := time.NewTicker(time.Millisecond * time.Duration(self.c.UserConfig.Gui.Spinner.Rate))
ticker := time.NewTicker(time.Millisecond * time.Duration(self.c.UserConfig().Gui.Spinner.Rate))
defer ticker.Stop()
for range ticker.C {
appStatus, color := self.statusMgr().GetStatusString(self.c.UserConfig)
appStatus, color := self.statusMgr().GetStatusString(self.c.UserConfig())
self.c.Views().AppStatus.FgColor = color
self.c.OnUIThread(func() error {
self.c.SetViewContent(self.c.Views().AppStatus, appStatus)
@ -124,7 +124,7 @@ func (self *AppStatusHelper) renderAppStatusSync(stop chan struct{}) {
for {
select {
case <-ticker.C:
appStatus, color := self.statusMgr().GetStatusString(self.c.UserConfig)
appStatus, color := self.statusMgr().GetStatusString(self.c.UserConfig())
self.c.Views().AppStatus.FgColor = color
self.c.SetViewContent(self.c.Views().AppStatus, appStatus)
// Redraw all views of the bottom line:

View File

@ -173,7 +173,7 @@ func (self *ConfirmationHelper) prepareConfirmationPanel(
suggestionsView.FgColor = theme.GocuiDefaultTextColor
suggestionsContext.SetSuggestions(opts.FindSuggestionsFunc(""))
suggestionsView.Visible = true
suggestionsView.Title = fmt.Sprintf(self.c.Tr.SuggestionsTitle, self.c.UserConfig.Keybinding.Universal.TogglePanel)
suggestionsView.Title = fmt.Sprintf(self.c.Tr.SuggestionsTitle, self.c.UserConfig().Keybinding.Universal.TogglePanel)
suggestionsView.Subtitle = ""
}

View File

@ -46,7 +46,7 @@ func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus str
if err := cmdObj.StreamOutput().Run(); err != nil {
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return fmt.Errorf(
self.c.Tr.GitCommandFailed, self.c.UserConfig.Keybinding.Universal.ExtrasMenu,
self.c.Tr.GitCommandFailed, self.c.UserConfig().Keybinding.Universal.ExtrasMenu,
)
}

View File

@ -46,6 +46,6 @@ func (self *HostHelper) getHostingServiceMgr() (*hosting_service.HostingServiceM
if err != nil {
return nil, err
}
configServices := self.c.UserConfig.Services
configServices := self.c.UserConfig().Services
return hosting_service.NewHostingServiceMgr(self.c.Log, self.c.Tr, remoteUrl, configServices), nil
}

View File

@ -99,7 +99,7 @@ func (self *InlineStatusHelper) start(opts InlineStatusOpts) {
self.contextsWithInlineStatus[opts.ContextKey] = info
go utils.Safe(func() {
ticker := time.NewTicker(time.Millisecond * time.Duration(self.c.UserConfig.Gui.Spinner.Rate))
ticker := time.NewTicker(time.Millisecond * time.Duration(self.c.UserConfig().Gui.Spinner.Rate))
defer ticker.Stop()
outer:
for {

View File

@ -112,7 +112,7 @@ func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error {
// we should end up with a command like 'git merge --continue'
// it's impossible for a rebase to require a commit so we'll use a subprocess only if it's a merge
needsSubprocess := (status == enums.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && self.c.UserConfig.Git.Merging.ManualCommit) ||
needsSubprocess := (status == enums.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && self.c.UserConfig().Git.Merging.ManualCommit) ||
// but we'll also use a subprocess if we have exec todos; those are likely to be lengthy build
// tasks whose output the user will want to see in the terminal
(status == enums.REBASE_MODE_REBASING && command != REBASE_OPTION_ABORT && self.hasExecTodos())
@ -435,7 +435,7 @@ func (self *MergeAndRebaseHelper) SquashMergeCommitted(refName, checkedOutBranch
if err = self.CheckMergeOrRebase(err); err != nil {
return err
}
message := utils.ResolvePlaceholderString(self.c.UserConfig.Git.Merging.SquashMergeMessage, map[string]string{
message := utils.ResolvePlaceholderString(self.c.UserConfig().Git.Merging.SquashMergeMessage, map[string]string{
"selectedRef": refName,
"currentBranch": checkedOutBranchName,
})

View File

@ -737,7 +737,7 @@ func (self *RefreshHelper) refreshStatus() {
repoName := self.c.Git().RepoPaths.RepoName()
status := presentation.FormatStatus(repoName, currentBranch, types.ItemOperationNone, linkedWorktreeName, workingTreeState, self.c.Tr, self.c.UserConfig)
status := presentation.FormatStatus(repoName, currentBranch, types.ItemOperationNone, linkedWorktreeName, workingTreeState, self.c.Tr, self.c.UserConfig())
self.c.SetViewContent(self.c.Views().Status, status)
}

View File

@ -275,7 +275,7 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
)
if suggestedBranchName == "" {
suggestedBranchName = self.c.UserConfig.Git.BranchPrefix
suggestedBranchName = self.c.UserConfig().Git.BranchPrefix
}
return self.c.Prompt(types.PromptOpts{

View File

@ -76,7 +76,7 @@ func (self *SearchHelper) DisplayFilterStatus(context types.IFilterableContext)
self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix)
promptView := self.promptView()
keybindingConfig := self.c.UserConfig.Keybinding
keybindingConfig := self.c.UserConfig().Keybinding
promptView.SetContent(fmt.Sprintf("matches for '%s' ", searchString) + theme.OptionsFgColor.Sprintf(self.c.Tr.ExitTextFilterMode, keybindings.Label(keybindingConfig.Universal.Return)))
}
@ -229,7 +229,7 @@ func (self *SearchHelper) OnPromptContentChanged(searchString string) {
case types.IFilterableContext:
context.SetSelection(0)
_ = context.GetView().SetOriginY(0)
context.SetFilter(searchString, self.c.UserConfig.Gui.UseFuzzySearch())
context.SetFilter(searchString, self.c.UserConfig().Gui.UseFuzzySearch())
_ = self.c.PostRefreshUpdate(context)
case types.ISearchableContext:
// do nothing
@ -246,7 +246,7 @@ func (self *SearchHelper) ReApplyFilter(context types.Context) {
filterableContext.SetSelection(0)
_ = filterableContext.GetView().SetOriginY(0)
}
filterableContext.ReApplyFilter(self.c.UserConfig.Gui.UseFuzzySearch())
filterableContext.ReApplyFilter(self.c.UserConfig().Gui.UseFuzzySearch())
}
}

View File

@ -66,7 +66,7 @@ func matchesToSuggestions(matches []string) []*types.Suggestion {
func (self *SuggestionsHelper) GetRemoteSuggestionsFunc() func(string) []*types.Suggestion {
remoteNames := self.getRemoteNames()
return FilterFunc(remoteNames, self.c.UserConfig.Gui.UseFuzzySearch())
return FilterFunc(remoteNames, self.c.UserConfig().Gui.UseFuzzySearch())
}
func (self *SuggestionsHelper) getBranchNames() []string {
@ -83,7 +83,7 @@ func (self *SuggestionsHelper) GetBranchNameSuggestionsFunc() func(string) []*ty
if input == "" {
matchingBranchNames = branchNames
} else {
matchingBranchNames = utils.FilterStrings(input, branchNames, self.c.UserConfig.Gui.UseFuzzySearch())
matchingBranchNames = utils.FilterStrings(input, branchNames, self.c.UserConfig().Gui.UseFuzzySearch())
}
return lo.Map(matchingBranchNames, func(branchName string, _ int) *types.Suggestion {
@ -129,7 +129,7 @@ func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*type
return func(input string) []*types.Suggestion {
matchingNames := []string{}
if self.c.UserConfig.Gui.UseFuzzySearch() {
if self.c.UserConfig().Gui.UseFuzzySearch() {
_ = self.c.Model().FilesTrie.VisitFuzzy(patricia.Prefix(input), true, func(prefix patricia.Prefix, item patricia.Item, skipped int) error {
matchingNames = append(matchingNames, item.(string))
return nil
@ -163,7 +163,7 @@ func (self *SuggestionsHelper) getRemoteBranchNames(separator string) []string {
}
func (self *SuggestionsHelper) GetRemoteBranchesSuggestionsFunc(separator string) func(string) []*types.Suggestion {
return FilterFunc(self.getRemoteBranchNames(separator), self.c.UserConfig.Gui.UseFuzzySearch())
return FilterFunc(self.getRemoteBranchNames(separator), self.c.UserConfig().Gui.UseFuzzySearch())
}
func (self *SuggestionsHelper) getTagNames() []string {
@ -175,7 +175,7 @@ func (self *SuggestionsHelper) getTagNames() []string {
func (self *SuggestionsHelper) GetTagsSuggestionsFunc() func(string) []*types.Suggestion {
tagNames := self.getTagNames()
return FilterFunc(tagNames, self.c.UserConfig.Gui.UseFuzzySearch())
return FilterFunc(tagNames, self.c.UserConfig().Gui.UseFuzzySearch())
}
func (self *SuggestionsHelper) GetRefsSuggestionsFunc() func(string) []*types.Suggestion {
@ -186,7 +186,7 @@ func (self *SuggestionsHelper) GetRefsSuggestionsFunc() func(string) []*types.Su
refNames := append(append(append(remoteBranchNames, localBranchNames...), tagNames...), additionalRefNames...)
return FilterFunc(refNames, self.c.UserConfig.Gui.UseFuzzySearch())
return FilterFunc(refNames, self.c.UserConfig().Gui.UseFuzzySearch())
}
func (self *SuggestionsHelper) GetAuthorsSuggestionsFunc() func(string) []*types.Suggestion {
@ -196,7 +196,7 @@ func (self *SuggestionsHelper) GetAuthorsSuggestionsFunc() func(string) []*types
slices.Sort(authors)
return FilterFunc(authors, self.c.UserConfig.Gui.UseFuzzySearch())
return FilterFunc(authors, self.c.UserConfig().Gui.UseFuzzySearch())
}
func FilterFunc(options []string, useFuzzySearch bool) func(string) []*types.Suggestion {

View File

@ -48,8 +48,8 @@ func (self *TagsHelper) OpenCreateTagPrompt(ref string, onCreate func()) error {
self.c.Tr.ForceTagPrompt,
map[string]string{
"tagName": tagName,
"cancelKey": self.c.UserConfig.Keybinding.Universal.Return,
"confirmKey": self.c.UserConfig.Keybinding.Universal.Confirm,
"cancelKey": self.c.UserConfig().Keybinding.Universal.Return,
"confirmKey": self.c.UserConfig().Keybinding.Universal.Confirm,
},
)
return self.c.Confirm(types.ConfirmOpts{

View File

@ -31,7 +31,7 @@ func (self *UpdateHelper) CheckForUpdateInBackground() {
if newVersion == "" {
return nil
}
if self.c.UserConfig.Update.Method == "background" {
if self.c.UserConfig().Update.Method == "background" {
self.startUpdating(newVersion)
return nil
}

View File

@ -87,7 +87,7 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
args := WindowArrangementArgs{
Width: width,
Height: height,
UserConfig: self.c.UserConfig,
UserConfig: self.c.UserConfig(),
CurrentWindow: self.windowHelper.CurrentWindow(),
CurrentSideWindow: self.c.Context().CurrentSide().GetWindowName(),
CurrentStaticWindow: self.c.Context().CurrentStatic().GetWindowName(),

View File

@ -136,7 +136,7 @@ func (self *WorkingTreeHelper) HandleCommitEditorPress() error {
}
func (self *WorkingTreeHelper) HandleWIPCommitPress() error {
skipHookPrefix := self.c.UserConfig.Git.SkipHookPrefix
skipHookPrefix := self.c.UserConfig().Git.SkipHookPrefix
if skipHookPrefix == "" {
return errors.New(self.c.Tr.SkipHookPrefixNotConfigured)
}
@ -209,7 +209,7 @@ func (self *WorkingTreeHelper) syncRefresh() error {
func (self *WorkingTreeHelper) prepareFilesForCommit() error {
noStagedFiles := !self.AnyStagedFiles()
if noStagedFiles && self.c.UserConfig.Gui.SkipNoStagedFilesWarning {
if noStagedFiles && self.c.UserConfig().Gui.SkipNoStagedFilesWarning {
self.c.LogAction(self.c.Tr.Actions.StageAllFiles)
err := self.c.Git().WorkingTree.StageAll()
if err != nil {
@ -223,10 +223,10 @@ func (self *WorkingTreeHelper) prepareFilesForCommit() error {
}
func (self *WorkingTreeHelper) commitPrefixConfigForRepo() *config.CommitPrefixConfig {
cfg, ok := self.c.UserConfig.Git.CommitPrefixes[self.c.Git().RepoPaths.RepoName()]
cfg, ok := self.c.UserConfig().Git.CommitPrefixes[self.c.Git().RepoPaths.RepoName()]
if ok {
return &cfg
}
return self.c.UserConfig.Git.CommitPrefix
return self.c.UserConfig().Git.CommitPrefix
}

View File

@ -51,7 +51,7 @@ func (self *ListController) HandleScrollRight() error {
}
func (self *ListController) HandleScrollUp() error {
scrollHeight := self.c.UserConfig.Gui.ScrollHeight
scrollHeight := self.c.UserConfig().Gui.ScrollHeight
self.context.GetViewTrait().ScrollUp(scrollHeight)
if self.context.RenderOnlyVisibleLines() {
return self.context.HandleRender()
@ -61,7 +61,7 @@ func (self *ListController) HandleScrollUp() error {
}
func (self *ListController) HandleScrollDown() error {
scrollHeight := self.c.UserConfig.Gui.ScrollHeight
scrollHeight := self.c.UserConfig().Gui.ScrollHeight
self.context.GetViewTrait().ScrollDown(scrollHeight)
if self.context.RenderOnlyVisibleLines() {
return self.context.HandleRender()
@ -106,10 +106,10 @@ func (self *ListController) handleLineChangeAux(f func(int), change int) error {
cursorMoved := before != after
if cursorMoved {
if change == -1 {
checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig,
checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig(),
self.context.ModelIndexToViewIndex(before), self.context.ModelIndexToViewIndex(after))
} else if change == 1 {
checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig,
checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig(),
self.context.ModelIndexToViewIndex(before), self.context.ModelIndexToViewIndex(after))
}
}

View File

@ -357,8 +357,8 @@ func (self *LocalCommitsController) reword(commit *models.Commit) error {
if err != nil {
return err
}
if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage {
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth)
if self.c.UserConfig().Git.Commit.AutoWrapCommitMessage {
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig().Git.Commit.AutoWrapWidth)
}
return self.c.Helpers().Commits.OpenCommitMessagePanel(
&helpers.OpenCommitMessagePanelOpts{
@ -438,7 +438,7 @@ func (self *LocalCommitsController) doRewordEditor() error {
}
func (self *LocalCommitsController) rewordEditor(commit *models.Commit) error {
if self.c.UserConfig.Gui.SkipRewordInEditorWarning {
if self.c.UserConfig().Gui.SkipRewordInEditorWarning {
return self.doRewordEditor()
} else {
return self.c.Confirm(types.ConfirmOpts{
@ -564,7 +564,7 @@ func (self *LocalCommitsController) findCommitForQuickStartInteractiveRebase() (
if !ok || index == 0 {
errorMsg := utils.ResolvePlaceholderString(self.c.Tr.CannotQuickStartInteractiveRebase, map[string]string{
"editKey": keybindings.Label(self.c.UserConfig.Keybinding.Universal.Edit),
"editKey": keybindings.Label(self.c.UserConfig().Keybinding.Universal.Edit),
})
return nil, errors.New(errorMsg)
@ -905,8 +905,8 @@ func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, inc
if err != nil {
return err
}
if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage {
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth)
if self.c.UserConfig().Git.Commit.AutoWrapCommitMessage {
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig().Git.Commit.AutoWrapWidth)
}
originalSubject, _, _ := strings.Cut(commitMessage, "\n")
return self.c.Helpers().Commits.OpenCommitMessagePanel(

View File

@ -173,14 +173,14 @@ func (self *MergeConflictsController) GetOnFocusLost() func(types.OnFocusLostOpt
func (self *MergeConflictsController) HandleScrollUp() error {
self.context().SetUserScrolling(true)
self.context().GetViewTrait().ScrollUp(self.c.UserConfig.Gui.ScrollHeight)
self.context().GetViewTrait().ScrollUp(self.c.UserConfig().Gui.ScrollHeight)
return nil
}
func (self *MergeConflictsController) HandleScrollDown() error {
self.context().SetUserScrolling(true)
self.context().GetViewTrait().ScrollDown(self.c.UserConfig.Gui.ScrollHeight)
self.context().GetViewTrait().ScrollDown(self.c.UserConfig().Gui.ScrollHeight)
return nil
}

View File

@ -173,7 +173,7 @@ func (self *PatchExplorerController) HandlePrevLine() error {
after := self.context.GetState().GetSelectedLineIdx()
if self.context.GetState().SelectingLine() {
checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig, before, after)
checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig(), before, after)
}
return nil
@ -185,7 +185,7 @@ func (self *PatchExplorerController) HandleNextLine() error {
after := self.context.GetState().GetSelectedLineIdx()
if self.context.GetState().SelectingLine() {
checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig, before, after)
checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig(), before, after)
}
return nil

View File

@ -25,7 +25,7 @@ func (self *QuitActions) quitAux() error {
return self.confirmQuitDuringUpdate()
}
if self.c.UserConfig.ConfirmOnQuit {
if self.c.UserConfig().ConfirmOnQuit {
return self.c.Confirm(types.ConfirmOpts{
Title: "",
Prompt: self.c.Tr.ConfirmQuit,
@ -88,7 +88,7 @@ func (self *QuitActions) Escape() error {
return self.c.Helpers().Repos.DispatchSwitchToRepo(repoPathStack.Pop(), context.NO_CONTEXT)
}
if self.c.UserConfig.QuitOnTopLevelReturn {
if self.c.UserConfig().QuitOnTopLevelReturn {
return self.Quit()
}

View File

@ -60,7 +60,7 @@ func (self *ShellCommandAction) GetShellCommandsHistorySuggestionsFunc() func(st
return func(input string) []*types.Suggestion {
history := self.c.GetAppState().ShellCommandsHistory
return helpers.FilterFunc(history, self.c.UserConfig.Gui.UseFuzzySearch())(input)
return helpers.FilterFunc(history, self.c.UserConfig().Gui.UseFuzzySearch())(input)
}
}

View File

@ -190,7 +190,7 @@ func (self *StagingController) ToggleStaged() error {
func (self *StagingController) DiscardSelection() error {
reset := func() error { return self.applySelectionAndRefresh(true) }
if !self.staged && !self.c.UserConfig.Gui.SkipDiscardChangeWarning {
if !self.staged && !self.c.UserConfig().Gui.SkipDiscardChangeWarning {
return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.DiscardChangeTitle,
Prompt: self.c.Tr.DiscardChangePrompt,

View File

@ -114,7 +114,7 @@ func (self *StashController) handleStashApply(stashEntry *models.StashEntry) err
return nil
}
if self.c.UserConfig.Gui.SkipStashWarning {
if self.c.UserConfig().Gui.SkipStashWarning {
return apply()
}
@ -138,7 +138,7 @@ func (self *StashController) handleStashPop(stashEntry *models.StashEntry) error
return nil
}
if self.c.UserConfig.Gui.SkipStashWarning {
if self.c.UserConfig().Gui.SkipStashWarning {
return pop()
}

View File

@ -89,15 +89,15 @@ func (self *StatusController) onClickMain(opts gocui.ViewMouseBindingOpts) error
}
func (self *StatusController) GetOnRenderToMain() func() error {
config := self.c.UserConfig.Gui
switch config.StatusPanelView {
case "dashboard":
return self.showDashboard
case "allBranchesLog":
return self.showAllBranchLogs
default:
return self.showDashboard
return func() error {
switch self.c.UserConfig().Gui.StatusPanelView {
case "dashboard":
return self.showDashboard()
case "allBranchesLog":
return self.showAllBranchLogs()
default:
return self.showDashboard()
}
}
}
@ -117,7 +117,7 @@ func (self *StatusController) onClick(opts gocui.ViewMouseBindingOpts) error {
return err
}
upstreamStatus := utils.Decolorise(presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now(), self.c.UserConfig))
upstreamStatus := utils.Decolorise(presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now(), self.c.UserConfig()))
repoName := self.c.Git().RepoPaths.RepoName()
workingTreeState := self.c.Git().Status.WorkingTreeState()
switch workingTreeState {

View File

@ -210,7 +210,7 @@ func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts)
return errors.New(self.c.Tr.UpdatesRejected)
}
forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing
forcePushDisabled := self.c.UserConfig().Git.DisableForcePushing
if forcePushDisabled {
return errors.New(self.c.Tr.UpdatesRejectedAndForcePushDisabled)
}
@ -233,7 +233,7 @@ func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts)
}
func (self *SyncController) requestToForcePush(currentBranch *models.Branch, opts pushOpts) error {
forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing
forcePushDisabled := self.c.UserConfig().Git.DisableForcePushing
if forcePushDisabled {
return errors.New(self.c.Tr.ForcePushDisabled)
}
@ -252,8 +252,8 @@ func (self *SyncController) forcePushPrompt() string {
return utils.ResolvePlaceholderString(
self.c.Tr.ForcePushPrompt,
map[string]string{
"cancelKey": self.c.UserConfig.Keybinding.Universal.Return,
"confirmKey": self.c.UserConfig.Keybinding.Universal.Confirm,
"cancelKey": self.c.UserConfig().Keybinding.Universal.Return,
"confirmKey": self.c.UserConfig().Keybinding.Universal.Confirm,
},
)
}

View File

@ -65,13 +65,13 @@ func (self *VerticalScrollController) GetMouseKeybindings(opts types.Keybindings
}
func (self *VerticalScrollController) HandleScrollUp() error {
self.context.GetViewTrait().ScrollUp(self.c.UserConfig.Gui.ScrollHeight)
self.context.GetViewTrait().ScrollUp(self.c.UserConfig().Gui.ScrollHeight)
return nil
}
func (self *VerticalScrollController) HandleScrollDown() error {
scrollHeight := self.c.UserConfig.Gui.ScrollHeight
scrollHeight := self.c.UserConfig().Gui.ScrollHeight
self.context.GetViewTrait().ScrollDown(scrollHeight)
if manager, ok := (*self.viewBufferManagerMap)[self.context.GetViewName()]; ok {

View File

@ -35,7 +35,7 @@ func (self *FilesController) createResetMenu() error {
return err
}
if self.c.UserConfig.Gui.AnimateExplosion {
if self.c.UserConfig().Gui.AnimateExplosion {
self.animateExplosion()
}

View File

@ -13,11 +13,11 @@ import (
const HORIZONTAL_SCROLL_FACTOR = 3
func (gui *Gui) scrollUpView(view *gocui.View) {
view.ScrollUp(gui.c.UserConfig.Gui.ScrollHeight)
view.ScrollUp(gui.c.UserConfig().Gui.ScrollHeight)
}
func (gui *Gui) scrollDownView(view *gocui.View) {
scrollHeight := gui.c.UserConfig.Gui.ScrollHeight
scrollHeight := gui.c.UserConfig().Gui.ScrollHeight
view.ScrollDown(scrollHeight)
if manager, ok := gui.viewBufferManagerMap[view.Name()]; ok {
@ -123,7 +123,7 @@ func (gui *Gui) handleCopySelectedSideContextItemToClipboard() error {
func (gui *Gui) handleCopySelectedSideContextItemCommitHashToClipboard() error {
return gui.handleCopySelectedSideContextItemToClipboardWithTruncation(
gui.UserConfig.Git.TruncateCopiedCommitHashesTo)
gui.UserConfig().Git.TruncateCopiedCommitHashesTo)
}
func (gui *Gui) handleCopySelectedSideContextItemToClipboardWithTruncation(maxWidth int) error {

View File

@ -5,6 +5,8 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"sync"
@ -35,6 +37,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/status"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/integration/components"
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
"github.com/jesseduffield/lazygit/pkg/tasks"
@ -137,6 +140,8 @@ type Gui struct {
c *helpers.HelperCommon
helpers *helpers.Helpers
previousLanguageConfig string
integrationTest integrationTypes.IntegrationTest
afterLayoutFuncs chan func() error
@ -307,6 +312,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()
@ -317,8 +332,28 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context
gui.g.SetFocusHandler(func(Focused bool) error {
if Focused {
oldConfig := gui.Config.GetUserConfig()
reloadErr, didChange := gui.Config.ReloadChangedUserConfigFiles()
if didChange && reloadErr == nil {
gui.c.Log.Info("User config changed - reloading")
reloadErr = gui.onUserConfigLoaded()
if err := gui.resetKeybindings(); err != nil {
return err
}
if err := gui.checkForChangedConfigsThatDontAutoReload(oldConfig, gui.Config.GetUserConfig()); err != nil {
return err
}
}
gui.c.Log.Info("Receiving focus - refreshing")
return gui.helpers.Refresh.Refresh(types.RefreshOptions{Mode: types.ASYNC})
refreshErr := gui.helpers.Refresh.Refresh(types.RefreshOptions{Mode: types.ASYNC})
if reloadErr != nil {
// An error from reloading the config is the more important one
// to report to the user
return reloadErr
}
return refreshErr
}
return nil
@ -342,6 +377,119 @@ 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()
gui.g.SearchEscapeKey = keybindings.GetKey(userConfig.Keybinding.Universal.Return)
gui.g.NextSearchMatchKey = keybindings.GetKey(userConfig.Keybinding.Universal.NextMatch)
gui.g.PrevSearchMatchKey = keybindings.GetKey(userConfig.Keybinding.Universal.PrevMatch)
gui.g.ShowListFooter = userConfig.Gui.ShowListFooter
gui.g.Mouse = userConfig.Gui.MouseEvents
if gui.previousLanguageConfig != userConfig.Gui.Language {
tr, err := i18n.NewTranslationSetFromConfig(gui.Log, userConfig.Gui.Language)
if err != nil {
return err
}
gui.c.Tr = tr
gui.previousLanguageConfig = userConfig.Gui.Language
}
// originally we could only hide the command log permanently via the config
// but now we do it via state. So we need to still support the config for the
// sake of backwards compatibility. We're making use of short circuiting here
gui.ShowExtrasWindow = userConfig.Gui.ShowCommandLog && !gui.c.GetAppState().HideCommandLog
authors.SetCustomAuthors(userConfig.Gui.AuthorColors)
if userConfig.Gui.NerdFontsVersion != "" {
icons.SetNerdFontsVersion(userConfig.Gui.NerdFontsVersion)
} else if userConfig.Gui.ShowIcons {
icons.SetNerdFontsVersion("2")
}
presentation.SetCustomBranches(userConfig.Gui.BranchColors)
return nil
}
func (gui *Gui) checkForChangedConfigsThatDontAutoReload(oldConfig *config.UserConfig, newConfig *config.UserConfig) error {
configsThatDontAutoReload := []string{
"Git.AutoFetch",
"Git.AutoRefresh",
"Refresher.RefreshInterval",
"Refresher.FetchInterval",
"Update.Method",
"Update.Days",
}
changedConfigs := []string{}
for _, config := range configsThatDontAutoReload {
old := reflect.ValueOf(oldConfig).Elem()
new := reflect.ValueOf(newConfig).Elem()
fieldNames := strings.Split(config, ".")
userFacingPath := make([]string, 0, len(fieldNames))
// navigate to the leaves in old and new config
for _, fieldName := range fieldNames {
f, _ := old.Type().FieldByName(fieldName)
userFacingName := f.Tag.Get("yaml")
if userFacingName == "" {
userFacingName = fieldName
}
userFacingPath = append(userFacingPath, userFacingName)
old = old.FieldByName(fieldName)
new = new.FieldByName(fieldName)
}
// if the value has changed, ...
if !old.Equal(new) {
// ... append it to the list of changed configs
changedConfigs = append(changedConfigs, strings.Join(userFacingPath, "."))
}
}
if len(changedConfigs) == 0 {
return nil
}
message := utils.ResolvePlaceholderString(
gui.c.Tr.NonReloadableConfigWarning,
map[string]string{
"configs": strings.Join(changedConfigs, "\n"),
},
)
return gui.c.Confirm(types.ConfirmOpts{
Title: gui.c.Tr.NonReloadableConfigWarningTitle,
Prompt: message,
})
}
// 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 {
@ -379,7 +527,7 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context {
BisectInfo: git_commands.NewNullBisectInfo(),
FilesTrie: patricia.NewTrie(),
Authors: map[string]*models.Author{},
MainBranches: git_commands.NewMainBranches(gui.UserConfig.Git.MainBranches, gui.os.Cmd),
MainBranches: git_commands.NewMainBranches(gui.c.Common, gui.os.Cmd),
},
Modes: &types.Modes{
Filtering: filtering.New(startArgs.FilterPath, ""),
@ -478,10 +626,10 @@ func NewGui(
RepoStateMap: map[Repo]*GuiRepoState{},
GuiLog: []string{},
// originally we could only hide the command log permanently via the config
// but now we do it via state. So we need to still support the config for the
// sake of backwards compatibility. We're making use of short circuiting here
ShowExtrasWindow: cmn.UserConfig.Gui.ShowCommandLog && !config.GetAppState().HideCommandLog,
// initializing this to true for the time being; it will be reset to the
// real value after loading the user config:
ShowExtrasWindow: true,
Mutexes: types.Mutexes{
RefreshingFilesMutex: &deadlock.Mutex{},
RefreshingBranchesMutex: &deadlock.Mutex{},
@ -538,14 +686,6 @@ func NewGui(
// TODO: reset these controllers upon changing repos due to state changing
gui.c = helperCommon
authors.SetCustomAuthors(gui.UserConfig.Gui.AuthorColors)
if gui.UserConfig.Gui.NerdFontsVersion != "" {
icons.SetNerdFontsVersion(gui.UserConfig.Gui.NerdFontsVersion)
} else if gui.UserConfig.Gui.ShowIcons {
icons.SetNerdFontsVersion("2")
}
presentation.SetCustomBranches(gui.UserConfig.Gui.BranchColors)
gui.BackgroundRoutineMgr = &BackgroundRoutineMgr{gui: gui}
gui.stateAccessor = &StateAccessor{gui: gui}
@ -658,25 +798,7 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
// breakpoints and stepping through code can easily take more than 30s.
deadlock.Opts.Disable = !gui.Debug || os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) != ""
if err := gui.Config.ReloadUserConfig(); err != nil {
return nil
}
userConfig := gui.UserConfig
gui.g.OnSearchEscape = func() error { gui.helpers.Search.Cancel(); return nil }
gui.g.SearchEscapeKey = keybindings.GetKey(userConfig.Keybinding.Universal.Return)
gui.g.NextSearchMatchKey = keybindings.GetKey(userConfig.Keybinding.Universal.NextMatch)
gui.g.PrevSearchMatchKey = keybindings.GetKey(userConfig.Keybinding.Universal.PrevMatch)
gui.g.ShowListFooter = userConfig.Gui.ShowListFooter
if userConfig.Gui.MouseEvents {
gui.g.Mouse = true
}
if err := gui.setColorScheme(); err != nil {
return err
}
gui.g.SetManager(gocui.ManagerFunc(gui.layout))
@ -735,7 +857,7 @@ func (gui *Gui) RunAndHandleError(startArgs appTypes.StartArgs) error {
}
func (gui *Gui) checkForDeprecatedEditConfigs() {
osConfig := &gui.UserConfig.OS
osConfig := &gui.UserConfig().OS
deprecatedConfigs := []struct {
config string
oldName string
@ -934,16 +1056,14 @@ func (gui *Gui) showBreakingChangesMessage() {
}
// setColorScheme sets the color scheme for the app based on the user config
func (gui *Gui) setColorScheme() error {
userConfig := gui.UserConfig
func (gui *Gui) setColorScheme() {
userConfig := gui.UserConfig()
theme.UpdateTheme(userConfig.Gui.Theme)
gui.g.FgColor = theme.InactiveBorderColor
gui.g.SelFgColor = theme.ActiveBorderColor
gui.g.FrameColor = theme.InactiveBorderColor
gui.g.SelFrameColor = theme.ActiveBorderColor
return nil
}
func (gui *Gui) onUIThread(f func() error) {

View File

@ -60,7 +60,7 @@ func (self *Gui) GetCheatsheetKeybindings() []*types.Binding {
}
func (self *Gui) keybindingOpts() types.KeybindingsOpts {
config := self.c.UserConfig.Keybinding
config := self.c.UserConfig().Keybinding
guards := types.KeybindingGuards{
OutsideFilterMode: self.outsideFilterMode,

View File

@ -260,7 +260,7 @@ func (gui *Gui) onRepoViewReset() error {
}
func (gui *Gui) onInitialViewsCreation() error {
if !gui.c.UserConfig.DisableStartupPopups {
if !gui.c.UserConfig().DisableStartupPopups {
storedPopupVersion := gui.c.GetAppState().StartupPopupVersion
if storedPopupVersion < StartupPopupVersion {
gui.showIntroPopupMessage()

View File

@ -324,7 +324,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
for i, s := range scenarios {
icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", ""))
c.UserConfig.Gui.ShowDivergenceFromBaseBranch = s.showDivergenceCfg
c.UserConfig().Gui.ShowDivergenceFromBaseBranch = s.showDivergenceCfg
worktrees := []*models.Worktree{}
if s.checkedOutByWorktree {
@ -332,7 +332,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
}
t.Run(fmt.Sprintf("getBranchDisplayStrings_%d", i), func(t *testing.T) {
strings := getBranchDisplayStrings(s.branch, s.itemOperation, s.fullDescription, false, s.viewWidth, c.Tr, c.UserConfig, worktrees, time.Time{})
strings := getBranchDisplayStrings(s.branch, s.itemOperation, s.fullDescription, false, s.viewWidth, c.Tr, c.UserConfig(), worktrees, time.Time{})
assert.Equal(t, s.expected, strings)
})
}

View File

@ -173,7 +173,7 @@ func GetCommitListDisplayStrings(
// Don't show a marker for the current branch
b.Name != currentBranchName &&
// Don't show a marker for main branches
!lo.Contains(common.UserConfig.Git.MainBranches, b.Name) &&
!lo.Contains(common.UserConfig().Git.MainBranches, b.Name) &&
// Don't show a marker for the head commit unless the
// rebase.updateRefs config is on
(hasRebaseUpdateRefsConfig || b.CommitHash != commits[0].Hash)
@ -370,7 +370,7 @@ func displayCommit(
hashString := ""
hashColor := getHashColor(commit, diffName, cherryPickedCommitHashSet, bisectStatus, bisectInfo)
hashLength := common.UserConfig.Gui.CommitHashLength
hashLength := common.UserConfig().Gui.CommitHashLength
if hashLength >= len(commit.Hash) {
hashString = hashColor.Sprint(commit.Hash)
} else if hashLength > 0 {
@ -440,9 +440,9 @@ func displayCommit(
mark = fmt.Sprintf("%s ", willBeRebased)
}
authorLength := common.UserConfig.Gui.CommitAuthorShortLength
authorLength := common.UserConfig().Gui.CommitAuthorShortLength
if fullDescription {
authorLength = common.UserConfig.Gui.CommitAuthorLongLength
authorLength = common.UserConfig().Gui.CommitAuthorLongLength
}
author := authors.AuthorWithLength(commit.AuthorName, authorLength)

View File

@ -1,7 +1,7 @@
package custom_commands
import (
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@ -9,7 +9,7 @@ import (
// Client is the entry point to this package. It returns a list of keybindings based on the config's user-defined custom commands.
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Command_Keybindings.md for more info.
type Client struct {
customCommands []config.CustomCommand
c *common.Common
handlerCreator *HandlerCreator
keybindingCreator *KeybindingCreator
}
@ -26,10 +26,9 @@ func NewClient(
helpers.MergeAndRebase,
)
keybindingCreator := NewKeybindingCreator(c)
customCommands := c.UserConfig.CustomCommands
return &Client{
customCommands: customCommands,
c: c.Common,
keybindingCreator: keybindingCreator,
handlerCreator: handlerCreator,
}
@ -37,7 +36,7 @@ func NewClient(
func (self *Client) GetCustomCommandKeybindings() ([]*types.Binding, error) {
bindings := []*types.Binding{}
for _, customCommand := range self.customCommands {
for _, customCommand := range self.c.UserConfig().CustomCommands {
handler := self.handlerCreator.call(customCommand)
compoundBindings, err := self.keybindingCreator.call(customCommand, handler)
if err != nil {

View File

@ -73,26 +73,12 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
}
func (gui *Gui) createAllViews() error {
frameRunes := []rune{'─', '│', '┌', '┐', '└', '┘'}
switch gui.c.UserConfig.Gui.Border {
case "double":
frameRunes = []rune{'═', '║', '╔', '╗', '╚', '╝'}
case "rounded":
frameRunes = []rune{'─', '│', '╭', '╮', '╰', '╯'}
case "hidden":
frameRunes = []rune{' ', ' ', ' ', ' ', ' ', ' '}
}
var err error
for _, mapping := range gui.orderedViewNameMappings() {
*mapping.viewPtr, err = gui.prepareView(mapping.name)
if err != nil && !gocui.IsUnknownView(err) {
return err
}
(*mapping.viewPtr).FrameRunes = frameRunes
(*mapping.viewPtr).FgColor = theme.GocuiDefaultTextColor
(*mapping.viewPtr).SelBgColor = theme.GocuiSelectedLineBgColor
(*mapping.viewPtr).InactiveViewSelBgColor = theme.GocuiInactiveViewSelectedLineBgColor
}
gui.Views.Options.Frame = false
@ -131,7 +117,6 @@ func (gui *Gui) createAllViews() error {
view.Title = gui.c.Tr.DiffTitle
view.Wrap = true
view.IgnoreCarriageReturns = true
view.CanScrollPastBottom = gui.c.UserConfig.Gui.ScrollPastBottom
}
gui.Views.Staging.Title = gui.c.Tr.UnstagedChanges
@ -166,11 +151,8 @@ func (gui *Gui) createAllViews() error {
gui.Views.CommitDescription.Visible = false
gui.Views.CommitDescription.Title = gui.c.Tr.CommitDescriptionTitle
gui.Views.CommitDescription.FgColor = theme.GocuiDefaultTextColor
gui.Views.CommitDescription.Editable = true
gui.Views.CommitDescription.Editor = gocui.EditorFunc(gui.commitDescriptionEditor)
gui.Views.CommitDescription.TextArea.AutoWrap = gui.c.UserConfig.Git.Commit.AutoWrapCommitMessage
gui.Views.CommitDescription.TextArea.AutoWrapWidth = gui.c.UserConfig.Git.Commit.AutoWrapWidth
gui.Views.Confirmation.Visible = false
gui.Views.Confirmation.Editor = gocui.EditorFunc(gui.promptEditor)
@ -192,8 +174,39 @@ func (gui *Gui) createAllViews() error {
gui.Views.Snake.Title = gui.c.Tr.SnakeTitle
gui.Views.Snake.FgColor = gocui.ColorGreen
if gui.c.UserConfig.Gui.ShowPanelJumps {
jumpBindings := gui.c.UserConfig.Keybinding.Universal.JumpToBlock
return nil
}
func (gui *Gui) configureViewProperties() {
frameRunes := []rune{'─', '│', '┌', '┐', '└', '┘'}
switch gui.c.UserConfig().Gui.Border {
case "double":
frameRunes = []rune{'═', '║', '╔', '╗', '╚', '╝'}
case "rounded":
frameRunes = []rune{'─', '│', '╭', '╮', '╰', '╯'}
case "hidden":
frameRunes = []rune{' ', ' ', ' ', ' ', ' ', ' '}
}
for _, mapping := range gui.orderedViewNameMappings() {
(*mapping.viewPtr).FrameRunes = frameRunes
(*mapping.viewPtr).BgColor = gui.g.BgColor
(*mapping.viewPtr).FgColor = theme.GocuiDefaultTextColor
(*mapping.viewPtr).SelBgColor = theme.GocuiSelectedLineBgColor
(*mapping.viewPtr).SelFgColor = gui.g.SelFgColor
(*mapping.viewPtr).InactiveViewSelBgColor = theme.GocuiInactiveViewSelectedLineBgColor
}
for _, view := range []*gocui.View{gui.Views.Main, gui.Views.Secondary, gui.Views.Staging, gui.Views.StagingSecondary, gui.Views.PatchBuilding, gui.Views.PatchBuildingSecondary, gui.Views.MergeConflicts} {
view.CanScrollPastBottom = gui.c.UserConfig().Gui.ScrollPastBottom
}
gui.Views.CommitDescription.FgColor = theme.GocuiDefaultTextColor
gui.Views.CommitDescription.TextArea.AutoWrap = gui.c.UserConfig().Git.Commit.AutoWrapCommitMessage
gui.Views.CommitDescription.TextArea.AutoWrapWidth = gui.c.UserConfig().Git.Commit.AutoWrapWidth
if gui.c.UserConfig().Gui.ShowPanelJumps {
jumpBindings := gui.c.UserConfig().Keybinding.Universal.JumpToBlock
jumpLabels := lo.Map(jumpBindings, func(binding string, _ int) string {
return fmt.Sprintf("[%s]", binding)
})
@ -212,7 +225,20 @@ func (gui *Gui) createAllViews() error {
gui.Views.ReflogCommits.TitlePrefix = jumpLabels[3]
gui.Views.Stash.TitlePrefix = jumpLabels[4]
}
} else {
gui.Views.Status.TitlePrefix = ""
return nil
gui.Views.Files.TitlePrefix = ""
gui.Views.Worktrees.TitlePrefix = ""
gui.Views.Submodules.TitlePrefix = ""
gui.Views.Branches.TitlePrefix = ""
gui.Views.Remotes.TitlePrefix = ""
gui.Views.Tags.TitlePrefix = ""
gui.Views.Commits.TitlePrefix = ""
gui.Views.ReflogCommits.TitlePrefix = ""
gui.Views.Stash.TitlePrefix = ""
}
}

View File

@ -225,6 +225,8 @@ type TranslationSet struct {
MergeToolPrompt string
IntroPopupMessage string
DeprecatedEditConfigWarning string
NonReloadableConfigWarningTitle string
NonReloadableConfigWarning string
GitconfigParseErr string
EditFile string
EditFileTooltip string
@ -985,6 +987,10 @@ for up-to-date information how to configure your editor.
`
const englishNonReloadableConfigWarning = `The following config settings were changed, but the change doesn't take effect immediately. Please quit and restart lazygit for changes to take effect:
{{configs}}`
// exporting this so we can use it in tests
func EnglishTranslationSet() *TranslationSet {
return &TranslationSet{
@ -1199,6 +1205,8 @@ func EnglishTranslationSet() *TranslationSet {
MergeToolPrompt: "Are you sure you want to open `git mergetool`?",
IntroPopupMessage: englishIntroPopupMessage,
DeprecatedEditConfigWarning: englishDeprecatedEditConfigWarning,
NonReloadableConfigWarningTitle: "Config changed",
NonReloadableConfigWarning: englishNonReloadableConfigWarning,
GitconfigParseErr: `Gogit failed to parse your gitconfig file due to the presence of unquoted '\' characters. Removing these should fix the issue.`,
EditFile: `Edit file`,
EditFileTooltip: "Open file in external editor.",

View File

@ -15,7 +15,7 @@ var Basic = NewIntegrationTest(NewIntegrationTestArgs{
CreateNCommits(10)
},
SetupConfig: func(cfg *config.AppConfig) {
cfg.AppState.GitLogShowGraph = "never"
cfg.GetAppState().GitLogShowGraph = "never"
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
markCommitAsBad := func() {

View File

@ -15,7 +15,7 @@ var ChooseTerms = NewIntegrationTest(NewIntegrationTestArgs{
CreateNCommits(10)
},
SetupConfig: func(cfg *config.AppConfig) {
cfg.AppState.GitLogShowGraph = "never"
cfg.GetAppState().GitLogShowGraph = "never"
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
markCommitAsFixed := func() {

View File

@ -14,7 +14,7 @@ var Skip = NewIntegrationTest(NewIntegrationTestArgs{
CreateNCommits(10)
},
SetupConfig: func(cfg *config.AppConfig) {
cfg.AppState.GitLogShowGraph = "never"
cfg.GetAppState().GitLogShowGraph = "never"
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().

View File

@ -11,7 +11,7 @@ var RebaseCopiedBranch = NewIntegrationTest(NewIntegrationTestArgs{
Skip: false,
GitVersion: AtLeast("2.38.0"),
SetupConfig: func(config *config.AppConfig) {
config.AppState.GitLogShowGraph = "never"
config.GetAppState().GitLogShowGraph = "never"
},
SetupRepo: func(shell *Shell) {
shell.

View File

@ -10,7 +10,7 @@ var RebaseOntoBaseBranch = NewIntegrationTest(NewIntegrationTestArgs{
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.UserConfig.Gui.ShowDivergenceFromBaseBranch = "arrowAndNumber"
config.GetUserConfig().Gui.ShowDivergenceFromBaseBranch = "arrowAndNumber"
},
SetupRepo: func(shell *Shell) {
shell.

View File

@ -10,7 +10,7 @@ var ShowDivergenceFromBaseBranch = NewIntegrationTest(NewIntegrationTestArgs{
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.UserConfig.Gui.ShowDivergenceFromBaseBranch = "arrowAndNumber"
config.GetUserConfig().Gui.ShowDivergenceFromBaseBranch = "arrowAndNumber"
},
SetupRepo: func(shell *Shell) {
shell.

View File

@ -10,7 +10,7 @@ var CherryPickDuringRebase = NewIntegrationTest(NewIntegrationTestArgs{
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.AppState.GitLogShowGraph = "never"
config.GetAppState().GitLogShowGraph = "never"
},
SetupRepo: func(shell *Shell) {
shell.

View File

@ -11,7 +11,7 @@ var AutoWrapMessage = NewIntegrationTest(NewIntegrationTestArgs{
Skip: false,
SetupConfig: func(config *config.AppConfig) {
// Use a ridiculously small width so that we don't have to use so much test data
config.UserConfig.Git.Commit.AutoWrapWidth = 20
config.GetUserConfig().Git.Commit.AutoWrapWidth = 20
},
SetupRepo: func(shell *Shell) {
shell.CreateFile("file", "file content")

View File

@ -9,8 +9,8 @@ var CommitWipWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Commit with skip hook and config commitPrefix is defined. Prefix is ignored when creating WIP commits.",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(testConfig *config.AppConfig) {
testConfig.UserConfig.Git.CommitPrefixes = map[string]config.CommitPrefixConfig{"repo": {Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}}
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Git.CommitPrefixes = map[string]config.CommitPrefixConfig{"repo": {Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}}
},
SetupRepo: func(shell *Shell) {
shell.NewBranch("feature/TEST-002")

View File

@ -9,8 +9,8 @@ var CommitWithGlobalPrefix = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Commit with defined config commitPrefix",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(testConfig *config.AppConfig) {
testConfig.UserConfig.Git.CommitPrefix = &config.CommitPrefixConfig{Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Git.CommitPrefix = &config.CommitPrefixConfig{Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}
},
SetupRepo: func(shell *Shell) {
shell.NewBranch("feature/TEST-001")

View File

@ -9,8 +9,8 @@ var CommitWithNonMatchingBranchName = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Commit with defined config commitPrefixes",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(testConfig *config.AppConfig) {
testConfig.UserConfig.Git.CommitPrefix = &config.CommitPrefixConfig{
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Git.CommitPrefix = &config.CommitPrefixConfig{
Pattern: "^\\w+\\/(\\w+-\\w+).*",
Replace: "[$1]: ",
}

View File

@ -9,8 +9,8 @@ var CommitWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Commit with defined config commitPrefixes",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(testConfig *config.AppConfig) {
testConfig.UserConfig.Git.CommitPrefixes = map[string]config.CommitPrefixConfig{"repo": {Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}}
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Git.CommitPrefixes = map[string]config.CommitPrefixConfig{"repo": {Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}}
},
SetupRepo: func(shell *Shell) {
shell.NewBranch("feature/TEST-001")

View File

@ -10,7 +10,7 @@ var Highlight = NewIntegrationTest(NewIntegrationTestArgs{
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.AppState.GitLogShowGraph = "always"
config.GetAppState().GitLogShowGraph = "always"
config.GetUserConfig().Gui.AuthorColors = map[string]string{
"CI": "red",
}

View File

@ -10,7 +10,7 @@ var NewBranchWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(cfg *config.AppConfig) {
cfg.UserConfig.Git.BranchPrefix = "myprefix/"
cfg.GetUserConfig().Git.BranchPrefix = "myprefix/"
},
SetupRepo: func(shell *Shell) {
shell.

View File

@ -10,8 +10,8 @@ var PasteCommitMessage = NewIntegrationTest(NewIntegrationTestArgs{
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.UserConfig.OS.CopyToClipboardCmd = "echo {{text}} > ../clipboard"
config.UserConfig.OS.ReadFromClipboardCmd = "cat ../clipboard"
config.GetUserConfig().OS.CopyToClipboardCmd = "echo {{text}} > ../clipboard"
config.GetUserConfig().OS.ReadFromClipboardCmd = "cat ../clipboard"
},
SetupRepo: func(shell *Shell) {
shell.EmptyCommit("subject\n\nbody 1st line\nbody 2nd line")

View File

@ -10,8 +10,8 @@ var PasteCommitMessageOverExisting = NewIntegrationTest(NewIntegrationTestArgs{
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.UserConfig.OS.CopyToClipboardCmd = "echo {{text}} > ../clipboard"
config.UserConfig.OS.ReadFromClipboardCmd = "cat ../clipboard"
config.GetUserConfig().OS.CopyToClipboardCmd = "echo {{text}} > ../clipboard"
config.GetUserConfig().OS.ReadFromClipboardCmd = "cat ../clipboard"
},
SetupRepo: func(shell *Shell) {
shell.EmptyCommit("subject\n\nbody 1st line\nbody 2nd line")

View File

@ -15,7 +15,7 @@ var AccessCommitProperties = NewIntegrationTest(NewIntegrationTestArgs{
shell.EmptyCommit("my change")
},
SetupConfig: func(cfg *config.AppConfig) {
cfg.UserConfig.CustomCommands = []config.CustomCommand{
cfg.GetUserConfig().CustomCommands = []config.CustomCommand{
{
Key: "X",
Context: "commits",

View File

@ -13,7 +13,7 @@ var BasicCommand = NewIntegrationTest(NewIntegrationTestArgs{
shell.EmptyCommit("blah")
},
SetupConfig: func(cfg *config.AppConfig) {
cfg.UserConfig.CustomCommands = []config.CustomCommand{
cfg.GetUserConfig().CustomCommands = []config.CustomCommand{
{
Key: "a",
Context: "files",

View File

@ -14,7 +14,7 @@ var CheckForConflicts = NewIntegrationTest(NewIntegrationTestArgs{
shared.MergeConflictsSetup(shell)
},
SetupConfig: func(cfg *config.AppConfig) {
cfg.UserConfig.CustomCommands = []config.CustomCommand{
cfg.GetUserConfig().CustomCommands = []config.CustomCommand{
{
Key: "m",
Context: "localBranches",

Some files were not shown because too many files have changed in this diff Show More