Class: Gitlab::Diff::File

Inherits:
Object
  • Object
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

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

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
46
# 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
  new_blob_lazy
  old_blob_lazy
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

#added_linesObject


232
233
234
235
236
# File 'lib/gitlab/diff/file.rb', line 232

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

#alternate_viewerObject


329
330
331
# File 'lib/gitlab/diff/file.rb', line 329

def alternate_viewer
  alternate_viewer_class&.new(self)
end

#binary?Boolean

Returns:

  • (Boolean)

303
304
305
306
307
# File 'lib/gitlab/diff/file.rb', line 303

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

#binary_in_repo?Boolean

Returns:

  • (Boolean)

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

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

#blobObject


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

def blob
  new_blob || old_blob
end

#content_changed?Boolean

Returns:

  • (Boolean)

276
277
278
279
280
281
# File 'lib/gitlab/diff/file.rb', line 276

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


157
158
159
# File 'lib/gitlab/diff/file.rb', line 157

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


102
103
104
105
106
107
108
109
# File 'lib/gitlab/diff/file.rb', line 102

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


171
172
173
174
# File 'lib/gitlab/diff/file.rb', line 171

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.


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

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)

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

def diffable?
  repository.attributes(file_path).fetch('diff') { true }
end

#different_type?Boolean

Returns:

  • (Boolean)

283
284
285
# File 'lib/gitlab/diff/file.rb', line 283

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

#empty?Boolean

rubocop: enable CodeReuse/ActiveRecord

Returns:

  • (Boolean)

299
300
301
# File 'lib/gitlab/diff/file.rb', line 299

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

#external_storageObject


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

def external_storage
  try_blobs(:external_storage)
end

#external_storage_error?Boolean

Returns:

  • (Boolean)

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

def external_storage_error?
  try_blobs(:external_storage_error?)
end

#file_hashObject


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

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

#file_identifierObject


244
245
246
# File 'lib/gitlab/diff/file.rb', line 244

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

#file_identifier_hashObject


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

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

#file_pathObject


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

def file_path
  new_path.presence || old_path
end

#fully_expanded?Boolean

Returns:

  • (Boolean)

357
358
359
360
361
362
363
364
365
# File 'lib/gitlab/diff/file.rb', line 357

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

#highlight_loaded?Boolean

Returns:

  • (Boolean)

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

def highlight_loaded?
  @highlighted_diff_lines.present?
end

#highlighted_diff_linesObject


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

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

#highlighted_diff_lines=(value) ⇒ Object


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

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

#line_code(line) ⇒ Object


67
68
69
70
71
# File 'lib/gitlab/diff/file.rb', line 67

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


96
97
98
99
# File 'lib/gitlab/diff/file.rb', line 96

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

#line_for_line_code(code) ⇒ Object


73
74
75
# File 'lib/gitlab/diff/file.rb', line 73

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

#line_for_position(pos) ⇒ Object


77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/gitlab/diff/file.rb', line 77

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


135
136
137
138
139
# File 'lib/gitlab/diff/file.rb', line 135

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

#new_blob_lines_between(from_line, to_line) ⇒ Object


147
148
149
150
151
152
153
154
155
# File 'lib/gitlab/diff/file.rb', line 147

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


119
120
121
122
123
124
125
# File 'lib/gitlab/diff/file.rb', line 119

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


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

def new_sha
  diff_refs&.head_sha
end

#next_line(index) ⇒ Object


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

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

#old_blobObject


141
142
143
144
145
# File 'lib/gitlab/diff/file.rb', line 141

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

#old_content_shaObject


127
128
129
130
131
132
133
# File 'lib/gitlab/diff/file.rb', line 127

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


111
112
113
# File 'lib/gitlab/diff/file.rb', line 111

def old_sha
  diff_refs&.base_sha
end

#parallel_diff_linesObject


204
205
206
# File 'lib/gitlab/diff/file.rb', line 204

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

#pathsObject


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

def paths
  [old_path, new_path].compact
end

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


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

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


91
92
93
94
# File 'lib/gitlab/diff/file.rb', line 91

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

#prev_line(index) ⇒ Object


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

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

#raw_diffObject


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

def raw_diff
  diff.diff.to_s
end

#raw_sizeObject

rubocop: disable CodeReuse/ActiveRecord


294
295
296
# File 'lib/gitlab/diff/file.rb', line 294

def raw_size
  valid_blobs.map(&:raw_size).sum
end

#removed_linesObject


238
239
240
241
242
# File 'lib/gitlab/diff/file.rb', line 238

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

#rendered_as_text?(ignore_errors: true) ⇒ Boolean

Returns:

  • (Boolean)

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

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

#rich_viewerObject


323
324
325
326
327
# File 'lib/gitlab/diff/file.rb', line 323

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

  @rich_viewer = rich_viewer_class&.new(self)
end

#simple_viewerObject


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

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

#sizeObject

rubocop: disable CodeReuse/ActiveRecord


288
289
290
# File 'lib/gitlab/diff/file.rb', line 288

def size
  valid_blobs.map(&:size).sum
end

#stored_externally?Boolean

Returns:

  • (Boolean)

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

def stored_externally?
  try_blobs(:stored_externally?)
end

#text?Boolean

Returns:

  • (Boolean)

309
310
311
312
313
# File 'lib/gitlab/diff/file.rb', line 309

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

#text_in_repo?Boolean

Returns:

  • (Boolean)

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

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.


179
180
181
182
183
184
185
186
187
188
# File 'lib/gitlab/diff/file.rb', line 179

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)

190
191
192
# File 'lib/gitlab/diff/file.rb', line 190

def unfolded?
  @unfolded
end

#viewerObject


315
316
317
# File 'lib/gitlab/diff/file.rb', line 315

def viewer
  rich_viewer || simple_viewer
end