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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
module Gitlab
module Conflict
class File
class MissingResolution < StandardError
end
CONTEXT_LINES = 3
attr_reader :merge_file_result, :their_path, :their_ref, :our_path, :our_ref, :repository
def initialize(merge_file_result, conflict, diff_refs:, repository:)
@merge_file_result = merge_file_result
@their_path = conflict[:theirs][:path]
@our_path = conflict[:ours][:path]
@their_ref = diff_refs.start_sha
@our_ref = diff_refs.head_sha
@repository = repository
end
# Array of Gitlab::Diff::Line objects
def lines
@lines ||= Gitlab::Conflict::Parser.new.parse(merge_file_result[:data],
our_path: our_path,
their_path: their_path,
parent: self)
end
def resolve!(resolution, index:, rugged:)
new_file = resolve_lines(resolution).map(&:text).join("\n")
oid = rugged.write(new_file, :blob)
our_mode = index.conflict_get(our_path)[:ours][:mode]
index.add(path: our_path, oid: oid, mode: our_mode)
index.conflict_remove(our_path)
end
def resolve_lines(resolution)
section_id = nil
lines.map do |line|
unless line.type
section_id = nil
next line
end
section_id ||= line_code(line)
case resolution[section_id]
when 'ours'
next unless line.type == 'new'
when 'theirs'
next unless line.type == 'old'
else
raise MissingResolution, "Missing resolution for section ID: #{section_id}"
end
line
end.compact
end
def highlight_lines!
their_file = lines.reject { |line| line.type == 'new' }.map(&:text).join("\n")
our_file = lines.reject { |line| line.type == 'old' }.map(&:text).join("\n")
their_highlight = Gitlab::Highlight.highlight(their_path, their_file, repository: repository).lines.map(&:html_safe)
our_highlight = Gitlab::Highlight.highlight(our_path, our_file, repository: repository).lines.map(&:html_safe)
lines.each do |line|
if line.type == 'old'
line.rich_text = their_highlight[line.old_line - 1]
else
line.rich_text = our_highlight[line.new_line - 1]
end
end
end
def sections
return @sections if @sections
candidate_match_headers = lines.map do |line|
" #{line.text}" if line.text.match(/\A[A-Za-z$_]/) && line.type.nil?
end
chunked_lines = lines.chunk { |line| line.type.nil? }
last_candidate_match_header = nil
match_line_header = nil
match_line = nil
@sections = chunked_lines.flat_map.with_index do |(no_conflict, lines), i|
section = nil
if no_conflict
conflict_before = i > 0
conflict_after = chunked_lines.peek
if conflict_before && conflict_after
if lines.length > CONTEXT_LINES * 2
head_lines = lines.first(CONTEXT_LINES)
tail_lines = lines.last(CONTEXT_LINES)
update_match_line_text(match_line, head_lines.last, candidate_match_headers)
match_line = create_match_line(tail_lines.first)
update_match_line_text(match_line, tail_lines.last, candidate_match_headers)
section = [
{ conflict: false, lines: head_lines },
{ conflict: false, lines: tail_lines.unshift(match_line) }
]
end
elsif conflict_after
tail_lines = lines.last(CONTEXT_LINES)
if lines.length > CONTEXT_LINES
match_line = create_match_line(tail_lines.first)
tail_lines.unshift(match_line)
end
lines = tail_lines
elsif conflict_before
lines = lines.first(CONTEXT_LINES)
end
end
update_match_line_text(match_line, lines.last, candidate_match_headers) unless section
section ||= { conflict: !no_conflict, lines: lines }
section[:id] = line_code(lines.first) unless no_conflict
section
end
end
def line_code(line)
Gitlab::Diff::LineCode.generate(our_path, line.new_pos, line.old_pos)
end
def create_match_line(line)
Gitlab::Diff::Line.new('', 'match', line.index, line.old_pos, line.new_pos)
end
def update_match_line_text(match_line, line, headers)
return unless match_line
header = headers.first(line.index).compact.last
match_line.text = "@@ -#{match_line.old_pos},#{line.old_pos} +#{match_line.new_pos},#{line.new_pos} @@#{header}"
end
def as_json(opts = nil)
{
old_path: their_path,
new_path: our_path,
sections: sections
}
end
end
end
end
|