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:
committed by
Jesse Duffield
parent
aedeba4fe3
commit
a0e7604f61
@ -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",
|
||||||
|
@ -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:
|
||||||
|
@ -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:
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user