mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-12 12:55:47 +02:00
Provide conflict resolution dialogs for non-textual conflicts
This commit is contained in:
parent
efcd71b296
commit
ebb576feac
5 changed files with 168 additions and 1 deletions
|
@ -341,6 +341,13 @@ func (self *WorkingTreeCommands) RemoveTrackedFiles(name string) error {
|
||||||
return self.cmd.New(cmdArgs).Run()
|
return self.cmd.New(cmdArgs).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *WorkingTreeCommands) RemoveConflictedFile(name string) error {
|
||||||
|
cmdArgs := NewGitCmd("rm").Arg("--", name).
|
||||||
|
ToArgv()
|
||||||
|
|
||||||
|
return self.cmd.New(cmdArgs).Run()
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveUntrackedFiles runs `git clean -fd`
|
// RemoveUntrackedFiles runs `git clean -fd`
|
||||||
func (self *WorkingTreeCommands) RemoveUntrackedFiles() error {
|
func (self *WorkingTreeCommands) RemoveUntrackedFiles() error {
|
||||||
cmdArgs := NewGitCmd("clean").Arg("-fd").ToArgv()
|
cmdArgs := NewGitCmd("clean").Arg("-fd").ToArgv()
|
||||||
|
|
|
@ -571,13 +571,59 @@ func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
|
||||||
return self.switchToMerge()
|
return self.switchToMerge()
|
||||||
}
|
}
|
||||||
if file.HasMergeConflicts {
|
if file.HasMergeConflicts {
|
||||||
return errors.New(self.c.Tr.FileStagingRequirements)
|
return self.handleNonInlineConflict(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.c.Context().Push(self.c.Contexts().Staging, opts)
|
self.c.Context().Push(self.c.Contexts().Staging, opts)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *FilesController) handleNonInlineConflict(file *models.File) error {
|
||||||
|
handle := func(command func(command string) error, logText string) error {
|
||||||
|
self.c.LogAction(logText)
|
||||||
|
if err := command(file.GetPath()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||||
|
}
|
||||||
|
keepItem := &types.MenuItem{
|
||||||
|
Label: self.c.Tr.MergeConflictKeepFile,
|
||||||
|
OnPress: func() error {
|
||||||
|
return handle(self.c.Git().WorkingTree.StageFile, self.c.Tr.Actions.ResolveConflictByKeepingFile)
|
||||||
|
},
|
||||||
|
Key: 'k',
|
||||||
|
}
|
||||||
|
deleteItem := &types.MenuItem{
|
||||||
|
Label: self.c.Tr.MergeConflictDeleteFile,
|
||||||
|
OnPress: func() error {
|
||||||
|
return handle(self.c.Git().WorkingTree.RemoveConflictedFile, self.c.Tr.Actions.ResolveConflictByDeletingFile)
|
||||||
|
},
|
||||||
|
Key: 'd',
|
||||||
|
}
|
||||||
|
items := []*types.MenuItem{}
|
||||||
|
switch file.ShortStatus {
|
||||||
|
case "DD":
|
||||||
|
// For "both deleted" conflicts, deleting the file is the only reasonable thing you can do.
|
||||||
|
// Restoring to the state before deletion is not the responsibility of a conflict resolution tool.
|
||||||
|
items = append(items, deleteItem)
|
||||||
|
case "DU", "UD":
|
||||||
|
// For these, we put the delete option first because it's the most common one,
|
||||||
|
// even if it's more destructive.
|
||||||
|
items = append(items, deleteItem, keepItem)
|
||||||
|
case "AU", "UA":
|
||||||
|
// For these, we put the keep option first because it's less destructive,
|
||||||
|
// and the chances between keep and delete are 50/50.
|
||||||
|
items = append(items, keepItem, deleteItem)
|
||||||
|
default:
|
||||||
|
panic("should only be called if there's a merge conflict")
|
||||||
|
}
|
||||||
|
return self.c.Menu(types.CreateMenuOptions{
|
||||||
|
Title: self.c.Tr.MergeConflictsTitle,
|
||||||
|
Prompt: file.GetMergeStateDescription(self.c.Tr),
|
||||||
|
Items: items,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (self *FilesController) toggleStagedAll() error {
|
func (self *FilesController) toggleStagedAll() error {
|
||||||
if err := self.toggleStagedAllWithLock(); err != nil {
|
if err := self.toggleStagedAllWithLock(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -107,6 +107,8 @@ type TranslationSet struct {
|
||||||
MergeConflictIncomingDiff string
|
MergeConflictIncomingDiff string
|
||||||
MergeConflictCurrentDiff string
|
MergeConflictCurrentDiff string
|
||||||
MergeConflictPressEnterToResolve string
|
MergeConflictPressEnterToResolve string
|
||||||
|
MergeConflictKeepFile string
|
||||||
|
MergeConflictDeleteFile string
|
||||||
Checkout string
|
Checkout string
|
||||||
CheckoutTooltip string
|
CheckoutTooltip string
|
||||||
CantCheckoutBranchWhilePulling string
|
CantCheckoutBranchWhilePulling string
|
||||||
|
@ -951,6 +953,8 @@ type Actions struct {
|
||||||
UnstageFile string
|
UnstageFile string
|
||||||
UnstageAllFiles string
|
UnstageAllFiles string
|
||||||
StageAllFiles string
|
StageAllFiles string
|
||||||
|
ResolveConflictByKeepingFile string
|
||||||
|
ResolveConflictByDeletingFile string
|
||||||
NotEnoughContextToStage string
|
NotEnoughContextToStage string
|
||||||
NotEnoughContextToDiscard string
|
NotEnoughContextToDiscard string
|
||||||
IgnoreExcludeFile string
|
IgnoreExcludeFile string
|
||||||
|
@ -1128,6 +1132,8 @@ func EnglishTranslationSet() *TranslationSet {
|
||||||
MergeConflictIncomingDiff: "Incoming changes:",
|
MergeConflictIncomingDiff: "Incoming changes:",
|
||||||
MergeConflictCurrentDiff: "Current changes:",
|
MergeConflictCurrentDiff: "Current changes:",
|
||||||
MergeConflictPressEnterToResolve: "Press %s to resolve.",
|
MergeConflictPressEnterToResolve: "Press %s to resolve.",
|
||||||
|
MergeConflictKeepFile: "Keep file",
|
||||||
|
MergeConflictDeleteFile: "Delete file",
|
||||||
Checkout: "Checkout",
|
Checkout: "Checkout",
|
||||||
CheckoutTooltip: "Checkout selected item.",
|
CheckoutTooltip: "Checkout selected item.",
|
||||||
CantCheckoutBranchWhilePulling: "You cannot checkout another branch while pulling the current branch",
|
CantCheckoutBranchWhilePulling: "You cannot checkout another branch while pulling the current branch",
|
||||||
|
@ -1968,6 +1974,8 @@ func EnglishTranslationSet() *TranslationSet {
|
||||||
UnstageFile: "Unstage file",
|
UnstageFile: "Unstage file",
|
||||||
UnstageAllFiles: "Unstage all files",
|
UnstageAllFiles: "Unstage all files",
|
||||||
StageAllFiles: "Stage all files",
|
StageAllFiles: "Stage all files",
|
||||||
|
ResolveConflictByKeepingFile: "Resolve by keeping file",
|
||||||
|
ResolveConflictByDeletingFile: "Resolve by deleting file",
|
||||||
NotEnoughContextToStage: "Staging or unstaging changes is not possible with a diff context size of 0. Increase the context using '%s'.",
|
NotEnoughContextToStage: "Staging or unstaging changes is not possible with a diff context size of 0. Increase the context using '%s'.",
|
||||||
NotEnoughContextToDiscard: "Discarding changes is not possible with a diff context size of 0. Increase the context using '%s'.",
|
NotEnoughContextToDiscard: "Discarding changes is not possible with a diff context size of 0. Increase the context using '%s'.",
|
||||||
IgnoreExcludeFile: "Ignore or exclude file",
|
IgnoreExcludeFile: "Ignore or exclude file",
|
||||||
|
|
105
pkg/integration/tests/conflicts/resolve_non_textual_conflicts.go
Normal file
105
pkg/integration/tests/conflicts/resolve_non_textual_conflicts.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package conflicts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ResolveNonTextualConflicts = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
|
Description: "Resolve non-textual merge conflicts (e.g. one side modified, the other side deleted)",
|
||||||
|
ExtraCmdArgs: []string{},
|
||||||
|
Skip: false,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
|
SetupRepo: func(shell *Shell) {
|
||||||
|
shell.RunShellCommand(`echo test1 > both-deleted1.txt`)
|
||||||
|
shell.RunShellCommand(`echo test2 > both-deleted2.txt`)
|
||||||
|
shell.RunShellCommand(`git checkout -b conflict && git add both-deleted1.txt both-deleted2.txt`)
|
||||||
|
shell.RunShellCommand(`echo haha1 > deleted-them1.txt && git add deleted-them1.txt`)
|
||||||
|
shell.RunShellCommand(`echo haha2 > deleted-them2.txt && git add deleted-them2.txt`)
|
||||||
|
shell.RunShellCommand(`echo haha1 > deleted-us1.txt && git add deleted-us1.txt`)
|
||||||
|
shell.RunShellCommand(`echo haha2 > deleted-us2.txt && git add deleted-us2.txt`)
|
||||||
|
shell.RunShellCommand(`git commit -m one`)
|
||||||
|
|
||||||
|
// stuff on other branch
|
||||||
|
shell.RunShellCommand(`git branch conflict_second`)
|
||||||
|
shell.RunShellCommand(`git mv both-deleted1.txt added-them-changed-us1.txt`)
|
||||||
|
shell.RunShellCommand(`git mv both-deleted2.txt added-them-changed-us2.txt`)
|
||||||
|
shell.RunShellCommand(`git rm deleted-them1.txt deleted-them2.txt`)
|
||||||
|
shell.RunShellCommand(`echo modded1 > deleted-us1.txt && git add deleted-us1.txt`)
|
||||||
|
shell.RunShellCommand(`echo modded2 > deleted-us2.txt && git add deleted-us2.txt`)
|
||||||
|
shell.RunShellCommand(`git commit -m "two"`)
|
||||||
|
|
||||||
|
// stuff on our branch
|
||||||
|
shell.RunShellCommand(`git checkout conflict_second`)
|
||||||
|
shell.RunShellCommand(`git mv both-deleted1.txt changed-them-added-us1.txt`)
|
||||||
|
shell.RunShellCommand(`git mv both-deleted2.txt changed-them-added-us2.txt`)
|
||||||
|
shell.RunShellCommand(`echo modded1 > deleted-them1.txt && git add deleted-them1.txt`)
|
||||||
|
shell.RunShellCommand(`echo modded2 > deleted-them2.txt && git add deleted-them2.txt`)
|
||||||
|
shell.RunShellCommand(`git rm deleted-us1.txt deleted-us2.txt`)
|
||||||
|
shell.RunShellCommand(`git commit -m "three"`)
|
||||||
|
shell.RunShellCommand(`git reset --hard conflict_second`)
|
||||||
|
shell.RunCommandExpectError([]string{"git", "merge", "conflict"})
|
||||||
|
},
|
||||||
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
|
resolve := func(filename string, menuChoice string) {
|
||||||
|
t.Views().Files().
|
||||||
|
NavigateToLine(Contains(filename)).
|
||||||
|
Tap(func() {
|
||||||
|
t.Views().Main().Content(Contains("Conflict:"))
|
||||||
|
}).
|
||||||
|
Press(keys.Universal.GoInto).
|
||||||
|
Tap(func() {
|
||||||
|
t.ExpectPopup().Menu().Title(Equals("Merge conflicts")).
|
||||||
|
Select(Contains(menuChoice)).
|
||||||
|
Confirm()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Views().Files().
|
||||||
|
IsFocused().
|
||||||
|
Lines(
|
||||||
|
Equals("▼ /").IsSelected(),
|
||||||
|
Equals(" UA added-them-changed-us1.txt"),
|
||||||
|
Equals(" UA added-them-changed-us2.txt"),
|
||||||
|
Equals(" DD both-deleted1.txt"),
|
||||||
|
Equals(" DD both-deleted2.txt"),
|
||||||
|
Equals(" AU changed-them-added-us1.txt"),
|
||||||
|
Equals(" AU changed-them-added-us2.txt"),
|
||||||
|
Equals(" UD deleted-them1.txt"),
|
||||||
|
Equals(" UD deleted-them2.txt"),
|
||||||
|
Equals(" DU deleted-us1.txt"),
|
||||||
|
Equals(" DU deleted-us2.txt"),
|
||||||
|
).
|
||||||
|
Tap(func() {
|
||||||
|
resolve("added-them-changed-us1.txt", "Delete file")
|
||||||
|
resolve("added-them-changed-us2.txt", "Keep file")
|
||||||
|
resolve("both-deleted1.txt", "Delete file")
|
||||||
|
resolve("both-deleted2.txt", "Delete file")
|
||||||
|
resolve("changed-them-added-us1.txt", "Delete file")
|
||||||
|
resolve("changed-them-added-us2.txt", "Keep file")
|
||||||
|
resolve("deleted-them1.txt", "Delete file")
|
||||||
|
resolve("deleted-them2.txt", "Keep file")
|
||||||
|
resolve("deleted-us1.txt", "Delete file")
|
||||||
|
resolve("deleted-us2.txt", "Keep file")
|
||||||
|
}).
|
||||||
|
Lines(
|
||||||
|
Equals("▼ /"),
|
||||||
|
Equals(" A added-them-changed-us2.txt"),
|
||||||
|
Equals(" D changed-them-added-us1.txt"),
|
||||||
|
Equals(" D deleted-them1.txt"),
|
||||||
|
Equals(" A deleted-us2.txt"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.FileSystem().
|
||||||
|
PathNotPresent("added-them-changed-us1.txt").
|
||||||
|
FileContent("added-them-changed-us2.txt", Equals("test2\n")).
|
||||||
|
PathNotPresent("both-deleted1.txt").
|
||||||
|
PathNotPresent("both-deleted2.txt").
|
||||||
|
PathNotPresent("changed-them-added-us1.txt").
|
||||||
|
FileContent("changed-them-added-us2.txt", Equals("test2\n")).
|
||||||
|
PathNotPresent("deleted-them1.txt").
|
||||||
|
FileContent("deleted-them2.txt", Equals("modded2\n")).
|
||||||
|
PathNotPresent("deleted-us1.txt").
|
||||||
|
FileContent("deleted-us2.txt", Equals("modded2\n"))
|
||||||
|
},
|
||||||
|
})
|
|
@ -141,6 +141,7 @@ var tests = []*components.IntegrationTest{
|
||||||
conflicts.ResolveExternally,
|
conflicts.ResolveExternally,
|
||||||
conflicts.ResolveMultipleFiles,
|
conflicts.ResolveMultipleFiles,
|
||||||
conflicts.ResolveNoAutoStage,
|
conflicts.ResolveNoAutoStage,
|
||||||
|
conflicts.ResolveNonTextualConflicts,
|
||||||
conflicts.ResolveWithoutTrailingLf,
|
conflicts.ResolveWithoutTrailingLf,
|
||||||
conflicts.UndoChooseHunk,
|
conflicts.UndoChooseHunk,
|
||||||
custom_commands.AccessCommitProperties,
|
custom_commands.AccessCommitProperties,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue