mirror of
https://github.com/jesseduffield/lazygit.git
synced 2024-12-12 11:15:00 +02:00
302 lines
7.7 KiB
Go
302 lines
7.7 KiB
Go
// 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"
|
|
"io/ioutil"
|
|
"math"
|
|
|
|
"github.com/jesseduffield/gocui"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
)
|
|
|
|
func (gui *Gui) handleSelectPrevConflictHunk() error {
|
|
return gui.withMergeConflictLock(func() error {
|
|
gui.takeOverMergeConflictScrolling()
|
|
gui.State.Panels.Merging.SelectPrevConflictHunk()
|
|
return gui.renderConflictsWithFocus()
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) handleSelectNextConflictHunk() error {
|
|
return gui.withMergeConflictLock(func() error {
|
|
gui.takeOverMergeConflictScrolling()
|
|
gui.State.Panels.Merging.SelectNextConflictHunk()
|
|
return gui.renderConflictsWithFocus()
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) handleSelectNextConflict() error {
|
|
return gui.withMergeConflictLock(func() error {
|
|
gui.takeOverMergeConflictScrolling()
|
|
gui.State.Panels.Merging.SelectNextConflict()
|
|
return gui.renderConflictsWithFocus()
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) handleSelectPrevConflict() error {
|
|
return gui.withMergeConflictLock(func() error {
|
|
gui.takeOverMergeConflictScrolling()
|
|
gui.State.Panels.Merging.SelectPrevConflict()
|
|
return gui.renderConflictsWithFocus()
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) handleMergeConflictUndo() error {
|
|
state := gui.State.Panels.Merging
|
|
|
|
ok := state.Undo()
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
gui.c.LogAction("Restoring file to previous state")
|
|
gui.LogCommand("Undoing last conflict resolution", false)
|
|
if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0o644); err != nil {
|
|
return err
|
|
}
|
|
|
|
return gui.renderConflictsWithFocus()
|
|
}
|
|
|
|
func (gui *Gui) handlePickHunk() error {
|
|
return gui.withMergeConflictLock(func() error {
|
|
ok, err := gui.resolveConflict(gui.State.Panels.Merging.Selection())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
if gui.State.Panels.Merging.AllConflictsResolved() {
|
|
return gui.onLastConflictResolved()
|
|
}
|
|
|
|
return gui.renderConflictsWithFocus()
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) handlePickAllHunks() error {
|
|
return gui.withMergeConflictLock(func() error {
|
|
ok, err := gui.resolveConflict(mergeconflicts.ALL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
if gui.State.Panels.Merging.AllConflictsResolved() {
|
|
return gui.onLastConflictResolved()
|
|
}
|
|
|
|
return gui.renderConflictsWithFocus()
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error) {
|
|
gui.takeOverMergeConflictScrolling()
|
|
|
|
state := gui.State.Panels.Merging
|
|
|
|
ok, content, err := state.ContentAfterConflictResolve(selection)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
|
|
var logStr string
|
|
switch selection {
|
|
case mergeconflicts.TOP:
|
|
logStr = "Picking top hunk"
|
|
case mergeconflicts.MIDDLE:
|
|
logStr = "Picking middle hunk"
|
|
case mergeconflicts.BOTTOM:
|
|
logStr = "Picking bottom hunk"
|
|
case mergeconflicts.ALL:
|
|
logStr = "Picking all hunks"
|
|
}
|
|
gui.c.LogAction("Resolve merge conflict")
|
|
gui.LogCommand(logStr, false)
|
|
state.PushContent(content)
|
|
return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0o644)
|
|
}
|
|
|
|
// precondition: we actually have conflicts to render
|
|
func (gui *Gui) renderConflicts(hasFocus bool) error {
|
|
state := gui.State.Panels.Merging.State
|
|
content := mergeconflicts.ColoredConflictFile(state, hasFocus)
|
|
|
|
if !gui.State.Panels.Merging.UserVerticalScrolling {
|
|
// TODO: find a way to not have to do this OnUIThread thing. Why doesn't it work
|
|
// without it given that we're calling the 'no scroll' variant below?
|
|
gui.OnUIThread(func() error {
|
|
gui.State.Panels.Merging.Lock()
|
|
defer gui.State.Panels.Merging.Unlock()
|
|
|
|
if !state.Active() {
|
|
return nil
|
|
}
|
|
|
|
gui.centerYPos(gui.Views.Main, state.GetConflictMiddle())
|
|
return nil
|
|
})
|
|
}
|
|
|
|
return gui.refreshMainViews(refreshMainOpts{
|
|
main: &viewUpdateOpts{
|
|
title: gui.c.Tr.MergeConflictsTitle,
|
|
task: NewRenderStringWithoutScrollTask(content),
|
|
context: gui.State.Contexts.Merging,
|
|
noWrap: true,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) renderConflictsWithFocus() error {
|
|
return gui.renderConflicts(true)
|
|
}
|
|
|
|
func (gui *Gui) renderConflictsWithLock(hasFocus bool) error {
|
|
return gui.withMergeConflictLock(func() error {
|
|
return gui.renderConflicts(hasFocus)
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) centerYPos(view *gocui.View, y int) {
|
|
ox, _ := view.Origin()
|
|
_, height := view.Size()
|
|
newOriginY := int(math.Max(0, float64(y-(height/2))))
|
|
_ = view.SetOrigin(ox, newOriginY)
|
|
}
|
|
|
|
func (gui *Gui) getMergingOptions() map[string]string {
|
|
keybindingConfig := gui.c.UserConfig.Keybinding
|
|
|
|
return map[string]string{
|
|
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevItem), gui.getKeyDisplay(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcSelectHunk,
|
|
fmt.Sprintf("%s %s", gui.getKeyDisplay(keybindingConfig.Universal.PrevBlock), gui.getKeyDisplay(keybindingConfig.Universal.NextBlock)): gui.c.Tr.LcNavigateConflicts,
|
|
gui.getKeyDisplay(keybindingConfig.Universal.Select): gui.c.Tr.LcPickHunk,
|
|
gui.getKeyDisplay(keybindingConfig.Main.PickBothHunks): gui.c.Tr.LcPickAllHunks,
|
|
gui.getKeyDisplay(keybindingConfig.Universal.Undo): gui.c.Tr.LcUndo,
|
|
}
|
|
}
|
|
|
|
func (gui *Gui) handleEscapeMerge() error {
|
|
if err := gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}}); err != nil {
|
|
return err
|
|
}
|
|
|
|
return gui.escapeMerge()
|
|
}
|
|
|
|
func (gui *Gui) onLastConflictResolved() error {
|
|
// as part of refreshing files, we handle the situation where a file has had
|
|
// its merge conflicts resolved.
|
|
return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
|
|
}
|
|
|
|
func (gui *Gui) resetMergeState() {
|
|
gui.takeOverMergeConflictScrolling()
|
|
gui.State.Panels.Merging.Reset()
|
|
}
|
|
|
|
func (gui *Gui) setMergeState(path string) (bool, error) {
|
|
content, err := gui.git.File.Cat(path)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
gui.State.Panels.Merging.SetContent(content, path)
|
|
|
|
return !gui.State.Panels.Merging.NoConflicts(), nil
|
|
}
|
|
|
|
func (gui *Gui) setMergeStateWithLock(path string) (bool, error) {
|
|
gui.State.Panels.Merging.Lock()
|
|
defer gui.State.Panels.Merging.Unlock()
|
|
|
|
return gui.setMergeState(path)
|
|
}
|
|
|
|
func (gui *Gui) resetMergeStateWithLock() {
|
|
gui.State.Panels.Merging.Lock()
|
|
defer gui.State.Panels.Merging.Unlock()
|
|
|
|
gui.resetMergeState()
|
|
}
|
|
|
|
func (gui *Gui) escapeMerge() error {
|
|
gui.resetMergeState()
|
|
|
|
// doing this in separate UI thread so that we're not still holding the lock by the time refresh the file
|
|
gui.OnUIThread(func() error {
|
|
return gui.pushContext(gui.State.Contexts.Files)
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (gui *Gui) renderingConflicts() bool {
|
|
currentView := gui.g.CurrentView()
|
|
if currentView != gui.Views.Main && currentView != gui.Views.Files {
|
|
return false
|
|
}
|
|
|
|
return gui.State.Panels.Merging.Active()
|
|
}
|
|
|
|
func (gui *Gui) withMergeConflictLock(f func() error) error {
|
|
gui.State.Panels.Merging.Lock()
|
|
defer gui.State.Panels.Merging.Unlock()
|
|
|
|
return f()
|
|
}
|
|
|
|
func (gui *Gui) takeOverMergeConflictScrolling() {
|
|
gui.State.Panels.Merging.UserVerticalScrolling = false
|
|
}
|
|
|
|
func (gui *Gui) setConflictsAndRender(path string, hasFocus bool) (bool, error) {
|
|
hasConflicts, err := gui.setMergeState(path)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if hasConflicts {
|
|
return true, gui.renderConflicts(hasFocus)
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (gui *Gui) setConflictsAndRenderWithLock(path string, hasFocus bool) (bool, error) {
|
|
gui.State.Panels.Merging.Lock()
|
|
defer gui.State.Panels.Merging.Unlock()
|
|
|
|
return gui.setConflictsAndRender(path, hasFocus)
|
|
}
|
|
|
|
func (gui *Gui) switchToMerge(path string) error {
|
|
gui.takeOverMergeConflictScrolling()
|
|
|
|
if gui.State.Panels.Merging.GetPath() != path {
|
|
hasConflicts, err := gui.setMergeStateWithLock(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !hasConflicts {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return gui.c.PushContext(gui.State.Contexts.Merging)
|
|
}
|