1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-12 11:15:00 +02:00
lazygit/pkg/gui/merge_panel.go

318 lines
8.0 KiB
Go
Raw Normal View History

2018-08-14 11:05:26 +02:00
// though this panel is called the merge panel, it's really going to use the main panel. This may change in the future
package gui
import (
"fmt"
2018-08-14 11:05:26 +02:00
"io/ioutil"
"math"
2020-08-18 14:41:14 +02:00
"github.com/go-errors/errors"
2018-08-14 11:05:26 +02:00
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
2021-04-10 09:31:23 +02:00
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
2021-04-01 15:48:13 +02:00
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
2018-08-14 11:05:26 +02:00
)
2021-04-01 15:48:13 +02:00
func (gui *Gui) handleSelectTop() error {
return gui.withMergeConflictLock(func() error {
gui.takeOverMergeConflictScrolling()
2021-04-18 10:07:10 +02:00
gui.State.Panels.Merging.SelectTopOption()
2021-04-01 15:48:13 +02:00
return gui.refreshMergePanel()
})
2018-08-14 11:05:26 +02:00
}
2021-04-01 15:48:13 +02:00
func (gui *Gui) handleSelectBottom() error {
return gui.withMergeConflictLock(func() error {
gui.takeOverMergeConflictScrolling()
2021-04-18 10:07:10 +02:00
gui.State.Panels.Merging.SelectBottomOption()
2021-04-01 15:48:13 +02:00
return gui.refreshMergePanel()
})
2018-08-14 11:05:26 +02:00
}
2021-04-01 15:48:13 +02:00
func (gui *Gui) handleSelectNextConflict() error {
return gui.withMergeConflictLock(func() error {
gui.takeOverMergeConflictScrolling()
2021-04-18 10:07:10 +02:00
gui.State.Panels.Merging.SelectNextConflict()
2021-04-01 15:48:13 +02:00
return gui.refreshMergePanel()
})
2018-08-14 11:05:26 +02:00
}
2021-04-01 15:48:13 +02:00
func (gui *Gui) handleSelectPrevConflict() error {
return gui.withMergeConflictLock(func() error {
gui.takeOverMergeConflictScrolling()
2021-04-18 10:07:10 +02:00
gui.State.Panels.Merging.SelectPrevConflict()
2021-04-01 15:48:13 +02:00
return gui.refreshMergePanel()
})
2018-08-14 11:05:26 +02:00
}
2020-11-16 11:38:26 +02:00
func (gui *Gui) pushFileSnapshot() error {
2021-04-18 10:07:10 +02:00
content, err := gui.catSelectedFile()
2018-08-14 11:05:26 +02:00
if err != nil {
return err
}
2021-04-18 10:07:10 +02:00
gui.State.Panels.Merging.PushFileSnapshot(content)
2018-08-14 11:05:26 +02:00
return nil
}
2021-04-01 15:48:13 +02:00
func (gui *Gui) handlePopFileSnapshot() error {
2021-04-18 10:07:10 +02:00
prevContent, ok := gui.State.Panels.Merging.PopFileSnapshot()
if !ok {
2018-08-14 11:05:26 +02:00
return nil
}
2021-04-18 10:07:10 +02:00
2020-08-16 09:45:12 +02:00
gitFile := gui.getSelectedFile()
if gitFile == nil {
return nil
2018-08-14 11:05:26 +02:00
}
2021-04-10 09:31:23 +02:00
gui.OnRunCommand(oscommands.NewCmdLogEntry("Undoing last conflict resolution", "Undo merge conflict resolution", false))
2020-03-09 02:34:10 +02:00
if err := ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644); err != nil {
return err
}
return gui.refreshMergePanel()
2018-08-14 11:05:26 +02:00
}
2021-04-01 15:48:13 +02:00
func (gui *Gui) handlePickHunk() error {
return gui.withMergeConflictLock(func() error {
gui.takeOverMergeConflictScrolling()
2021-04-18 10:07:10 +02:00
ok, err := gui.resolveConflict(gui.State.Panels.Merging.Selection())
if err != nil {
2021-04-01 15:48:13 +02:00
return err
}
2020-03-09 02:34:10 +02:00
2021-04-18 10:07:10 +02:00
if !ok {
return nil
2021-04-01 15:48:13 +02:00
}
2018-12-08 07:54:54 +02:00
2021-04-18 10:07:10 +02:00
if gui.State.Panels.Merging.IsFinalConflict() {
2021-04-01 15:48:13 +02:00
if err := gui.handleCompleteMerge(); err != nil {
return err
}
}
return gui.refreshMergePanel()
})
}
func (gui *Gui) handlePickBothHunks() error {
return gui.withMergeConflictLock(func() error {
gui.takeOverMergeConflictScrolling()
2021-04-01 15:48:13 +02:00
2021-04-18 10:07:10 +02:00
ok, err := gui.resolveConflict(mergeconflicts.BOTH)
if err != nil {
2018-12-08 07:54:54 +02:00
return err
}
2021-04-18 10:07:10 +02:00
if !ok {
return nil
2021-04-01 15:48:13 +02:00
}
2021-04-18 10:07:10 +02:00
2021-04-01 15:48:13 +02:00
return gui.refreshMergePanel()
})
2018-08-14 11:05:26 +02:00
}
2021-04-18 10:07:10 +02:00
func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error) {
2021-04-01 15:48:13 +02:00
gitFile := gui.getSelectedFile()
if gitFile == nil {
2021-04-18 10:07:10 +02:00
return false, nil
2020-03-09 02:34:10 +02:00
}
2021-04-18 10:07:10 +02:00
ok, output, err := gui.State.Panels.Merging.ContentAfterConflictResolve(gitFile.Name, selection)
2018-08-14 11:05:26 +02:00
if err != nil {
2021-04-18 10:07:10 +02:00
return false, err
2018-08-14 11:05:26 +02:00
}
2021-04-01 15:48:13 +02:00
2021-04-18 10:07:10 +02:00
if !ok {
return false, nil
}
if err := gui.pushFileSnapshot(); err != nil {
return false, gui.surfaceError(err)
2021-04-01 15:48:13 +02:00
}
2021-04-10 09:31:23 +02:00
var logStr string
switch selection {
case mergeconflicts.TOP:
logStr = "Picking top hunk"
case mergeconflicts.BOTTOM:
logStr = "Picking bottom hunk"
case mergeconflicts.BOTH:
logStr = "Picking both hunks"
}
gui.OnRunCommand(oscommands.NewCmdLogEntry(logStr, "Resolve merge conflict", false))
2021-04-18 10:07:10 +02:00
return true, ioutil.WriteFile(gitFile.Name, []byte(output), 0644)
2018-08-14 11:05:26 +02:00
}
func (gui *Gui) refreshMergePanelWithLock() error {
2021-04-01 15:48:13 +02:00
return gui.withMergeConflictLock(gui.refreshMergePanel)
}
func (gui *Gui) refreshMergePanel() error {
2018-12-08 07:54:54 +02:00
panelState := gui.State.Panels.Merging
2020-11-16 11:38:26 +02:00
cat, err := gui.catSelectedFile()
2018-08-14 11:05:26 +02:00
if err != nil {
2020-08-23 01:46:28 +02:00
return gui.refreshMainViews(refreshMainOpts{
2020-08-18 14:23:36 +02:00
main: &viewUpdateOpts{
title: "",
2021-04-04 15:51:59 +02:00
task: NewRenderStringTask(err.Error()),
2020-08-18 14:23:36 +02:00
},
})
2018-08-14 11:05:26 +02:00
}
2021-04-18 10:07:10 +02:00
panelState.SetConflictsFromCat(cat)
2020-08-18 14:23:36 +02:00
2021-04-18 10:07:10 +02:00
if panelState.NoConflicts() {
2018-12-08 07:54:54 +02:00
return gui.handleCompleteMerge()
2018-08-14 11:05:26 +02:00
}
2018-12-08 07:54:54 +02:00
2019-02-25 13:11:35 +02:00
hasFocus := gui.currentViewName() == "main"
2021-04-18 10:07:10 +02:00
content := mergeconflicts.ColoredConflictFile(cat, panelState.State, hasFocus)
2020-11-16 11:38:26 +02:00
if err := gui.scrollToConflict(); err != nil {
2020-03-29 02:49:37 +02:00
return err
}
2020-08-23 01:46:28 +02:00
return gui.refreshMainViews(refreshMainOpts{
2020-08-18 14:02:35 +02:00
main: &viewUpdateOpts{
2020-10-04 02:00:48 +02:00
title: gui.Tr.MergeConflictsTitle,
2021-04-04 15:51:59 +02:00
task: NewRenderStringWithoutScrollTask(content),
2020-08-18 14:02:35 +02:00
noWrap: true,
},
})
2018-08-14 11:05:26 +02:00
}
2020-11-16 11:38:26 +02:00
func (gui *Gui) catSelectedFile() (string, error) {
2020-08-16 09:45:12 +02:00
item := gui.getSelectedFile()
if item == nil {
2020-10-04 02:00:48 +02:00
return "", errors.New(gui.Tr.NoFilesDisplay)
}
2020-08-16 09:45:12 +02:00
if item.Type != "file" {
2020-10-04 02:00:48 +02:00
return "", errors.New(gui.Tr.NotAFile)
}
2020-08-16 09:45:12 +02:00
cat, err := gui.GitCommand.CatFile(item.Name)
if err != nil {
gui.Log.Error(err)
2020-08-18 14:41:14 +02:00
return "", err
}
return cat, nil
}
2020-11-16 11:38:26 +02:00
func (gui *Gui) scrollToConflict() error {
if gui.State.Panels.Merging.UserScrolling {
return nil
}
2018-12-08 07:54:54 +02:00
panelState := gui.State.Panels.Merging
2021-04-18 10:07:10 +02:00
if panelState.NoConflicts() {
2018-08-14 11:05:26 +02:00
return nil
}
2021-04-04 15:51:59 +02:00
2021-04-18 10:07:10 +02:00
gui.centerYPos(gui.Views.Main, panelState.GetConflictMiddle())
return nil
}
func (gui *Gui) centerYPos(view *gocui.View, y int) {
ox, _ := view.Origin()
_, height := view.Size()
newOriginY := int(math.Max(0, float64(y-(height/2))))
2018-12-08 07:54:54 +02:00
gui.g.Update(func(g *gocui.Gui) error {
2021-04-18 10:07:10 +02:00
return view.SetOrigin(ox, newOriginY)
2018-12-08 07:54:54 +02:00
})
2018-08-14 11:05:26 +02:00
}
func (gui *Gui) getMergingOptions() map[string]string {
2020-10-03 06:54:55 +02:00
keybindingConfig := gui.Config.GetUserConfig().Keybinding
return map[string]string{
2020-10-04 02:00:48 +02:00
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.Tr.LcSelectHunk,
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevBlock), gui.getKeyDisplay(keybindingConfig.Universal.NextBlock)): gui.Tr.LcNavigateConflicts,
gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.Tr.LcPickHunk,
gui.getKeyDisplay(keybindingConfig.Main.PickBothHunks): gui.Tr.LcPickBothHunks,
gui.getKeyDisplay(keybindingConfig.Universal.Undo): gui.Tr.LcUndo,
}
2018-08-14 11:05:26 +02:00
}
2020-08-15 09:23:16 +02:00
func (gui *Gui) handleEscapeMerge() error {
gui.takeOverMergeConflictScrolling()
2021-04-18 10:07:10 +02:00
gui.State.Panels.Merging.Reset()
if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil {
2019-02-16 12:30:29 +02:00
return err
}
2018-12-08 07:54:54 +02:00
// it's possible this method won't be called from the merging view so we need to
// ensure we only 'return' focus if we already have it
2021-04-04 15:51:59 +02:00
if gui.g.CurrentView() == gui.Views.Main {
2021-04-03 06:56:11 +02:00
return gui.pushContext(gui.State.Contexts.Files)
2018-08-14 11:05:26 +02:00
}
2018-12-08 07:54:54 +02:00
return nil
2018-08-14 11:05:26 +02:00
}
2018-12-08 07:54:54 +02:00
func (gui *Gui) handleCompleteMerge() error {
2020-11-16 11:38:26 +02:00
if err := gui.stageSelectedFile(); err != nil {
2019-02-16 12:30:29 +02:00
return err
}
if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil {
2019-02-16 12:30:29 +02:00
return err
}
2019-02-16 12:01:17 +02:00
// if we got conflicts after unstashing, we don't want to call any git
// commands to continue rebasing/merging here
2020-11-16 11:38:26 +02:00
if gui.GitCommand.WorkingTreeState() == commands.REBASE_MODE_NORMAL {
2020-08-15 09:23:16 +02:00
return gui.handleEscapeMerge()
2019-02-16 12:01:17 +02:00
}
2018-12-08 07:54:54 +02:00
// if there are no more files with merge conflicts, we should ask whether the user wants to continue
if !gui.anyFilesWithMergeConflicts() {
2021-04-01 15:48:13 +02:00
return gui.promptToContinueRebase()
2018-12-02 18:34:15 +02:00
}
2020-08-15 09:23:16 +02:00
return gui.handleEscapeMerge()
2018-08-14 11:05:26 +02:00
}
2019-03-03 07:11:20 +02:00
2021-04-01 15:48:13 +02:00
// promptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress
func (gui *Gui) promptToContinueRebase() error {
gui.takeOverMergeConflictScrolling()
2020-08-15 08:38:16 +02:00
return gui.ask(askOpts{
2020-08-23 11:28:59 +02:00
title: "continue",
2020-10-04 02:00:48 +02:00
prompt: gui.Tr.ConflictsResolved,
2020-08-23 11:28:59 +02:00
handlersManageFocus: true,
2020-08-15 08:36:39 +02:00
handleConfirm: func() error {
2021-04-03 06:56:11 +02:00
if err := gui.pushContext(gui.State.Contexts.Files); err != nil {
2020-08-23 11:28:59 +02:00
return err
}
2020-08-15 08:36:39 +02:00
return gui.genericMergeCommand("continue")
},
2020-08-23 11:28:59 +02:00
handleClose: func() error {
2021-04-03 06:56:11 +02:00
return gui.pushContext(gui.State.Contexts.Files)
2020-08-23 11:28:59 +02:00
},
2020-08-15 08:36:39 +02:00
})
2019-03-03 07:11:20 +02:00
}
func (gui *Gui) canScrollMergePanel() bool {
2021-04-04 17:10:23 +02:00
currentView := gui.g.CurrentView()
if currentView != gui.Views.Main && currentView != gui.Views.Files {
return false
}
2020-08-16 09:45:12 +02:00
file := gui.getSelectedFile()
if file == nil {
return false
}
return file.HasInlineMergeConflicts
}
2021-04-01 15:48:13 +02:00
func (gui *Gui) withMergeConflictLock(f func() error) error {
2021-04-18 10:07:10 +02:00
gui.State.Panels.Merging.Lock()
defer gui.State.Panels.Merging.Unlock()
2021-04-01 15:48:13 +02:00
return f()
}
func (gui *Gui) takeOverMergeConflictScrolling() {
2021-04-01 15:48:13 +02:00
gui.State.Panels.Merging.UserScrolling = false
}