172 lines
4.1 KiB
Ruby
172 lines
4.1 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,
|
|
:base_sha,
|
|
:start_sha,
|
|
:head_sha,
|
|
:old_line,
|
|
:new_line,
|
|
:width,
|
|
:height,
|
|
:x,
|
|
:y,
|
|
: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)
|
|
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 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
|
|
}
|
|
|
|
Gitlab::SafeRequestStore.fetch(key) { find_diff_file(repository) }
|
|
end
|
|
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 on_image?
|
|
position_type == 'image'
|
|
end
|
|
|
|
def on_text?
|
|
position_type == 'text'
|
|
end
|
|
|
|
private
|
|
|
|
def find_diff_file(repository)
|
|
return unless diff_refs.complete?
|
|
return unless comparison = diff_refs.compare_in(repository.project)
|
|
|
|
file = comparison.diffs(diff_options).diff_files.first
|
|
|
|
# We need to unfold diff lines according to the position in order
|
|
# to correctly calculate the line code and trace position changes.
|
|
file&.unfold_diff_lines(self)
|
|
|
|
file
|
|
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
|