summaryrefslogtreecommitdiffstats
path: root/pkg/gui/mergeconflicts/state.go
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2021-04-18 18:07:10 +1000
committerJesse Duffield <jessedduffield@gmail.com>2021-04-18 18:58:09 +1000
commit98375dc902513a8b0759985a270d8d895cba1199 (patch)
treecf3d55df66def3da29b03be9985a70ad1eb73435 /pkg/gui/mergeconflicts/state.go
parente73de332a1efcc8f25e5e0f4f9e0a42d7aabb923 (diff)
refactor merge panel
Diffstat (limited to 'pkg/gui/mergeconflicts/state.go')
-rw-r--r--pkg/gui/mergeconflicts/state.go185
1 files changed, 185 insertions, 0 deletions
diff --git a/pkg/gui/mergeconflicts/state.go b/pkg/gui/mergeconflicts/state.go
new file mode 100644
index 000000000..17ac02a0b
--- /dev/null
+++ b/pkg/gui/mergeconflicts/state.go
@@ -0,0 +1,185 @@
+package mergeconflicts
+
+import (
+ "bufio"
+ "os"
+ "strings"
+ "sync"
+
+ "github.com/golang-collections/collections/stack"
+ "github.com/jesseduffield/lazygit/pkg/utils"
+)
+
+type Selection int
+
+const (
+ TOP Selection = iota
+ BOTTOM
+ BOTH
+)
+
+// mergeConflict : A git conflict with a start middle and end corresponding to line
+// numbers in the file where the conflict bars appear
+type mergeConflict struct {
+ start int
+ middle int
+ end int
+}
+
+type State struct {
+ sync.Mutex
+ conflictIndex int
+ conflictTop bool
+ conflicts []*mergeConflict
+ EditHistory *stack.Stack
+}
+
+func NewState() *State {
+ return &State{
+ Mutex: sync.Mutex{},
+ conflictIndex: 0,
+ conflictTop: true,
+ conflicts: []*mergeConflict{},
+ EditHistory: stack.New(),
+ }
+}
+
+func (s *State) SelectTopOption() {
+ s.conflictTop = true
+}
+
+func (s *State) SelectBottomOption() {
+ s.conflictTop = false
+}
+
+func (s *State) SelectNextConflict() {
+ if s.conflictIndex < len(s.conflicts)-1 {
+ s.conflictIndex++
+ }
+}
+
+func (s *State) SelectPrevConflict() {
+ if s.conflictIndex > 0 {
+ s.conflictIndex--
+ }
+}
+
+func (s *State) PushFileSnapshot(content string) {
+ s.EditHistory.Push(content)
+}
+
+func (s *State) PopFileSnapshot() (string, bool) {
+ if s.EditHistory.Len() == 0 {
+ return "", false
+ }
+
+ return s.EditHistory.Pop().(string), true
+}
+
+func (s *State) currentConflict() *mergeConflict {
+ if len(s.conflicts) == 0 {
+ return nil
+ }
+
+ return s.conflicts[s.conflictIndex]
+}
+
+func (s *State) SetConflictsFromCat(cat string) {
+ s.setConflicts(findConflicts(cat))
+}
+
+func findConflicts(content string) []*mergeConflict {
+ conflicts := make([]*mergeConflict, 0)
+
+ if content == "" {
+ return conflicts
+ }
+
+ var newConflict *mergeConflict
+ for i, line := range utils.SplitLines(content) {
+ trimmedLine := strings.TrimPrefix(line, "++")
+ switch trimmedLine {
+ case "<<<<<<< HEAD", "<<<<<<< MERGE_HEAD", "<<<<<<< Updated upstream", "<<<<<<< ours":
+ newConflict = &mergeConflict{start: i}
+ case "=======":
+ newConflict.middle = i
+ default:
+ if strings.HasPrefix(trimmedLine, ">>>>>>> ") {
+ newConflict.end = i
+ conflicts = append(conflicts, newConflict)
+ }
+ }
+
+ }
+ return conflicts
+}
+
+func (s *State) setConflicts(conflicts []*mergeConflict) {
+ s.conflicts = conflicts
+
+ if s.conflictIndex > len(s.conflicts)-1 {
+ s.conflictIndex = len(s.conflicts) - 1
+ } else if s.conflictIndex < 0 {
+ s.conflictIndex = 0
+ }
+}
+
+func (s *State) NoConflicts() bool {
+ return len(s.conflicts) == 0
+}
+
+func (s *State) Selection() Selection {
+ if s.conflictTop {
+ return TOP
+ } else {
+ return BOTTOM
+ }
+}
+
+func (s *State) IsFinalConflict() bool {
+ return len(s.conflicts) == 1
+}
+
+func (s *State) Reset() {
+ s.EditHistory = stack.New()
+}
+
+func (s *State) GetConflictMiddle() int {
+ return s.currentConflict().middle
+}
+
+func (s *State) ContentAfterConflictResolve(path string, selection Selection) (bool, string, error) {
+ conflict := s.currentConflict()
+ if conflict == nil {
+ return false, "", nil
+ }
+
+ file, err := os.Open(path)
+ if err != nil {
+ return false, "", err
+ }
+ defer file.Close()
+
+ reader := bufio.NewReader(file)
+ content := ""
+ for i := 0; true; i++ {
+ line, err := reader.ReadString('\n')
+ if err != nil {
+ break
+ }
+ if !isIndexToDelete(i, conflict, selection) {
+ content += line
+ }
+ }
+
+ return true, content, nil
+}
+
+func isIndexToDelete(i int, conflict *mergeConflict, 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)
+}