Commit cd7c2cb6 authored by Paco Guzman's avatar Paco Guzman
Browse files

Cache highlighted diff lines for merge requests

Introducing the concept of SafeDiffs which relates 
diffs with UI highlighting.
parent 195b20e1
......@@ -10,6 +10,7 @@ v 8.11.0 (unreleased)
- The Repository class is now instrumented
- Cache the commit author in RequestStore to avoid extra lookups in PostReceive
- Expand commit message width in repo view (ClemMakesApps)
- Cache highlighted diff lines for merge requests
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
- Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
- Optimize maximum user access level lookup in loading of notes
......
module DiffForPath
extend ActiveSupport::Concern
def render_diff_for_path(diffs, diff_refs, project)
diff_file = safe_diff_files(diffs, diff_refs: diff_refs, repository: project.repository).find do |diff|
def render_diff_for_path(diffs)
diff_file = diffs.diff_files.find do |diff|
diff.old_path == params[:old_path] && diff.new_path == params[:new_path]
end
......@@ -14,7 +14,7 @@ def render_diff_for_path(diffs, diff_refs, project)
locals = {
diff_file: diff_file,
diff_commit: diff_commit,
diff_refs: diff_refs,
diff_refs: diffs.diff_refs,
blob: blob,
project: project
}
......
......@@ -28,7 +28,7 @@ def show
end
def diff_for_path
render_diff_for_path(@diffs, @commit.diff_refs, @project)
render_diff_for_path(SafeDiffs::Commit.new(@commit, diff_options: diff_options))
end
def builds
......@@ -110,7 +110,7 @@ def define_commit_vars
opts = diff_options
opts[:ignore_whitespace_change] = true if params[:format] == 'diff'
@diffs = commit.diffs(opts)
@diffs = SafeDiffs::Commit.new(commit, diff_options: opts)
@notes_count = commit.notes.count
end
......
......@@ -21,7 +21,7 @@ def show
def diff_for_path
return render_404 unless @compare
render_diff_for_path(@diffs, @diff_refs, @project)
render_diff_for_path(SafeDiffs::Compare.new(@compare, project: @project, diff_options: diff_options))
end
def create
......@@ -46,12 +46,12 @@ def define_diff_vars
@commit = @project.commit(@head_ref)
@base_commit = @project.merge_base_commit(@start_ref, @head_ref)
@diffs = @compare.diffs(diff_options)
@diff_refs = Gitlab::Diff::DiffRefs.new(
diff_refs = Gitlab::Diff::DiffRefs.new(
base_sha: @base_commit.try(:sha),
start_sha: @start_commit.try(:sha),
head_sha: @commit.try(:sha)
)
@diffs = SafeDiffs::Compare.new(@compare, project: @project, diff_options: diff_options, diff_refs: diff_refs)
@diff_notes_disabled = true
@grouped_diff_discussions = {}
......
......@@ -103,9 +103,8 @@ def diff_for_path
end
define_commit_vars
diffs = @merge_request.diffs(diff_options)
render_diff_for_path(diffs, @merge_request.diff_refs, @merge_request.project)
render_diff_for_path(SafeDiffs::MergeRequest.new(merge_request, diff_options: diff_options))
end
def commits
......@@ -153,7 +152,12 @@ def new
@commits = @merge_request.compare_commits.reverse
@commit = @merge_request.diff_head_commit
@base_commit = @merge_request.diff_base_commit
@diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
if @merge_request.compare
@diffs = SafeDiffs::Compare.new(@merge_request.compare,
project: @merge_request.project,
diff_refs: @merge_request.diff_refs,
diff_options: diff_options)
end
@diff_notes_disabled = true
@pipeline = @merge_request.pipeline
......
......@@ -206,10 +206,10 @@ def commit_person_link(commit, options = {})
end
end
def view_file_btn(commit_sha, diff, project)
def view_file_btn(commit_sha, diff_new_path, project)
link_to(
namespace_project_blob_path(project.namespace, project,
tree_join(commit_sha, diff.new_path)),
tree_join(commit_sha, diff_new_path)),
class: 'btn view-file js-view-file btn-file-option'
) do
raw('View file @') + content_tag(:span, commit_sha[0..6],
......
......@@ -23,18 +23,17 @@ def diff_view
end
def diff_options
options = { ignore_whitespace_change: hide_whitespace?, no_collapse: expand_all_diffs? }
options = SafeDiffs.default_options.merge(
ignore_whitespace_change: hide_whitespace?,
no_collapse: expand_all_diffs?
)
if action_name == 'diff_for_path'
options[:no_collapse] = true
options[:paths] = params.values_at(:old_path, :new_path)
end
Commit.max_diff_options.merge(options)
end
def safe_diff_files(diffs, diff_refs: nil, repository: nil)
diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) }
options
end
def unfold_bottom_class(bottom)
......
......@@ -313,6 +313,8 @@ def reload_diff
merge_request_diff.reload_content
MergeRequests::MergeRequestDiffCacheService.new.execute(self)
new_diff_refs = self.diff_refs
update_diff_notes_positions(
......
module SafeDiffs
def self.default_options
::Commit.max_diff_options.merge(ignore_whitespace_change: false, no_collapse: false)
end
end
module SafeDiffs
class Base
attr_reader :project, :diff_options, :diff_view, :diff_refs
delegate :count, :real_size, to: :diff_files
def initialize(diffs, project:, diff_options:, diff_refs: nil)
@diffs = diffs
@project = project
@diff_options = diff_options
@diff_refs = diff_refs
end
def diff_files
@diff_files ||= begin
diffs = @diffs.decorate! do |diff|
Gitlab::Diff::File.new(diff, diff_refs: @diff_refs, repository: @project.repository)
end
highlight!(diffs)
diffs
end
end
private
def highlight!(diff_files)
if cacheable?
cache_highlight!(diff_files)
else
diff_files.each { |diff_file| highlight_diff_file!(diff_file) }
end
end
def cacheable?
false
end
def cache_highlight!
raise NotImplementedError
end
def highlight_diff_file_from_cache!(diff_file, cache_diff_lines)
diff_file.diff_lines = cache_diff_lines.map do |line|
Gitlab::Diff::Line.init_from_hash(line)
end
end
def highlight_diff_file!(diff_file)
diff_file.diff_lines = Gitlab::Diff::Highlight.new(diff_file, repository: diff_file.repository).highlight
diff_file.highlighted_diff_lines = diff_file.diff_lines # To be used on parallel diff
diff_file
end
end
end
module SafeDiffs
class Commit < Base
def initialize(commit, diff_options:)
super(commit.diffs(diff_options),
project: commit.project,
diff_options: diff_options,
diff_refs: commit.diff_refs)
end
end
end
module SafeDiffs
class Compare < Base
def initialize(compare, project:, diff_options:, diff_refs: nil)
super(compare.diffs(diff_options),
project: project,
diff_options: diff_options,
diff_refs: diff_refs)
end
end
end
module SafeDiffs
class MergeRequest < Base
def initialize(merge_request, diff_options:)
@merge_request = merge_request
super(merge_request.diffs(diff_options),
project: merge_request.project,
diff_options: diff_options,
diff_refs: merge_request.diff_refs)
end
private
#
# If we find the highlighted diff files lines on the cache we replace existing diff_files lines (no highlighted)
# for the highlighted ones, so we just skip their execution.
# If the highlighted diff files lines are not cached we calculate and cache them.
#
# The content of the cache is and Hash where the key correspond to the file_path and the values are Arrays of
# hashes than represent serialized diff lines.
#
def cache_highlight!(diff_files)
highlighted_cache = Rails.cache.read(cache_key) || {}
highlighted_cache_was_empty = highlighted_cache.empty?
diff_files.each do |diff_file|
file_path = diff_file.file_path
if highlighted_cache[file_path]
highlight_diff_file_from_cache!(diff_file, highlighted_cache[file_path])
else
highlight_diff_file!(diff_file)
highlighted_cache[file_path] = diff_file.diff_lines.map(&:to_hash)
end
end
if highlighted_cache_was_empty
Rails.cache.write(cache_key, highlighted_cache)
end
diff_files
end
def cacheable?
@merge_request.merge_request_diff.present?
end
def cache_key
[@merge_request.merge_request_diff, 'highlighted-safe-diff-files', diff_options]
end
end
end
module MergeRequests
class MergeRequestDiffCacheService
def execute(merge_request)
# Executing the iteration we cache all the highlighted diff information
SafeDiffs::MergeRequest.new(merge_request, diff_options: SafeDiffs.default_options).diff_files.to_a
end
end
end
......@@ -75,7 +75,7 @@
- blob = diff_file.blob
- if blob && blob.respond_to?(:text?) && blob_text_viewable?(blob)
%table.code.white
- diff_file.highlighted_diff_lines.each do |line|
- diff_file.diff_lines.each do |line|
= render "projects/diffs/line", line: line, diff_file: diff_file, plain: true
- else
No preview for this file type
......
......@@ -7,7 +7,7 @@
= render "ci_menu"
- else
%div.block-connector
= render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @commit.diff_refs
= render "projects/diffs/diffs", diff_files: @diffs.diff_files, project: @diffs.project, diff_refs: @diffs.diff_refs
= render "projects/notes/notes_with_form"
- if can_collaborate_with_project?
- %w(revert cherry-pick).each do |type|
......
......@@ -8,7 +8,7 @@
- if @commits.present?
= render "projects/commits/commit_list"
= render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @diff_refs
= render "projects/diffs/diffs", diff_files: @diffs.diff_files, project: @diffs.project, diff_refs: @diffs.diff_refs
- else
.light-well
.center
......
......@@ -2,8 +2,6 @@
- if diff_view == 'parallel'
- fluid_layout true
- diff_files = safe_diff_files(diffs, diff_refs: diff_refs, repository: project.repository)
.content-block.oneline-block.files-changed
.inline-parallel-buttons
- if !expand_all_diffs? && diff_files.any? { |diff_file| diff_file.collapsed? }
......
......@@ -15,6 +15,6 @@
from_merge_request_id: @merge_request.id,
skip_visible_check: true)
= view_file_btn(diff_commit.id, diff_file, project)
= view_file_btn(diff_commit.id, diff_file.new_path, project)
= render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, diff_refs: diff_refs, blob: blob, project: project
= render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, blob: blob, project: project
......@@ -5,7 +5,7 @@
%table.text-file.code.js-syntax-highlight{ data: diff_view_data, class: too_big ? 'hide' : '' }
- last_line = 0
- diff_file.highlighted_diff_lines.each do |line|
- diff_file.diff_lines.each do |line|
- last_line = line.new_pos
= render "projects/diffs/line", line: line, diff_file: diff_file
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment