f9565e3039
After installing a new gem, batch-loader, a construct can be used to queue data to be fetched in bulk. The gem was also introduced in both gitlab-org/gitlab-ce!14680 and gitlab-org/gitlab-ce!14846, but those mrs are not merged yet. For the generation of diffs, both the old blob and the new blob need to be loaded. This for every file in the diff, too. Now we collect all these so we do 1 fetch. Three `.allow_n_plus_1_calls` have been removed, which I expect to be valid, but this needs to be confirmed by a full CI run. Possibly closes: - https://gitlab.com/gitlab-org/gitlab-ce/issues/37445 - https://gitlab.com/gitlab-org/gitlab-ce/issues/37599 - https://gitlab.com/gitlab-org/gitlab-ce/issues/37431
293 lines
7.5 KiB
Ruby
293 lines
7.5 KiB
Ruby
module Gitlab
|
|
module Diff
|
|
class File
|
|
attr_reader :diff, :repository, :diff_refs, :fallback_diff_refs
|
|
|
|
delegate :new_file?, :deleted_file?, :renamed_file?,
|
|
:old_path, :new_path, :a_mode, :b_mode, :mode_changed?,
|
|
:submodule?, :expanded?, :too_large?, :collapsed?, :line_count, :has_binary_notice?, to: :diff, prefix: false
|
|
|
|
# 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.
|
|
RICH_VIEWERS = [
|
|
DiffViewer::Image
|
|
].sort_by { |v| v.binary? ? 0 : 1 }.freeze
|
|
|
|
def initialize(diff, repository:, diff_refs: nil, fallback_diff_refs: nil)
|
|
@diff = diff
|
|
@repository = repository
|
|
@diff_refs = diff_refs
|
|
@fallback_diff_refs = fallback_diff_refs
|
|
|
|
# Ensure items are collected in the the batch
|
|
new_blob
|
|
old_blob
|
|
end
|
|
|
|
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
|
|
|
|
def line_code(line)
|
|
return if line.meta?
|
|
|
|
Gitlab::Git.diff_line_code(file_path, line.new_pos, line.old_pos)
|
|
end
|
|
|
|
def line_for_line_code(code)
|
|
diff_lines.find { |line| line_code(line) == code }
|
|
end
|
|
|
|
def line_for_position(pos)
|
|
diff_lines.find { |line| position(line) == pos }
|
|
end
|
|
|
|
def position_for_line_code(code)
|
|
line = line_for_line_code(code)
|
|
position(line) if line
|
|
end
|
|
|
|
def line_code_for_position(pos)
|
|
line = line_for_position(pos)
|
|
line_code(line) if line
|
|
end
|
|
|
|
def old_sha
|
|
diff_refs&.base_sha
|
|
end
|
|
|
|
def new_sha
|
|
diff_refs&.head_sha
|
|
end
|
|
|
|
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
|
|
|
|
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
|
|
|
|
def new_blob
|
|
return unless new_content_sha
|
|
|
|
Blob.lazy(repository.project, new_content_sha, file_path)
|
|
end
|
|
|
|
def old_blob
|
|
return unless old_content_sha
|
|
|
|
Blob.lazy(repository.project, old_content_sha, old_path)
|
|
end
|
|
|
|
def content_sha
|
|
new_content_sha || old_content_sha
|
|
end
|
|
|
|
def blob
|
|
new_blob || old_blob
|
|
end
|
|
|
|
attr_writer :highlighted_diff_lines
|
|
|
|
# Array of Gitlab::Diff::Line objects
|
|
def diff_lines
|
|
@diff_lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a
|
|
end
|
|
|
|
def highlighted_diff_lines
|
|
@highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight
|
|
end
|
|
|
|
# Array[<Hash>] with right/left keys that contains Gitlab::Diff::Line objects which text is hightlighted
|
|
def parallel_diff_lines
|
|
@parallel_diff_lines ||= Gitlab::Diff::ParallelDiff.new(self).parallelize
|
|
end
|
|
|
|
def raw_diff
|
|
diff.diff.to_s
|
|
end
|
|
|
|
def next_line(index)
|
|
diff_lines[index + 1]
|
|
end
|
|
|
|
def prev_line(index)
|
|
diff_lines[index - 1] if index > 0
|
|
end
|
|
|
|
def paths
|
|
[old_path, new_path].compact
|
|
end
|
|
|
|
def file_path
|
|
new_path.presence || old_path
|
|
end
|
|
|
|
def added_lines
|
|
diff_lines.count(&:added?)
|
|
end
|
|
|
|
def removed_lines
|
|
diff_lines.count(&:removed?)
|
|
end
|
|
|
|
def file_identifier
|
|
"#{file_path}-#{new_file?}-#{deleted_file?}-#{renamed_file?}"
|
|
end
|
|
|
|
def diffable?
|
|
repository.attributes(file_path).fetch('diff') { true }
|
|
end
|
|
|
|
def binary?
|
|
has_binary_notice? || old_blob&.binary? || new_blob&.binary?
|
|
end
|
|
|
|
def text?
|
|
!binary?
|
|
end
|
|
|
|
def external_storage_error?
|
|
old_blob&.external_storage_error? || new_blob&.external_storage_error?
|
|
end
|
|
|
|
def stored_externally?
|
|
old_blob&.stored_externally? || new_blob&.stored_externally?
|
|
end
|
|
|
|
def external_storage
|
|
old_blob&.external_storage || new_blob&.external_storage
|
|
end
|
|
|
|
def content_changed?
|
|
return blobs_changed? if diff_refs
|
|
return false if new_file? || deleted_file? || renamed_file?
|
|
|
|
text? && diff_lines.any?
|
|
end
|
|
|
|
def different_type?
|
|
old_blob && new_blob && old_blob.binary? != new_blob.binary?
|
|
end
|
|
|
|
def size
|
|
[old_blob&.size, new_blob&.size].compact.sum
|
|
end
|
|
|
|
def raw_size
|
|
[old_blob&.raw_size, new_blob&.raw_size].compact.sum
|
|
end
|
|
|
|
def raw_binary?
|
|
old_blob&.raw_binary? || new_blob&.raw_binary?
|
|
end
|
|
|
|
def raw_text?
|
|
!raw_binary? && !different_type?
|
|
end
|
|
|
|
def simple_viewer
|
|
@simple_viewer ||= simple_viewer_class.new(self)
|
|
end
|
|
|
|
def rich_viewer
|
|
return @rich_viewer if defined?(@rich_viewer)
|
|
|
|
@rich_viewer = rich_viewer_class&.new(self)
|
|
end
|
|
|
|
def rendered_as_text?(ignore_errors: true)
|
|
simple_viewer.is_a?(DiffViewer::Text) && (ignore_errors || simple_viewer.render_error.nil?)
|
|
end
|
|
|
|
private
|
|
|
|
def text_position_properties(line)
|
|
{ old_line: line.old_line, new_line: line.new_line }
|
|
end
|
|
|
|
def image_position_properties(image_point)
|
|
image_point.to_h
|
|
end
|
|
|
|
def blobs_changed?
|
|
old_blob && new_blob && old_blob.id != new_blob.id
|
|
end
|
|
|
|
def simple_viewer_class
|
|
return DiffViewer::NotDiffable unless diffable?
|
|
|
|
if content_changed?
|
|
if raw_text?
|
|
DiffViewer::Text
|
|
else
|
|
DiffViewer::NoPreview
|
|
end
|
|
elsif new_file?
|
|
if raw_text?
|
|
DiffViewer::Text
|
|
else
|
|
DiffViewer::Added
|
|
end
|
|
elsif deleted_file?
|
|
if raw_text?
|
|
DiffViewer::Text
|
|
else
|
|
DiffViewer::Deleted
|
|
end
|
|
elsif renamed_file?
|
|
DiffViewer::Renamed
|
|
elsif mode_changed?
|
|
DiffViewer::ModeChanged
|
|
else
|
|
DiffViewer::NoPreview
|
|
end
|
|
end
|
|
|
|
def rich_viewer_class
|
|
viewer_class_from(RICH_VIEWERS)
|
|
end
|
|
|
|
def viewer_class_from(classes)
|
|
return unless diffable?
|
|
return if different_type? || external_storage_error?
|
|
return unless new_file? || deleted_file? || content_changed?
|
|
|
|
verify_binary = !stored_externally?
|
|
|
|
classes.find { |viewer_class| viewer_class.can_render?(self, verify_binary: verify_binary) }
|
|
end
|
|
end
|
|
end
|
|
end
|