1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-31 14:24:25 +03:00

Support git config merge.conflictStyle diff3

This commit is contained in:
Ryooooooga
2021-08-22 01:03:05 +09:00
committed by Jesse Duffield
parent aedeba4fe3
commit a0e7604f61
7 changed files with 140 additions and 68 deletions

View File

@ -1485,14 +1485,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "main", ViewName: "main",
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)}, Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.PrevItem), Key: gui.getKey(config.Universal.PrevItem),
Handler: gui.handleSelectTop, Handler: gui.handleSelectPrevConflictHunk,
Description: gui.Tr.SelectTop, Description: gui.Tr.SelectTop,
}, },
{ {
ViewName: "main", ViewName: "main",
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)}, Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
Key: gui.getKey(config.Universal.NextItem), Key: gui.getKey(config.Universal.NextItem),
Handler: gui.handleSelectBottom, Handler: gui.handleSelectNextConflictHunk,
Description: gui.Tr.SelectBottom, Description: gui.Tr.SelectBottom,
}, },
{ {
@ -1500,14 +1500,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)}, Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
Key: gocui.MouseWheelUp, Key: gocui.MouseWheelUp,
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleSelectTop, Handler: gui.handleSelectPrevConflictHunk,
}, },
{ {
ViewName: "main", ViewName: "main",
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)}, Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)},
Key: gocui.MouseWheelDown, Key: gocui.MouseWheelDown,
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleSelectBottom, Handler: gui.handleSelectNextConflictHunk,
}, },
{ {
ViewName: "main", ViewName: "main",
@ -1528,14 +1528,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)}, Contexts: []string{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.handleSelectPrevConflictHunk,
}, },
{ {
ViewName: "main", ViewName: "main",
Contexts: []string{string(MAIN_MERGING_CONTEXT_KEY)}, Contexts: []string{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.handleSelectNextConflictHunk,
}, },
{ {
ViewName: "main", ViewName: "main",

View File

@ -14,18 +14,18 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts" "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
) )
func (gui *Gui) handleSelectTop() error { func (gui *Gui) handleSelectPrevConflictHunk() error {
return gui.withMergeConflictLock(func() error { return gui.withMergeConflictLock(func() error {
gui.takeOverMergeConflictScrolling() gui.takeOverMergeConflictScrolling()
gui.State.Panels.Merging.SelectTopOption() gui.State.Panels.Merging.SelectPrevConflictHunk()
return gui.refreshMergePanel() return gui.refreshMergePanel()
}) })
} }
func (gui *Gui) handleSelectBottom() error { func (gui *Gui) handleSelectNextConflictHunk() error {
return gui.withMergeConflictLock(func() error { return gui.withMergeConflictLock(func() error {
gui.takeOverMergeConflictScrolling() gui.takeOverMergeConflictScrolling()
gui.State.Panels.Merging.SelectBottomOption() gui.State.Panels.Merging.SelectNextConflictHunk()
return gui.refreshMergePanel() return gui.refreshMergePanel()
}) })
} }
@ -135,6 +135,8 @@ func (gui *Gui) resolveConflict(selection mergeconflicts.Selection) (bool, error
switch selection { switch selection {
case mergeconflicts.TOP: case mergeconflicts.TOP:
logStr = "Picking top hunk" logStr = "Picking top hunk"
case mergeconflicts.MIDDLE:
logStr = "Picking middle hunk"
case mergeconflicts.BOTTOM: case mergeconflicts.BOTTOM:
logStr = "Picking bottom hunk" logStr = "Picking bottom hunk"
case mergeconflicts.BOTH: case mergeconflicts.BOTH:

View File

@ -12,7 +12,8 @@ type LineType int
const ( const (
START LineType = iota START LineType = iota
MIDDLE ANCESTOR
TARGET
END END
NOT_A_MARKER NOT_A_MARKER
) )
@ -28,10 +29,14 @@ func findConflicts(content string) []*mergeConflict {
for i, line := range utils.SplitLines(content) { for i, line := range utils.SplitLines(content) {
switch determineLineType(line) { switch determineLineType(line) {
case START: case START:
newConflict = &mergeConflict{start: i} newConflict = &mergeConflict{start: i, ancestor: -1}
case MIDDLE: case ANCESTOR:
if newConflict != nil { if newConflict != nil {
newConflict.middle = i newConflict.ancestor = i
}
case TARGET:
if newConflict != nil {
newConflict.target = i
} }
case END: case END:
if newConflict != nil { if newConflict != nil {
@ -54,8 +59,10 @@ func determineLineType(line string) LineType {
switch { switch {
case strings.HasPrefix(trimmedLine, "<<<<<<< "): case strings.HasPrefix(trimmedLine, "<<<<<<< "):
return START return START
case strings.HasPrefix(trimmedLine, "||||||| "):
return ANCESTOR
case trimmedLine == "=======": case trimmedLine == "=======":
return MIDDLE return TARGET
case strings.HasPrefix(trimmedLine, ">>>>>>> "): case strings.HasPrefix(trimmedLine, ">>>>>>> "):
return END return END
default: default:

View File

@ -43,12 +43,16 @@ func TestDetermineLineType(t *testing.T) {
}, },
{ {
line: "=======", line: "=======",
expected: MIDDLE, expected: TARGET,
}, },
{ {
line: ">>>>>>> blah", line: ">>>>>>> blah",
expected: END, expected: END,
}, },
{
line: "||||||| adf33b9",
expected: ANCESTOR,
},
} }
for _, s := range scenarios { for _, s := range scenarios {

View File

@ -16,11 +16,11 @@ func ColoredConflictFile(content string, state *State, hasFocus bool) string {
var outputBuffer bytes.Buffer var outputBuffer bytes.Buffer
for i, line := range utils.SplitLines(content) { for i, line := range utils.SplitLines(content) {
textStyle := theme.DefaultTextColor 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 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() textStyle = textStyle.MergeStyle(theme.SelectedRangeBgColor).SetBold()
} }
if i == conflict.end && len(remainingConflicts) > 0 { if i == conflict.end && len(remainingConflicts) > 0 {
@ -35,6 +35,19 @@ func shiftConflict(conflicts []*mergeConflict) (*mergeConflict, []*mergeConflict
return conflicts[0], conflicts[1:] return conflicts[0], conflicts[1:]
} }
func shouldHighlightLine(index int, conflict *mergeConflict, top bool) bool { func shouldHighlightLine(index int, conflict *mergeConflict, selection Selection) bool {
return (index >= conflict.start && index <= conflict.middle && top) || (index >= conflict.middle && index <= conflict.end && !top) 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
}
} }

View File

@ -11,42 +11,62 @@ type Selection int
const ( const (
TOP Selection = iota TOP Selection = iota
MIDDLE
BOTTOM BOTTOM
BOTH 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 // numbers in the file where the conflict markers appear
type mergeConflict struct { type mergeConflict struct {
start int start int
middle int ancestor int
end int target int
end int
} }
type State struct { type State struct {
sync.Mutex sync.Mutex
conflictIndex int conflictIndex int
conflictTop bool conflictSelection Selection
conflicts []*mergeConflict conflicts []*mergeConflict
EditHistory *stack.Stack EditHistory *stack.Stack
} }
func NewState() *State { func NewState() *State {
return &State{ return &State{
Mutex: sync.Mutex{}, Mutex: sync.Mutex{},
conflictIndex: 0, conflictIndex: 0,
conflictTop: true, conflictSelection: TOP,
conflicts: []*mergeConflict{}, conflicts: []*mergeConflict{},
EditHistory: stack.New(), EditHistory: stack.New(),
} }
} }
func (s *State) SelectTopOption() { func (s *State) SelectPrevConflictHunk() {
s.conflictTop = true 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() { func (s *State) SelectNextConflictHunk() {
s.conflictTop = false 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() { func (s *State) SelectNextConflict() {
@ -100,11 +120,7 @@ func (s *State) NoConflicts() bool {
} }
func (s *State) Selection() Selection { func (s *State) Selection() Selection {
if s.conflictTop { return s.conflictSelection
return TOP
} else {
return BOTTOM
}
} }
func (s *State) IsFinalConflict() bool { func (s *State) IsFinalConflict() bool {
@ -116,7 +132,7 @@ func (s *State) Reset() {
} }
func (s *State) GetConflictMiddle() int { func (s *State) GetConflictMiddle() int {
return s.currentConflict().middle return s.currentConflict().target
} }
func (s *State) ContentAfterConflictResolve(path string, selection Selection) (bool, string, error) { 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 { func isIndexToDelete(i int, conflict *mergeConflict, selection Selection) bool {
isMarkerLine := isMarkerLine :=
i == conflict.middle || i == conflict.start ||
i == conflict.start || i == conflict.ancestor ||
i == conflict.target ||
i == conflict.end i == conflict.end
isUnwantedContent := var isWantedContent bool
(selection == BOTTOM && conflict.start < i && i < conflict.middle) || switch selection {
(selection == TOP && conflict.middle < i && i < conflict.end) case TOP:
if conflict.ancestor >= 0 {
return isMarkerLine || isUnwantedContent 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
} }

View File

@ -58,37 +58,57 @@ bar
======= =======
baz baz
>>>>>>> branch >>>>>>> branch
<<<<<<< HEAD
foo
||||||| fffffff
bar
=======
baz
>>>>>>> branch
`, `,
expected: []*mergeConflict{ expected: []*mergeConflict{
{ {
start: 0, start: 0,
middle: 2, ancestor: -1,
end: 4, target: 2,
end: 4,
}, },
{ {
start: 6, start: 6,
middle: 9, ancestor: -1,
end: 11, target: 9,
end: 11,
}, },
{ {
start: 13, start: 13,
middle: 15, ancestor: -1,
end: 17, target: 15,
end: 17,
}, },
{ {
start: 19, start: 19,
middle: 21, ancestor: -1,
end: 23, target: 21,
end: 23,
}, },
{ {
start: 25, start: 25,
middle: 27, ancestor: -1,
end: 29, target: 27,
end: 29,
}, },
{ {
start: 31, start: 31,
middle: 34, ancestor: -1,
end: 36, target: 34,
end: 36,
},
{
start: 38,
ancestor: 40,
target: 42,
end: 44,
}, },
}, },
}, },