Class: Gitlab::Diff::File
- Inherits:
-
Object
- Object
- Gitlab::Diff::File
show all
- Includes:
- Utils::StrongMemoize
- Defined in:
- lib/gitlab/diff/file.rb
Constant Summary
collapse
- RICH_VIEWERS =
Finding a viewer for a diff file happens based only on extension and whether the diff file blobs are binary or text, which means 1 diff file should only be matched by 1 viewer, and the order of these viewers doesn’t really matter.
However, when the diff file blobs are LFS pointers, we cannot know for sure whether the file being pointed to is binary or text. In this case, we match only on extension, preferring binary viewers over text ones if both exist, since the large files referred to in “Large File Storage” are much more likely to be binary than text.
[
DiffViewer::Image
].sort_by { |v| v.binary? ? 0 : 1 }.freeze
- ROWS_CONTENT_VISIBILITY_THRESHOLD =
Diff file with more than 200 diff line rows could slow down the page interactions we enable content visibility on every row if it reaches this threshold to reduce diff impact on page reflows
200
Instance Attribute Summary collapse
Instance Method Summary
collapse
Constructor Details
#initialize(diff, repository:, diff_refs: nil, fallback_diff_refs: nil, stats: nil, unique_identifier: nil, max_blob_size: nil) ⇒ File
Returns a new instance of File.
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
# File 'lib/gitlab/diff/file.rb', line 33
def initialize(
diff,
repository:,
diff_refs: nil,
fallback_diff_refs: nil,
stats: nil,
unique_identifier: nil,
max_blob_size: nil
)
@diff = diff
@stats = stats
@repository = repository
@diff_refs = diff_refs
@fallback_diff_refs = fallback_diff_refs
@unique_identifier = unique_identifier
@max_blob_size = max_blob_size
@unfolded = false
@linked = false
add_blobs_to_batch_loader
end
|
Instance Attribute Details
#diff ⇒ Object
Returns the value of attribute diff.
8
9
10
|
# File 'lib/gitlab/diff/file.rb', line 8
def diff
@diff
end
|
#diff_refs ⇒ Object
Returns the value of attribute diff_refs.
8
9
10
|
# File 'lib/gitlab/diff/file.rb', line 8
def diff_refs
@diff_refs
end
|
#fallback_diff_refs ⇒ Object
Returns the value of attribute fallback_diff_refs.
8
9
10
|
# File 'lib/gitlab/diff/file.rb', line 8
def fallback_diff_refs
@fallback_diff_refs
end
|
#linked ⇒ Object
Returns the value of attribute linked.
9
10
11
|
# File 'lib/gitlab/diff/file.rb', line 9
def linked
@linked
end
|
#max_blob_size ⇒ Object
Returns the value of attribute max_blob_size.
8
9
10
|
# File 'lib/gitlab/diff/file.rb', line 8
def max_blob_size
@max_blob_size
end
|
#repository ⇒ Object
Returns the value of attribute repository.
8
9
10
|
# File 'lib/gitlab/diff/file.rb', line 8
def repository
@repository
end
|
#unique_identifier ⇒ Object
Returns the value of attribute unique_identifier.
8
9
10
|
# File 'lib/gitlab/diff/file.rb', line 8
def unique_identifier
@unique_identifier
end
|
Instance Method Details
#add_blobs_to_batch_loader ⇒ Object
407
408
409
410
|
# File 'lib/gitlab/diff/file.rb', line 407
def add_blobs_to_batch_loader
new_blob_lazy
old_blob_lazy
end
|
#added_lines ⇒ Object
250
251
252
253
254
|
# File 'lib/gitlab/diff/file.rb', line 250
def added_lines
strong_memoize(:added_lines) do
@stats&.additions || diff_lines.count(&:added?)
end
end
|
#ai_reviewable? ⇒ Boolean
412
413
414
|
# File 'lib/gitlab/diff/file.rb', line 412
def ai_reviewable?
diffable? && text?
end
|
#alternate_viewer ⇒ Object
349
350
351
|
# File 'lib/gitlab/diff/file.rb', line 349
def alternate_viewer
alternate_viewer_class&.new(self)
end
|
#binary? ⇒ Boolean
323
324
325
326
327
|
# File 'lib/gitlab/diff/file.rb', line 323
def binary?
strong_memoize(:is_binary) do
try_blobs(:binary?)
end
end
|
#binary_in_repo? ⇒ Boolean
279
280
281
|
# File 'lib/gitlab/diff/file.rb', line 279
def binary_in_repo?
has_binary_notice? || try_blobs(:binary_in_repo?)
end
|
#blob ⇒ Object
171
172
173
|
# File 'lib/gitlab/diff/file.rb', line 171
def blob
new_blob || old_blob
end
|
#code_review_id ⇒ Object
270
271
272
|
# File 'lib/gitlab/diff/file.rb', line 270
def code_review_id
Digest::SHA1.hexdigest("#{file_identifier}-#{blob&.id}")
end
|
#content_changed? ⇒ Boolean
299
300
301
302
303
304
305
|
# File 'lib/gitlab/diff/file.rb', line 299
def content_changed?
return blobs_changed? if diff_refs && new_blob
return false if new_file? || deleted_file? || renamed_file?
text? && diff_lines.any?
end
|
#content_sha ⇒ Object
167
168
169
|
# File 'lib/gitlab/diff/file.rb', line 167
def content_sha
new_content_sha || old_content_sha
end
|
#diff_hunk(diff_line) ⇒ Object
Returns the raw diff content up to the given line index
112
113
114
115
116
117
118
119
|
# File 'lib/gitlab/diff/file.rb', line 112
def diff_hunk(diff_line)
diff_line_index = diff_line.index
diff_line_index += 1 unless diff_lines.first.match?
diff_lines.select { |line| line.index <= diff_line_index }.map(&:text).join("\n")
end
|
#diff_lines ⇒ Object
Array of Gitlab::Diff::Line objects
184
185
186
187
|
# File 'lib/gitlab/diff/file.rb', line 184
def diff_lines
@diff_lines ||=
Gitlab::Diff::Parser.new.parse(raw_diff.each_line, diff_file: self).to_a
end
|
#diff_lines_for_serializer ⇒ Object
This adds the bottom match line to the array if needed. It contains the data to load more context lines.
359
360
361
362
363
364
|
# File 'lib/gitlab/diff/file.rb', line 359
def diff_lines_for_serializer
lines = diff_lines_with_match_tail
return if lines.empty?
lines
end
|
#diff_lines_with_match_tail ⇒ Object
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
|
# File 'lib/gitlab/diff/file.rb', line 366
def diff_lines_with_match_tail
lines = highlighted_diff_lines
return [] if lines.empty?
return [] if blob.nil?
last_line = lines.last
if last_line.new_pos < total_blob_lines(blob) && !deleted_file?
match_line = Gitlab::Diff::Line.new("", 'match', nil, last_line.old_pos, last_line.new_pos)
lines.push(match_line)
end
lines
end
|
#diffable? ⇒ Boolean
275
276
277
|
# File 'lib/gitlab/diff/file.rb', line 275
def diffable?
diffable_by_attribute? && !text_with_binary_notice?
end
|
#diffable_text? ⇒ Boolean
424
425
426
|
# File 'lib/gitlab/diff/file.rb', line 424
def diffable_text?
!too_large? && diffable? && text? && !whitespace_only?
end
|
#different_type? ⇒ Boolean
307
308
309
|
# File 'lib/gitlab/diff/file.rb', line 307
def different_type?
old_blob && new_blob && old_blob.binary? != new_blob.binary?
end
|
#empty? ⇒ Boolean
319
320
321
|
# File 'lib/gitlab/diff/file.rb', line 319
def empty?
valid_blobs.map(&:empty?).all?
end
|
#expand_to_full! ⇒ Object
Expands the diff to show the complete file content with changes merged in. This replicates the frontend convertExpandLines behavior from legacy diffs, but performs the merging on the backend for rapid diffs rendering.
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
|
# File 'lib/gitlab/diff/file.rb', line 441
def expand_to_full!
return if blob.binary_in_repo?
presenter = Blobs::UnfoldPresenter.new(blob, { full: true })
blob_lines = presenter.diff_lines(with_positions_and_indent: true).to_a
merged_lines = []
diff_lines_with_match_tail.each_with_index do |line, index|
if line.type == 'match'
prev_line = index == 0 ? nil : diff_lines_with_match_tail[index - 1]
next_line = diff_lines_with_match_tail[index + 1]
start_index = prev_line ? prev_line.new_pos : 0
end_index = next_line ? next_line.new_pos - 1 : blob_lines.count
expanded_lines = blob_lines[start_index..end_index]
if prev_line
expanded_lines.each_with_index do |expanded_line, line_index|
expanded_line.old_pos = prev_line.old_pos + 1 + line_index
expanded_line.new_pos = prev_line.new_pos + 1 + line_index
end
end
merged_lines.concat(expanded_lines)
else
merged_lines << line
end
end
self.highlighted_diff_lines = merged_lines
@expanded_to_full = true
end
|
#external_storage ⇒ Object
295
296
297
|
# File 'lib/gitlab/diff/file.rb', line 295
def external_storage
try_blobs(:external_storage)
end
|
#external_storage_error? ⇒ Boolean
287
288
289
|
# File 'lib/gitlab/diff/file.rb', line 287
def external_storage_error?
try_blobs(:external_storage_error?)
end
|
#file_hash ⇒ Object
245
246
247
|
# File 'lib/gitlab/diff/file.rb', line 245
def file_hash
Digest::SHA1.hexdigest(file_path)
end
|
#file_identifier ⇒ Object
262
263
264
|
# File 'lib/gitlab/diff/file.rb', line 262
def file_identifier
"#{file_path}-#{new_file?}-#{deleted_file?}-#{renamed_file?}"
end
|
#file_identifier_hash ⇒ Object
266
267
268
|
# File 'lib/gitlab/diff/file.rb', line 266
def file_identifier_hash
Digest::SHA1.hexdigest(file_identifier)
end
|
#file_path ⇒ Object
241
242
243
|
# File 'lib/gitlab/diff/file.rb', line 241
def file_path
new_path.presence || old_path
end
|
#fully_expanded? ⇒ Boolean
383
384
385
386
387
388
389
390
391
|
# File 'lib/gitlab/diff/file.rb', line 383
def fully_expanded?
return true if binary?
lines = diff_lines_for_serializer
return true if lines.nil?
lines.none? { |line| line.type.to_s == 'match' }
end
|
#has_renderable? ⇒ Boolean
56
57
58
|
# File 'lib/gitlab/diff/file.rb', line 56
def has_renderable?
rendered&.has_renderable?
end
|
#highlight_loaded? ⇒ Boolean
211
212
213
|
# File 'lib/gitlab/diff/file.rb', line 211
def highlight_loaded?
@highlighted_diff_lines.present?
end
|
#highlighted_diff_lines ⇒ Object
215
216
217
218
|
# File 'lib/gitlab/diff/file.rb', line 215
def highlighted_diff_lines
@highlighted_diff_lines ||=
Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight
end
|
#highlighted_diff_lines=(value) ⇒ Object
175
176
177
178
179
180
181
|
# File 'lib/gitlab/diff/file.rb', line 175
def highlighted_diff_lines=(value)
clear_memoization(:diff_lines_for_serializer)
clear_memoization(:diff_lines_with_match_tail)
@highlighted_diff_lines = value
end
|
#image_diff? ⇒ Boolean
432
433
434
435
436
|
# File 'lib/gitlab/diff/file.rb', line 432
def image_diff?
return false if different_type? || external_storage_error?
DiffViewer::Image.can_render?(self, verify_binary: !stored_externally?)
end
|
#ipynb? ⇒ Boolean
403
404
405
|
# File 'lib/gitlab/diff/file.rb', line 403
def ipynb?
file_path.ends_with?('.ipynb')
end
|
#line_code(line) ⇒ Object
79
80
81
|
# File 'lib/gitlab/diff/file.rb', line 79
def line_code(line)
line.legacy_id(file_path)
end
|
#line_code_for_position(pos) ⇒ Object
106
107
108
109
|
# File 'lib/gitlab/diff/file.rb', line 106
def line_code_for_position(pos)
line = line_for_position(pos)
line_code(line) if line
end
|
#line_for_line_code(code) ⇒ Object
83
84
85
|
# File 'lib/gitlab/diff/file.rb', line 83
def line_for_line_code(code)
diff_lines.find { |line| line_code(line) == code }
end
|
#line_for_position(pos) ⇒ Object
87
88
89
90
91
92
93
94
95
96
97
98
99
|
# File 'lib/gitlab/diff/file.rb', line 87
def line_for_position(pos)
return unless pos.position_type == 'text'
diff_lines
.reverse_each
.find { |line| line.old_line == pos.old_line && line.new_line == pos.new_line }
end
|
#manually_expanded? ⇒ Boolean
474
475
476
|
# File 'lib/gitlab/diff/file.rb', line 474
def manually_expanded?
@expanded_to_full || false
end
|
#modified_file? ⇒ Boolean
416
417
418
|
# File 'lib/gitlab/diff/file.rb', line 416
def modified_file?
new_file? || deleted_file? || content_changed?
end
|
#new_blob ⇒ Object
145
146
147
148
149
|
# File 'lib/gitlab/diff/file.rb', line 145
def new_blob
strong_memoize(:new_blob) do
new_blob_lazy&.itself
end
end
|
#new_blob_lines_between(from_line, to_line) ⇒ Object
157
158
159
160
161
162
163
164
165
|
# File 'lib/gitlab/diff/file.rb', line 157
def new_blob_lines_between(from_line, to_line)
return [] unless new_blob
from_index = from_line - 1
to_index = to_line - 1
new_blob.load_all_data!
new_blob.data.lines[from_index..to_index]
end
|
#new_content_sha ⇒ Object
129
130
131
132
133
134
135
|
# File 'lib/gitlab/diff/file.rb', line 129
def new_content_sha
return if deleted_file?
return @new_content_sha if defined?(@new_content_sha)
refs = diff_refs || fallback_diff_refs
@new_content_sha = refs&.head_sha
end
|
#new_sha ⇒ Object
125
126
127
|
# File 'lib/gitlab/diff/file.rb', line 125
def new_sha
diff_refs&.head_sha
end
|
#next_line(index) ⇒ Object
229
230
231
|
# File 'lib/gitlab/diff/file.rb', line 229
def next_line(index)
diff_lines[index + 1]
end
|
#no_preview? ⇒ Boolean
420
421
422
|
# File 'lib/gitlab/diff/file.rb', line 420
def no_preview?
collapsed? || !modified_file? || (empty? && !content_changed? && !submodule?)
end
|
#old_blob ⇒ Object
151
152
153
154
155
|
# File 'lib/gitlab/diff/file.rb', line 151
def old_blob
strong_memoize(:old_blob) do
old_blob_lazy&.itself
end
end
|
#old_content_sha ⇒ Object
137
138
139
140
141
142
143
|
# File 'lib/gitlab/diff/file.rb', line 137
def old_content_sha
return if new_file?
return @old_content_sha if defined?(@old_content_sha)
refs = diff_refs || fallback_diff_refs
@old_content_sha = refs&.base_sha
end
|
#old_sha ⇒ Object
121
122
123
|
# File 'lib/gitlab/diff/file.rb', line 121
def old_sha
diff_refs&.base_sha
end
|
#parallel_diff_lines ⇒ Object
Array with right/left keys that contains Gitlab::Diff::Line objects which text is highlighted
#paths ⇒ Object
237
238
239
|
# File 'lib/gitlab/diff/file.rb', line 237
def paths
[old_path, new_path].compact
end
|
#position(position_marker, position_type: :text) ⇒ Object
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
# File 'lib/gitlab/diff/file.rb', line 60
def position(position_marker, position_type: :text)
return unless diff_refs
data = {
diff_refs: diff_refs,
position_type: position_type.to_s,
old_path: old_path,
new_path: new_path
}
if position_type == :text
data.merge!(text_position_properties(position_marker))
else
data.merge!(image_position_properties(position_marker))
end
Position.new(data)
end
|
#position_for_line_code(code) ⇒ Object
101
102
103
104
|
# File 'lib/gitlab/diff/file.rb', line 101
def position_for_line_code(code)
line = line_for_line_code(code)
position(line) if line
end
|
#prev_line(index) ⇒ Object
233
234
235
|
# File 'lib/gitlab/diff/file.rb', line 233
def prev_line(index)
diff_lines[index - 1] if index > 0
end
|
#raw_diff ⇒ Object
225
226
227
|
# File 'lib/gitlab/diff/file.rb', line 225
def raw_diff
diff.diff.to_s
end
|
#raw_size ⇒ Object
315
316
317
|
# File 'lib/gitlab/diff/file.rb', line 315
def raw_size
valid_blobs.sum(&:raw_size)
end
|
#removed_lines ⇒ Object
256
257
258
259
260
|
# File 'lib/gitlab/diff/file.rb', line 256
def removed_lines
strong_memoize(:removed_lines) do
@stats&.deletions || diff_lines.count(&:removed?)
end
end
|
#rendered ⇒ Object
393
394
395
396
397
|
# File 'lib/gitlab/diff/file.rb', line 393
def rendered
return unless ipynb? && modified_file? && !collapsed? && !too_large?
strong_memoize(:rendered) { Rendered::Notebook::DiffFile.new(self) }
end
|
#rendered? ⇒ Boolean
399
400
401
|
# File 'lib/gitlab/diff/file.rb', line 399
def rendered?
false
end
|
#rendered_as_text?(ignore_errors: true) ⇒ Boolean
353
354
355
|
# File 'lib/gitlab/diff/file.rb', line 353
def rendered_as_text?(ignore_errors: true)
simple_viewer.is_a?(DiffViewer::Text) && (ignore_errors || simple_viewer.render_error.nil?)
end
|
#rich_viewer ⇒ Object
343
344
345
346
347
|
# File 'lib/gitlab/diff/file.rb', line 343
def rich_viewer
return @rich_viewer if defined?(@rich_viewer)
@rich_viewer = rich_viewer_class&.new(self)
end
|
#simple_viewer ⇒ Object
339
340
341
|
# File 'lib/gitlab/diff/file.rb', line 339
def simple_viewer
@simple_viewer ||= simple_viewer_class.new(self)
end
|
#size ⇒ Object
311
312
313
|
# File 'lib/gitlab/diff/file.rb', line 311
def size
valid_blobs.sum(&:size)
end
|
#stored_externally? ⇒ Boolean
291
292
293
|
# File 'lib/gitlab/diff/file.rb', line 291
def stored_externally?
try_blobs(:stored_externally?)
end
|
#text? ⇒ Boolean
329
330
331
332
333
|
# File 'lib/gitlab/diff/file.rb', line 329
def text?
strong_memoize(:is_text) do
!binary? && !different_type?
end
end
|
#text_in_repo? ⇒ Boolean
283
284
285
|
# File 'lib/gitlab/diff/file.rb', line 283
def text_in_repo?
!binary_in_repo?
end
|
#unfold_diff_lines(position) ⇒ Object
Changes diff_lines according to the given position. That is, it checks whether the position requires blob lines into the diff in order to be presented.
196
197
198
199
200
201
202
203
204
205
|
# File 'lib/gitlab/diff/file.rb', line 196
def unfold_diff_lines(position)
return unless position
unfolder = Gitlab::Diff::LinesUnfolder.new(self, position)
if unfolder.unfold_required?
@diff_lines = unfolder.unfolded_diff_lines
@unfolded = true
end
end
|
#unfolded? ⇒ Boolean
207
208
209
|
# File 'lib/gitlab/diff/file.rb', line 207
def unfolded?
@unfolded
end
|
#viewer ⇒ Object
335
336
337
|
# File 'lib/gitlab/diff/file.rb', line 335
def viewer
rich_viewer || simple_viewer
end
|
#whitespace_only? ⇒ Boolean
428
429
430
|
# File 'lib/gitlab/diff/file.rb', line 428
def whitespace_only?
!collapsed? && diff_lines_for_serializer.nil? && (added_lines != 0 || removed_lines != 0)
end
|