mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Route space to the conflict picker for non-textual conflicts
For a non-textual conflict (e.g. DD/AU/UA/UD/DU), pressing space used to run the normal stage path, which did something unclear: `git add` happens to resolve the conflict by keeping the file, but that's neither obvious nor symmetric. Route a single such file to the same Keep/Delete picker that enter opens, so space and enter agree. For a range selection that includes one of these conflicts, staging makes no sense, so disable it with a toast that points the user at resolving them one at a time. (Entering a range was already disabled with the standard toast.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
41d92aac36
commit
860f89e0c9
|
|
@ -44,7 +44,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
|||
{
|
||||
Keys: opts.GetKeys(opts.Config.Universal.Select),
|
||||
Handler: self.withItems(self.press),
|
||||
GetDisabledReason: self.require(self.withFileTreeViewModelMutex(self.itemsSelected())),
|
||||
GetDisabledReason: self.require(self.withFileTreeViewModelMutex(self.itemsSelected(self.canStageSelection))),
|
||||
Description: self.c.Tr.Stage,
|
||||
Tooltip: self.c.Tr.StageTooltip,
|
||||
DisplayOnScreen: true,
|
||||
|
|
@ -583,6 +583,12 @@ func (self *FilesController) pressWithLock(selectedNodes []*filetree.FileNode) e
|
|||
}
|
||||
|
||||
func (self *FilesController) press(nodes []*filetree.FileNode) error {
|
||||
// A single file with a conflict that can only be resolved through a dialog
|
||||
// can't be staged; route it to the same picker that `enter` uses instead.
|
||||
if len(nodes) == 1 && self.conflictNeedsResolutionDialog(nodes[0].File) {
|
||||
return self.openConflictResolutionMenu(nodes[0].File)
|
||||
}
|
||||
|
||||
if err := self.pressWithLock(nodes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -716,6 +722,27 @@ func (self *FilesController) conflictNeedsResolutionDialog(file *models.File) bo
|
|||
return !file.HasInlineMergeConflicts
|
||||
}
|
||||
|
||||
// canStageSelection disables staging when a multiple selection includes a file
|
||||
// with a conflict that must be resolved through a dialog; those have to be
|
||||
// resolved one at a time.
|
||||
func (self *FilesController) canStageSelection(nodes []*filetree.FileNode) *types.DisabledReason {
|
||||
if len(nodes) > 1 {
|
||||
for _, node := range nodes {
|
||||
if node.SomeFile(self.conflictNeedsResolutionDialog) {
|
||||
return &types.DisabledReason{
|
||||
Text: utils.ResolvePlaceholderString(
|
||||
self.c.Tr.StageConflictsRangeDisabled, map[string]string{
|
||||
"goIntoKey": self.c.UserConfig().Keybinding.Universal.GoInto.String(),
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *FilesController) openConflictResolutionMenu(file *models.File) error {
|
||||
handle := func(command func(command string) error, logText string) error {
|
||||
self.c.LogAction(logText)
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ type TranslationSet struct {
|
|||
MergeConflictPressEnterToResolve string
|
||||
MergeConflictKeepFile string
|
||||
MergeConflictDeleteFile string
|
||||
StageConflictsRangeDisabled string
|
||||
Checkout string
|
||||
CheckoutTooltip string
|
||||
CantCheckoutBranchWhilePulling string
|
||||
|
|
@ -1200,6 +1201,7 @@ func EnglishTranslationSet() *TranslationSet {
|
|||
MergeConflictPressEnterToResolve: "Press %s to resolve.",
|
||||
MergeConflictKeepFile: "Keep file",
|
||||
MergeConflictDeleteFile: "Delete file",
|
||||
StageConflictsRangeDisabled: "Cannot stage a selection that includes files with merge conflicts; resolve them individually with {{.goIntoKey}} first.",
|
||||
Checkout: "Checkout",
|
||||
CheckoutTooltip: "Checkout selected item.",
|
||||
CantCheckoutBranchWhilePulling: "You cannot checkout another branch while pulling the current branch",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
package conflicts
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var SpaceOnNonTextualConflict = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Pressing space on a non-textual conflict opens the resolution menu; staging is disabled for a range that includes one",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.GetUserConfig().Gui.ShowFileTree = false
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.RunShellCommand(`echo 1 > foo && echo 1 > bar`)
|
||||
shell.RunShellCommand(`git checkout -b base && git add . && git commit -m base`)
|
||||
|
||||
// theirs: delete foo, modify bar
|
||||
shell.RunShellCommand(`git checkout -b theirs`)
|
||||
shell.RunShellCommand(`git rm foo && echo 2 > bar && git add bar && git commit -m theirs`)
|
||||
|
||||
// ours: modify foo, delete bar
|
||||
shell.RunShellCommand(`git checkout base && git checkout -b ours`)
|
||||
shell.RunShellCommand(`echo 2 > foo && git add foo && git rm bar && git commit -m ours`)
|
||||
|
||||
shell.RunCommandExpectError([]string{"git", "merge", "theirs"})
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("DU bar"),
|
||||
Contains("UD foo"),
|
||||
).
|
||||
// Pressing space on a single non-textual conflict opens the
|
||||
// resolution menu rather than trying to stage it.
|
||||
NavigateToLine(Contains("bar")).
|
||||
PressPrimaryAction().
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().Title(Equals("Merge conflicts")).Cancel()
|
||||
}).
|
||||
// Staging is disabled for a range selection that includes a conflict.
|
||||
Press(keys.Universal.ToggleRangeSelect).
|
||||
NavigateToLine(Contains("foo")).
|
||||
PressPrimaryAction().
|
||||
Tap(func() {
|
||||
t.ExpectToast(Contains("Cannot stage a selection that includes files with merge conflicts"))
|
||||
}).
|
||||
// Entering a range selection is disabled too, with the usual toast.
|
||||
Press(keys.Universal.GoInto).
|
||||
Tap(func() {
|
||||
t.ExpectToast(Contains("does not support range selection"))
|
||||
})
|
||||
},
|
||||
})
|
||||
|
|
@ -172,6 +172,7 @@ var tests = []*components.IntegrationTest{
|
|||
conflicts.ResolveNoAutoStage,
|
||||
conflicts.ResolveNonTextualConflicts,
|
||||
conflicts.ResolveWithoutTrailingLf,
|
||||
conflicts.SpaceOnNonTextualConflict,
|
||||
conflicts.UndoChooseHunk,
|
||||
custom_commands.AccessCommitProperties,
|
||||
custom_commands.BasicCommand,
|
||||
|
|
|
|||
Loading…
Reference in a new issue