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:
commit
a251f6ad6c
@ -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
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
40
pkg/integration/tests/custom_commands/check_for_conflicts.go
Normal file
40
pkg/integration/tests/custom_commands/check_for_conflicts.go
Normal 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()
|
||||||
|
},
|
||||||
|
})
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user