Class: Gitlab::Diff::File

Inherits:
Object
  • Object
show all
Includes:
Utils::StrongMemoize
Defined in:
lib/gitlab/diff/file.rb

Direct Known Subclasses

Rendered::Notebook::DiffFile

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

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) ⇒ File

Returns a new instance of File.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/gitlab/diff/file.rb', line 27

def initialize(
  diff,
  repository:,
  diff_refs: nil,
  fallback_diff_refs: nil,
  stats: nil,
  unique_identifier: nil)

  @diff = diff
  @stats = stats
  @repository = repository
  @diff_refs = diff_refs
  @fallback_diff_refs = fallback_diff_refs
  @unique_identifier = unique_identifier
  @unfolded = false

  # Ensure items are collected in the the batch
  add_blobs_to_batch_loader
end

Instance Attribute Details

#diffObject (readonly)

Returns the value of attribute diff.



8
9
10
# File 'lib/gitlab/diff/file.rb', line 8

def diff
  @diff
end

#diff_refsObject (readonly)

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_refsObject (readonly)

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

#repositoryObject (readonly)

Returns the value of attribute repository.



8
9
10
# File 'lib/gitlab/diff/file.rb', line 8

def repository
  @repository
end

#unique_identifierObject (readonly)

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_loaderObject



380
381
382
383
# File 'lib/gitlab/diff/file.rb', line 380

def add_blobs_to_batch_loader
  new_blob_lazy
  old_blob_lazy
end

#added_linesObject



235
236
237
238
239
# File 'lib/gitlab/diff/file.rb', line 235

def added_lines
  strong_memoize(:added_lines) do
    @stats&.additions || diff_lines.count(&:added?)
  end
end

#alternate_viewerObject



332
333
334
# File 'lib/gitlab/diff/file.rb', line 332

def alternate_viewer
  alternate_viewer_class&.new(self)
end

#binary?Boolean

Returns:

  • (Boolean)


306
307
308
309
310
# File 'lib/gitlab/diff/file.rb', line 306

def binary?
  strong_memoize(:is_binary) do
    try_blobs(:binary?)
  end
end

#binary_in_repo?Boolean

Returns:

  • (Boolean)


259
260
261
# File 'lib/gitlab/diff/file.rb', line 259

def binary_in_repo?
  has_binary_notice? || try_blobs(:binary_in_repo?)
end

#blobObject



164
165
166
# File 'lib/gitlab/diff/file.rb', line 164

def blob
  new_blob || old_blob
end

#content_changed?Boolean

Returns:

  • (Boolean)


279
280
281
282
283
284
# File 'lib/gitlab/diff/file.rb', line 279

def content_changed?
  return blobs_changed? if diff_refs
  return false if new_file? || deleted_file? || renamed_file?

  text? && diff_lines.any?
end

#content_shaObject



160
161
162
# File 'lib/gitlab/diff/file.rb', line 160

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



105
106
107
108
109
110
111
112
# File 'lib/gitlab/diff/file.rb', line 105

def diff_hunk(diff_line)
  diff_line_index = diff_line.index
  # @@ (match) header is not kept if it's found in the top of the file,
  # therefore we should keep an extra line on this scenario.
  diff_line_index += 1 unless diff_lines.first.match?

  diff_lines.select { |line| line.index <= diff_line_index }.map(&:text).join("\n")
end

#diff_linesObject

Array of Gitlab::Diff::Line objects



174
175
176
177
# File 'lib/gitlab/diff/file.rb', line 174

def diff_lines
  @diff_lines ||=
    Gitlab::Diff::Parser.new.parse(raw_diff.each_line, diff_file: self).to_a
end

#diff_lines_for_serializerObject

This adds the bottom match line to the array if needed. It contains the data to load more context lines.



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/gitlab/diff/file.rb', line 342

def diff_lines_for_serializer
  strong_memoize(:diff_lines_for_serializer) do
    lines = highlighted_diff_lines

    next if lines.empty?
    next 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
end

#diffable?Boolean

Returns:

  • (Boolean)


255
256
257
# File 'lib/gitlab/diff/file.rb', line 255

def diffable?
  diffable_by_attribute? && !text_with_binary_notice?
end

#different_type?Boolean

Returns:

  • (Boolean)


286
287
288
# File 'lib/gitlab/diff/file.rb', line 286

def different_type?
  old_blob && new_blob && old_blob.binary? != new_blob.binary?
end

#empty?Boolean

rubocop: enable CodeReuse/ActiveRecord

Returns:

  • (Boolean)


302
303
304
# File 'lib/gitlab/diff/file.rb', line 302

def empty?
  valid_blobs.map(&:empty?).all?
end

#external_storageObject



275
276
277
# File 'lib/gitlab/diff/file.rb', line 275

def external_storage
  try_blobs(:external_storage)
end

#external_storage_error?Boolean

Returns:

  • (Boolean)


267
268
269
# File 'lib/gitlab/diff/file.rb', line 267

def external_storage_error?
  try_blobs(:external_storage_error?)
end

#file_hashObject



231
232
233
# File 'lib/gitlab/diff/file.rb', line 231

def file_hash
  Digest::SHA1.hexdigest(file_path)
end

#file_identifierObject



247
248
249
# File 'lib/gitlab/diff/file.rb', line 247

def file_identifier
  "#{file_path}-#{new_file?}-#{deleted_file?}-#{renamed_file?}"
end

#file_identifier_hashObject



251
252
253
# File 'lib/gitlab/diff/file.rb', line 251

def file_identifier_hash
  Digest::SHA1.hexdigest(file_identifier)
end

#file_pathObject



227
228
229
# File 'lib/gitlab/diff/file.rb', line 227

def file_path
  new_path.presence || old_path
end

#fully_expanded?Boolean

Returns:

  • (Boolean)


360
361
362
363
364
365
366
367
368
# File 'lib/gitlab/diff/file.rb', line 360

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

Returns:

  • (Boolean)


47
48
49
# File 'lib/gitlab/diff/file.rb', line 47

def has_renderable?
  rendered&.has_renderable?
end

#highlight_loaded?Boolean

Returns:

  • (Boolean)


197
198
199
# File 'lib/gitlab/diff/file.rb', line 197

def highlight_loaded?
  @highlighted_diff_lines.present?
end

#highlighted_diff_linesObject



201
202
203
204
# File 'lib/gitlab/diff/file.rb', line 201

def highlighted_diff_lines
  @highlighted_diff_lines ||=
    Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight
end

#highlighted_diff_lines=(value) ⇒ Object



168
169
170
171
# File 'lib/gitlab/diff/file.rb', line 168

def highlighted_diff_lines=(value)
  clear_memoization(:diff_lines_for_serializer)
  @highlighted_diff_lines = value
end

#ipynb?Boolean

Returns:

  • (Boolean)


376
377
378
# File 'lib/gitlab/diff/file.rb', line 376

def ipynb?
  file_path.ends_with?('.ipynb')
end

#line_code(line) ⇒ Object



70
71
72
73
74
# File 'lib/gitlab/diff/file.rb', line 70

def line_code(line)
  return if line.meta?

  Gitlab::Git.diff_line_code(file_path, line.new_pos, line.old_pos)
end

#line_code_for_position(pos) ⇒ Object



99
100
101
102
# File 'lib/gitlab/diff/file.rb', line 99

def line_code_for_position(pos)
  line = line_for_position(pos)
  line_code(line) if line
end

#line_for_line_code(code) ⇒ Object



76
77
78
# File 'lib/gitlab/diff/file.rb', line 76

def line_for_line_code(code)
  diff_lines.find { |line| line_code(line) == code }
end

#line_for_position(pos) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/gitlab/diff/file.rb', line 80

def line_for_position(pos)
  return unless pos.position_type == 'text'

  # This method is normally used to find which line the diff was
  # commented on, and in this context, it's normally the raw diff persisted
  # at `note_diff_files`, which is a fraction of the entire diff
  # (it goes from the first line, to the commented line, or
  # one line below). Therefore it's more performant to fetch
  # from bottom to top instead of the other way around.
  diff_lines
    .reverse_each
    .find { |line| line.old_line == pos.old_line && line.new_line == pos.new_line }
end

#new_blobObject



138
139
140
141
142
# File 'lib/gitlab/diff/file.rb', line 138

def new_blob
  strong_memoize(:new_blob) do
    new_blob_lazy&.itself
  end
end

#new_blob_lines_between(from_line, to_line) ⇒ Object



150
151
152
153
154
155
156
157
158
# File 'lib/gitlab/diff/file.rb', line 150

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_shaObject



122
123
124
125
126
127
128
# File 'lib/gitlab/diff/file.rb', line 122

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_shaObject



118
119
120
# File 'lib/gitlab/diff/file.rb', line 118

def new_sha
  diff_refs&.head_sha
end

#next_line(index) ⇒ Object



215
216
217
# File 'lib/gitlab/diff/file.rb', line 215

def next_line(index)
  diff_lines[index + 1]
end

#old_blobObject



144
145
146
147
148
# File 'lib/gitlab/diff/file.rb', line 144

def old_blob
  strong_memoize(:old_blob) do
    old_blob_lazy&.itself
  end
end

#old_content_shaObject



130
131
132
133
134
135
136
# File 'lib/gitlab/diff/file.rb', line 130

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_shaObject



114
115
116
# File 'lib/gitlab/diff/file.rb', line 114

def old_sha
  diff_refs&.base_sha
end

#parallel_diff_linesObject

Array with right/left keys that contains Gitlab::Diff::Line objects which text is highlighted



207
208
209
# File 'lib/gitlab/diff/file.rb', line 207

def parallel_diff_lines
  @parallel_diff_lines ||= Gitlab::Diff::ParallelDiff.new(self).parallelize
end

#pathsObject



223
224
225
# File 'lib/gitlab/diff/file.rb', line 223

def paths
  [old_path, new_path].compact
end

#position(position_marker, position_type: :text) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/gitlab/diff/file.rb', line 51

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



94
95
96
97
# File 'lib/gitlab/diff/file.rb', line 94

def position_for_line_code(code)
  line = line_for_line_code(code)
  position(line) if line
end

#prev_line(index) ⇒ Object



219
220
221
# File 'lib/gitlab/diff/file.rb', line 219

def prev_line(index)
  diff_lines[index - 1] if index > 0
end

#raw_diffObject



211
212
213
# File 'lib/gitlab/diff/file.rb', line 211

def raw_diff
  diff.diff.to_s
end

#raw_sizeObject

rubocop: disable CodeReuse/ActiveRecord



297
298
299
# File 'lib/gitlab/diff/file.rb', line 297

def raw_size
  valid_blobs.sum(&:raw_size)
end

#removed_linesObject



241
242
243
244
245
# File 'lib/gitlab/diff/file.rb', line 241

def removed_lines
  strong_memoize(:removed_lines) do
    @stats&.deletions || diff_lines.count(&:removed?)
  end
end

#renderedObject



370
371
372
373
374
# File 'lib/gitlab/diff/file.rb', line 370

def rendered
  return unless ipynb? && modified_file? && !collapsed? && !too_large?

  strong_memoize(:rendered) { Rendered::Notebook::DiffFile.new(self) }
end

#rendered_as_text?(ignore_errors: true) ⇒ Boolean

Returns:

  • (Boolean)


336
337
338
# File 'lib/gitlab/diff/file.rb', line 336

def rendered_as_text?(ignore_errors: true)
  simple_viewer.is_a?(DiffViewer::Text) && (ignore_errors || simple_viewer.render_error.nil?)
end

#rich_viewerObject



326
327
328
329
330
# File 'lib/gitlab/diff/file.rb', line 326

def rich_viewer
  return @rich_viewer if defined?(@rich_viewer)

  @rich_viewer = rich_viewer_class&.new(self)
end

#simple_viewerObject



322
323
324
# File 'lib/gitlab/diff/file.rb', line 322

def simple_viewer
  @simple_viewer ||= simple_viewer_class.new(self)
end

#sizeObject

rubocop: disable CodeReuse/ActiveRecord



291
292
293
# File 'lib/gitlab/diff/file.rb', line 291

def size
  valid_blobs.sum(&:size)
end

#stored_externally?Boolean

Returns:

  • (Boolean)


271
272
273
# File 'lib/gitlab/diff/file.rb', line 271

def stored_externally?
  try_blobs(:stored_externally?)
end

#text?Boolean

Returns:

  • (Boolean)


312
313
314
315
316
# File 'lib/gitlab/diff/file.rb', line 312

def text?
  strong_memoize(:is_text) do
    !binary? && !different_type?
  end
end

#text_in_repo?Boolean

Returns:

  • (Boolean)


263
264
265
# File 'lib/gitlab/diff/file.rb', line 263

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.



182
183
184
185
186
187
188
189
190
191
# File 'lib/gitlab/diff/file.rb', line 182

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

Returns:

  • (Boolean)


193
194
195
# File 'lib/gitlab/diff/file.rb', line 193

def unfolded?
  @unfolded
end

#viewerObject



318
319
320
# File 'lib/gitlab/diff/file.rb', line 318

def viewer
  rich_viewer || simple_viewer
end