gitlab-org--gitlab-foss/lib/gitlab/diff/highlight.rb
Sean McGivern cdf3ae04f8 Fix 500 error when diff context line has broken encoding
Rugged sometimes chops a context line in between bytes, resulting in the context
line having an invalid encoding: https://github.com/libgit2/rugged/issues/716

When that happens, we will try to detect the encoding for the diff, and
sometimes we'll get it wrong. If that difference in encoding results in a
difference in string lengths between the diff and the underlying blobs, we'd
fail to highlight any inline diffs, and return a 500 status to the user.

As we're using the underlying blobs, the encoding is 'correct' anyway, so if the
string range is invalid, we can just discard the inline diff highlighting. We
still report to Sentry to ensure that we can investigate further in future.
2018-02-22 12:26:23 +00:00

89 lines
2.6 KiB
Ruby

module Gitlab
module Diff
class Highlight
attr_reader :diff_file, :diff_lines, :raw_lines, :repository
delegate :old_path, :new_path, :old_sha, :new_sha, to: :diff_file, prefix: :diff
def initialize(diff_lines, repository: nil)
@repository = repository
if diff_lines.is_a?(Gitlab::Diff::File)
@diff_file = diff_lines
@diff_lines = @diff_file.diff_lines
else
@diff_lines = diff_lines
end
@raw_lines = @diff_lines.map(&:text)
end
def highlight
@diff_lines.map.with_index do |diff_line, i|
diff_line = diff_line.dup
# ignore highlighting for "match" lines
next diff_line if diff_line.meta?
rich_line = highlight_line(diff_line) || diff_line.text
if line_inline_diffs = inline_diffs[i]
begin
rich_line = InlineDiffMarker.new(diff_line.text, rich_line).mark(line_inline_diffs)
# This should only happen when the encoding of the diff doesn't
# match the blob, which is a bug. But we shouldn't fail to render
# completely in that case, even though we want to report the error.
rescue RangeError => e
if Gitlab::Sentry.enabled?
Gitlab::Sentry.context
Raven.capture_exception(e)
end
end
end
diff_line.text = rich_line
diff_line
end
end
private
def highlight_line(diff_line)
return unless diff_file && diff_file.diff_refs
rich_line =
if diff_line.unchanged? || diff_line.added?
new_lines[diff_line.new_pos - 1]&.html_safe
elsif diff_line.removed?
old_lines[diff_line.old_pos - 1]&.html_safe
end
# Only update text if line is found. This will prevent
# issues with submodules given the line only exists in diff content.
if rich_line
line_prefix = diff_line.text =~ /\A(.)/ ? $1 : ' '
"#{line_prefix}#{rich_line}".html_safe
end
end
def inline_diffs
@inline_diffs ||= InlineDiff.for_lines(@raw_lines)
end
def old_lines
@old_lines ||= highlighted_blob_lines(diff_file.old_blob)
end
def new_lines
@new_lines ||= highlighted_blob_lines(diff_file.new_blob)
end
def highlighted_blob_lines(blob)
return [] unless blob
blob.load_all_data!
Gitlab::Highlight.highlight(blob.path, blob.data, repository: repository).lines
end
end
end
end