summaryrefslogtreecommitdiff
path: root/lib/gitlab/git/conflict/parser.rb
blob: 20de8ebde4ee9a394180752d10a4b3b7c0cb6c79 (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
# frozen_string_literal: true

module Gitlab
  module Git
    module Conflict
      class Parser
        UnresolvableError = Class.new(StandardError)
        UnmergeableFile = Class.new(UnresolvableError)

        # Recoverable errors - the conflict can be resolved in an editor, but not with
        # sections.
        ParserError = Class.new(StandardError)
        UnexpectedDelimiter = Class.new(ParserError)
        MissingEndDelimiter = Class.new(ParserError)

        class << self
          def parse(text, our_path:, their_path:, parent_file: nil)
            validate_text!(text)

            line_obj_index = 0
            line_old = 1
            line_new = 1
            type = nil
            lines = []
            conflict_start = "<<<<<<< #{our_path}"
            conflict_middle = '======='
            conflict_end = ">>>>>>> #{their_path}"

            text.each_line.map do |line|
              full_line = line.delete("\n")

              if full_line == conflict_start
                validate_delimiter!(type.nil?)

                type = 'new'
              elsif full_line == conflict_middle
                validate_delimiter!(type == 'new')

                type = 'old'
              elsif full_line == conflict_end
                validate_delimiter!(type == 'old')

                type = nil
              elsif line[0] == '\\'
                type = 'nonewline'
                lines << {
                  full_line: full_line,
                  type: type,
                  line_obj_index: line_obj_index,
                  line_old: line_old,
                  line_new: line_new
                }
              else
                lines << {
                  full_line: full_line,
                  type: type,
                  line_obj_index: line_obj_index,
                  line_old: line_old,
                  line_new: line_new
                }

                line_old += 1 if type != 'new'
                line_new += 1 if type != 'old'

                line_obj_index += 1
              end
            end

            raise MissingEndDelimiter unless type.nil?

            lines
          end

          private

          def validate_text!(text)
            raise UnmergeableFile if text.blank? # Typically a binary file
            raise UnmergeableFile if text.length > 200.kilobytes
          end

          def validate_delimiter!(condition)
            raise UnexpectedDelimiter unless condition
          end
        end
      end
    end
  end
end