diff --git a/docs/Custom_Command_Keybindings.md b/docs/Custom_Command_Keybindings.md index 7cc3d035b..6b0a090ed 100644 --- a/docs/Custom_Command_Keybindings.md +++ b/docs/Custom_Command_Keybindings.md @@ -59,6 +59,12 @@ For a given custom command, here are the allowed fields: | description | Label for the custom command when displayed in the keybindings menu | no | | stream | Whether you want to stream the command's output to the Command Log panel | no | | showOutput | Whether you want to show the command's output in a popup within Lazygit | no | +| after | Actions to take after the command has completed | no | + +Here are the options for the `after` key: +| _field_ | _description_ | required | +|-----------------|----------------------|-| +| checkForConflicts | true/false. If true, check for merge conflicts | no | ## Contexts diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index 8faff4326..5b7edc9e4 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -349,16 +349,21 @@ type OSConfig struct { OpenLinkCommand string `yaml:"openLinkCommand,omitempty"` } +type CustomCommandAfterHook struct { + CheckForConflicts bool `yaml:"checkForConflicts"` +} + type CustomCommand struct { - Key string `yaml:"key"` - Context string `yaml:"context"` - Command string `yaml:"command"` - Subprocess bool `yaml:"subprocess"` - Prompts []CustomCommandPrompt `yaml:"prompts"` - LoadingText string `yaml:"loadingText"` - Description string `yaml:"description"` - Stream bool `yaml:"stream"` - ShowOutput bool `yaml:"showOutput"` + Key string `yaml:"key"` + Context string `yaml:"context"` + Command string `yaml:"command"` + Subprocess bool `yaml:"subprocess"` + Prompts []CustomCommandPrompt `yaml:"prompts"` + LoadingText string `yaml:"loadingText"` + Description string `yaml:"description"` + Stream bool `yaml:"stream"` + ShowOutput bool `yaml:"showOutput"` + After CustomCommandAfterHook `yaml:"after"` } type CustomCommandPrompt struct { diff --git a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go index 21ab44201..0e83ccf25 100644 --- a/pkg/gui/controllers/helpers/merge_and_rebase_helper.go +++ b/pkg/gui/controllers/helpers/merge_and_rebase_helper.go @@ -137,33 +137,47 @@ func (self *MergeAndRebaseHelper) CheckMergeOrRebase(result error) error { } else if strings.Contains(result.Error(), "No rebase in progress?") { // assume in this case that we're already done return nil - } else if isMergeConflictErr(result.Error()) { - mode := self.workingTreeStateNoun() - return self.c.Menu(types.CreateMenuOptions{ - Title: self.c.Tr.FoundConflictsTitle, - Items: []*types.MenuItem{ - { - Label: self.c.Tr.ViewConflictsMenuItem, - OnPress: func() error { - return self.c.PushContext(self.c.Contexts().Files) - }, - Key: 'v', - }, - { - Label: fmt.Sprintf(self.c.Tr.AbortMenuItem, mode), - OnPress: func() error { - return self.genericMergeCommand(REBASE_OPTION_ABORT) - }, - Key: 'a', - }, - }, - HideCancel: true, - }) + } else { + return self.CheckForConflicts(result) + } +} + +func (self *MergeAndRebaseHelper) CheckForConflicts(result error) error { + if result == nil { + return nil + } + + if isMergeConflictErr(result.Error()) { + return self.PromptForConflictHandling() } else { return self.c.ErrorMsg(result.Error()) } } +func (self *MergeAndRebaseHelper) PromptForConflictHandling() error { + mode := self.workingTreeStateNoun() + return self.c.Menu(types.CreateMenuOptions{ + Title: self.c.Tr.FoundConflictsTitle, + Items: []*types.MenuItem{ + { + Label: self.c.Tr.ViewConflictsMenuItem, + OnPress: func() error { + return self.c.PushContext(self.c.Contexts().Files) + }, + Key: 'v', + }, + { + Label: fmt.Sprintf(self.c.Tr.AbortMenuItem, mode), + OnPress: func() error { + return self.genericMergeCommand(REBASE_OPTION_ABORT) + }, + Key: 'a', + }, + }, + HideCancel: true, + }) +} + func (self *MergeAndRebaseHelper) AbortMergeOrRebaseWithConfirm() error { // prompt user to confirm that they want to abort, then do it mode := self.workingTreeStateNoun() diff --git a/pkg/gui/services/custom_commands/client.go b/pkg/gui/services/custom_commands/client.go index 5e456ff6e..c746f0579 100644 --- a/pkg/gui/services/custom_commands/client.go +++ b/pkg/gui/services/custom_commands/client.go @@ -19,7 +19,12 @@ func NewClient( helpers *helpers.Helpers, ) *Client { sessionStateLoader := NewSessionStateLoader(c, helpers.Refs) - handlerCreator := NewHandlerCreator(c, sessionStateLoader, helpers.Suggestions) + handlerCreator := NewHandlerCreator( + c, + sessionStateLoader, + helpers.Suggestions, + helpers.MergeAndRebase, + ) keybindingCreator := NewKeybindingCreator(c) customCommands := c.UserConfig.CustomCommands diff --git a/pkg/gui/services/custom_commands/handler_creator.go b/pkg/gui/services/custom_commands/handler_creator.go index 4d6580b03..eea596ab8 100644 --- a/pkg/gui/services/custom_commands/handler_creator.go +++ b/pkg/gui/services/custom_commands/handler_creator.go @@ -17,27 +17,30 @@ import ( // takes a custom command and returns a function that will be called when the corresponding user-defined keybinding is pressed type HandlerCreator struct { - c *helpers.HelperCommon - sessionStateLoader *SessionStateLoader - resolver *Resolver - menuGenerator *MenuGenerator - suggestionsHelper *helpers.SuggestionsHelper + c *helpers.HelperCommon + sessionStateLoader *SessionStateLoader + resolver *Resolver + menuGenerator *MenuGenerator + suggestionsHelper *helpers.SuggestionsHelper + mergeAndRebaseHelper *helpers.MergeAndRebaseHelper } func NewHandlerCreator( c *helpers.HelperCommon, sessionStateLoader *SessionStateLoader, suggestionsHelper *helpers.SuggestionsHelper, + mergeAndRebaseHelper *helpers.MergeAndRebaseHelper, ) *HandlerCreator { resolver := NewResolver(c.Common) menuGenerator := NewMenuGenerator(c.Common) return &HandlerCreator{ - c: c, - sessionStateLoader: sessionStateLoader, - resolver: resolver, - menuGenerator: menuGenerator, - suggestionsHelper: suggestionsHelper, + c: c, + sessionStateLoader: sessionStateLoader, + resolver: resolver, + menuGenerator: menuGenerator, + suggestionsHelper: suggestionsHelper, + mergeAndRebaseHelper: mergeAndRebaseHelper, } } @@ -272,7 +275,16 @@ func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, ses cmdObj.StreamOutput() } output, err := cmdObj.RunWithOutput() + + if refreshErr := self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}); err != nil { + self.c.Log.Error(refreshErr) + } + if err != nil { + if customCommand.After.CheckForConflicts { + return self.mergeAndRebaseHelper.CheckForConflicts(err) + } + return self.c.Error(err) } @@ -280,11 +292,9 @@ func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, ses if strings.TrimSpace(output) == "" { output = self.c.Tr.EmptyOutput } - if err = self.c.Alert(cmdStr, output); err != nil { - return self.c.Error(err) - } - return self.c.Refresh(types.RefreshOptions{}) + return self.c.Alert(cmdStr, output) } - return self.c.Refresh(types.RefreshOptions{}) + + return nil }) } diff --git a/pkg/integration/tests/custom_commands/check_for_conflicts.go b/pkg/integration/tests/custom_commands/check_for_conflicts.go new file mode 100644 index 000000000..cb8ac7c77 --- /dev/null +++ b/pkg/integration/tests/custom_commands/check_for_conflicts.go @@ -0,0 +1,40 @@ +package custom_commands + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" + "github.com/jesseduffield/lazygit/pkg/integration/tests/shared" +) + +var CheckForConflicts = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Run a command and check for conflicts after", + ExtraCmdArgs: []string{}, + Skip: false, + SetupRepo: func(shell *Shell) { + shared.MergeConflictsSetup(shell) + }, + SetupConfig: func(cfg *config.AppConfig) { + cfg.UserConfig.CustomCommands = []config.CustomCommand{ + { + Key: "m", + Context: "localBranches", + Command: "git merge {{ .SelectedLocalBranch.Name | quote }}", + After: config.CustomCommandAfterHook{ + CheckForConflicts: true, + }, + }, + } + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Branches(). + Focus(). + TopLines( + Contains("first-change-branch"), + Contains("second-change-branch"), + ). + NavigateToLine(Contains("second-change-branch")). + Press("m") + + t.Common().AcknowledgeConflicts() + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 55085f989..af0ac1c5b 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -75,6 +75,7 @@ var tests = []*components.IntegrationTest{ conflicts.UndoChooseHunk, custom_commands.BasicCmdAtRuntime, custom_commands.BasicCmdFromConfig, + custom_commands.CheckForConflicts, custom_commands.ComplexCmdAtRuntime, custom_commands.FormPrompts, custom_commands.MenuFromCommand,