1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-03-05 15:15:49 +02:00

Allow checking for merge conflicts after running a custom command (#2773)

This commit is contained in:
Jesse Duffield 2023-07-13 18:43:25 +10:00 committed by GitHub
commit a251f6ad6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 128 additions and 47 deletions

View File

@ -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 | | 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 | | 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 | | 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 ## Contexts

View File

@ -349,16 +349,21 @@ type OSConfig struct {
OpenLinkCommand string `yaml:"openLinkCommand,omitempty"` OpenLinkCommand string `yaml:"openLinkCommand,omitempty"`
} }
type CustomCommandAfterHook struct {
CheckForConflicts bool `yaml:"checkForConflicts"`
}
type CustomCommand struct { type CustomCommand struct {
Key string `yaml:"key"` Key string `yaml:"key"`
Context string `yaml:"context"` Context string `yaml:"context"`
Command string `yaml:"command"` Command string `yaml:"command"`
Subprocess bool `yaml:"subprocess"` Subprocess bool `yaml:"subprocess"`
Prompts []CustomCommandPrompt `yaml:"prompts"` Prompts []CustomCommandPrompt `yaml:"prompts"`
LoadingText string `yaml:"loadingText"` LoadingText string `yaml:"loadingText"`
Description string `yaml:"description"` Description string `yaml:"description"`
Stream bool `yaml:"stream"` Stream bool `yaml:"stream"`
ShowOutput bool `yaml:"showOutput"` ShowOutput bool `yaml:"showOutput"`
After CustomCommandAfterHook `yaml:"after"`
} }
type CustomCommandPrompt struct { type CustomCommandPrompt struct {

View File

@ -137,33 +137,47 @@ func (self *MergeAndRebaseHelper) CheckMergeOrRebase(result error) error {
} else if strings.Contains(result.Error(), "No rebase in progress?") { } else if strings.Contains(result.Error(), "No rebase in progress?") {
// assume in this case that we're already done // assume in this case that we're already done
return nil return nil
} else if isMergeConflictErr(result.Error()) { } else {
mode := self.workingTreeStateNoun() return self.CheckForConflicts(result)
return self.c.Menu(types.CreateMenuOptions{ }
Title: self.c.Tr.FoundConflictsTitle, }
Items: []*types.MenuItem{
{ func (self *MergeAndRebaseHelper) CheckForConflicts(result error) error {
Label: self.c.Tr.ViewConflictsMenuItem, if result == nil {
OnPress: func() error { return nil
return self.c.PushContext(self.c.Contexts().Files) }
},
Key: 'v', if isMergeConflictErr(result.Error()) {
}, return self.PromptForConflictHandling()
{
Label: fmt.Sprintf(self.c.Tr.AbortMenuItem, mode),
OnPress: func() error {
return self.genericMergeCommand(REBASE_OPTION_ABORT)
},
Key: 'a',
},
},
HideCancel: true,
})
} else { } else {
return self.c.ErrorMsg(result.Error()) 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 { func (self *MergeAndRebaseHelper) AbortMergeOrRebaseWithConfirm() error {
// prompt user to confirm that they want to abort, then do it // prompt user to confirm that they want to abort, then do it
mode := self.workingTreeStateNoun() mode := self.workingTreeStateNoun()

View File

@ -19,7 +19,12 @@ func NewClient(
helpers *helpers.Helpers, helpers *helpers.Helpers,
) *Client { ) *Client {
sessionStateLoader := NewSessionStateLoader(c, helpers.Refs) sessionStateLoader := NewSessionStateLoader(c, helpers.Refs)
handlerCreator := NewHandlerCreator(c, sessionStateLoader, helpers.Suggestions) handlerCreator := NewHandlerCreator(
c,
sessionStateLoader,
helpers.Suggestions,
helpers.MergeAndRebase,
)
keybindingCreator := NewKeybindingCreator(c) keybindingCreator := NewKeybindingCreator(c)
customCommands := c.UserConfig.CustomCommands customCommands := c.UserConfig.CustomCommands

View File

@ -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 // takes a custom command and returns a function that will be called when the corresponding user-defined keybinding is pressed
type HandlerCreator struct { type HandlerCreator struct {
c *helpers.HelperCommon c *helpers.HelperCommon
sessionStateLoader *SessionStateLoader sessionStateLoader *SessionStateLoader
resolver *Resolver resolver *Resolver
menuGenerator *MenuGenerator menuGenerator *MenuGenerator
suggestionsHelper *helpers.SuggestionsHelper suggestionsHelper *helpers.SuggestionsHelper
mergeAndRebaseHelper *helpers.MergeAndRebaseHelper
} }
func NewHandlerCreator( func NewHandlerCreator(
c *helpers.HelperCommon, c *helpers.HelperCommon,
sessionStateLoader *SessionStateLoader, sessionStateLoader *SessionStateLoader,
suggestionsHelper *helpers.SuggestionsHelper, suggestionsHelper *helpers.SuggestionsHelper,
mergeAndRebaseHelper *helpers.MergeAndRebaseHelper,
) *HandlerCreator { ) *HandlerCreator {
resolver := NewResolver(c.Common) resolver := NewResolver(c.Common)
menuGenerator := NewMenuGenerator(c.Common) menuGenerator := NewMenuGenerator(c.Common)
return &HandlerCreator{ return &HandlerCreator{
c: c, c: c,
sessionStateLoader: sessionStateLoader, sessionStateLoader: sessionStateLoader,
resolver: resolver, resolver: resolver,
menuGenerator: menuGenerator, menuGenerator: menuGenerator,
suggestionsHelper: suggestionsHelper, suggestionsHelper: suggestionsHelper,
mergeAndRebaseHelper: mergeAndRebaseHelper,
} }
} }
@ -272,7 +275,16 @@ func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, ses
cmdObj.StreamOutput() cmdObj.StreamOutput()
} }
output, err := cmdObj.RunWithOutput() 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 err != nil {
if customCommand.After.CheckForConflicts {
return self.mergeAndRebaseHelper.CheckForConflicts(err)
}
return self.c.Error(err) return self.c.Error(err)
} }
@ -280,11 +292,9 @@ func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, ses
if strings.TrimSpace(output) == "" { if strings.TrimSpace(output) == "" {
output = self.c.Tr.EmptyOutput output = self.c.Tr.EmptyOutput
} }
if err = self.c.Alert(cmdStr, output); err != nil { return self.c.Alert(cmdStr, output)
return self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{})
} }
return self.c.Refresh(types.RefreshOptions{})
return nil
}) })
} }

View File

@ -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()
},
})

View File

@ -75,6 +75,7 @@ var tests = []*components.IntegrationTest{
conflicts.UndoChooseHunk, conflicts.UndoChooseHunk,
custom_commands.BasicCmdAtRuntime, custom_commands.BasicCmdAtRuntime,
custom_commands.BasicCmdFromConfig, custom_commands.BasicCmdFromConfig,
custom_commands.CheckForConflicts,
custom_commands.ComplexCmdAtRuntime, custom_commands.ComplexCmdAtRuntime,
custom_commands.FormPrompts, custom_commands.FormPrompts,
custom_commands.MenuFromCommand, custom_commands.MenuFromCommand,