summaryrefslogtreecommitdiffstats
path: root/pkg/gui/mergeconflicts/find_conflicts.go
blob: c4d3a51a8b0848185cbffdc6f5bee02799fecffa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package mergeconflicts

import (
	"bufio"
	"bytes"
	"io"
	"os"
	"strings"

	"github.com/jesseduffield/lazygit/pkg/utils"
)

// LineType tells us whether a given line is a start/middle/end marker of a conflict,
// or if it's not a marker at all
type LineType int

const (
	START LineType = iota
	ANCESTOR
	TARGET
	END
	NOT_A_MARKER
)

func findConflicts(content string) []*mergeConflict {
	conflicts := make([]*mergeConflict, 0)

	if content == "" {
		return conflicts
	}

	var newConflict *mergeConflict
	for i, line := range utils.SplitLines(content) {
		switch determineLineType(line) {
		case START:
			newConflict = &mergeConflict{start: i, ancestor: -1}
		case ANCESTOR:
			if newConflict != nil {
				newConflict.ancestor = i
			}
		case TARGET:
			if newConflict != nil {
				newConflict.target = i
			}
		case END:
			if newConflict != nil {
				newConflict.end = i
				conflicts = append(conflicts, newConflict)
			}
			// reset value to avoid any possible silent mutations in further iterations
			newConflict = nil
		default:
			// line isn't a merge conflict marker so we just continue
		}
	}

	return conflicts
}

var (
	CONFLICT_START       = "<<<<<<< "
	CONFLICT_END         = ">>>>>>> "
	CONFLICT_START_BYTES = []byte(CONFLICT_START)
	CONFLICT_END_BYTES   = []byte(CONFLICT_END)
)

func determineLineType(line string) LineType {
	// TODO: find out whether we ever actually get this prefix
	trimmedLine := strings.TrimPrefix(line, "++")

	switch {
	case strings.HasPrefix(trimmedLine, CONFLICT_START):
		return START
	case strings.HasPrefix(trimmedLine, "||||||| "):
		return ANCESTOR
	case trimmedLine == "=======":
		return TARGET
	case strings.HasPrefix(trimmedLine, CONFLICT_END):
		return END
	default:
		return NOT_A_MARKER
	}
}

// tells us whether a file actually has inline merge conflicts. We need to run this
// because git will continue showing a status of 'UU' even after the conflicts have
// been resolved in the user's editor
func FileHasConflictMarkers(path string) (bool, error) {
	file, err := os.Open(path)
	if err != nil {
		return false, err
	}

	defer file.Close()

	return fileHasConflictMarkersAux(file), nil
}

// Efficiently scans through a file looking for merge conflict markers. Returns true if it does
func fileHasConflictMarkersAux(file io.Reader) bool {
	scanner := bufio.NewScanner(file)
	scanner.Split(utils.ScanLinesAndTruncateWhenLongerThanBuffer(bufio.MaxScanTokenSize))
	for scanner.Scan() {
		line := scanner.Bytes()

		// only searching for start/end markers because the others are more ambiguous
		if bytes.HasPrefix(line, CONFLICT_START_BYTES) {
			return true
		}

		if bytes.HasPrefix(line, CONFLICT_END_BYTES) {
			return true
		}
	}

	return false
}