mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-06 23:46:13 +02:00
refactor
This commit is contained in:
parent
cc9293b386
commit
eeeef9ca86
@ -1410,42 +1410,42 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
||||||
Key: gui.getKey(config.Universal.Select),
|
Key: gui.getKey(config.Universal.Select),
|
||||||
Handler: gui.handlePickHunk,
|
Handler: gui.wrappedHandler(gui.handlePickHunk),
|
||||||
Description: gui.Tr.PickHunk,
|
Description: gui.Tr.PickHunk,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
||||||
Key: gui.getKey(config.Main.PickBothHunks),
|
Key: gui.getKey(config.Main.PickBothHunks),
|
||||||
Handler: gui.handlePickBothHunks,
|
Handler: gui.wrappedHandler(gui.handlePickBothHunks),
|
||||||
Description: gui.Tr.PickBothHunks,
|
Description: gui.Tr.PickBothHunks,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
||||||
Key: gui.getKey(config.Universal.PrevBlock),
|
Key: gui.getKey(config.Universal.PrevBlock),
|
||||||
Handler: gui.handleSelectPrevConflict,
|
Handler: gui.wrappedHandler(gui.handleSelectPrevConflict),
|
||||||
Description: gui.Tr.PrevConflict,
|
Description: gui.Tr.PrevConflict,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
||||||
Key: gui.getKey(config.Universal.NextBlock),
|
Key: gui.getKey(config.Universal.NextBlock),
|
||||||
Handler: gui.handleSelectNextConflict,
|
Handler: gui.wrappedHandler(gui.handleSelectNextConflict),
|
||||||
Description: gui.Tr.NextConflict,
|
Description: gui.Tr.NextConflict,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
||||||
Key: gui.getKey(config.Universal.PrevItem),
|
Key: gui.getKey(config.Universal.PrevItem),
|
||||||
Handler: gui.handleSelectTop,
|
Handler: gui.wrappedHandler(gui.handleSelectTop),
|
||||||
Description: gui.Tr.SelectTop,
|
Description: gui.Tr.SelectTop,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
||||||
Key: gui.getKey(config.Universal.NextItem),
|
Key: gui.getKey(config.Universal.NextItem),
|
||||||
Handler: gui.handleSelectBottom,
|
Handler: gui.wrappedHandler(gui.handleSelectBottom),
|
||||||
Description: gui.Tr.SelectBottom,
|
Description: gui.Tr.SelectBottom,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1453,48 +1453,48 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
||||||
Key: gocui.MouseWheelUp,
|
Key: gocui.MouseWheelUp,
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleSelectTop,
|
Handler: gui.wrappedHandler(gui.handleSelectTop),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
||||||
Key: gocui.MouseWheelDown,
|
Key: gocui.MouseWheelDown,
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleSelectBottom,
|
Handler: gui.wrappedHandler(gui.handleSelectBottom),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
||||||
Key: gui.getKey(config.Universal.PrevBlockAlt),
|
Key: gui.getKey(config.Universal.PrevBlockAlt),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleSelectPrevConflict,
|
Handler: gui.wrappedHandler(gui.handleSelectPrevConflict),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
||||||
Key: gui.getKey(config.Universal.NextBlockAlt),
|
Key: gui.getKey(config.Universal.NextBlockAlt),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleSelectNextConflict,
|
Handler: gui.wrappedHandler(gui.handleSelectNextConflict),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
||||||
Key: gui.getKey(config.Universal.PrevItemAlt),
|
Key: gui.getKey(config.Universal.PrevItemAlt),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleSelectTop,
|
Handler: gui.wrappedHandler(gui.handleSelectTop),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
||||||
Key: gui.getKey(config.Universal.NextItemAlt),
|
Key: gui.getKey(config.Universal.NextItemAlt),
|
||||||
Modifier: gocui.ModNone,
|
Modifier: gocui.ModNone,
|
||||||
Handler: gui.handleSelectBottom,
|
Handler: gui.wrappedHandler(gui.handleSelectBottom),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ViewName: "main",
|
ViewName: "main",
|
||||||
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
Contexts: []string{MAIN_MERGING_CONTEXT_KEY},
|
||||||
Key: gui.getKey(config.Universal.Undo),
|
Key: gui.getKey(config.Universal.Undo),
|
||||||
Handler: gui.handlePopFileSnapshot,
|
Handler: gui.wrappedHandler(gui.handlePopFileSnapshot),
|
||||||
Description: gui.Tr.LcUndo,
|
Description: gui.Tr.LcUndo,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -4,132 +4,146 @@ package gui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/fatih/color"
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/golang-collections/collections/stack"
|
"github.com/golang-collections/collections/stack"
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gui *Gui) findConflicts(content string) []commands.Conflict {
|
func (gui *Gui) handleSelectTop() error {
|
||||||
conflicts := make([]commands.Conflict, 0)
|
return gui.withMergeConflictLock(func() error {
|
||||||
|
gui.takeOverScrolling()
|
||||||
|
gui.State.Panels.Merging.ConflictTop = true
|
||||||
|
return gui.refreshMergePanel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if content == "" {
|
func (gui *Gui) handleSelectBottom() error {
|
||||||
return conflicts
|
return gui.withMergeConflictLock(func() error {
|
||||||
}
|
gui.takeOverScrolling()
|
||||||
|
gui.State.Panels.Merging.ConflictTop = false
|
||||||
|
return gui.refreshMergePanel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var newConflict commands.Conflict
|
func (gui *Gui) handleSelectNextConflict() error {
|
||||||
for i, line := range utils.SplitLines(content) {
|
return gui.withMergeConflictLock(func() error {
|
||||||
trimmedLine := strings.TrimPrefix(line, "++")
|
gui.takeOverScrolling()
|
||||||
if trimmedLine == "<<<<<<< HEAD" || trimmedLine == "<<<<<<< MERGE_HEAD" || trimmedLine == "<<<<<<< Updated upstream" || trimmedLine == "<<<<<<< ours" {
|
if gui.State.Panels.Merging.ConflictIndex >= len(gui.State.Panels.Merging.Conflicts)-1 {
|
||||||
newConflict = commands.Conflict{Start: i}
|
return nil
|
||||||
} else if trimmedLine == "=======" {
|
|
||||||
newConflict.Middle = i
|
|
||||||
} else if strings.HasPrefix(trimmedLine, ">>>>>>> ") {
|
|
||||||
newConflict.End = i
|
|
||||||
conflicts = append(conflicts, newConflict)
|
|
||||||
}
|
}
|
||||||
}
|
gui.State.Panels.Merging.ConflictIndex++
|
||||||
return conflicts
|
return gui.refreshMergePanel()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) shiftConflict(conflicts []commands.Conflict) (commands.Conflict, []commands.Conflict) {
|
func (gui *Gui) handleSelectPrevConflict() error {
|
||||||
return conflicts[0], conflicts[1:]
|
return gui.withMergeConflictLock(func() error {
|
||||||
}
|
gui.takeOverScrolling()
|
||||||
|
if gui.State.Panels.Merging.ConflictIndex <= 0 {
|
||||||
func (gui *Gui) shouldHighlightLine(index int, conflict commands.Conflict, top bool) bool {
|
return nil
|
||||||
return (index >= conflict.Start && index <= conflict.Middle && top) || (index >= conflict.Middle && index <= conflict.End && !top)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) coloredConflictFile(content string, conflicts []commands.Conflict, conflictIndex int, conflictTop, hasFocus bool) string {
|
|
||||||
if len(conflicts) == 0 {
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
conflict, remainingConflicts := gui.shiftConflict(conflicts)
|
|
||||||
var outputBuffer bytes.Buffer
|
|
||||||
for i, line := range utils.SplitLines(content) {
|
|
||||||
colourAttr := theme.DefaultTextColor
|
|
||||||
if i == conflict.Start || i == conflict.Middle || i == conflict.End {
|
|
||||||
colourAttr = color.FgRed
|
|
||||||
}
|
}
|
||||||
colour := color.New(colourAttr)
|
gui.State.Panels.Merging.ConflictIndex--
|
||||||
if hasFocus && conflictIndex < len(conflicts) && conflicts[conflictIndex] == conflict && gui.shouldHighlightLine(i, conflict, conflictTop) {
|
return gui.refreshMergePanel()
|
||||||
colour.Add(color.Bold)
|
})
|
||||||
colour.Add(theme.SelectedRangeBgColor)
|
|
||||||
}
|
|
||||||
if i == conflict.End && len(remainingConflicts) > 0 {
|
|
||||||
conflict, remainingConflicts = gui.shiftConflict(remainingConflicts)
|
|
||||||
}
|
|
||||||
outputBuffer.WriteString(utils.ColoredStringDirect(line, colour) + "\n")
|
|
||||||
}
|
|
||||||
return outputBuffer.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) takeOverScrolling() {
|
func (gui *Gui) pushFileSnapshot() error {
|
||||||
gui.State.Panels.Merging.UserScrolling = false
|
gitFile := gui.getSelectedFile()
|
||||||
}
|
if gitFile == nil {
|
||||||
|
|
||||||
func (gui *Gui) handleSelectTop(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
gui.State.Panels.Merging.ConflictsMutex.Lock()
|
|
||||||
defer gui.State.Panels.Merging.ConflictsMutex.Unlock()
|
|
||||||
|
|
||||||
gui.takeOverScrolling()
|
|
||||||
gui.State.Panels.Merging.ConflictTop = true
|
|
||||||
return gui.refreshMergePanel()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleSelectBottom(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
gui.State.Panels.Merging.ConflictsMutex.Lock()
|
|
||||||
defer gui.State.Panels.Merging.ConflictsMutex.Unlock()
|
|
||||||
|
|
||||||
gui.takeOverScrolling()
|
|
||||||
gui.State.Panels.Merging.ConflictTop = false
|
|
||||||
return gui.refreshMergePanel()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handleSelectNextConflict(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
gui.State.Panels.Merging.ConflictsMutex.Lock()
|
|
||||||
defer gui.State.Panels.Merging.ConflictsMutex.Unlock()
|
|
||||||
|
|
||||||
gui.takeOverScrolling()
|
|
||||||
if gui.State.Panels.Merging.ConflictIndex >= len(gui.State.Panels.Merging.Conflicts)-1 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
gui.State.Panels.Merging.ConflictIndex++
|
content, err := gui.GitCommand.CatFile(gitFile.Name)
|
||||||
return gui.refreshMergePanel()
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gui.State.Panels.Merging.EditHistory.Push(content)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleSelectPrevConflict(g *gocui.Gui, v *gocui.View) error {
|
func (gui *Gui) handlePopFileSnapshot() error {
|
||||||
gui.State.Panels.Merging.ConflictsMutex.Lock()
|
if gui.State.Panels.Merging.EditHistory.Len() == 0 {
|
||||||
defer gui.State.Panels.Merging.ConflictsMutex.Unlock()
|
|
||||||
|
|
||||||
gui.takeOverScrolling()
|
|
||||||
if gui.State.Panels.Merging.ConflictIndex <= 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
gui.State.Panels.Merging.ConflictIndex--
|
prevContent := gui.State.Panels.Merging.EditHistory.Pop().(string)
|
||||||
|
gitFile := gui.getSelectedFile()
|
||||||
|
if gitFile == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return gui.refreshMergePanel()
|
return gui.refreshMergePanel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) isIndexToDelete(i int, conflict commands.Conflict, pick string) bool {
|
func (gui *Gui) handlePickHunk() error {
|
||||||
return i == conflict.Middle ||
|
return gui.withMergeConflictLock(func() error {
|
||||||
i == conflict.Start ||
|
conflict := gui.getCurrentConflict()
|
||||||
i == conflict.End ||
|
if conflict == nil {
|
||||||
pick != "both" &&
|
return nil
|
||||||
(pick == "bottom" && i > conflict.Start && i < conflict.Middle) ||
|
}
|
||||||
(pick == "top" && i > conflict.Middle && i < conflict.End)
|
|
||||||
|
gui.takeOverScrolling()
|
||||||
|
|
||||||
|
if err := gui.pushFileSnapshot(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
selection := mergeconflicts.BOTTOM
|
||||||
|
if gui.State.Panels.Merging.ConflictTop {
|
||||||
|
selection = mergeconflicts.TOP
|
||||||
|
}
|
||||||
|
err := gui.resolveConflict(*conflict, selection)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if that was the last conflict, finish the merge for this file
|
||||||
|
if len(gui.State.Panels.Merging.Conflicts) == 1 {
|
||||||
|
if err := gui.handleCompleteMerge(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gui.refreshMergePanel()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) resolveConflict(conflict commands.Conflict, pick string) error {
|
func (gui *Gui) handlePickBothHunks() error {
|
||||||
|
return gui.withMergeConflictLock(func() error {
|
||||||
|
conflict := gui.getCurrentConflict()
|
||||||
|
if conflict == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.takeOverScrolling()
|
||||||
|
|
||||||
|
if err := gui.pushFileSnapshot(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err := gui.resolveConflict(*conflict, mergeconflicts.BOTH)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return gui.refreshMergePanel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) getCurrentConflict() *commands.Conflict {
|
||||||
|
if len(gui.State.Panels.Merging.Conflicts) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gui.State.Panels.Merging.Conflicts[gui.State.Panels.Merging.ConflictIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) resolveConflict(conflict commands.Conflict, selection mergeconflicts.Selection) error {
|
||||||
gitFile := gui.getSelectedFile()
|
gitFile := gui.getSelectedFile()
|
||||||
if gitFile == nil {
|
if gitFile == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -147,93 +161,15 @@ func (gui *Gui) resolveConflict(conflict commands.Conflict, pick string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !gui.isIndexToDelete(i, conflict, pick) {
|
if !mergeconflicts.IsIndexToDelete(i, conflict, selection) {
|
||||||
output += line
|
output += line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ioutil.WriteFile(gitFile.Name, []byte(output), 0644)
|
return ioutil.WriteFile(gitFile.Name, []byte(output), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) pushFileSnapshot() error {
|
|
||||||
gitFile := gui.getSelectedFile()
|
|
||||||
if gitFile == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
content, err := gui.GitCommand.CatFile(gitFile.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gui.State.Panels.Merging.EditHistory.Push(content)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
if gui.State.Panels.Merging.EditHistory.Len() == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
prevContent := gui.State.Panels.Merging.EditHistory.Pop().(string)
|
|
||||||
gitFile := gui.getSelectedFile()
|
|
||||||
if gitFile == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gui.refreshMergePanel()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handlePickHunk(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
gui.State.Panels.Merging.ConflictsMutex.Lock()
|
|
||||||
defer gui.State.Panels.Merging.ConflictsMutex.Unlock()
|
|
||||||
|
|
||||||
gui.takeOverScrolling()
|
|
||||||
|
|
||||||
conflict := gui.State.Panels.Merging.Conflicts[gui.State.Panels.Merging.ConflictIndex]
|
|
||||||
if err := gui.pushFileSnapshot(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pick := "bottom"
|
|
||||||
if gui.State.Panels.Merging.ConflictTop {
|
|
||||||
pick = "top"
|
|
||||||
}
|
|
||||||
err := gui.resolveConflict(conflict, pick)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if that was the last conflict, finish the merge for this file
|
|
||||||
if len(gui.State.Panels.Merging.Conflicts) == 1 {
|
|
||||||
if err := gui.handleCompleteMerge(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return gui.refreshMergePanel()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) handlePickBothHunks(g *gocui.Gui, v *gocui.View) error {
|
|
||||||
gui.State.Panels.Merging.ConflictsMutex.Lock()
|
|
||||||
defer gui.State.Panels.Merging.ConflictsMutex.Unlock()
|
|
||||||
|
|
||||||
gui.takeOverScrolling()
|
|
||||||
|
|
||||||
conflict := gui.State.Panels.Merging.Conflicts[gui.State.Panels.Merging.ConflictIndex]
|
|
||||||
if err := gui.pushFileSnapshot(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err := gui.resolveConflict(conflict, "both")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return gui.refreshMergePanel()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gui *Gui) refreshMergePanelWithLock() error {
|
func (gui *Gui) refreshMergePanelWithLock() error {
|
||||||
gui.State.Panels.Merging.ConflictsMutex.Lock()
|
return gui.withMergeConflictLock(gui.refreshMergePanel)
|
||||||
defer gui.State.Panels.Merging.ConflictsMutex.Unlock()
|
|
||||||
|
|
||||||
return gui.refreshMergePanel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) refreshMergePanel() error {
|
func (gui *Gui) refreshMergePanel() error {
|
||||||
@ -248,7 +184,7 @@ func (gui *Gui) refreshMergePanel() error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
panelState.Conflicts = gui.findConflicts(cat)
|
panelState.Conflicts = mergeconflicts.FindConflicts(cat)
|
||||||
|
|
||||||
// handle potential fixes that the user made in their editor since we last refreshed
|
// handle potential fixes that the user made in their editor since we last refreshed
|
||||||
if len(panelState.Conflicts) == 0 {
|
if len(panelState.Conflicts) == 0 {
|
||||||
@ -258,7 +194,7 @@ func (gui *Gui) refreshMergePanel() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasFocus := gui.currentViewName() == "main"
|
hasFocus := gui.currentViewName() == "main"
|
||||||
content := gui.coloredConflictFile(cat, panelState.Conflicts, panelState.ConflictIndex, panelState.ConflictTop, hasFocus)
|
content := mergeconflicts.ColoredConflictFile(cat, panelState.Conflicts, panelState.ConflictIndex, panelState.ConflictTop, hasFocus)
|
||||||
|
|
||||||
if err := gui.scrollToConflict(); err != nil {
|
if err := gui.scrollToConflict(); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -353,13 +289,13 @@ func (gui *Gui) handleCompleteMerge() error {
|
|||||||
}
|
}
|
||||||
// if there are no more files with merge conflicts, we should ask whether the user wants to continue
|
// if there are no more files with merge conflicts, we should ask whether the user wants to continue
|
||||||
if !gui.anyFilesWithMergeConflicts() {
|
if !gui.anyFilesWithMergeConflicts() {
|
||||||
return gui.promptToContinue()
|
return gui.promptToContinueRebase()
|
||||||
}
|
}
|
||||||
return gui.handleEscapeMerge()
|
return gui.handleEscapeMerge()
|
||||||
}
|
}
|
||||||
|
|
||||||
// promptToContinue asks the user if they want to continue the rebase/merge that's in progress
|
// promptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress
|
||||||
func (gui *Gui) promptToContinue() error {
|
func (gui *Gui) promptToContinueRebase() error {
|
||||||
gui.takeOverScrolling()
|
gui.takeOverScrolling()
|
||||||
|
|
||||||
return gui.ask(askOpts{
|
return gui.ask(askOpts{
|
||||||
@ -392,3 +328,14 @@ func (gui *Gui) canScrollMergePanel() bool {
|
|||||||
|
|
||||||
return file.HasInlineMergeConflicts
|
return file.HasInlineMergeConflicts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) withMergeConflictLock(f func() error) error {
|
||||||
|
gui.State.Panels.Merging.ConflictsMutex.Lock()
|
||||||
|
defer gui.State.Panels.Merging.ConflictsMutex.Unlock()
|
||||||
|
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) takeOverScrolling() {
|
||||||
|
gui.State.Panels.Merging.UserScrolling = false
|
||||||
|
}
|
||||||
|
86
pkg/gui/mergeconflicts/merge_conflicts.go
Normal file
86
pkg/gui/mergeconflicts/merge_conflicts.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package mergeconflicts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Selection int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TOP Selection = iota
|
||||||
|
BOTTOM
|
||||||
|
BOTH
|
||||||
|
)
|
||||||
|
|
||||||
|
func FindConflicts(content string) []commands.Conflict {
|
||||||
|
conflicts := make([]commands.Conflict, 0)
|
||||||
|
|
||||||
|
if content == "" {
|
||||||
|
return conflicts
|
||||||
|
}
|
||||||
|
|
||||||
|
var newConflict commands.Conflict
|
||||||
|
for i, line := range utils.SplitLines(content) {
|
||||||
|
trimmedLine := strings.TrimPrefix(line, "++")
|
||||||
|
switch trimmedLine {
|
||||||
|
case "<<<<<<< HEAD", "<<<<<<< MERGE_HEAD", "<<<<<<< Updated upstream", "<<<<<<< ours":
|
||||||
|
newConflict = commands.Conflict{Start: i}
|
||||||
|
case "=======":
|
||||||
|
newConflict.Middle = i
|
||||||
|
default:
|
||||||
|
if strings.HasPrefix(trimmedLine, ">>>>>>> ") {
|
||||||
|
newConflict.End = i
|
||||||
|
conflicts = append(conflicts, newConflict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return conflicts
|
||||||
|
}
|
||||||
|
|
||||||
|
func ColoredConflictFile(content string, conflicts []commands.Conflict, conflictIndex int, conflictTop, hasFocus bool) string {
|
||||||
|
if len(conflicts) == 0 {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
conflict, remainingConflicts := shiftConflict(conflicts)
|
||||||
|
var outputBuffer bytes.Buffer
|
||||||
|
for i, line := range utils.SplitLines(content) {
|
||||||
|
colourAttr := theme.DefaultTextColor
|
||||||
|
if i == conflict.Start || i == conflict.Middle || i == conflict.End {
|
||||||
|
colourAttr = color.FgRed
|
||||||
|
}
|
||||||
|
colour := color.New(colourAttr)
|
||||||
|
if hasFocus && conflictIndex < len(conflicts) && conflicts[conflictIndex] == conflict && shouldHighlightLine(i, conflict, conflictTop) {
|
||||||
|
colour.Add(color.Bold)
|
||||||
|
colour.Add(theme.SelectedRangeBgColor)
|
||||||
|
}
|
||||||
|
if i == conflict.End && len(remainingConflicts) > 0 {
|
||||||
|
conflict, remainingConflicts = shiftConflict(remainingConflicts)
|
||||||
|
}
|
||||||
|
outputBuffer.WriteString(utils.ColoredStringDirect(line, colour) + "\n")
|
||||||
|
}
|
||||||
|
return outputBuffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsIndexToDelete(i int, conflict commands.Conflict, selection Selection) bool {
|
||||||
|
return i == conflict.Middle ||
|
||||||
|
i == conflict.Start ||
|
||||||
|
i == conflict.End ||
|
||||||
|
selection != BOTH &&
|
||||||
|
(selection == BOTTOM && i > conflict.Start && i < conflict.Middle) ||
|
||||||
|
(selection == TOP && i > conflict.Middle && i < conflict.End)
|
||||||
|
}
|
||||||
|
|
||||||
|
func shiftConflict(conflicts []commands.Conflict) (commands.Conflict, []commands.Conflict) {
|
||||||
|
return conflicts[0], conflicts[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldHighlightLine(index int, conflict commands.Conflict, top bool) bool {
|
||||||
|
return (index >= conflict.Start && index <= conflict.Middle && top) || (index >= conflict.Middle && index <= conflict.End && !top)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user