gitlab-org--gitlab-foss/lib/gitlab/diff/file.rb
Sean McGivern e8525e107d Show un-highlighted diffs when blobs are the same
For some old merge requests, we don't have enough information to figure out the
old blob and the new blob for the file. This means that we can't highlight the
diff correctly, but we can still display it without highlighting.
2017-08-24 11:11:18 +01:00

280 lines
7.1 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, 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
end
def position(line)
return unless diff_refs
Position.new(
old_path: old_path,
new_path: new_path,
old_line: line.old_line,
new_line: line.new_line,
diff_refs: diff_refs
)
end
def line_code(line)
return if line.meta?
Gitlab::Diff::LineCode.generate(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 @new_blob if defined?(@new_blob)
sha = new_content_sha
return @new_blob = nil unless sha
@new_blob = repository.blob_at(sha, file_path)
end
def old_blob
return @old_blob if defined?(@old_blob)
sha = old_content_sha
return @old_blob = nil unless sha
@old_blob = repository.blob_at(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?
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 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