From 93f2961e4db74d266b5547a68e3a63d9c68fe99f Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Wed, 28 Aug 2024 22:18:31 +0200 Subject: [PATCH] Add config git.autoStageResolvedConflicts --- docs/Config.md | 6 ++ pkg/config/user_config.go | 6 ++ pkg/gui/controllers/helpers/refresh_helper.go | 48 +++++------ .../tests/conflicts/resolve_no_auto_stage.go | 81 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + schema/config.json | 5 ++ 6 files changed, 124 insertions(+), 23 deletions(-) create mode 100644 pkg/integration/tests/conflicts/resolve_no_auto_stage.go diff --git a/docs/Config.md b/docs/Config.md index ce0c4bee1f6..b4c4a92d6a0 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -305,6 +305,12 @@ git: # If true, pass the --all arg to git fetch fetchAll: true + # If true, lazygit will automatically stage files that used to have merge + # conflicts but no longer do; and it will also ask you if you want to + # continue a merge or rebase if you've resolved all conflicts. If false, it + # won't do either of these things. + autoStageResolvedConflicts: true + # Command used when displaying the current branch git log in the main window branchLogCmd: git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} -- diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index d74174376f7..01c546048c2 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -224,6 +224,11 @@ type GitConfig struct { AutoRefresh bool `yaml:"autoRefresh"` // If true, pass the --all arg to git fetch FetchAll bool `yaml:"fetchAll"` + // If true, lazygit will automatically stage files that used to have merge + // conflicts but no longer do; and it will also ask you if you want to + // continue a merge or rebase if you've resolved all conflicts. If false, it + // won't do either of these things. + AutoStageResolvedConflicts bool `yaml:"autoStageResolvedConflicts"` // Command used when displaying the current branch git log in the main window BranchLogCmd string `yaml:"branchLogCmd"` // Command used to display git log of all branches in the main window. @@ -753,6 +758,7 @@ func GetDefaultConfig() *UserConfig { AutoFetch: true, AutoRefresh: true, FetchAll: true, + AutoStageResolvedConflicts: true, BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --", AllBranchesLogCmd: "git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium", DisableForcePushing: false, diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go index c43c3ddabb7..24f10fcb86a 100644 --- a/pkg/gui/controllers/helpers/refresh_helper.go +++ b/pkg/gui/controllers/helpers/refresh_helper.go @@ -547,33 +547,35 @@ func (self *RefreshHelper) refreshFilesAndSubmodules() error { func (self *RefreshHelper) refreshStateFiles() error { fileTreeViewModel := self.c.Contexts().Files.FileTreeViewModel - // If git thinks any of our files have inline merge conflicts, but they actually don't, - // we stage them. - // Note that if files with merge conflicts have both arisen and have been resolved - // between refreshes, we won't stage them here. This is super unlikely though, - // and this approach spares us from having to call `git status` twice in a row. - // Although this also means that at startup we won't be staging anything until - // we call git status again. - pathsToStage := []string{} prevConflictFileCount := 0 - for _, file := range self.c.Model().Files { - if file.HasMergeConflicts { - prevConflictFileCount++ - } - if file.HasInlineMergeConflicts { - hasConflicts, err := mergeconflicts.FileHasConflictMarkers(file.Name) - if err != nil { - self.c.Log.Error(err) - } else if !hasConflicts { - pathsToStage = append(pathsToStage, file.Name) + if self.c.UserConfig().Git.AutoStageResolvedConflicts { + // If git thinks any of our files have inline merge conflicts, but they actually don't, + // we stage them. + // Note that if files with merge conflicts have both arisen and have been resolved + // between refreshes, we won't stage them here. This is super unlikely though, + // and this approach spares us from having to call `git status` twice in a row. + // Although this also means that at startup we won't be staging anything until + // we call git status again. + pathsToStage := []string{} + for _, file := range self.c.Model().Files { + if file.HasMergeConflicts { + prevConflictFileCount++ + } + if file.HasInlineMergeConflicts { + hasConflicts, err := mergeconflicts.FileHasConflictMarkers(file.Name) + if err != nil { + self.c.Log.Error(err) + } else if !hasConflicts { + pathsToStage = append(pathsToStage, file.Name) + } } } - } - if len(pathsToStage) > 0 { - self.c.LogAction(self.c.Tr.Actions.StageResolvedFiles) - if err := self.c.Git().WorkingTree.StageFiles(pathsToStage); err != nil { - return err + if len(pathsToStage) > 0 { + self.c.LogAction(self.c.Tr.Actions.StageResolvedFiles) + if err := self.c.Git().WorkingTree.StageFiles(pathsToStage); err != nil { + return err + } } } diff --git a/pkg/integration/tests/conflicts/resolve_no_auto_stage.go b/pkg/integration/tests/conflicts/resolve_no_auto_stage.go new file mode 100644 index 00000000000..35a509126d7 --- /dev/null +++ b/pkg/integration/tests/conflicts/resolve_no_auto_stage.go @@ -0,0 +1,81 @@ +package conflicts + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" + "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" +) + +var ResolveNoAutoStage = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Resolving conflicts without auto-staging", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + config.GetUserConfig().Git.AutoStageResolvedConflicts = false + }, + SetupRepo: func(shell *Shell) { + shared.CreateMergeConflictFiles(shell) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + IsFocused(). + Lines( + Contains("UU").Contains("file1").IsSelected(), + Contains("UU").Contains("file2"), + ). + PressEnter() + + t.Views().MergeConflicts(). + IsFocused(). + SelectedLines( + Contains("<<<<<<< HEAD"), + Contains("First Change"), + Contains("======="), + ). + PressPrimaryAction() + + t.Views().Files(). + IsFocused(). + // Resolving the conflict didn't auto-stage it + Lines( + Contains("UU").Contains("file1").IsSelected(), + Contains("UU").Contains("file2"), + ). + // So do that manually + PressPrimaryAction(). + Lines( + Contains("UU").Contains("file2").IsSelected(), + ). + // Trying to stage a file that still has conflicts is not allowed: + PressPrimaryAction(). + Tap(func() { + t.ExpectPopup().Alert(). + Title(Equals("Error")). + Content(Contains("Cannot stage/unstage directory containing files with inline merge conflicts.")). + Confirm() + }). + PressEnter() + + // coincidentally these files have the same conflict + t.Views().MergeConflicts(). + IsFocused(). + SelectedLines( + Contains("<<<<<<< HEAD"), + Contains("First Change"), + Contains("======="), + ). + PressPrimaryAction() + + t.Views().Files(). + IsFocused(). + // Again, resolving the conflict didn't auto-stage it + Lines( + Contains("UU").Contains("file2").IsSelected(), + ). + // Doing that manually now works: + PressPrimaryAction(). + Lines( + Contains("A").Contains("file3").IsSelected(), + ) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index de074232f17..8512b0b53ea 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -115,6 +115,7 @@ var tests = []*components.IntegrationTest{ conflicts.Filter, conflicts.ResolveExternally, conflicts.ResolveMultipleFiles, + conflicts.ResolveNoAutoStage, conflicts.UndoChooseHunk, custom_commands.AccessCommitProperties, custom_commands.BasicCommand, diff --git a/schema/config.json b/schema/config.json index 28318ddd60d..f1aa4544b4d 100644 --- a/schema/config.json +++ b/schema/config.json @@ -573,6 +573,11 @@ "description": "If true, pass the --all arg to git fetch", "default": true }, + "autoStageResolvedConflicts": { + "type": "boolean", + "description": "If true, lazygit will automatically stage files that used to have merge\nconflicts but no longer do; and it will also ask you if you want to\ncontinue a merge or rebase if you've resolved all conflicts. If false, it\nwon't do either of these things.", + "default": true + }, "branchLogCmd": { "type": "string", "description": "Command used when displaying the current branch git log in the main window",