gitlab-org--gitlab-foss/lib/gitlab/diff/position.rb

198 lines
4.9 KiB
Ruby

# frozen_string_literal: true
# Defines a specific location, identified by paths line numbers and image coordinates,
# within a specific diff, identified by start, head and base commit ids.
module Gitlab
module Diff
class Position
attr_accessor :formatter
delegate :old_path,
:new_path,
:file_identifier_hash,
:base_sha,
:start_sha,
:head_sha,
:old_line,
:new_line,
:width,
:height,
:x,
:y,
:line_range,
:position_type, to: :formatter
# A position can belong to a text line or to an image coordinate
# it depends of the position_type argument.
# Text position will have: new_line and old_line
# Image position will have: width, height, x, y
def initialize(attrs = {})
@formatter = get_formatter_class(attrs[:position_type]).new(attrs)
end
# `Gitlab::Diff::Position` objects are stored as serialized attributes in
# `DiffNote`, which use YAML to encode and decode objects.
# `#init_with` and `#encode_with` can be used to customize the en/decoding
# behavior. In this case, we override these to prevent memoized instance
# variables like `@diff_file` and `@diff_line` from being serialized.
def init_with(coder)
initialize(coder['attributes'])
self
end
def encode_with(coder)
coder['attributes'] = formatter.to_h
end
def key
formatter.key
end
def ==(other)
other.is_a?(self.class) &&
other.diff_refs == diff_refs &&
other.old_path == old_path &&
other.new_path == new_path &&
other.formatter == formatter
end
def to_h
formatter.to_h
end
def inspect
%(#<#{self.class}:#{object_id} #{to_h}>)
end
def complete?
file_path.present? && formatter.complete? && diff_refs.complete?
end
def to_json(opts = nil)
Gitlab::Json.generate(formatter.to_h, opts)
end
def as_json(opts = nil)
to_h.as_json(opts)
end
def type
formatter.line_age
end
def unfoldable?
on_text? && unchanged?
end
def unchanged?
type.nil?
end
def added?
type == 'new'
end
def removed?
type == 'old'
end
def paths
[old_path, new_path].compact.uniq
end
def file_path
new_path.presence || old_path
end
def diff_refs
@diff_refs ||= DiffRefs.new(base_sha: base_sha, start_sha: start_sha, head_sha: head_sha)
end
def unfolded_diff?(repository)
diff_file(repository)&.unfolded?
end
def diff_file(repository)
return @diff_file if defined?(@diff_file)
@diff_file = begin
key = {
project_id: repository.project.id,
start_sha: start_sha,
head_sha: head_sha,
path: file_path
}
# Takes action when creating diff notes (multiple calls are
# submitted to this method).
Gitlab::SafeRequestStore.fetch(key) { find_diff_file(repository) }
end
# We need to unfold diff lines according to the position in order
# to correctly calculate the line code and trace position changes.
@diff_file&.tap { |file| file.unfold_diff_lines(self) }
end
def diff_options
{ paths: paths, expanded: true, include_stats: false }
end
def diff_line(repository)
@diff_line ||= diff_file(repository)&.line_for_position(self)
end
def line_code(repository)
@line_code ||= diff_file(repository)&.line_code_for_position(self)
end
def file_hash
@file_hash ||= Digest::SHA1.hexdigest(file_path)
end
def on_image?
position_type == 'image'
end
def on_text?
position_type == 'text'
end
def find_diff_file_from(diffable)
diff_files = diffable.diffs(diff_options).diff_files
if Feature.enabled?(:file_identifier_hash) && file_identifier_hash.present?
diff_files.find { |df| df.file_identifier_hash == file_identifier_hash }
else
diff_files.first
end
end
def multiline?
return unless on_text? && line_range
line_range['start'] != line_range['end']
end
private
def find_diff_file(repository)
return unless diff_refs.complete?
return unless comparison = diff_refs.compare_in(repository.project)
find_diff_file_from(comparison)
end
def get_formatter_class(type)
type ||= "text"
case type
when 'image'
Gitlab::Diff::Formatters::ImageFormatter
else
Gitlab::Diff::Formatters::TextFormatter
end
end
end
end
end