mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-10 20:05:50 +02:00
Allow checking for merge conflicts after running a custom command
We have a use-case to rebind 'm' to the merge action in the branches panel. There's three ways to handle this: 1) For all global keybindings, define a per-panel key that invokes it 2) Give a name to all controller actions and allow them to be invoked in custom commands 3) Allow checking for merge conflicts after running a custom command so that users can add their own 'git merge' custom command that matches the in-built action Option 1 is hairy, Option 2 though good for users introduces new backwards compatibility issues that I don't want to do right now, and option 3 is trivially easy to implement so that's what I'm doing. I've put this under an 'after' key so that we can add more things later. I'm imagining other things like being able to move the cursor to a newly added item etc. I considered always running this hook by default but I'd rather not: it's matching on the output text and I'd rather something like that be explicitly opted-into to avoid cases where we erroneously believe that there are conflicts.
This commit is contained in:
parent
1ded318666
commit
b61ca21a84
7 changed files with 128 additions and 47 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
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,
|
||||
custom_commands.BasicCmdAtRuntime,
|
||||
custom_commands.BasicCmdFromConfig,
|
||||
custom_commands.CheckForConflicts,
|
||||
custom_commands.ComplexCmdAtRuntime,
|
||||
custom_commands.FormPrompts,
|
||||
custom_commands.MenuFromCommand,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue