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
|
class LegacyDiffNote < Note
serialize :st_diff
validates :line_code, presence: true, line_code: true
before_create :set_diff
class << self
def build_discussion_id(noteable_type, noteable_id, line_code, active = true)
[super(noteable_type, noteable_id), line_code, active].join("-")
end
end
def diff_note?
true
end
def legacy_diff_note?
true
end
def discussion_id
@discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code, active?)
end
def diff_file_hash
line_code.split('_')[0] if line_code
end
def diff_old_line
line_code.split('_')[1].to_i if line_code
end
def diff_new_line
line_code.split('_')[2].to_i if line_code
end
def diff
@diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map)
end
def diff_file_path
diff.new_path.presence || diff.old_path
end
def diff_lines
@diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
end
def diff_line
@diff_line ||= diff_lines.find { |line| generate_line_code(line) == self.line_code }
end
def diff_line_text
diff_line.try(:text)
end
def diff_line_type
diff_line.try(:type)
end
def highlighted_diff_lines
Gitlab::Diff::Highlight.new(diff_lines).highlight
end
def truncated_diff_lines
max_number_of_lines = 16
prev_match_line = nil
prev_lines = []
highlighted_diff_lines.each do |line|
if line.type == "match"
prev_lines.clear
prev_match_line = line
else
prev_lines << line
break if generate_line_code(line) == self.line_code
prev_lines.shift if prev_lines.length >= max_number_of_lines
end
end
prev_lines
end
# Check if this note is part of an "active" discussion
#
# This will always return true for anything except MergeRequest noteables,
# which have special logic.
#
# If the note's current diff cannot be matched in the MergeRequest's current
# diff, it's considered inactive.
def active?
return @active if defined?(@active)
return true if for_commit?
return true unless self.diff
return false unless noteable
noteable_diff = find_noteable_diff
if noteable_diff
parsed_lines = Gitlab::Diff::Parser.new.parse(noteable_diff.diff.each_line)
@active = parsed_lines.any? { |line_obj| line_obj.text == diff_line_text }
else
@active = false
end
@active
end
private
def find_diff
return nil unless noteable
return @diff if defined?(@diff)
@diff = noteable.diffs(Commit.max_diff_options).find do |d|
d.new_path && Digest::SHA1.hexdigest(d.new_path) == diff_file_hash
end
end
def set_diff
# First lets find notes with same diff
# before iterating over all mr diffs
diff = diff_for_line_code unless for_merge_request?
diff ||= find_diff
self.st_diff = diff.to_hash if diff
end
def diff_for_line_code
attributes = {
noteable_type: noteable_type,
line_code: line_code
}
if for_commit?
attributes[:commit_id] = commit_id
else
attributes[:noteable_id] = noteable_id
end
self.class.where(attributes).last.try(:diff)
end
def generate_line_code(line)
Gitlab::Diff::LineCode.generate(diff_file_path, line.new_pos, line.old_pos)
end
# Find the diff on noteable that matches our own
def find_noteable_diff
diffs = noteable.diffs(Commit.max_diff_options)
diffs.find { |d| d.new_path == self.diff.new_path }
end
end
|