mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-02-01 13:17:53 +02:00
Support git config merge.conflictStyle diff3
This commit is contained in:
parent
aedeba4fe3
commit
a0e7604f61
@ -1485,14 +1485,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
ViewName: "main",
|
||||
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
|
||||
Key: gui.getKey(config.Universal.PrevItem),
|
||||
Handler: gui.handleSelectTop,
|
||||
Handler: gui.handleSelectPrevConflictHunk,
|
||||
Description: gui.Tr.SelectTop,
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
|
||||
Key: gui.getKey(config.Universal.NextItem),
|
||||
Handler: gui.handleSelectBottom,
|
||||
Handler: gui.handleSelectNextConflictHunk,
|
||||
Description: gui.Tr.SelectBottom,
|
||||
},
|
||||
{
|
||||
@ -1500,14 +1500,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
|
||||
Key: gocui.MouseWheelUp,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectTop,
|
||||
Handler: gui.handleSelectPrevConflictHunk,
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
|
||||
Key: gocui.MouseWheelDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectBottom,
|
||||
Handler: gui.handleSelectNextConflictHunk,
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
@ -1528,14 +1528,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
|
||||
Key: gui.getKey(config.Universal.PrevItemAlt),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectTop,
|
||||
Handler: gui.handleSelectPrevConflictHunk,
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
|
||||
Key: gui.getKey(config.Universal.NextItemAlt),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectBottom,
|
||||
Handler: gui.handleSelectNextConflictHunk,
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
|
@ -14,18 +14,18 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleSelectTop() error {
|
||||
func (gui *Gui) handleSelectPrevConflictHunk() error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
gui.State.Panels.Merging.SelectTopOption()
|
||||
gui.State.Panels.Merging.SelectPrevConflictHunk()
|
||||
return gui.refreshMergePanel()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSelectBottom() error {
|
||||
func (gui *Gui) handleSelectNextConflictHunk() error {
|
||||
return gui.withMergeConflictLock(func() error {
|
||||
gui.takeOverMergeConflictScrolling()
|
||||
gui.State.Panels.Merging.SelectBottomOption()
|
||||
gui.State.Panels.Merging.SelectNextConflictHunk()
|
||||
return gui.refreshMergePanel()
|
||||
})
|
||||
}
|
||||
@ -135,6 +135,8 @@ func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error
|
||||
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.BOTH:
|
||||
|
@ -12,7 +12,8 @@ type LineType int
|
||||
|
||||
const (
|
||||
START LineType = iota
|
||||
MIDDLE
|
||||
ANCESTOR
|
||||
TARGET
|
||||
END
|
||||
NOT_A_MARKER
|
||||
)
|
||||
@ -28,10 +29,14 @@ func findConflicts(content string) []*mergeConflict {
|
||||
for i, line := range utils.SplitLines(content) {
|
||||
switch determineLineType(line) {
|
||||
case START:
|
||||
newConflict = &mergeConflict{start: i}
|
||||
case MIDDLE:
|
||||
newConflict = &mergeConflict{start: i, ancestor: -1}
|
||||
case ANCESTOR:
|
||||
if newConflict != nil {
|
||||
newConflict.middle = i
|
||||
newConflict.ancestor = i
|
||||
}
|
||||
case TARGET:
|
||||
if newConflict != nil {
|
||||
newConflict.target = i
|
||||
}
|
||||
case END:
|
||||
if newConflict != nil {
|
||||
@ -54,8 +59,10 @@ func determineLineType(line string) LineType {
|
||||
switch {
|
||||
case strings.HasPrefix(trimmedLine, "<<<<<<< "):
|
||||
return START
|
||||
case strings.HasPrefix(trimmedLine, "||||||| "):
|
||||
return ANCESTOR
|
||||
case trimmedLine == "=======":
|
||||
return MIDDLE
|
||||
return TARGET
|
||||
case strings.HasPrefix(trimmedLine, ">>>>>>> "):
|
||||
return END
|
||||
default:
|
||||
|
@ -43,12 +43,16 @@ func TestDetermineLineType(t *testing.T) {
|
||||
},
|
||||
{
|
||||
line: "=======",
|
||||
expected: MIDDLE,
|
||||
expected: TARGET,
|
||||
},
|
||||
{
|
||||
line: ">>>>>>> blah",
|
||||
expected: END,
|
||||
},
|
||||
{
|
||||
line: "||||||| adf33b9",
|
||||
expected: ANCESTOR,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
|
@ -16,11 +16,11 @@ func ColoredConflictFile(content string, state *State, hasFocus bool) string {
|
||||
var outputBuffer bytes.Buffer
|
||||
for i, line := range utils.SplitLines(content) {
|
||||
textStyle := theme.DefaultTextColor
|
||||
if i == conflict.start || i == conflict.middle || i == conflict.end {
|
||||
if i == conflict.start || i == conflict.ancestor || i == conflict.target || i == conflict.end {
|
||||
textStyle = style.FgRed
|
||||
}
|
||||
|
||||
if hasFocus && state.conflictIndex < len(state.conflicts) && *state.conflicts[state.conflictIndex] == *conflict && shouldHighlightLine(i, conflict, state.conflictTop) {
|
||||
if hasFocus && state.conflictIndex < len(state.conflicts) && *state.conflicts[state.conflictIndex] == *conflict && shouldHighlightLine(i, conflict, state.conflictSelection) {
|
||||
textStyle = textStyle.MergeStyle(theme.SelectedRangeBgColor).SetBold()
|
||||
}
|
||||
if i == conflict.end && len(remainingConflicts) > 0 {
|
||||
@ -35,6 +35,19 @@ func shiftConflict(conflicts []*mergeConflict) (*mergeConflict, []*mergeConflict
|
||||
return conflicts[0], conflicts[1:]
|
||||
}
|
||||
|
||||
func shouldHighlightLine(index int, conflict *mergeConflict, top bool) bool {
|
||||
return (index >= conflict.start && index <= conflict.middle && top) || (index >= conflict.middle && index <= conflict.end && !top)
|
||||
func shouldHighlightLine(index int, conflict *mergeConflict, selection Selection) bool {
|
||||
switch selection {
|
||||
case TOP:
|
||||
if conflict.ancestor >= 0 {
|
||||
return index >= conflict.start && index <= conflict.ancestor
|
||||
} else {
|
||||
return index >= conflict.start && index <= conflict.target
|
||||
}
|
||||
case MIDDLE:
|
||||
return index >= conflict.ancestor && index <= conflict.target
|
||||
case BOTTOM:
|
||||
return index >= conflict.target && index <= conflict.end
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -11,42 +11,62 @@ type Selection int
|
||||
|
||||
const (
|
||||
TOP Selection = iota
|
||||
MIDDLE
|
||||
BOTTOM
|
||||
BOTH
|
||||
)
|
||||
|
||||
// mergeConflict : A git conflict with a start middle and end corresponding to line
|
||||
// mergeConflict : A git conflict with a start, ancestor (if exists), target, and end corresponding to line
|
||||
// numbers in the file where the conflict markers appear
|
||||
type mergeConflict struct {
|
||||
start int
|
||||
middle int
|
||||
end int
|
||||
start int
|
||||
ancestor int
|
||||
target int
|
||||
end int
|
||||
}
|
||||
|
||||
type State struct {
|
||||
sync.Mutex
|
||||
conflictIndex int
|
||||
conflictTop bool
|
||||
conflicts []*mergeConflict
|
||||
EditHistory *stack.Stack
|
||||
conflictIndex int
|
||||
conflictSelection Selection
|
||||
conflicts []*mergeConflict
|
||||
EditHistory *stack.Stack
|
||||
}
|
||||
|
||||
func NewState() *State {
|
||||
return &State{
|
||||
Mutex: sync.Mutex{},
|
||||
conflictIndex: 0,
|
||||
conflictTop: true,
|
||||
conflicts: []*mergeConflict{},
|
||||
EditHistory: stack.New(),
|
||||
Mutex: sync.Mutex{},
|
||||
conflictIndex: 0,
|
||||
conflictSelection: TOP,
|
||||
conflicts: []*mergeConflict{},
|
||||
EditHistory: stack.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) SelectTopOption() {
|
||||
s.conflictTop = true
|
||||
func (s *State) SelectPrevConflictHunk() {
|
||||
switch s.conflictSelection {
|
||||
case MIDDLE:
|
||||
s.conflictSelection = TOP
|
||||
case BOTTOM:
|
||||
if s.currentConflict().ancestor >= 0 {
|
||||
s.conflictSelection = MIDDLE
|
||||
} else {
|
||||
s.conflictSelection = TOP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) SelectBottomOption() {
|
||||
s.conflictTop = false
|
||||
func (s *State) SelectNextConflictHunk() {
|
||||
switch s.conflictSelection {
|
||||
case TOP:
|
||||
if s.currentConflict().ancestor >= 0 {
|
||||
s.conflictSelection = MIDDLE
|
||||
} else {
|
||||
s.conflictSelection = BOTTOM
|
||||
}
|
||||
case MIDDLE:
|
||||
s.conflictSelection = BOTTOM
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) SelectNextConflict() {
|
||||
@ -100,11 +120,7 @@ func (s *State) NoConflicts() bool {
|
||||
}
|
||||
|
||||
func (s *State) Selection() Selection {
|
||||
if s.conflictTop {
|
||||
return TOP
|
||||
} else {
|
||||
return BOTTOM
|
||||
}
|
||||
return s.conflictSelection
|
||||
}
|
||||
|
||||
func (s *State) IsFinalConflict() bool {
|
||||
@ -116,7 +132,7 @@ func (s *State) Reset() {
|
||||
}
|
||||
|
||||
func (s *State) GetConflictMiddle() int {
|
||||
return s.currentConflict().middle
|
||||
return s.currentConflict().target
|
||||
}
|
||||
|
||||
func (s *State) ContentAfterConflictResolve(path string, selection Selection) (bool, string, error) {
|
||||
@ -141,13 +157,23 @@ func (s *State) ContentAfterConflictResolve(path string, selection Selection) (b
|
||||
|
||||
func isIndexToDelete(i int, conflict *mergeConflict, selection Selection) bool {
|
||||
isMarkerLine :=
|
||||
i == conflict.middle ||
|
||||
i == conflict.start ||
|
||||
i == conflict.start ||
|
||||
i == conflict.ancestor ||
|
||||
i == conflict.target ||
|
||||
i == conflict.end
|
||||
|
||||
isUnwantedContent :=
|
||||
(selection == BOTTOM && conflict.start < i && i < conflict.middle) ||
|
||||
(selection == TOP && conflict.middle < i && i < conflict.end)
|
||||
|
||||
return isMarkerLine || isUnwantedContent
|
||||
var isWantedContent bool
|
||||
switch selection {
|
||||
case TOP:
|
||||
if conflict.ancestor >= 0 {
|
||||
isWantedContent = conflict.start < i && i < conflict.ancestor
|
||||
} else {
|
||||
isWantedContent = conflict.start < i && i < conflict.target
|
||||
}
|
||||
case MIDDLE:
|
||||
isWantedContent = conflict.ancestor < i && i < conflict.target
|
||||
case BOTTOM:
|
||||
isWantedContent = conflict.target < i && i < conflict.end
|
||||
}
|
||||
return isMarkerLine || !isWantedContent
|
||||
}
|
||||
|
@ -58,37 +58,57 @@ bar
|
||||
=======
|
||||
baz
|
||||
>>>>>>> branch
|
||||
|
||||
<<<<<<< HEAD
|
||||
foo
|
||||
||||||| fffffff
|
||||
bar
|
||||
=======
|
||||
baz
|
||||
>>>>>>> branch
|
||||
`,
|
||||
expected: []*mergeConflict{
|
||||
{
|
||||
start: 0,
|
||||
middle: 2,
|
||||
end: 4,
|
||||
start: 0,
|
||||
ancestor: -1,
|
||||
target: 2,
|
||||
end: 4,
|
||||
},
|
||||
{
|
||||
start: 6,
|
||||
middle: 9,
|
||||
end: 11,
|
||||
start: 6,
|
||||
ancestor: -1,
|
||||
target: 9,
|
||||
end: 11,
|
||||
},
|
||||
{
|
||||
start: 13,
|
||||
middle: 15,
|
||||
end: 17,
|
||||
start: 13,
|
||||
ancestor: -1,
|
||||
target: 15,
|
||||
end: 17,
|
||||
},
|
||||
{
|
||||
start: 19,
|
||||
middle: 21,
|
||||
end: 23,
|
||||
start: 19,
|
||||
ancestor: -1,
|
||||
target: 21,
|
||||
end: 23,
|
||||
},
|
||||
{
|
||||
start: 25,
|
||||
middle: 27,
|
||||
end: 29,
|
||||
start: 25,
|
||||
ancestor: -1,
|
||||
target: 27,
|
||||
end: 29,
|
||||
},
|
||||
{
|
||||
start: 31,
|
||||
middle: 34,
|
||||
end: 36,
|
||||
start: 31,
|
||||
ancestor: -1,
|
||||
target: 34,
|
||||
end: 36,
|
||||
},
|
||||
{
|
||||
start: 38,
|
||||
ancestor: 40,
|
||||
target: 42,
|
||||
end: 44,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user