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)) * [ ] 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) * [ ] 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)) * [ ] 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 * [ ] Docs have been updated if necessary
* [ ] You've read through your own file changes for silly mistakes etc * [ ] You've read through your own file changes for silly mistakes etc

View File

@ -1,6 +1,6 @@
# User Config # User Config
Default path for the config file: Default path for the global config file:
- Linux: `~/.config/lazygit/config.yml` - Linux: `~/.config/lazygit/config.yml`
- MacOS: `~/Library/Application\ Support/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"` - 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 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 ```yaml

View File

@ -12,7 +12,7 @@
* `pkg/commands/models`: Contains model structs that represent commits, branches, files, etc. * `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/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/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/constants`: Contains some constant strings (e.g. links to docs)
* `pkg/env`: Contains code relating to setting/getting environment variables * `pkg/env`: Contains code relating to setting/getting environment variables
* `pkg/i18n`: Contains internationalised strings * `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)`. 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 ## 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). 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/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d 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/kill v0.0.0-20220618033138-bfbe04675d10
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e 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/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 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/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.20240818082312-49cc572a9ffa h1:XZX6Rf60E3IuF1K+fvxjIr29f4p9kNY83mveGoJ5Uuo=
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/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 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo= 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= 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) { func NewCommon(config config.AppConfigurer) (*common.Common, error) {
userConfig := config.GetUserConfig() userConfig := config.GetUserConfig()
appState := config.GetAppState() appState := config.GetAppState()
var err error
log := newLogger(config) log := newLogger(config)
tr, err := i18n.NewTranslationSetFromConfig(log, userConfig.Gui.Language) // Initialize with English for the time being; the real translation set for
if err != nil { // the configured language will be read after reading the user config
return nil, err tr := i18n.EnglishTranslationSet()
}
return &common.Common{ cmn := &common.Common{
Log: log, Log: log,
Tr: tr, Tr: tr,
UserConfig: userConfig,
AppState: appState, AppState: appState,
Debug: config.GetDebug(), Debug: config.GetDebug(),
Fs: afero.NewOsFs(), Fs: afero.NewOsFs(),
}, nil }
cmn.SetUserConfig(userConfig)
return cmn, nil
} }
func newLogger(cfg config.AppConfigurer) *logrus.Entry { func newLogger(cfg config.AppConfigurer) *logrus.Entry {
@ -195,7 +193,7 @@ func (app *App) setupRepo(
var shouldInitRepo bool var shouldInitRepo bool
initialBranchArg := "" initialBranchArg := ""
switch app.UserConfig.NotARepository { switch app.UserConfig().NotARepository {
case "prompt": case "prompt":
// Offer to initialize a new repository in current directory. // Offer to initialize a new repository in current directory.
fmt.Print(app.Tr.CreateRepo) fmt.Print(app.Tr.CreateRepo)

View File

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

View File

@ -63,6 +63,11 @@ func generateAtDir(cheatsheetDir string) {
if err != nil { if err != nil {
log.Fatal(err) 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) mApp, _ := app.NewApp(mConfig, nil, common)
path := cheatsheetDir + "/Keybindings_" + lang + ".md" path := cheatsheetDir + "/Keybindings_" + lang + ".md"
file, err := os.Create(path) 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 { func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj {
branchLogCmdTemplate := self.UserConfig.Git.BranchLogCmd branchLogCmdTemplate := self.UserConfig().Git.BranchLogCmd
templateValues := map[string]string{ templateValues := map[string]string{
"branchName": self.cmd.Quote(branchName), "branchName": self.cmd.Quote(branchName),
} }
@ -236,7 +236,7 @@ func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
} }
cmdArgs := NewGitCmd("merge"). cmdArgs := NewGitCmd("merge").
Arg("--no-edit"). 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.FastForwardOnly, "--ff-only").
ArgIf(opts.Squash, "--squash", "--ff"). ArgIf(opts.Squash, "--squash", "--ff").
Arg(branchName). Arg(branchName).
@ -248,9 +248,9 @@ func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj { func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj {
// Only choose between non-empty, non-identical commands // Only choose between non-empty, non-identical commands
candidates := lo.Uniq(lo.WithoutEmpty(append([]string{ 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) 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 { onWorker(func() error {
return self.GetBehindBaseBranchValuesForAllBranches(branches, mainBranches, renderFunc) 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 { func (self *CommitCommands) CommitCmdObj(summary string, description string) oscommands.ICmdObj {
messageArgs := self.commitMessageArgs(summary, description) messageArgs := self.commitMessageArgs(summary, description)
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix skipHookPrefix := self.UserConfig().Git.SkipHookPrefix
cmdArgs := NewGitCmd("commit"). cmdArgs := NewGitCmd("commit").
ArgIf(skipHookPrefix != "" && strings.HasPrefix(summary, skipHookPrefix), "--no-verify"). ArgIf(skipHookPrefix != "" && strings.HasPrefix(summary, skipHookPrefix), "--no-verify").
@ -148,7 +148,7 @@ func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj {
} }
func (self *CommitCommands) signoffFlag() string { func (self *CommitCommands) signoffFlag() string {
if self.UserConfig.Git.Commit.SignOff { if self.UserConfig().Git.Commit.SignOff {
return "--signoff" return "--signoff"
} else { } else {
return "" return ""
@ -258,13 +258,13 @@ func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
func (self *CommitCommands) ShowCmdObj(hash string, filterPath string) oscommands.ICmdObj { func (self *CommitCommands) ShowCmdObj(hash string, filterPath string) oscommands.ICmdObj {
contextSize := self.AppState.DiffContextSize contextSize := self.AppState.DiffContextSize
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
cmdArgs := NewGitCmd("show"). cmdArgs := NewGitCmd("show").
Config("diff.noprefix=false"). Config("diff.noprefix=false").
ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd). ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd).
ArgIfElse(extDiffCmd != "", "--ext-diff", "--no-ext-diff"). ArgIfElse(extDiffCmd != "", "--ext-diff", "--no-ext-diff").
Arg("--submodule"). Arg("--submodule").
Arg("--color="+self.UserConfig.Git.Paging.ColorArg). Arg("--color="+self.UserConfig().Git.Paging.ColorArg).
Arg(fmt.Sprintf("--unified=%d", contextSize)). Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg("--stat"). Arg("--stat").
Arg("--decorate"). 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 := scenario.opts
opts.MainBranches = NewMainBranches(scenario.mainBranches, cmd) opts.MainBranches = NewMainBranches(common, cmd)
commits, err := builder.GetCommits(opts) commits, err := builder.GetCommits(opts)
assert.Equal(t, scenario.expectedCommits, commits) assert.Equal(t, scenario.expectedCommits, commits)

View File

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

View File

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

View File

@ -17,7 +17,7 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
} }
func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj { func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != "" useExtDiff := extDiffCmd != ""
return self.cmd.New( return self.cmd.New(
@ -26,7 +26,7 @@ func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
ConfigIf(useExtDiff, "diff.external="+extDiffCmd). ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff"). ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
Arg("--submodule"). Arg("--submodule").
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)). Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
Arg(diffArgs...). Arg(diffArgs...).
Dir(self.repoPaths.worktreePath). Dir(self.repoPaths.worktreePath).
ToArgv(), 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) { func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (string, error) {
editor := self.UserConfig.OS.EditCommand editor := self.UserConfig().OS.EditCommand
if editor == "" { if editor == "" {
editor = self.config.GetCoreEditor() editor = self.config.GetCoreEditor()
@ -60,7 +60,7 @@ func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (
"line": strconv.Itoa(lineNumber), "line": strconv.Itoa(lineNumber),
} }
editCmdTemplate := self.UserConfig.OS.EditCommandTemplate editCmdTemplate := self.UserConfig().OS.EditCommandTemplate
if len(editCmdTemplate) == 0 { if len(editCmdTemplate) == 0 {
switch editor { switch editor {
case "emacs", "nano", "vi", "vim", "nvim": 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) { func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) {
// Legacy support for old config; to be removed at some point // 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. // If multiple files are selected, we'll simply edit just the first one.
// It's not worth fixing this for the legacy support. // It's not worth fixing this for the legacy support.
if cmdStr, err := self.GetEditCmdStrLegacy(filenames[0], 1); err == nil { 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) }) quotedFilenames := lo.Map(filenames, func(filename string, _ int) string { return self.cmd.Quote(filename) })
templateValues := map[string]string{ 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) { func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (string, bool) {
// Legacy support for old config; to be removed at some point // 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 { if cmdStr, err := self.GetEditCmdStrLegacy(filename, lineNumber); err == nil {
return cmdStr, true 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{ templateValues := map[string]string{
"filename": self.cmd.Quote(filename), "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 { func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber int) string {
// Legacy support for old config; to be removed at some point // 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 { if cmdStr, err := self.GetEditCmdStrLegacy(filename, lineNumber); err == nil {
return cmdStr return cmdStr
} }
} }
template := config.GetEditAtLineAndWaitTemplate(&self.UserConfig.OS, self.guessDefaultEditor) template := config.GetEditAtLineAndWaitTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
templateValues := map[string]string{ templateValues := map[string]string{
"filename": self.cmd.Quote(filename), "filename": self.cmd.Quote(filename),
@ -136,7 +136,7 @@ func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber
} }
func (self *FileCommands) GetOpenDirInEditorCmdStr(path string) (string, bool) { 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{ templateValues := map[string]string{
"dir": self.cmd.Quote(path), "dir": self.cmd.Quote(path),

View File

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

View File

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

View File

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

View File

@ -84,7 +84,7 @@ func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
cmdArgs := NewGitCmd("stash").Arg("show"). cmdArgs := NewGitCmd("stash").Arg("show").
Arg("-p"). Arg("-p").
Arg("--stat"). 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)). Arg(fmt.Sprintf("--unified=%d", self.AppState.DiffContextSize)).
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space"). ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
Arg(fmt.Sprintf("--find-renames=%d%%", self.AppState.RenameSimilarityThreshold)). 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 { 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 := self.cmd.New(cmdArgs)
cmdObj.PromptOnCredentialRequest(task) cmdObj.PromptOnCredentialRequest(task)
@ -72,7 +72,7 @@ func (self *SyncCommands) Fetch(task gocui.Task) error {
} }
func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj { 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 := self.cmd.New(cmdArgs)
cmdObj.DontLog().FailOnCredentialRequest() cmdObj.DontLog().FailOnCredentialRequest()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
package common package common
import ( import (
"sync/atomic"
"github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -11,10 +13,18 @@ import (
type Common struct { type Common struct {
Log *logrus.Entry Log *logrus.Entry
Tr *i18n.TranslationSet Tr *i18n.TranslationSet
UserConfig *config.UserConfig userConfig atomic.Pointer[config.UserConfig]
AppState *config.AppState AppState *config.AppState
Debug bool Debug bool
// for interacting with the filesystem. We use afero rather than the default // for interacting with the filesystem. We use afero rather than the default
// `os` package for the sake of mocking the filesystem in tests // `os` package for the sake of mocking the filesystem in tests
Fs afero.Fs 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 ( import (
"fmt" "fmt"
"log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/adrg/xdg" "github.com/adrg/xdg"
"github.com/jesseduffield/lazygit/pkg/utils/yaml_utils" "github.com/jesseduffield/lazygit/pkg/utils/yaml_utils"
"github.com/samber/lo"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// AppConfig contains the base configuration fields required for lazygit. // AppConfig contains the base configuration fields required for lazygit.
type AppConfig struct { type AppConfig struct {
Debug bool `long:"debug" env:"DEBUG" default:"false"` debug bool `long:"debug" env:"DEBUG" default:"false"`
Version string `long:"version" env:"VERSION" default:"unversioned"` version string `long:"version" env:"VERSION" default:"unversioned"`
BuildDate string `long:"build-date" env:"BUILD_DATE"` buildDate string `long:"build-date" env:"BUILD_DATE"`
Name string `long:"name" env:"NAME" default:"lazygit"` name string `long:"name" env:"NAME" default:"lazygit"`
BuildSource string `long:"build-source" env:"BUILD_SOURCE" default:""` buildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
UserConfig *UserConfig userConfig *UserConfig
UserConfigPaths []string globalUserConfigFiles []*ConfigFile
DeafultConfFiles bool userConfigFiles []*ConfigFile
UserConfigDir string userConfigDir string
TempDir string tempDir string
AppState *AppState appState *AppState
IsNewRepo bool
} }
type AppConfigurer interface { type AppConfigurer interface {
@ -38,13 +40,29 @@ type AppConfigurer interface {
GetUserConfig() *UserConfig GetUserConfig() *UserConfig
GetUserConfigPaths() []string GetUserConfigPaths() []string
GetUserConfigDir() string GetUserConfigDir() string
ReloadUserConfig() error ReloadUserConfigForRepo(repoConfigFiles []*ConfigFile) error
ReloadChangedUserConfigFiles() (error, bool)
GetTempDir() string GetTempDir() string
GetAppState() *AppState GetAppState() *AppState
SaveAppState() error 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 // NewAppConfig makes a new app config
func NewAppConfig( func NewAppConfig(
name string, name string,
@ -60,17 +78,22 @@ func NewAppConfig(
return nil, err return nil, err
} }
var userConfigPaths []string var configFiles []*ConfigFile
customConfigFiles := os.Getenv("LG_CONFIG_FILE") customConfigFiles := os.Getenv("LG_CONFIG_FILE")
if customConfigFiles != "" { if customConfigFiles != "" {
// Load user defined config files // 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 { } else {
// Load default config files // 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 { if err != nil {
return nil, err return nil, err
} }
@ -92,26 +115,22 @@ func NewAppConfig(
} }
appConfig := &AppConfig{ appConfig := &AppConfig{
Name: name, name: name,
Version: version, version: version,
BuildDate: date, buildDate: date,
Debug: debuggingFlag, debug: debuggingFlag,
BuildSource: buildSource, buildSource: buildSource,
UserConfig: userConfig, userConfig: userConfig,
UserConfigPaths: userConfigPaths, globalUserConfigFiles: configFiles,
UserConfigDir: configDir, userConfigFiles: configFiles,
TempDir: tempDir, userConfigDir: configDir,
AppState: appState, tempDir: tempDir,
IsNewRepo: false, appState: appState,
} }
return appConfig, nil return appConfig, nil
} }
func isCustomConfigFile(path string) bool {
return path != filepath.Join(ConfigDir(), ConfigFilename)
}
func ConfigDir() string { func ConfigDir() string {
_, filePath := findConfigFile("config.yml") _, filePath := findConfigFile("config.yml")
@ -123,23 +142,31 @@ func findOrCreateConfigDir() (string, error) {
return folder, os.MkdirAll(folder, 0o755) return folder, os.MkdirAll(folder, 0o755)
} }
func loadUserConfigWithDefaults(configFiles []string) (*UserConfig, error) { func loadUserConfigWithDefaults(configFiles []*ConfigFile) (*UserConfig, error) {
return loadUserConfig(configFiles, GetDefaultConfig()) return loadUserConfig(configFiles, GetDefaultConfig())
} }
func loadUserConfig(configFiles []string, base *UserConfig) (*UserConfig, error) { func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, error) {
for _, path := range configFiles { for _, configFile := range configFiles {
if _, err := os.Stat(path); err != nil { path := configFile.Path
statInfo, err := os.Stat(path)
if err == nil {
configFile.exists = true
configFile.modDate = statInfo.ModTime()
} else {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
return nil, err return nil, err
} }
// if use has supplied their own custom config file path(s), we assume switch configFile.Policy {
// the files have already been created, so we won't go and create them here. case ConfigFilePolicyErrorIfMissing:
if isCustomConfigFile(path) {
return nil, err return nil, err
}
case ConfigFilePolicySkipIfMissing:
configFile.exists = false
continue
case ConfigFilePolicyCreateIfMissing:
file, err := os.Create(path) file, err := os.Create(path)
if err != nil { if err != nil {
if os.IsPermission(err) { if os.IsPermission(err) {
@ -149,6 +176,14 @@ func loadUserConfig(configFiles []string, base *UserConfig) (*UserConfig, error)
return nil, err return nil, err
} }
file.Close() file.Close()
configFile.exists = true
statInfo, err := os.Stat(configFile.Path)
if err != nil {
return nil, err
}
configFile.modDate = statInfo.ModTime()
}
} }
content, err := os.ReadFile(path) content, err := os.ReadFile(path)
@ -220,53 +255,81 @@ func changeNullKeybindingsToDisabled(changedContent []byte) ([]byte, error) {
} }
func (c *AppConfig) GetDebug() bool { func (c *AppConfig) GetDebug() bool {
return c.Debug return c.debug
} }
func (c *AppConfig) GetVersion() string { func (c *AppConfig) GetVersion() string {
return c.Version return c.version
} }
func (c *AppConfig) GetName() string { func (c *AppConfig) GetName() string {
return c.Name return c.name
} }
// GetBuildSource returns the source of the build. For builds from goreleaser // GetBuildSource returns the source of the build. For builds from goreleaser
// this will be binaryBuild // this will be binaryBuild
func (c *AppConfig) GetBuildSource() string { func (c *AppConfig) GetBuildSource() string {
return c.BuildSource return c.buildSource
} }
// GetUserConfig returns the user config // GetUserConfig returns the user config
func (c *AppConfig) GetUserConfig() *UserConfig { func (c *AppConfig) GetUserConfig() *UserConfig {
return c.UserConfig return c.userConfig
} }
// GetAppState returns the app state // GetAppState returns the app state
func (c *AppConfig) GetAppState() *AppState { func (c *AppConfig) GetAppState() *AppState {
return c.AppState return c.appState
} }
func (c *AppConfig) GetUserConfigPaths() []string { 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 { func (c *AppConfig) GetUserConfigDir() string {
return c.UserConfigDir return c.userConfigDir
} }
func (c *AppConfig) ReloadUserConfig() error { func (c *AppConfig) ReloadUserConfigForRepo(repoConfigFiles []*ConfigFile) error {
userConfig, err := loadUserConfigWithDefaults(c.UserConfigPaths) configFiles := append(c.globalUserConfigFiles, repoConfigFiles...)
userConfig, err := loadUserConfigWithDefaults(configFiles)
if err != nil { if err != nil {
return err return err
} }
c.UserConfig = userConfig c.userConfig = userConfig
c.userConfigFiles = configFiles
return nil 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 { func (c *AppConfig) GetTempDir() string {
return c.TempDir return c.tempDir
} }
// findConfigFile looks for a possibly existing config file. // 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)) 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 // SaveAppState marshalls the AppState struct and writes it to the disk
func (c *AppConfig) SaveAppState() error { func (c *AppConfig) SaveAppState() error {
marshalledAppState, err := yaml.Marshal(c.AppState) marshalledAppState, err := yaml.Marshal(c.appState)
if err != nil { if err != nil {
return err return err
} }
@ -363,6 +421,24 @@ func loadAppState() (*AppState, error) {
return appState, nil 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 // AppState stores data between runs of the app like when the last update check
// was performed and which other repos have been checked out // was performed and which other repos have been checked out
type AppState struct { type AppState struct {

View File

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

View File

@ -25,7 +25,7 @@ func (self *BackgroundRoutineMgr) PauseBackgroundRefreshes(pause bool) {
} }
func (self *BackgroundRoutineMgr) startBackgroundRoutines() { func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
userConfig := self.gui.UserConfig userConfig := self.gui.UserConfig()
if userConfig.Git.AutoFetch { if userConfig.Git.AutoFetch {
fetchInterval := userConfig.Refresher.FetchInterval fetchInterval := userConfig.Refresher.FetchInterval
@ -77,7 +77,7 @@ func (self *BackgroundRoutineMgr) startBackgroundFetch() {
self.gui.waitForIntro.Wait() self.gui.waitForIntro.Wait()
isNew := self.gui.IsNewRepo isNew := self.gui.IsNewRepo
userConfig := self.gui.UserConfig userConfig := self.gui.UserConfig()
if !isNew { if !isNew {
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second) 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() { func (gui *Gui) printCommandLogHeader() {
introStr := fmt.Sprintf( introStr := fmt.Sprintf(
gui.c.Tr.CommandLogHeader, 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)) fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr))
if gui.c.UserConfig.Gui.ShowRandomTip { if gui.c.UserConfig().Gui.ShowRandomTip {
fmt.Fprintf( fmt.Fprintf(
gui.Views.Extras, gui.Views.Extras,
"%s: %s", "%s: %s",
@ -70,7 +70,7 @@ func (gui *Gui) printCommandLogHeader() {
} }
func (gui *Gui) getRandomTip() string { func (gui *Gui) getRandomTip() string {
config := gui.c.UserConfig.Keybinding config := gui.c.UserConfig().Keybinding
formattedKey := func(key string) string { formattedKey := func(key string) string {
return keybindings.Label(key) return keybindings.Label(key)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,7 +26,7 @@ func NewRemotesContext(c *ContextCommon) *RemotesContext {
getDisplayStrings := func(_ int, _ int) [][]string { getDisplayStrings := func(_ int, _ int) [][]string {
return presentation.GetRemoteListDisplayStrings( 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{ 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) { func (self *SearchTrait) RenderSearchStatus(index int, total int) {
keybindingConfig := self.c.UserConfig.Keybinding keybindingConfig := self.c.UserConfig().Keybinding
if total == 0 { if total == 0 {
self.c.SetViewContent( self.c.SetViewContent(

View File

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

View File

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

View File

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

View File

@ -311,8 +311,8 @@ func (self *BasicCommitsController) canCopyCommits(selectedCommits []*models.Com
func (self *BasicCommitsController) handleOldCherryPickKey() error { func (self *BasicCommitsController) handleOldCherryPickKey() error {
msg := utils.ResolvePlaceholderString(self.c.Tr.OldCherryPickKeyWarning, msg := utils.ResolvePlaceholderString(self.c.Tr.OldCherryPickKeyWarning,
map[string]string{ map[string]string{
"copy": keybindings.Label(self.c.UserConfig.Keybinding.Commits.CherryPickCopy), "copy": keybindings.Label(self.c.UserConfig().Keybinding.Commits.CherryPickCopy),
"paste": keybindings.Label(self.c.UserConfig.Keybinding.Commits.PasteCommits), "paste": keybindings.Label(self.c.UserConfig().Keybinding.Commits.PasteCommits),
}) })
return errors.New(msg) 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) return false, errors.New(self.c.Tr.CommitWithoutMessageErr)
} }
if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage { if self.c.UserConfig().Git.Commit.AutoWrapCommitMessage {
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth) commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig().Git.Commit.AutoWrapWidth)
} }
self.c.Helpers().Commits.UpdateCommitPanelView(commitMessage) self.c.Helpers().Commits.UpdateCommitPanelView(commitMessage)
return true, nil return true, nil

View File

@ -46,7 +46,7 @@ func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) [
// We assume that whenever things are deletable, they // We assume that whenever things are deletable, they
// are also editable, so we show both keybindings // are also editable, so we show both keybindings
subtitle = fmt.Sprintf(self.c.Tr.SuggestionsSubtitle, 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 self.c.Views().Suggestions.Subtitle = subtitle
return self.c.Context().Replace(self.c.Contexts().Suggestions) 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 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() mainShowsStaged := !split && node.GetHasStagedChanges()
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged) 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}}) 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( Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.DiscardAllTooltip, self.c.Tr.DiscardAllTooltip,
map[string]string{ map[string]string{

View File

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

View File

@ -173,7 +173,7 @@ func (self *ConfirmationHelper) prepareConfirmationPanel(
suggestionsView.FgColor = theme.GocuiDefaultTextColor suggestionsView.FgColor = theme.GocuiDefaultTextColor
suggestionsContext.SetSuggestions(opts.FindSuggestionsFunc("")) suggestionsContext.SetSuggestions(opts.FindSuggestionsFunc(""))
suggestionsView.Visible = true 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 = "" suggestionsView.Subtitle = ""
} }

View File

@ -46,7 +46,7 @@ func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus str
if err := cmdObj.StreamOutput().Run(); err != nil { if err := cmdObj.StreamOutput().Run(); err != nil {
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) _ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return fmt.Errorf( 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 { if err != nil {
return nil, err 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 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 self.contextsWithInlineStatus[opts.ContextKey] = info
go utils.Safe(func() { 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() defer ticker.Stop()
outer: outer:
for { 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' // 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 // 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 // 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 // tasks whose output the user will want to see in the terminal
(status == enums.REBASE_MODE_REBASING && command != REBASE_OPTION_ABORT && self.hasExecTodos()) (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 { if err = self.CheckMergeOrRebase(err); err != nil {
return err 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, "selectedRef": refName,
"currentBranch": checkedOutBranchName, "currentBranch": checkedOutBranchName,
}) })

View File

@ -737,7 +737,7 @@ func (self *RefreshHelper) refreshStatus() {
repoName := self.c.Git().RepoPaths.RepoName() 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) 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 == "" { if suggestedBranchName == "" {
suggestedBranchName = self.c.UserConfig.Git.BranchPrefix suggestedBranchName = self.c.UserConfig().Git.BranchPrefix
} }
return self.c.Prompt(types.PromptOpts{ 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) self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix)
promptView := self.promptView() 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))) 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: case types.IFilterableContext:
context.SetSelection(0) context.SetSelection(0)
_ = context.GetView().SetOriginY(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) _ = self.c.PostRefreshUpdate(context)
case types.ISearchableContext: case types.ISearchableContext:
// do nothing // do nothing
@ -246,7 +246,7 @@ func (self *SearchHelper) ReApplyFilter(context types.Context) {
filterableContext.SetSelection(0) filterableContext.SetSelection(0)
_ = filterableContext.GetView().SetOriginY(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 { func (self *SuggestionsHelper) GetRemoteSuggestionsFunc() func(string) []*types.Suggestion {
remoteNames := self.getRemoteNames() remoteNames := self.getRemoteNames()
return FilterFunc(remoteNames, self.c.UserConfig.Gui.UseFuzzySearch()) return FilterFunc(remoteNames, self.c.UserConfig().Gui.UseFuzzySearch())
} }
func (self *SuggestionsHelper) getBranchNames() []string { func (self *SuggestionsHelper) getBranchNames() []string {
@ -83,7 +83,7 @@ func (self *SuggestionsHelper) GetBranchNameSuggestionsFunc() func(string) []*ty
if input == "" { if input == "" {
matchingBranchNames = branchNames matchingBranchNames = branchNames
} else { } 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 { 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 { return func(input string) []*types.Suggestion {
matchingNames := []string{} 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 { _ = self.c.Model().FilesTrie.VisitFuzzy(patricia.Prefix(input), true, func(prefix patricia.Prefix, item patricia.Item, skipped int) error {
matchingNames = append(matchingNames, item.(string)) matchingNames = append(matchingNames, item.(string))
return nil return nil
@ -163,7 +163,7 @@ func (self *SuggestionsHelper) getRemoteBranchNames(separator string) []string {
} }
func (self *SuggestionsHelper) GetRemoteBranchesSuggestionsFunc(separator string) func(string) []*types.Suggestion { 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 { func (self *SuggestionsHelper) getTagNames() []string {
@ -175,7 +175,7 @@ func (self *SuggestionsHelper) getTagNames() []string {
func (self *SuggestionsHelper) GetTagsSuggestionsFunc() func(string) []*types.Suggestion { func (self *SuggestionsHelper) GetTagsSuggestionsFunc() func(string) []*types.Suggestion {
tagNames := self.getTagNames() 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 { 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...) 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 { func (self *SuggestionsHelper) GetAuthorsSuggestionsFunc() func(string) []*types.Suggestion {
@ -196,7 +196,7 @@ func (self *SuggestionsHelper) GetAuthorsSuggestionsFunc() func(string) []*types
slices.Sort(authors) 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 { 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, self.c.Tr.ForceTagPrompt,
map[string]string{ map[string]string{
"tagName": tagName, "tagName": tagName,
"cancelKey": self.c.UserConfig.Keybinding.Universal.Return, "cancelKey": self.c.UserConfig().Keybinding.Universal.Return,
"confirmKey": self.c.UserConfig.Keybinding.Universal.Confirm, "confirmKey": self.c.UserConfig().Keybinding.Universal.Confirm,
}, },
) )
return self.c.Confirm(types.ConfirmOpts{ return self.c.Confirm(types.ConfirmOpts{

View File

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

View File

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

View File

@ -136,7 +136,7 @@ func (self *WorkingTreeHelper) HandleCommitEditorPress() error {
} }
func (self *WorkingTreeHelper) HandleWIPCommitPress() error { func (self *WorkingTreeHelper) HandleWIPCommitPress() error {
skipHookPrefix := self.c.UserConfig.Git.SkipHookPrefix skipHookPrefix := self.c.UserConfig().Git.SkipHookPrefix
if skipHookPrefix == "" { if skipHookPrefix == "" {
return errors.New(self.c.Tr.SkipHookPrefixNotConfigured) return errors.New(self.c.Tr.SkipHookPrefixNotConfigured)
} }
@ -209,7 +209,7 @@ func (self *WorkingTreeHelper) syncRefresh() error {
func (self *WorkingTreeHelper) prepareFilesForCommit() error { func (self *WorkingTreeHelper) prepareFilesForCommit() error {
noStagedFiles := !self.AnyStagedFiles() 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) self.c.LogAction(self.c.Tr.Actions.StageAllFiles)
err := self.c.Git().WorkingTree.StageAll() err := self.c.Git().WorkingTree.StageAll()
if err != nil { if err != nil {
@ -223,10 +223,10 @@ func (self *WorkingTreeHelper) prepareFilesForCommit() error {
} }
func (self *WorkingTreeHelper) commitPrefixConfigForRepo() *config.CommitPrefixConfig { 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 { if ok {
return &cfg 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 { func (self *ListController) HandleScrollUp() error {
scrollHeight := self.c.UserConfig.Gui.ScrollHeight scrollHeight := self.c.UserConfig().Gui.ScrollHeight
self.context.GetViewTrait().ScrollUp(scrollHeight) self.context.GetViewTrait().ScrollUp(scrollHeight)
if self.context.RenderOnlyVisibleLines() { if self.context.RenderOnlyVisibleLines() {
return self.context.HandleRender() return self.context.HandleRender()
@ -61,7 +61,7 @@ func (self *ListController) HandleScrollUp() error {
} }
func (self *ListController) HandleScrollDown() error { func (self *ListController) HandleScrollDown() error {
scrollHeight := self.c.UserConfig.Gui.ScrollHeight scrollHeight := self.c.UserConfig().Gui.ScrollHeight
self.context.GetViewTrait().ScrollDown(scrollHeight) self.context.GetViewTrait().ScrollDown(scrollHeight)
if self.context.RenderOnlyVisibleLines() { if self.context.RenderOnlyVisibleLines() {
return self.context.HandleRender() return self.context.HandleRender()
@ -106,10 +106,10 @@ func (self *ListController) handleLineChangeAux(f func(int), change int) error {
cursorMoved := before != after cursorMoved := before != after
if cursorMoved { if cursorMoved {
if change == -1 { 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)) self.context.ModelIndexToViewIndex(before), self.context.ModelIndexToViewIndex(after))
} else if change == 1 { } 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)) 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 { if err != nil {
return err return err
} }
if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage { if self.c.UserConfig().Git.Commit.AutoWrapCommitMessage {
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth) commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig().Git.Commit.AutoWrapWidth)
} }
return self.c.Helpers().Commits.OpenCommitMessagePanel( return self.c.Helpers().Commits.OpenCommitMessagePanel(
&helpers.OpenCommitMessagePanelOpts{ &helpers.OpenCommitMessagePanelOpts{
@ -438,7 +438,7 @@ func (self *LocalCommitsController) doRewordEditor() error {
} }
func (self *LocalCommitsController) rewordEditor(commit *models.Commit) error { func (self *LocalCommitsController) rewordEditor(commit *models.Commit) error {
if self.c.UserConfig.Gui.SkipRewordInEditorWarning { if self.c.UserConfig().Gui.SkipRewordInEditorWarning {
return self.doRewordEditor() return self.doRewordEditor()
} else { } else {
return self.c.Confirm(types.ConfirmOpts{ return self.c.Confirm(types.ConfirmOpts{
@ -564,7 +564,7 @@ func (self *LocalCommitsController) findCommitForQuickStartInteractiveRebase() (
if !ok || index == 0 { if !ok || index == 0 {
errorMsg := utils.ResolvePlaceholderString(self.c.Tr.CannotQuickStartInteractiveRebase, map[string]string{ 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) return nil, errors.New(errorMsg)
@ -905,8 +905,8 @@ func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, inc
if err != nil { if err != nil {
return err return err
} }
if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage { if self.c.UserConfig().Git.Commit.AutoWrapCommitMessage {
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth) commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig().Git.Commit.AutoWrapWidth)
} }
originalSubject, _, _ := strings.Cut(commitMessage, "\n") originalSubject, _, _ := strings.Cut(commitMessage, "\n")
return self.c.Helpers().Commits.OpenCommitMessagePanel( return self.c.Helpers().Commits.OpenCommitMessagePanel(

View File

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

View File

@ -173,7 +173,7 @@ func (self *PatchExplorerController) HandlePrevLine() error {
after := self.context.GetState().GetSelectedLineIdx() after := self.context.GetState().GetSelectedLineIdx()
if self.context.GetState().SelectingLine() { 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 return nil
@ -185,7 +185,7 @@ func (self *PatchExplorerController) HandleNextLine() error {
after := self.context.GetState().GetSelectedLineIdx() after := self.context.GetState().GetSelectedLineIdx()
if self.context.GetState().SelectingLine() { 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 return nil

View File

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

View File

@ -60,7 +60,7 @@ func (self *ShellCommandAction) GetShellCommandsHistorySuggestionsFunc() func(st
return func(input string) []*types.Suggestion { return func(input string) []*types.Suggestion {
history := self.c.GetAppState().ShellCommandsHistory 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 { func (self *StagingController) DiscardSelection() error {
reset := func() error { return self.applySelectionAndRefresh(true) } 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{ return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.DiscardChangeTitle, Title: self.c.Tr.DiscardChangeTitle,
Prompt: self.c.Tr.DiscardChangePrompt, Prompt: self.c.Tr.DiscardChangePrompt,

View File

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

View File

@ -89,15 +89,15 @@ func (self *StatusController) onClickMain(opts gocui.ViewMouseBindingOpts) error
} }
func (self *StatusController) GetOnRenderToMain() func() error { func (self *StatusController) GetOnRenderToMain() func() error {
config := self.c.UserConfig.Gui return func() error {
switch self.c.UserConfig().Gui.StatusPanelView {
switch config.StatusPanelView {
case "dashboard": case "dashboard":
return self.showDashboard return self.showDashboard()
case "allBranchesLog": case "allBranchesLog":
return self.showAllBranchLogs return self.showAllBranchLogs()
default: default:
return self.showDashboard return self.showDashboard()
}
} }
} }
@ -117,7 +117,7 @@ func (self *StatusController) onClick(opts gocui.ViewMouseBindingOpts) error {
return err 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() repoName := self.c.Git().RepoPaths.RepoName()
workingTreeState := self.c.Git().Status.WorkingTreeState() workingTreeState := self.c.Git().Status.WorkingTreeState()
switch 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) return errors.New(self.c.Tr.UpdatesRejected)
} }
forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing forcePushDisabled := self.c.UserConfig().Git.DisableForcePushing
if forcePushDisabled { if forcePushDisabled {
return errors.New(self.c.Tr.UpdatesRejectedAndForcePushDisabled) 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 { func (self *SyncController) requestToForcePush(currentBranch *models.Branch, opts pushOpts) error {
forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing forcePushDisabled := self.c.UserConfig().Git.DisableForcePushing
if forcePushDisabled { if forcePushDisabled {
return errors.New(self.c.Tr.ForcePushDisabled) return errors.New(self.c.Tr.ForcePushDisabled)
} }
@ -252,8 +252,8 @@ func (self *SyncController) forcePushPrompt() string {
return utils.ResolvePlaceholderString( return utils.ResolvePlaceholderString(
self.c.Tr.ForcePushPrompt, self.c.Tr.ForcePushPrompt,
map[string]string{ map[string]string{
"cancelKey": self.c.UserConfig.Keybinding.Universal.Return, "cancelKey": self.c.UserConfig().Keybinding.Universal.Return,
"confirmKey": self.c.UserConfig.Keybinding.Universal.Confirm, "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 { 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 return nil
} }
func (self *VerticalScrollController) HandleScrollDown() error { func (self *VerticalScrollController) HandleScrollDown() error {
scrollHeight := self.c.UserConfig.Gui.ScrollHeight scrollHeight := self.c.UserConfig().Gui.ScrollHeight
self.context.GetViewTrait().ScrollDown(scrollHeight) self.context.GetViewTrait().ScrollDown(scrollHeight)
if manager, ok := (*self.viewBufferManagerMap)[self.context.GetViewName()]; ok { if manager, ok := (*self.viewBufferManagerMap)[self.context.GetViewName()]; ok {

View File

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

View File

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

View File

@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"path/filepath"
"reflect"
"sort" "sort"
"strings" "strings"
"sync" "sync"
@ -35,6 +37,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/status" "github.com/jesseduffield/lazygit/pkg/gui/status"
"github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/integration/components" "github.com/jesseduffield/lazygit/pkg/integration/components"
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
"github.com/jesseduffield/lazygit/pkg/tasks" "github.com/jesseduffield/lazygit/pkg/tasks"
@ -137,6 +140,8 @@ type Gui struct {
c *helpers.HelperCommon c *helpers.HelperCommon
helpers *helpers.Helpers helpers *helpers.Helpers
previousLanguageConfig string
integrationTest integrationTypes.IntegrationTest integrationTest integrationTypes.IntegrationTest
afterLayoutFuncs chan func() error afterLayoutFuncs chan func() error
@ -307,6 +312,16 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context
return err 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) contextToPush := gui.resetState(startArgs)
gui.resetHelpersAndControllers() gui.resetHelpersAndControllers()
@ -317,8 +332,28 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context
gui.g.SetFocusHandler(func(Focused bool) error { gui.g.SetFocusHandler(func(Focused bool) error {
if Focused { 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") 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 return nil
@ -342,6 +377,119 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context
return nil 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 // resetState reuses the repo state from our repo state map, if the repo was
// open before; otherwise it creates a new one. // open before; otherwise it creates a new one.
func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context { 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(), BisectInfo: git_commands.NewNullBisectInfo(),
FilesTrie: patricia.NewTrie(), FilesTrie: patricia.NewTrie(),
Authors: map[string]*models.Author{}, 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{ Modes: &types.Modes{
Filtering: filtering.New(startArgs.FilterPath, ""), Filtering: filtering.New(startArgs.FilterPath, ""),
@ -478,10 +626,10 @@ func NewGui(
RepoStateMap: map[Repo]*GuiRepoState{}, RepoStateMap: map[Repo]*GuiRepoState{},
GuiLog: []string{}, GuiLog: []string{},
// originally we could only hide the command log permanently via the config // initializing this to true for the time being; it will be reset to the
// but now we do it via state. So we need to still support the config for the // real value after loading the user config:
// sake of backwards compatibility. We're making use of short circuiting here ShowExtrasWindow: true,
ShowExtrasWindow: cmn.UserConfig.Gui.ShowCommandLog && !config.GetAppState().HideCommandLog,
Mutexes: types.Mutexes{ Mutexes: types.Mutexes{
RefreshingFilesMutex: &deadlock.Mutex{}, RefreshingFilesMutex: &deadlock.Mutex{},
RefreshingBranchesMutex: &deadlock.Mutex{}, RefreshingBranchesMutex: &deadlock.Mutex{},
@ -538,14 +686,6 @@ func NewGui(
// TODO: reset these controllers upon changing repos due to state changing // TODO: reset these controllers upon changing repos due to state changing
gui.c = helperCommon 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.BackgroundRoutineMgr = &BackgroundRoutineMgr{gui: gui}
gui.stateAccessor = &StateAccessor{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. // breakpoints and stepping through code can easily take more than 30s.
deadlock.Opts.Disable = !gui.Debug || os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) != "" 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.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)) gui.g.SetManager(gocui.ManagerFunc(gui.layout))
@ -735,7 +857,7 @@ func (gui *Gui) RunAndHandleError(startArgs appTypes.StartArgs) error {
} }
func (gui *Gui) checkForDeprecatedEditConfigs() { func (gui *Gui) checkForDeprecatedEditConfigs() {
osConfig := &gui.UserConfig.OS osConfig := &gui.UserConfig().OS
deprecatedConfigs := []struct { deprecatedConfigs := []struct {
config string config string
oldName string oldName string
@ -934,16 +1056,14 @@ func (gui *Gui) showBreakingChangesMessage() {
} }
// setColorScheme sets the color scheme for the app based on the user config // setColorScheme sets the color scheme for the app based on the user config
func (gui *Gui) setColorScheme() error { func (gui *Gui) setColorScheme() {
userConfig := gui.UserConfig userConfig := gui.UserConfig()
theme.UpdateTheme(userConfig.Gui.Theme) theme.UpdateTheme(userConfig.Gui.Theme)
gui.g.FgColor = theme.InactiveBorderColor gui.g.FgColor = theme.InactiveBorderColor
gui.g.SelFgColor = theme.ActiveBorderColor gui.g.SelFgColor = theme.ActiveBorderColor
gui.g.FrameColor = theme.InactiveBorderColor gui.g.FrameColor = theme.InactiveBorderColor
gui.g.SelFrameColor = theme.ActiveBorderColor gui.g.SelFrameColor = theme.ActiveBorderColor
return nil
} }
func (gui *Gui) onUIThread(f func() error) { 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 { func (self *Gui) keybindingOpts() types.KeybindingsOpts {
config := self.c.UserConfig.Keybinding config := self.c.UserConfig().Keybinding
guards := types.KeybindingGuards{ guards := types.KeybindingGuards{
OutsideFilterMode: self.outsideFilterMode, OutsideFilterMode: self.outsideFilterMode,

View File

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

View File

@ -324,7 +324,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
for i, s := range scenarios { for i, s := range scenarios {
icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", "")) icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", ""))
c.UserConfig.Gui.ShowDivergenceFromBaseBranch = s.showDivergenceCfg c.UserConfig().Gui.ShowDivergenceFromBaseBranch = s.showDivergenceCfg
worktrees := []*models.Worktree{} worktrees := []*models.Worktree{}
if s.checkedOutByWorktree { if s.checkedOutByWorktree {
@ -332,7 +332,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
} }
t.Run(fmt.Sprintf("getBranchDisplayStrings_%d", i), func(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) assert.Equal(t, s.expected, strings)
}) })
} }

View File

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

View File

@ -1,7 +1,7 @@
package custom_commands package custom_commands
import ( 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/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/types" "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. // 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. // See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Command_Keybindings.md for more info.
type Client struct { type Client struct {
customCommands []config.CustomCommand c *common.Common
handlerCreator *HandlerCreator handlerCreator *HandlerCreator
keybindingCreator *KeybindingCreator keybindingCreator *KeybindingCreator
} }
@ -26,10 +26,9 @@ func NewClient(
helpers.MergeAndRebase, helpers.MergeAndRebase,
) )
keybindingCreator := NewKeybindingCreator(c) keybindingCreator := NewKeybindingCreator(c)
customCommands := c.UserConfig.CustomCommands
return &Client{ return &Client{
customCommands: customCommands, c: c.Common,
keybindingCreator: keybindingCreator, keybindingCreator: keybindingCreator,
handlerCreator: handlerCreator, handlerCreator: handlerCreator,
} }
@ -37,7 +36,7 @@ func NewClient(
func (self *Client) GetCustomCommandKeybindings() ([]*types.Binding, error) { func (self *Client) GetCustomCommandKeybindings() ([]*types.Binding, error) {
bindings := []*types.Binding{} bindings := []*types.Binding{}
for _, customCommand := range self.customCommands { for _, customCommand := range self.c.UserConfig().CustomCommands {
handler := self.handlerCreator.call(customCommand) handler := self.handlerCreator.call(customCommand)
compoundBindings, err := self.keybindingCreator.call(customCommand, handler) compoundBindings, err := self.keybindingCreator.call(customCommand, handler)
if err != nil { if err != nil {

View File

@ -73,26 +73,12 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
} }
func (gui *Gui) createAllViews() error { 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 var err error
for _, mapping := range gui.orderedViewNameMappings() { for _, mapping := range gui.orderedViewNameMappings() {
*mapping.viewPtr, err = gui.prepareView(mapping.name) *mapping.viewPtr, err = gui.prepareView(mapping.name)
if err != nil && !gocui.IsUnknownView(err) { if err != nil && !gocui.IsUnknownView(err) {
return 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 gui.Views.Options.Frame = false
@ -131,7 +117,6 @@ func (gui *Gui) createAllViews() error {
view.Title = gui.c.Tr.DiffTitle view.Title = gui.c.Tr.DiffTitle
view.Wrap = true view.Wrap = true
view.IgnoreCarriageReturns = true view.IgnoreCarriageReturns = true
view.CanScrollPastBottom = gui.c.UserConfig.Gui.ScrollPastBottom
} }
gui.Views.Staging.Title = gui.c.Tr.UnstagedChanges 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.Visible = false
gui.Views.CommitDescription.Title = gui.c.Tr.CommitDescriptionTitle gui.Views.CommitDescription.Title = gui.c.Tr.CommitDescriptionTitle
gui.Views.CommitDescription.FgColor = theme.GocuiDefaultTextColor
gui.Views.CommitDescription.Editable = true gui.Views.CommitDescription.Editable = true
gui.Views.CommitDescription.Editor = gocui.EditorFunc(gui.commitDescriptionEditor) 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.Visible = false
gui.Views.Confirmation.Editor = gocui.EditorFunc(gui.promptEditor) 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.Title = gui.c.Tr.SnakeTitle
gui.Views.Snake.FgColor = gocui.ColorGreen gui.Views.Snake.FgColor = gocui.ColorGreen
if gui.c.UserConfig.Gui.ShowPanelJumps { return nil
jumpBindings := gui.c.UserConfig.Keybinding.Universal.JumpToBlock }
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 { jumpLabels := lo.Map(jumpBindings, func(binding string, _ int) string {
return fmt.Sprintf("[%s]", binding) return fmt.Sprintf("[%s]", binding)
}) })
@ -212,7 +225,20 @@ func (gui *Gui) createAllViews() error {
gui.Views.ReflogCommits.TitlePrefix = jumpLabels[3] gui.Views.ReflogCommits.TitlePrefix = jumpLabels[3]
gui.Views.Stash.TitlePrefix = jumpLabels[4] 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 MergeToolPrompt string
IntroPopupMessage string IntroPopupMessage string
DeprecatedEditConfigWarning string DeprecatedEditConfigWarning string
NonReloadableConfigWarningTitle string
NonReloadableConfigWarning string
GitconfigParseErr string GitconfigParseErr string
EditFile string EditFile string
EditFileTooltip 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 // exporting this so we can use it in tests
func EnglishTranslationSet() *TranslationSet { func EnglishTranslationSet() *TranslationSet {
return &TranslationSet{ return &TranslationSet{
@ -1199,6 +1205,8 @@ func EnglishTranslationSet() *TranslationSet {
MergeToolPrompt: "Are you sure you want to open `git mergetool`?", MergeToolPrompt: "Are you sure you want to open `git mergetool`?",
IntroPopupMessage: englishIntroPopupMessage, IntroPopupMessage: englishIntroPopupMessage,
DeprecatedEditConfigWarning: englishDeprecatedEditConfigWarning, 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.`, GitconfigParseErr: `Gogit failed to parse your gitconfig file due to the presence of unquoted '\' characters. Removing these should fix the issue.`,
EditFile: `Edit file`, EditFile: `Edit file`,
EditFileTooltip: "Open file in external editor.", EditFileTooltip: "Open file in external editor.",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ var AutoWrapMessage = NewIntegrationTest(NewIntegrationTestArgs{
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) { SetupConfig: func(config *config.AppConfig) {
// Use a ridiculously small width so that we don't have to use so much test data // 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) { SetupRepo: func(shell *Shell) {
shell.CreateFile("file", "file content") 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.", Description: "Commit with skip hook and config commitPrefix is defined. Prefix is ignored when creating WIP commits.",
ExtraCmdArgs: []string{}, ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(testConfig *config.AppConfig) { SetupConfig: func(cfg *config.AppConfig) {
testConfig.UserConfig.Git.CommitPrefixes = map[string]config.CommitPrefixConfig{"repo": {Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}} cfg.GetUserConfig().Git.CommitPrefixes = map[string]config.CommitPrefixConfig{"repo": {Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}}
}, },
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {
shell.NewBranch("feature/TEST-002") shell.NewBranch("feature/TEST-002")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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