diff options
Diffstat (limited to 'lib/gitlab/diff')
-rw-r--r-- | lib/gitlab/diff/file.rb | 33 | ||||
-rw-r--r-- | lib/gitlab/diff/suggestion.rb | 52 | ||||
-rw-r--r-- | lib/gitlab/diff/suggestion_diff.rb | 37 | ||||
-rw-r--r-- | lib/gitlab/diff/suggestions_parser.rb | 51 |
4 files changed, 161 insertions, 12 deletions
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index dbee47a19ee..c46087e65de 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -133,11 +133,15 @@ module Gitlab end def new_blob - new_blob_lazy&.itself + strong_memoize(:new_blob) do + new_blob_lazy&.itself + end end def old_blob - old_blob_lazy&.itself + strong_memoize(:old_blob) do + old_blob_lazy&.itself + end end def new_blob_lines_between(from_line, to_line) @@ -158,7 +162,10 @@ module Gitlab new_blob || old_blob end - attr_writer :highlighted_diff_lines + def highlighted_diff_lines=(value) + clear_memoization(:diff_lines_for_serializer) + @highlighted_diff_lines = value + end # Array of Gitlab::Diff::Line objects def diff_lines @@ -314,19 +321,21 @@ module Gitlab # This adds the bottom match line to the array if needed. It contains # the data to load more context lines. def diff_lines_for_serializer - lines = highlighted_diff_lines + strong_memoize(:diff_lines_for_serializer) do + lines = highlighted_diff_lines - return if lines.empty? - return if blob.nil? + next if lines.empty? + next if blob.nil? - last_line = lines.last + 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 + 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 + lines + end end def fully_expanded? diff --git a/lib/gitlab/diff/suggestion.rb b/lib/gitlab/diff/suggestion.rb new file mode 100644 index 00000000000..027c7a31bcf --- /dev/null +++ b/lib/gitlab/diff/suggestion.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Gitlab + module Diff + class Suggestion + include Suggestible + include Gitlab::Utils::StrongMemoize + + attr_reader :diff_file, :lines_above, :lines_below, + :target_line + + def initialize(text, line:, above:, below:, diff_file:) + @text = text + @target_line = line + @lines_above = above.to_i + @lines_below = below.to_i + @diff_file = diff_file + end + + def to_hash + { + from_content: from_content, + to_content: to_content, + lines_above: @lines_above, + lines_below: @lines_below + } + end + + def from_content + strong_memoize(:from_content) do + fetch_from_content + end + end + + def to_content + # The parsed suggestion doesn't have information about the correct + # ending characters (we may have a line break, or not), so we take + # this information from the last line being changed (last + # characters). + endline_chars = line_break_chars(from_content.lines.last) + "#{@text}#{endline_chars}" + end + + private + + def line_break_chars(line) + match = /\r\n|\r|\n/.match(line) + match[0] if match + end + end + end +end diff --git a/lib/gitlab/diff/suggestion_diff.rb b/lib/gitlab/diff/suggestion_diff.rb new file mode 100644 index 00000000000..ee153c226b7 --- /dev/null +++ b/lib/gitlab/diff/suggestion_diff.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Gitlab + module Diff + class SuggestionDiff + include Gitlab::Utils::StrongMemoize + + delegate :from_content, :to_content, :from_line, to: :@suggestible + + def initialize(suggestible) + @suggestible = suggestible + end + + def diff_lines + Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a + end + + private + + def raw_diff + "#{diff_header}\n#{from_content_as_diff}#{to_content_as_diff}" + end + + def diff_header + "@@ -#{from_line} +#{from_line}" + end + + def from_content_as_diff + from_content.lines.map { |line| line.prepend('-') }.join + end + + def to_content_as_diff + to_content.lines.map { |line| line.prepend('+') }.join + end + end + end +end diff --git a/lib/gitlab/diff/suggestions_parser.rb b/lib/gitlab/diff/suggestions_parser.rb new file mode 100644 index 00000000000..c8c03d5d001 --- /dev/null +++ b/lib/gitlab/diff/suggestions_parser.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Gitlab + module Diff + class SuggestionsParser + # Matches for instance "-1", "+1" or "-1+2". + SUGGESTION_CONTEXT = /^(\-(?<above>\d+))?(\+(?<below>\d+))?$/.freeze + + class << self + # Returns an array of Gitlab::Diff::Suggestion which represents each + # suggestion in the given text. + # + def parse(text, position:, project:) + return [] unless position.complete? + + html = Banzai.render(text, project: nil, no_original_data: true) + doc = Nokogiri::HTML(html) + suggestion_nodes = doc.search('pre.suggestion') + + return [] if suggestion_nodes.empty? + + diff_file = position.diff_file(project.repository) + + suggestion_nodes.map do |node| + lang_param = node['data-lang-params'] + + lines_above, lines_below = nil + + if lang_param && suggestion_params = fetch_suggestion_params(lang_param) + lines_above, lines_below = + suggestion_params[:above], + suggestion_params[:below] + end + + Gitlab::Diff::Suggestion.new(node.text, + line: position.new_line, + above: lines_above.to_i, + below: lines_below.to_i, + diff_file: diff_file) + end + end + + private + + def fetch_suggestion_params(lang_param) + lang_param.match(SUGGESTION_CONTEXT) + end + end + end + end +end |