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 (
|
2020-01-07 09:50:25 -08:00
|
|
|
"fmt"
|
2018-08-14 11:05:26 +02:00
|
|
|
"io/ioutil"
|
|
|
|
"math"
|
|
|
|
|
2020-08-18 22:41:14 +10:00
|
|
|
"github.com/go-errors/errors"
|
2018-08-14 11:05:26 +02:00
|
|
|
"github.com/jesseduffield/gocui"
|
2021-04-10 17:31:23 +10:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
2021-12-30 13:35:10 +11:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
2021-04-02 00:48:13 +11:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
2018-08-14 11:05:26 +02:00
|
|
|
)
|
|
|
|
|
2021-08-22 01:03:05 +09:00
|
|
|
func (gui *Gui) handleSelectPrevConflictHunk() error {
|
2021-04-02 00:48:13 +11:00
|
|
|
return gui.withMergeConflictLock(func() error {
|
2021-04-02 01:11:54 +11:00
|
|
|
gui.takeOverMergeConflictScrolling()
|
2021-08-22 01:03:05 +09:00
|
|
|
gui.State.Panels.Merging.SelectPrevConflictHunk()
|
2021-04-02 00:48:13 +11:00
|
|
|
return gui.refreshMergePanel()
|
|
|
|
})
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
2021-08-22 01:03:05 +09:00
|
|
|
func (gui *Gui) handleSelectNextConflictHunk() error {
|
2021-04-02 00:48:13 +11:00
|
|
|
return gui.withMergeConflictLock(func() error {
|
2021-04-02 01:11:54 +11:00
|
|
|
gui.takeOverMergeConflictScrolling()
|
2021-08-22 01:03:05 +09:00
|
|
|
gui.State.Panels.Merging.SelectNextConflictHunk()
|
2021-04-02 00:48:13 +11:00
|
|
|
return gui.refreshMergePanel()
|
|
|
|
})
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
2021-04-02 00:48:13 +11:00
|
|
|
func (gui *Gui) handleSelectNextConflict() error {
|
|
|
|
return gui.withMergeConflictLock(func() error {
|
2021-04-02 01:11:54 +11:00
|
|
|
gui.takeOverMergeConflictScrolling()
|
2021-04-18 18:07:10 +10:00
|
|
|
gui.State.Panels.Merging.SelectNextConflict()
|
2021-04-02 00:48:13 +11:00
|
|
|
return gui.refreshMergePanel()
|
|
|
|
})
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
2021-04-02 00:48:13 +11:00
|
|
|
func (gui *Gui) handleSelectPrevConflict() error {
|
|
|
|
return gui.withMergeConflictLock(func() error {
|
2021-04-02 01:11:54 +11:00
|
|
|
gui.takeOverMergeConflictScrolling()
|
2021-04-18 18:07:10 +10:00
|
|
|
gui.State.Panels.Merging.SelectPrevConflict()
|
2021-04-02 00:48:13 +11:00
|
|
|
return gui.refreshMergePanel()
|
|
|
|
})
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
2020-11-16 20:38:26 +11:00
|
|
|
func (gui *Gui) pushFileSnapshot() error {
|
2021-04-18 18:07:10 +10:00
|
|
|
content, err := gui.catSelectedFile()
|
2018-08-14 11:05:26 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-04-18 18:07:10 +10:00
|
|
|
gui.State.Panels.Merging.PushFileSnapshot(content)
|
2018-08-14 11:05:26 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-04-02 00:48:13 +11:00
|
|
|
func (gui *Gui) handlePopFileSnapshot() error {
|
2021-04-18 18:07:10 +10:00
|
|
|
prevContent, ok := gui.State.Panels.Merging.PopFileSnapshot()
|
|
|
|
if !ok {
|
2018-08-14 11:05:26 +02:00
|
|
|
return nil
|
|
|
|
}
|
2021-04-18 18:07:10 +10:00
|
|
|
|
2020-08-16 17:45:12 +10:00
|
|
|
gitFile := gui.getSelectedFile()
|
|
|
|
if gitFile == nil {
|
|
|
|
return nil
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
2021-04-10 17:31:23 +10:00
|
|
|
gui.OnRunCommand(oscommands.NewCmdLogEntry("Undoing last conflict resolution", "Undo merge conflict resolution", false))
|
2020-03-09 11:34:10 +11:00
|
|
|
if err := ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-02-16 12:07:27 +11:00
|
|
|
return gui.refreshMergePanel()
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
2021-04-02 00:48:13 +11:00
|
|
|
func (gui *Gui) handlePickHunk() error {
|
|
|
|
return gui.withMergeConflictLock(func() error {
|
2021-04-02 01:11:54 +11:00
|
|
|
gui.takeOverMergeConflictScrolling()
|
2020-05-19 18:29:56 +10:00
|
|
|
|
2021-04-18 18:07:10 +10:00
|
|
|
ok, err := gui.resolveConflict(gui.State.Panels.Merging.Selection())
|
|
|
|
if err != nil {
|
2021-04-02 00:48:13 +11:00
|
|
|
return err
|
|
|
|
}
|
2020-03-09 11:34:10 +11:00
|
|
|
|
2021-04-18 18:07:10 +10:00
|
|
|
if !ok {
|
|
|
|
return nil
|
2021-04-02 00:48:13 +11:00
|
|
|
}
|
2018-12-08 16:54:54 +11:00
|
|
|
|
2021-04-18 18:07:10 +10:00
|
|
|
if gui.State.Panels.Merging.IsFinalConflict() {
|
2021-04-02 00:48:13 +11:00
|
|
|
if err := gui.handleCompleteMerge(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return gui.refreshMergePanel()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-08-24 21:38:34 +09:00
|
|
|
func (gui *Gui) handlePickAllHunks() error {
|
2021-04-02 00:48:13 +11:00
|
|
|
return gui.withMergeConflictLock(func() error {
|
2021-04-02 01:11:54 +11:00
|
|
|
gui.takeOverMergeConflictScrolling()
|
2021-04-02 00:48:13 +11:00
|
|
|
|
2021-08-24 21:33:19 +09:00
|
|
|
ok, err := gui.resolveConflict(mergeconflicts.ALL)
|
2021-04-18 18:07:10 +10:00
|
|
|
if err != nil {
|
2018-12-08 16:54:54 +11:00
|
|
|
return err
|
|
|
|
}
|
2021-04-18 18:07:10 +10:00
|
|
|
|
|
|
|
if !ok {
|
|
|
|
return nil
|
2021-04-02 00:48:13 +11:00
|
|
|
}
|
2021-04-18 18:07:10 +10:00
|
|
|
|
2021-04-02 00:48:13 +11:00
|
|
|
return gui.refreshMergePanel()
|
|
|
|
})
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
2021-04-18 18:07:10 +10:00
|
|
|
func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error) {
|
2021-04-02 00:48:13 +11:00
|
|
|
gitFile := gui.getSelectedFile()
|
|
|
|
if gitFile == nil {
|
2021-04-18 18:07:10 +10:00
|
|
|
return false, nil
|
2020-03-09 11:34:10 +11:00
|
|
|
}
|
2021-04-18 18:07:10 +10: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 18:07:10 +10:00
|
|
|
return false, err
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
2021-04-02 00:48:13 +11:00
|
|
|
|
2021-04-18 18:07:10 +10:00
|
|
|
if !ok {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := gui.pushFileSnapshot(); err != nil {
|
|
|
|
return false, gui.surfaceError(err)
|
2021-04-02 00:48:13 +11:00
|
|
|
}
|
2021-04-10 17:31:23 +10:00
|
|
|
|
|
|
|
var logStr string
|
|
|
|
switch selection {
|
|
|
|
case mergeconflicts.TOP:
|
|
|
|
logStr = "Picking top hunk"
|
2021-08-22 01:03:05 +09:00
|
|
|
case mergeconflicts.MIDDLE:
|
|
|
|
logStr = "Picking middle hunk"
|
2021-04-10 17:31:23 +10:00
|
|
|
case mergeconflicts.BOTTOM:
|
|
|
|
logStr = "Picking bottom hunk"
|
2021-08-24 21:33:19 +09:00
|
|
|
case mergeconflicts.ALL:
|
|
|
|
logStr = "Picking all hunks"
|
2021-04-10 17:31:23 +10:00
|
|
|
}
|
|
|
|
gui.OnRunCommand(oscommands.NewCmdLogEntry(logStr, "Resolve merge conflict", false))
|
2021-04-18 18:07:10 +10:00
|
|
|
return true, ioutil.WriteFile(gitFile.Name, []byte(output), 0644)
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
2021-04-02 00:15:39 +11:00
|
|
|
func (gui *Gui) refreshMergePanelWithLock() error {
|
2021-04-02 00:48:13 +11:00
|
|
|
return gui.withMergeConflictLock(gui.refreshMergePanel)
|
2021-04-02 00:15:39 +11:00
|
|
|
}
|
|
|
|
|
2019-02-16 12:07:27 +11:00
|
|
|
func (gui *Gui) refreshMergePanel() error {
|
2018-12-08 16:54:54 +11:00
|
|
|
panelState := gui.State.Panels.Merging
|
2020-11-16 20:38:26 +11:00
|
|
|
cat, err := gui.catSelectedFile()
|
2018-08-14 11:05:26 +02:00
|
|
|
if err != nil {
|
2020-08-23 09:46:28 +10:00
|
|
|
return gui.refreshMainViews(refreshMainOpts{
|
2020-08-18 22:23:36 +10:00
|
|
|
main: &viewUpdateOpts{
|
|
|
|
title: "",
|
2021-04-04 23:51:59 +10:00
|
|
|
task: NewRenderStringTask(err.Error()),
|
2020-08-18 22:23:36 +10:00
|
|
|
},
|
|
|
|
})
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
2021-04-18 18:07:10 +10:00
|
|
|
panelState.SetConflictsFromCat(cat)
|
2020-08-18 22:23:36 +10:00
|
|
|
|
2021-04-18 18:07:10 +10:00
|
|
|
if panelState.NoConflicts() {
|
2018-12-08 16:54:54 +11:00
|
|
|
return gui.handleCompleteMerge()
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
2018-12-08 16:54:54 +11:00
|
|
|
|
2019-02-25 22:11:35 +11:00
|
|
|
hasFocus := gui.currentViewName() == "main"
|
2021-04-18 18:07:10 +10:00
|
|
|
content := mergeconflicts.ColoredConflictFile(cat, panelState.State, hasFocus)
|
2020-01-12 14:22:30 +11:00
|
|
|
|
2020-11-16 20:38:26 +11:00
|
|
|
if err := gui.scrollToConflict(); err != nil {
|
2020-03-29 11:49:37 +11:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-23 09:46:28 +10:00
|
|
|
return gui.refreshMainViews(refreshMainOpts{
|
2020-08-18 22:02:35 +10:00
|
|
|
main: &viewUpdateOpts{
|
2020-10-04 11:00:48 +11:00
|
|
|
title: gui.Tr.MergeConflictsTitle,
|
2021-04-04 23:51:59 +10:00
|
|
|
task: NewRenderStringWithoutScrollTask(content),
|
2020-08-18 22:02:35 +10:00
|
|
|
noWrap: true,
|
|
|
|
},
|
|
|
|
})
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
2020-11-16 20:38:26 +11:00
|
|
|
func (gui *Gui) catSelectedFile() (string, error) {
|
2020-08-16 17:45:12 +10:00
|
|
|
item := gui.getSelectedFile()
|
|
|
|
if item == nil {
|
2020-10-04 11:00:48 +11:00
|
|
|
return "", errors.New(gui.Tr.NoFilesDisplay)
|
2020-05-19 18:01:29 +10:00
|
|
|
}
|
2020-08-16 17:45:12 +10:00
|
|
|
|
2020-05-19 18:01:29 +10:00
|
|
|
if item.Type != "file" {
|
2020-10-04 11:00:48 +11:00
|
|
|
return "", errors.New(gui.Tr.NotAFile)
|
2020-05-19 18:01:29 +10:00
|
|
|
}
|
2020-08-16 17:45:12 +10:00
|
|
|
|
2020-05-19 18:01:29 +10:00
|
|
|
cat, err := gui.GitCommand.CatFile(item.Name)
|
|
|
|
if err != nil {
|
|
|
|
gui.Log.Error(err)
|
2020-08-18 22:41:14 +10:00
|
|
|
return "", err
|
2020-05-19 18:01:29 +10:00
|
|
|
}
|
|
|
|
return cat, nil
|
|
|
|
}
|
|
|
|
|
2020-11-16 20:38:26 +11:00
|
|
|
func (gui *Gui) scrollToConflict() error {
|
2021-11-02 20:35:53 +11:00
|
|
|
if gui.State.Panels.Merging.UserVerticalScrolling {
|
2020-05-19 18:29:56 +10:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-12-08 16:54:54 +11:00
|
|
|
panelState := gui.State.Panels.Merging
|
2021-04-18 18:07:10 +10:00
|
|
|
if panelState.NoConflicts() {
|
2018-08-14 11:05:26 +02:00
|
|
|
return nil
|
|
|
|
}
|
2021-04-04 23:51:59 +10:00
|
|
|
|
2021-04-18 18:07:10 +10: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 16:54:54 +11:00
|
|
|
gui.g.Update(func(g *gocui.Gui) error {
|
2021-04-18 18:07:10 +10:00
|
|
|
return view.SetOrigin(ox, newOriginY)
|
2018-12-08 16:54:54 +11:00
|
|
|
})
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
2020-08-23 10:50:27 +10:00
|
|
|
func (gui *Gui) getMergingOptions() map[string]string {
|
2021-12-29 11:50:20 +11:00
|
|
|
keybindingConfig := gui.UserConfig.Keybinding
|
2020-10-03 14:54:55 +10:00
|
|
|
|
2020-08-23 10:50:27 +10:00
|
|
|
return map[string]string{
|
2020-10-04 11:00:48 +11: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,
|
2021-08-24 21:38:34 +09:00
|
|
|
gui.getKeyDisplay(keybindingConfig.Main.PickBothHunks): gui.Tr.LcPickAllHunks,
|
2020-10-04 11:00:48 +11:00
|
|
|
gui.getKeyDisplay(keybindingConfig.Universal.Undo): gui.Tr.LcUndo,
|
2020-08-23 10:50:27 +10:00
|
|
|
}
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
2020-08-15 17:23:16 +10:00
|
|
|
func (gui *Gui) handleEscapeMerge() error {
|
2021-04-02 01:11:54 +11:00
|
|
|
gui.takeOverMergeConflictScrolling()
|
2020-05-19 18:29:56 +10:00
|
|
|
|
2021-04-18 18:07:10 +10:00
|
|
|
gui.State.Panels.Merging.Reset()
|
2021-03-31 23:55:06 +11:00
|
|
|
if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil {
|
2019-02-16 21:30:29 +11:00
|
|
|
return err
|
|
|
|
}
|
2018-12-08 16:54:54 +11: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 23:51:59 +10:00
|
|
|
if gui.g.CurrentView() == gui.Views.Main {
|
2021-04-03 15:56:11 +11:00
|
|
|
return gui.pushContext(gui.State.Contexts.Files)
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
2018-12-08 16:54:54 +11:00
|
|
|
return nil
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
2018-12-08 16:54:54 +11:00
|
|
|
func (gui *Gui) handleCompleteMerge() error {
|
2020-11-16 20:38:26 +11:00
|
|
|
if err := gui.stageSelectedFile(); err != nil {
|
2019-02-16 21:30:29 +11:00
|
|
|
return err
|
|
|
|
}
|
2021-03-31 23:55:06 +11:00
|
|
|
if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil {
|
2019-02-16 21:30:29 +11:00
|
|
|
return err
|
|
|
|
}
|
2019-02-16 21:01:17 +11:00
|
|
|
// if we got conflicts after unstashing, we don't want to call any git
|
|
|
|
// commands to continue rebasing/merging here
|
2021-12-30 13:35:10 +11:00
|
|
|
if gui.GitCommand.WorkingTreeState() == enums.REBASE_MODE_NONE {
|
2020-08-15 17:23:16 +10:00
|
|
|
return gui.handleEscapeMerge()
|
2019-02-16 21:01:17 +11:00
|
|
|
}
|
2018-12-08 16:54:54 +11:00
|
|
|
// if there are no more files with merge conflicts, we should ask whether the user wants to continue
|
|
|
|
if !gui.anyFilesWithMergeConflicts() {
|
2021-04-02 00:48:13 +11:00
|
|
|
return gui.promptToContinueRebase()
|
2018-12-02 17:34:15 +01:00
|
|
|
}
|
2020-08-15 17:23:16 +10:00
|
|
|
return gui.handleEscapeMerge()
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
2019-03-03 16:11:20 +11:00
|
|
|
|
2021-04-02 00:48:13 +11:00
|
|
|
// promptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress
|
|
|
|
func (gui *Gui) promptToContinueRebase() error {
|
2021-04-02 01:11:54 +11:00
|
|
|
gui.takeOverMergeConflictScrolling()
|
2020-05-19 18:29:56 +10:00
|
|
|
|
2020-08-15 16:38:16 +10:00
|
|
|
return gui.ask(askOpts{
|
2020-08-23 19:28:59 +10:00
|
|
|
title: "continue",
|
2020-10-04 11:00:48 +11:00
|
|
|
prompt: gui.Tr.ConflictsResolved,
|
2020-08-23 19:28:59 +10:00
|
|
|
handlersManageFocus: true,
|
2020-08-15 16:36:39 +10:00
|
|
|
handleConfirm: func() error {
|
2021-04-03 15:56:11 +11:00
|
|
|
if err := gui.pushContext(gui.State.Contexts.Files); err != nil {
|
2020-08-23 19:28:59 +10:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-11-01 08:58:58 +11:00
|
|
|
return gui.genericMergeCommand(REBASE_OPTION_CONTINUE)
|
2020-08-15 16:36:39 +10:00
|
|
|
},
|
2020-08-23 19:28:59 +10:00
|
|
|
handleClose: func() error {
|
2021-04-03 15:56:11 +11:00
|
|
|
return gui.pushContext(gui.State.Contexts.Files)
|
2020-08-23 19:28:59 +10:00
|
|
|
},
|
2020-08-15 16:36:39 +10:00
|
|
|
})
|
2019-03-03 16:11:20 +11:00
|
|
|
}
|
2020-05-19 18:29:56 +10:00
|
|
|
|
|
|
|
func (gui *Gui) canScrollMergePanel() bool {
|
2021-04-05 01:10:23 +10:00
|
|
|
currentView := gui.g.CurrentView()
|
|
|
|
if currentView != gui.Views.Main && currentView != gui.Views.Files {
|
2020-05-19 18:29:56 +10:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-08-16 17:45:12 +10:00
|
|
|
file := gui.getSelectedFile()
|
|
|
|
if file == nil {
|
2020-05-19 18:29:56 +10:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return file.HasInlineMergeConflicts
|
|
|
|
}
|
2021-04-02 00:48:13 +11:00
|
|
|
|
|
|
|
func (gui *Gui) withMergeConflictLock(f func() error) error {
|
2021-04-18 18:07:10 +10:00
|
|
|
gui.State.Panels.Merging.Lock()
|
|
|
|
defer gui.State.Panels.Merging.Unlock()
|
2021-04-02 00:48:13 +11:00
|
|
|
|
|
|
|
return f()
|
|
|
|
}
|
|
|
|
|
2021-04-02 01:11:54 +11:00
|
|
|
func (gui *Gui) takeOverMergeConflictScrolling() {
|
2021-11-02 20:35:53 +11:00
|
|
|
gui.State.Panels.Merging.UserVerticalScrolling = false
|
2021-04-02 00:48:13 +11:00
|
|
|
}
|