mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-15 01:34:26 +02:00
improve merge conflict flow
This commit is contained in:
@ -7,9 +7,7 @@ import (
|
||||
"io/ioutil"
|
||||
"math"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
||||
)
|
||||
|
||||
@ -17,7 +15,7 @@ func (gui *Gui) handleSelectPrevConflictHunk() error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
gui.State.Panels.Merging.SelectPrevConflictHunk()
|
||||
return gui.refreshMergePanel()
|
||||
return gui.renderConflictsWithFocus()
|
||||
})
|
||||
}
|
||||
|
||||
@ -25,7 +23,7 @@ func (gui *Gui) handleSelectNextConflictHunk() error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
gui.State.Panels.Merging.SelectNextConflictHunk()
|
||||
return gui.refreshMergePanel()
|
||||
return gui.renderConflictsWithFocus()
|
||||
})
|
||||
}
|
||||
|
||||
@ -33,7 +31,7 @@ func (gui *Gui) handleSelectNextConflict() error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
gui.State.Panels.Merging.SelectNextConflict()
|
||||
return gui.refreshMergePanel()
|
||||
return gui.renderConflictsWithFocus()
|
||||
})
|
||||
}
|
||||
|
||||
@ -41,42 +39,29 @@ func (gui *Gui) handleSelectPrevConflict() error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
gui.State.Panels.Merging.SelectPrevConflict()
|
||||
return gui.refreshMergePanel()
|
||||
return gui.renderConflictsWithFocus()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) pushFileSnapshot() error {
|
||||
content, err := gui.catSelectedFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.State.Panels.Merging.PushFileSnapshot(content)
|
||||
return nil
|
||||
}
|
||||
func (gui *Gui) handleMergeConflictUndo() error {
|
||||
state := gui.State.Panels.Merging
|
||||
|
||||
func (gui *Gui) handlePopFileSnapshot() error {
|
||||
prevContent, ok := gui.State.Panels.Merging.PopFileSnapshot()
|
||||
ok := state.Undo()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
gitFile := gui.getSelectedFile()
|
||||
if gitFile == nil {
|
||||
return nil
|
||||
}
|
||||
gui.logAction("Restoring file to previous state")
|
||||
gui.logCommand("Undoing last conflict resolution", false)
|
||||
if err := ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644); err != nil {
|
||||
if err := ioutil.WriteFile(state.GetPath(), []byte(state.GetContent()), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshMergePanel()
|
||||
return gui.renderConflictsWithFocus()
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePickHunk() error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
|
||||
ok, err := gui.resolveConflict(gui.State.Panels.Merging.Selection())
|
||||
if err != nil {
|
||||
return err
|
||||
@ -86,20 +71,16 @@ func (gui *Gui) handlePickHunk() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if gui.State.Panels.Merging.IsFinalConflict() {
|
||||
if err := gui.handleCompleteMerge(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
if gui.State.Panels.Merging.AllConflictsResolved() {
|
||||
return gui.onLastConflictResolved()
|
||||
}
|
||||
return gui.refreshMergePanel()
|
||||
|
||||
return gui.renderConflictsWithFocus()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePickAllHunks() error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
|
||||
ok, err := gui.resolveConflict(mergeconflicts.ALL)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -109,17 +90,20 @@ func (gui *Gui) handlePickAllHunks() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.refreshMergePanel()
|
||||
if gui.State.Panels.Merging.AllConflictsResolved() {
|
||||
return gui.onLastConflictResolved()
|
||||
}
|
||||
|
||||
return gui.renderConflictsWithFocus()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error) {
|
||||
gitFile := gui.getSelectedFile()
|
||||
if gitFile == nil {
|
||||
return false, nil
|
||||
}
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
|
||||
ok, output, err := gui.State.Panels.Merging.ContentAfterConflictResolve(gitFile.Name, selection)
|
||||
state := gui.State.Panels.Merging
|
||||
|
||||
ok, content, err := state.ContentAfterConflictResolve(selection)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -128,10 +112,6 @@ func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := gui.pushFileSnapshot(); err != nil {
|
||||
return false, gui.surfaceError(err)
|
||||
}
|
||||
|
||||
var logStr string
|
||||
switch selection {
|
||||
case mergeconflicts.TOP:
|
||||
@ -145,40 +125,14 @@ func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error
|
||||
}
|
||||
gui.logAction("Resolve merge conflict")
|
||||
gui.logCommand(logStr, false)
|
||||
return true, ioutil.WriteFile(gitFile.Name, []byte(output), 0644)
|
||||
state.PushContent(content)
|
||||
return true, ioutil.WriteFile(state.GetPath(), []byte(content), 0644)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshMergePanelWithLock() error {
|
||||
return gui.withMergeConflictLock(gui.refreshMergePanel)
|
||||
}
|
||||
|
||||
// not re-using state here because we can run into issues with mutexes when
|
||||
// doing that.
|
||||
func (gui *Gui) renderConflictsFromFilesPanel() error {
|
||||
state := mergeconflicts.NewState()
|
||||
_, err := gui.renderConflicts(state, false)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (gui *Gui) renderConflicts(state *mergeconflicts.State, hasFocus bool) (bool, error) {
|
||||
cat, err := gui.catSelectedFile()
|
||||
if err != nil {
|
||||
return false, gui.refreshMainViews(refreshMainOpts{
|
||||
main: &viewUpdateOpts{
|
||||
title: "",
|
||||
task: NewRenderStringTask(err.Error()),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
state.SetConflictsFromCat(cat)
|
||||
|
||||
if state.NoConflicts() {
|
||||
return false, gui.handleCompleteMerge()
|
||||
}
|
||||
|
||||
content := mergeconflicts.ColoredConflictFile(cat, state, hasFocus)
|
||||
// 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
|
||||
@ -189,7 +143,7 @@ func (gui *Gui) renderConflicts(state *mergeconflicts.State, hasFocus bool) (boo
|
||||
})
|
||||
}
|
||||
|
||||
return true, gui.refreshMainViews(refreshMainOpts{
|
||||
return gui.refreshMainViews(refreshMainOpts{
|
||||
main: &viewUpdateOpts{
|
||||
title: gui.Tr.MergeConflictsTitle,
|
||||
task: NewRenderStringWithoutScrollTask(content),
|
||||
@ -198,35 +152,8 @@ func (gui *Gui) renderConflicts(state *mergeconflicts.State, hasFocus bool) (boo
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshMergePanel() error {
|
||||
conflictsFound, err := gui.renderConflicts(gui.State.Panels.Merging.State, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !conflictsFound {
|
||||
return gui.handleCompleteMerge()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) catSelectedFile() (string, error) {
|
||||
item := gui.getSelectedFile()
|
||||
if item == nil {
|
||||
return "", errors.New(gui.Tr.NoFilesDisplay)
|
||||
}
|
||||
|
||||
if item.Type != "file" {
|
||||
return "", errors.New(gui.Tr.NotAFile)
|
||||
}
|
||||
|
||||
cat, err := gui.Git.File.Cat(item.Name)
|
||||
if err != nil {
|
||||
gui.Log.Error(err)
|
||||
return "", err
|
||||
}
|
||||
return cat, nil
|
||||
func (gui *Gui) renderConflictsWithFocus() error {
|
||||
return gui.renderConflicts(true)
|
||||
}
|
||||
|
||||
func (gui *Gui) centerYPos(view *gocui.View, y int) {
|
||||
@ -256,68 +183,47 @@ func (gui *Gui) handleEscapeMerge() error {
|
||||
return gui.escapeMerge()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCompleteMerge() error {
|
||||
if err := gui.stageSelectedFile(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}}); err != nil {
|
||||
return err
|
||||
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.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{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
|
||||
}
|
||||
|
||||
// if there are no more files with merge conflicts, we should ask whether the user wants to continue
|
||||
if gui.Git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && !gui.anyFilesWithMergeConflicts() {
|
||||
return gui.promptToContinueRebase()
|
||||
}
|
||||
gui.State.Panels.Merging.SetContent(content, path)
|
||||
|
||||
return gui.escapeMerge()
|
||||
return !gui.State.Panels.Merging.NoConflicts(), nil
|
||||
}
|
||||
|
||||
func (gui *Gui) escapeMerge() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
|
||||
gui.State.Panels.Merging.Reset()
|
||||
gui.resetMergeState()
|
||||
|
||||
// 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
|
||||
if gui.g.CurrentView() == gui.Views.Main {
|
||||
|
||||
if gui.currentContext().GetKey() == MAIN_MERGING_CONTEXT_KEY {
|
||||
return gui.pushContext(gui.State.Contexts.Files)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// promptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress
|
||||
func (gui *Gui) promptToContinueRebase() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
|
||||
return gui.ask(askOpts{
|
||||
title: "continue",
|
||||
prompt: gui.Tr.ConflictsResolved,
|
||||
handlersManageFocus: true,
|
||||
handleConfirm: func() error {
|
||||
if err := gui.pushContext(gui.State.Contexts.Files); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.genericMergeCommand(REBASE_OPTION_CONTINUE)
|
||||
},
|
||||
handleClose: func() error {
|
||||
return gui.pushContext(gui.State.Contexts.Files)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) canScrollMergePanel() bool {
|
||||
func (gui *Gui) renderingConflicts() bool {
|
||||
currentView := gui.g.CurrentView()
|
||||
if currentView != gui.Views.Main && currentView != gui.Views.Files {
|
||||
return false
|
||||
}
|
||||
|
||||
file := gui.getSelectedFile()
|
||||
if file == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return file.HasInlineMergeConflicts
|
||||
return gui.State.Panels.Merging.Active()
|
||||
}
|
||||
|
||||
func (gui *Gui) withMergeConflictLock(f func() error) error {
|
||||
|
Reference in New Issue
Block a user