Commit f4282412 authored by Douwe Maan's avatar Douwe Maan

Merge branch '20034-safe-diffs' into 'master'

Cache diff syntax highlighted output

## What does this MR do?

Cache highlighted diffs for merge requests when they are requested for existing merge requests but after each change on the merge request diff recalculate again the cache.

I've introduced the concept of SafeDiffs which are the diffs that we're going to show on the UI and will be highlighted so maybe we can extend cache capabilities to more diffs.

The more problematic part is what and how we cache the highlighted lines, for the moment what I did was store in Redis as Gitlab::Diff::Line serialized as an array of json objects, similar of what we do for commits and diffs on merge request diffs.

The next step can be add a new field in the merge_request_diff object if we find it worth it. But let's first confirm this is the way to go

## What are the relevant issue numbers?

Closes #20034

## Screenshots (if relevant)

## Does this MR meet the acceptance criteria?

- [x] [CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG) entry added
- ~~[ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)~~
- ~~[ ] API support added~~
- Tests
  - [x] Added for this feature/bug
  - [x] All builds are passing
- [x] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
- [x] Branch has no merge conflicts with `master` (if you do - rebase it please)
- [x] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)


See merge request !5401
parents 1ee11136 c008a1a9
......@@ -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 @@ module DiffForPath
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 @@ class Projects::CommitController < Projects::ApplicationController
end
def diff_for_path
render_diff_for_path(@diffs, @commit.diff_refs, @project)
render_diff_for_path(@commit.diffs(diff_options))
end
def builds
......
......@@ -21,7 +21,7 @@ class Projects::CompareController < Projects::ApplicationController
def diff_for_path
return render_404 unless @compare
render_diff_for_path(@diffs, @diff_refs, @project)
render_diff_for_path(@compare.diffs(diff_options))
end
def create
......@@ -40,18 +40,12 @@ class Projects::CompareController < Projects::ApplicationController
@compare = CompareService.new.execute(@project, @head_ref, @project, @start_ref)
if @compare
@commits = Commit.decorate(@compare.commits, @project)
@start_commit = @project.commit(@start_ref)
@commit = @project.commit(@head_ref)
@base_commit = @project.merge_base_commit(@start_ref, @head_ref)
@commits = @compare.commits
@start_commit = @compare.start_commit
@commit = @compare.commit
@base_commit = @compare.base_commit
@diffs = @compare.diffs(diff_options)
@diff_refs = Gitlab::Diff::DiffRefs.new(
base_sha: @base_commit.try(:sha),
start_sha: @start_commit.try(:sha),
head_sha: @commit.try(:sha)
)
@diff_notes_disabled = true
@grouped_diff_discussions = {}
......
......@@ -85,7 +85,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format|
format.html { define_discussion_vars }
format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } }
format.json do
@diffs = @merge_request.diffs(diff_options)
render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") }
end
end
end
......@@ -103,9 +107,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
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(@merge_request.diffs(diff_options))
end
def commits
......@@ -153,7 +156,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@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
@diffs = @merge_request.diffs(diff_options) if @merge_request.compare
@diff_notes_disabled = true
@pipeline = @merge_request.pipeline
......
......@@ -206,10 +206,10 @@ module CommitsHelper
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],
......
......@@ -30,11 +30,7 @@ module DiffHelper
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)
......
......@@ -104,7 +104,7 @@ class Commit
end
def diff_line_count
@diff_line_count ||= Commit::diff_line_count(self.diffs)
@diff_line_count ||= Commit::diff_line_count(raw_diffs)
@diff_line_count
end
......@@ -317,6 +317,14 @@ class Commit
nil
end
def raw_diffs(*args)
raw.diffs(*args)
end
def diffs(diff_options = nil)
Gitlab::Diff::FileCollection::Commit.new(self, diff_options: diff_options)
end
private
def find_author_by_any_email
......@@ -326,7 +334,7 @@ class Commit
def repo_changes
changes = { added: [], modified: [], removed: [] }
diffs.each do |diff|
raw_diffs.each do |diff|
if diff.deleted_file
changes[:removed] << diff.old_path
elsif diff.renamed_file || diff.new_file
......
class Compare
delegate :same, :head, :base, to: :@compare
attr_reader :project
def self.decorate(compare, project)
if compare.is_a?(Compare)
compare
else
self.new(compare, project)
end
end
def initialize(compare, project)
@compare = compare
@project = project
end
def commits
@commits ||= Commit.decorate(@compare.commits, project)
end
def start_commit
return @start_commit if defined?(@start_commit)
commit = @compare.base
@start_commit = commit ? ::Commit.new(commit, project) : nil
end
def head_commit
return @head_commit if defined?(@head_commit)
commit = @compare.head
@head_commit = commit ? ::Commit.new(commit, project) : nil
end
alias_method :commit, :head_commit
def base_commit
return @base_commit if defined?(@base_commit)
@base_commit = if start_commit && head_commit
project.merge_base_commit(start_commit.id, head_commit.id)
else
nil
end
end
def raw_diffs(*args)
@compare.diffs(*args)
end
def diffs(diff_options = nil)
Gitlab::Diff::FileCollection::Compare.new(self,
project: project,
diff_options: diff_options,
diff_refs: diff_refs)
end
def diff_refs
Gitlab::Diff::DiffRefs.new(
base_sha: base_commit.try(:sha),
start_sha: start_commit.try(:sha),
head_sha: commit.try(:sha)
)
end
end
......@@ -85,7 +85,7 @@ class LegacyDiffNote < Note
return nil unless noteable
return @diff if defined?(@diff)
@diff = noteable.diffs(Commit.max_diff_options).find do |d|
@diff = noteable.raw_diffs(Commit.max_diff_options).find do |d|
d.new_path && Digest::SHA1.hexdigest(d.new_path) == diff_file_hash
end
end
......@@ -116,7 +116,7 @@ class LegacyDiffNote < Note
# Find the diff on noteable that matches our own
def find_noteable_diff
diffs = noteable.diffs(Commit.max_diff_options)
diffs = noteable.raw_diffs(Commit.max_diff_options)
diffs.find { |d| d.new_path == self.diff.new_path }
end
end
......@@ -164,8 +164,16 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
end
def diffs(*args)
merge_request_diff ? merge_request_diff.diffs(*args) : compare.diffs(*args)
def raw_diffs(*args)
merge_request_diff ? merge_request_diff.diffs(*args) : compare.raw_diffs(*args)
end
def diffs(diff_options = nil)
if self.compare
self.compare.diffs(diff_options)
else
Gitlab::Diff::FileCollection::MergeRequest.new(self, diff_options: diff_options)
end
end
def diff_size
......@@ -313,6 +321,8 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff.reload_content
MergeRequests::MergeRequestDiffCacheService.new.execute(self)
new_diff_refs = self.diff_refs
update_diff_notes_positions(
......
......@@ -372,7 +372,7 @@ class Repository
# We don't want to flush the cache if the commit didn't actually make any
# changes to any of the possible avatar files.
if revision && commit = self.commit(revision)
return unless commit.diffs.
return unless commit.raw_diffs.
any? { |diff| AVATAR_FILES.include?(diff.new_path) }
end
......
......@@ -20,10 +20,12 @@ class CompareService
)
end
Gitlab::Git::Compare.new(
raw_compare = Gitlab::Git::Compare.new(
target_project.repository.raw_repository,
target_branch,
source_sha,
source_sha
)
Compare.new(raw_compare, target_project)
end
end
......@@ -34,7 +34,7 @@ module MergeRequests
# At this point we decide if merge request can be created
# If we have at least one commit to merge -> creation allowed
if commits.present?
merge_request.compare_commits = Commit.decorate(commits, merge_request.source_project)
merge_request.compare_commits = commits
merge_request.can_be_created = true
merge_request.compare = compare
else
......
module MergeRequests
class MergeRequestDiffCacheService
def execute(merge_request)
# Executing the iteration we cache all the highlighted diff information
merge_request.diffs.diff_files.to_a
end
end
end
......@@ -2,7 +2,7 @@
= nav_link(path: 'commit#show') do
= link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do
Changes
%span.badge= @diffs.count
%span.badge= @diffs.size
= nav_link(path: 'commit#builds') do
= link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id) do
Builds
......
......@@ -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", diffs: @diffs
= 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", diffs: @diffs
- else
.light-well
.center
......
- show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true)
- diff_files = diffs.diff_files
- 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? }
= link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: nil)), class: 'btn btn-default'
- if show_whitespace_toggle
- if current_controller?(:commit)
= commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs')
= commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs')
- elsif current_controller?(:merge_requests)
= diff_merge_request_whitespace_link(@project, @merge_request, class: 'hidden-xs')
= diff_merge_request_whitespace_link(diffs.project, @merge_request, class: 'hidden-xs')
- elsif current_controller?(:compare)
= diff_compare_whitespace_link(@project, params[:from], params[:to], class: 'hidden-xs')
= diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'hidden-xs')
.btn-group
= inline_diff_btn
= parallel_diff_btn
......@@ -23,12 +22,12 @@
- if diff_files.overflow?
= render 'projects/diffs/warning', diff_files: diff_files
.files{data: {can_create_note: (!@diff_notes_disabled && can?(current_user, :create_note, @project))}}
.files{data: {can_create_note: (!@diff_notes_disabled && can?(current_user, :create_note, diffs.project))}}
- diff_files.each_with_index do |diff_file, index|
- diff_commit = commit_for_diff(diff_file)
- blob = diff_file.blob(diff_commit)
- next unless blob
- blob.load_all_data!(project.repository) unless blob.only_display_raw?
- blob.load_all_data!(diffs.project.repository) unless blob.only_display_raw?
= render 'projects/diffs/file', i: index, project: project,
diff_file: diff_file, diff_commit: diff_commit, blob: blob, diff_refs: diff_refs
= render 'projects/diffs/file', i: index, project: diffs.project,
diff_file: diff_file, diff_commit: diff_commit, blob: blob
......@@ -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
......@@ -42,7 +42,7 @@
%h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
%p To preserve performance the line changes are not shown.
- else
= render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs, show_whitespace_toggle: false
= render "projects/diffs/diffs", diffs: @diffs, show_whitespace_toggle: false
- if @pipeline
#builds.builds.tab-pane
= render "projects/merge_requests/show/builds"
......
- if @merge_request_diff.collected?
= render "projects/diffs/diffs", diffs: @merge_request.diffs(diff_options),
project: @merge_request.project, diff_refs: @merge_request.diff_refs
= render "projects/diffs/diffs", diffs: @diffs
- elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
- else
......
......@@ -33,25 +33,14 @@ class EmailsOnPushWorker
reverse_compare = false
if action == :push
merge_base_sha = project.merge_base_commit(before_sha, after_sha).try(:sha)
compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha)
diff_refs = Gitlab::Diff::DiffRefs.new(
base_sha: merge_base_sha,
start_sha: before_sha,
head_sha: after_sha
)
compare = CompareService.new.execute(project, before_sha, project, after_sha)
diff_refs = compare.diff_refs
return false if compare.same
if compare.commits.empty?
compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha)
diff_refs = Gitlab::Diff::DiffRefs.new(
base_sha: merge_base_sha,
start_sha: after_sha,
head_sha: before_sha
)
compare = CompareService.new.execute(project, after_sha, project, before_sha)
diff_refs = compare.diff_refs
reverse_compare = true
......
......@@ -141,8 +141,10 @@ class IrkerWorker
end
def files_count(commit)
files = "#{commit.diffs.real_size} file"
files += 's' if commit.diffs.count > 1
diffs = commit.raw_diffs
files = "#{diffs.real_size} file"
files += 's' if diffs.size > 1
files
end
......
......@@ -54,7 +54,7 @@ module API
sha = params[:sha]
commit = user_project.commit(sha)
not_found! "Commit" unless commit
commit.diffs.to_a
commit.raw_diffs.to_a
end
# Get a commit's comments
......@@ -96,7 +96,7 @@ module API
}
if params[:path] && params[:line] && params[:line_type]
commit.diffs(all_diffs: true).each do |diff|
commit.raw_diffs(all_diffs: true).each do |diff|
next unless diff.new_path == params[:path]
lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
......
......@@ -224,7 +224,7 @@ module API
class MergeRequestChanges < MergeRequest
expose :diffs, as: :changes, using: Entities::RepoDiff do |compare, _|
compare.diffs(all_diffs: true).to_a
compare.raw_diffs(all_diffs: true).to_a
end
end
......
......@@ -63,15 +63,18 @@ module Gitlab
diff_refs.try(:head_sha)
end
attr_writer :highlighted_diff_lines
# Array of Gitlab::Diff::Line objects
def diff_lines
@lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a
@diff_lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a
end
def highlighted_diff_lines
@highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight
end
# Array[<Hash>] with right/left keys that contains Gitlab::Diff::Line objects which text is hightlighted
def parallel_diff_lines
@parallel_diff_lines ||= Gitlab::Diff::ParallelDiff.new(self).parallelize
end
......
module Gitlab
module Diff
module FileCollection
class Base
attr_reader :project, :diff_options, :diff_view, :diff_refs
delegate :count, :size, :real_size, to: :diff_files
def self.default_options
::Commit.max_diff_options.merge(ignore_whitespace_change: false, no_collapse: false)
end
def initialize(diffable, project:, diff_options: nil, diff_refs: nil)
diff_options = self.class.default_options.merge(diff_options || {})
@diffable = diffable
@diffs = diffable.raw_diffs(diff_options)
@project = project
@diff_options = diff_options
@diff_refs = diff_refs
end
def diff_files
@diff_files ||= @diffs.decorate! { |diff| decorate_diff!(diff) }
end
private
def decorate_diff!(diff)
Gitlab::Diff::File.new(diff, repository: project.repository, diff_refs: diff_refs)
end
end
end
end
end
module Gitlab
module Diff
module FileCollection
class Commit < Base
def initialize(commit, diff_options:)
super(commit,
project: commit.project,
diff_options: diff_options,
diff_refs: commit.diff_refs)
end
end
end
end
end
module Gitlab
module Diff
module FileCollection
class Compare < Base
def initialize(compare, project:, diff_options:, diff_refs: nil)
super(compare,
project: project,
diff_options: diff_options,
diff_refs: diff_refs)
end
end
end
end
end
module Gitlab
module Diff
module FileCollection
class MergeRequest < Base
def initialize(merge_request, diff_options:)
@merge_request = merge_request
super(merge_request,
project: merge_request.project,
diff_options: diff_options,
diff_refs: merge_request.diff_refs)
end
def diff_files
super.tap { |_| store_highlight_cache }
end
private
# Extracted method to highlight in the same iteration to the diff_collection.
def decorate_diff!(diff)
diff_file = super
cache_highlight!(diff_file) if cacheable?
diff_file
end
def highlight_diff_file_from_cache!(diff_file, cache_diff_lines)
diff_file.highlighted_diff_lines = cache_diff_lines.map do |line|
Gitlab::Diff::Line.init_from_hash(line)
end
end
#
# 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 a Hash where the key correspond to the file_path and the values are Arrays of
# hashes that represent serialized diff lines.
#
def cache_highlight!(diff_file)
file_path = diff_file.file_path
if highlight_cache[file_path]
highlight_diff_file_from_cache!(diff_file, highlight_cache[file_path])
else
highlight_cache[file_path] = diff_file.highlighted_diff_lines.map(&:to_hash)
end
end
def highlight_cache
return @highlight_cache if defined?(@highlight_cache)
@highlight_cache = Rails.cache.read(cache_key) || {}
@highlight_cache_was_empty = @highlight_cache.empty?
@highlight_cache
end
def store_highlight_cache
Rails.cache.write(cache_key, highlight_cache) if @highlight_cache_was_empty
end
def cacheable?
@merge_request.merge_request_diff.present?
end
def cache_key
[@merge_request.merge_request_diff, 'highlighted-diff-files', diff_options]
end
end
end
end
end
......@@ -40,8 +40,6 @@ module Gitlab
def highlight_line(diff_line)
return unless diff_file && diff_file.diff_refs
line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' '
rich_line =
if diff_line.unchanged? || diff_line.added?
new_lines[diff_line.new_pos - 1]
......@@ -51,7 +49,10 @@ module Gitlab
# Only update text if line is found. This will prevent
# issues with submodules given the line only exists in diff content.
"#{line_prefix}#{rich_line}".html_safe if rich_line
if rich_line
line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' '
"#{line_prefix}#{rich_line}".html_safe
end
end
def inline_diffs
......
......@@ -9,6 +9,20 @@ module Gitlab
@old_pos, @new_pos = old_pos, new_pos
end
def self.init_from_hash(hash)
new(hash[:text], hash[:type], hash[:index], hash[:old_pos], hash[:new_pos])
end
def serialize_keys
@serialize_keys ||= %i(text type index old_pos new_pos)
end
def to_hash
hash = {}
serialize_keys.each { |key| hash[key] = send(key) }
hash
end
def old_line
old_pos unless added? || meta?
end
......
......@@ -35,21 +35,22 @@ module Gitlab
def commits
return unless compare
@commits ||= Commit.decorate(compare.commits, project)
@commits ||= compare.commits
end
def diffs
return unless compare
@diffs ||= safe_diff_files(compare.diffs(max_files: 30), diff_refs: diff_refs, repository: project.repository)
# This diff is more moderated in number of files and lines
@diffs ||= compare.diffs(max_files: 30, max_lines: 5000, no_collapse: true).diff_files
end
def diffs_count
diffs.count if diffs
diffs.size if diffs
end
def compare
@opts[:compare]
@opts[:compare] if @opts[:compare]
end
def diff_refs
......@@ -97,16 +98,18 @@ module Gitlab
if commits.length > 1
namespace_project_compare_url(project_namespace,
project,
from: Commit.new(compare.base, project),
to: Commit.new(compare.head, project))
from: compare.start_commit,
to: compare.head_commit)
else
namespace_project_commit_url(project_namespace,
project, commits.first)
project,
commits.first)
end
else
unless @action == :delete
namespace_project_tree_url(project_namespace,
project, ref_name)
project,
ref_name)
end
end
end
......
......@@ -83,7 +83,7 @@ describe Projects::CommitController do
let(:format) { :diff }
it "should really only be a git diff" do
go(id: commit.id, format: format)
go(id: '66eceea0db202bb39c4e445e8ca28689645366c5', format: format)
expect(response.body).to start_with("diff --git")
end
......@@ -92,8 +92,9 @@ describe Projects::CommitController do
go(id: '66eceea0db202bb39c4e445e8ca28689645366c5', format: format, w: 1)
expect(response.body).to start_with("diff --git")
# without whitespace option, there are more than 2 diff_splits
diff_splits = assigns(:diffs).first.diff.split("\n")
# without whitespace option, there are more than 2 diff_splits for other formats
diff_splits = assigns(:diffs).diff_files.first.diff.diff.split("\n")
expect(diff_splits.length).to be <= 2
end
end
......@@ -266,9 +267,9 @@ describe Projects::CommitController do
end
it 'only renders the diffs for the path given' do
expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
meth.call(diffs, diff_refs, project)
expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
meth.call(diffs)
end
diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path)
......
......@@ -19,7 +19,7 @@ describe Projects::CompareController do
to: ref_to)
expect(response).to be_success
expect(assigns(:diffs).first).not_to be_nil
expect(assigns(:diffs).diff_files.first).not_to be_nil
expect(assigns(:commits).length).to be >= 1
end
......@@ -32,10 +32,11 @@ describe Projects::CompareController do
w: 1)
expect(response).to be_success
expect(assigns(:diffs).first).not_to be_nil
diff_file = assigns(:diffs).diff_files.first
expect(diff_file).not_to be_nil
expect(assigns(:commits).length).to be >= 1
# without whitespace option, there are more than 2 diff_splits
diff_splits = assigns(:diffs).first.diff.split("\n")
diff_splits = diff_file.diff.diff.split("\n")
expect(diff_splits.length).to be <= 2
end
......@@ -48,7 +49,7 @@ describe Projects::CompareController do
to: ref_to)
expect(response).to be_success
expect(assigns(:diffs).to_a).to eq([])
expect(assigns(:diffs).diff_files.to_a).to eq([])
expect(assigns(:commits)).to eq([])
end
......@@ -87,9 +88,9 @@ describe Projects::CompareController do
end
it 'only renders the diffs for the path given' do
expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
meth.call(diffs, diff_refs, project)
expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
meth.call(diffs)
end
diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path)
......
......@@ -392,9 +392,9 @@ describe Projects::MergeRequestsController do
end
it 'only renders the diffs for the path given' do
expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
meth.call(diffs, diff_refs, project)
expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
meth.call(diffs)
end
diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
......@@ -455,9 +455,9 @@ describe Projects::MergeRequestsController do
end
it 'only renders the diffs for the path given' do
expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
meth.call(diffs, diff_refs, project)
expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
meth.call(diffs)
end
diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
......@@ -477,9 +477,9 @@ describe Projects::MergeRequestsController do
end
it 'only renders the diffs for the path given' do
expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
meth.call(diffs, diff_refs, project)
expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
meth.call(diffs)
end
diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
......
......@@ -6,7 +6,7 @@ describe DiffHelper do
let(:project) { create(:project) }
let(:repository) { project.repository }
let(:commit) { project.commit(sample_commit.id) }
let(:diffs) { commit.diffs }
let(:diffs) { commit.raw_diffs }
let(:diff) { diffs.first }
let(:diff_refs) { [commit.parent, commit] }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) }
......@@ -32,16 +32,6 @@ describe DiffHelper do
end
describe 'diff_options' do
it 'should return hard limit for a diff if force diff is true' do
allow(controller).to receive(:params) { { force_show_diff: true } }
expect(diff_options).to include(Commit.max_diff_options)
end
it 'should return hard limit for a diff if expand_all_diffs is true' do
allow(controller).to receive(:params) { { expand_all_diffs: true } }
expect(diff_options).to include(Commit.max_diff_options)
end
it 'should return no collapse false' do
expect(diff_options).to include(no_collapse: false)
end
......@@ -55,6 +45,18 @@ describe DiffHelper do
allow(controller).to receive(:action_name) { 'diff_for_path' }
expect(diff_options).to include(no_collapse: true)
end
it 'should return paths if action name diff_for_path and param old path' do
allow(controller).to receive(:params) { { old_path: 'lib/wadus.rb' } }
allow(controller).to receive(:action_name) { 'diff_for_path' }
expect(diff_options[:paths]).to include('lib/wadus.rb')
end
it 'should return paths if action name diff_for_path and param new path' do
allow(controller).to receive(:params) { { new_path: 'lib/wadus.rb' } }
allow(controller).to receive(:action_name) { 'diff_for_path' }
expect(diff_options[:paths]).to include('lib/wadus.rb')
end
end
describe 'unfold_bottom_class' do
......
......@@ -5,7 +5,7 @@ describe Gitlab::Diff::File, lib: true do
let(:project) { create(:project) }
let(:commit) { project.commit(sample_commit.id) }
let(:diff) { commit.diffs.first }
let(:diff) { commit.raw_diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
describe '#diff_lines' do
......
......@@ -5,7 +5,7 @@ describe Gitlab::Diff::Highlight, lib: true do
let(:project) { create(:project) }
let(:commit) { project.commit(sample_commit.id) }
let(:diff) { commit.diffs.first }
let(:diff) { commit.raw_diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
describe '#highlight' do
......
......@@ -6,7 +6,7 @@ describe Gitlab::Diff::LineMapper, lib: true do
let(:project) { create(:project) }
let(:repository) { project.repository }
let(:commit) { project.commit(sample_commit.id) }
let(:diffs) { commit.diffs }
let(:diffs) { commit.raw_diffs }
let(:diff) { diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) }
subject { described_class.new(diff_file) }
......
......@@ -6,7 +6,7 @@ describe Gitlab::Diff::ParallelDiff, lib: true do
let(:project) { create(:project) }
let(:repository) { project.repository }
let(:commit) { project.commit(sample_commit.id) }
let(:diffs) { commit.diffs }
let(:diffs) { commit.raw_diffs }
let(:diff) { diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) }
subject { described_class.new(diff_file) }
......
......@@ -5,7 +5,7 @@ describe Gitlab::Diff::Parser, lib: true do
let(:project) { create(:project) }
let(:commit) { project.commit(sample_commit.id) }
let(:diff) { commit.diffs.first }
let(:diff) { commit.raw_diffs.first }
let(:parser) { Gitlab::Diff::Parser.new }
describe '#parse' do
......
......@@ -16,9 +16,12 @@ describe Gitlab::Email::Message::RepositoryPush do
{ author_id: author.id, ref: 'master', action: :push, compare: compare,
send_from_committer_email: true }
end
let(:compare) do
let(:raw_compare) do
Gitlab::Git::Compare.new(project.repository.raw_repository,
sample_image_commit.id, sample_commit.id)
sample_image_commit.id, sample_commit.id)
end
let(:compare) do
Compare.decorate(raw_compare, project)
end
describe '#project' do
......@@ -62,17 +65,17 @@ describe Gitlab::Email::Message::RepositoryPush do
describe '#diffs_count' do
subject { message.diffs_count }
it { is_expected.to eq compare.diffs.count }
it { is_expected.to eq raw_compare.diffs.size }
end
describe '#compare' do
subject { message.compare }
it { is_expected.to be_an_instance_of Gitlab::Git::Compare }
it { is_expected.to be_an_instance_of Compare }
end
describe '#compare_timeout' do
subject { message.compare_timeout }
it { is_expected.to eq compare.diffs.overflow? }
it { is_expected.to eq raw_compare.diffs.overflow? }
end
describe '#reverse_compare?' do
......
......@@ -944,8 +944,9 @@ describe Notify do
describe 'email on push with multiple commits' do
let(:example_site_path) { root_path }
let(:user) { create(:user) }
let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) }
let(:commits) { Commit.decorate(compare.commits, nil) }
let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) }
let(:compare) { Compare.decorate(raw_compare, project) }
let(:commits) { compare.commits }
let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) }
let(:send_from_committer_email) { false }
let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) }
......@@ -1046,8 +1047,9 @@ describe Notify do
describe 'email on push with a single commit' do
let(:example_site_path) { root_path }
let(:user) { create(:user) }
let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) }
let(:commits) { Commit.decorate(compare.commits, nil) }
let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) }
let(:compare) { Compare.decorate(raw_compare, project) }
let(:commits) { compare.commits }
let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) }
let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) }
......
require 'spec_helper'
describe Compare, models: true do
include RepoHelpers
let(:project) { create(:project, :public) }
let(:commit) { project.commit }
let(:start_commit) { sample_image_commit }
let(:head_commit) { sample_commit }
let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, start_commit.id, head_commit.id) }
subject { described_class.new(raw_compare, project) }
describe '#start_commit' do
it 'returns raw compare base commit' do
expect(subject.start_commit.id).to eq(start_commit.id)
end
it 'returns nil if compare base commit is nil' do
expect(raw_compare).to receive(:base).and_return(nil)
expect(subject.start_commit).to eq(nil)
end
end
describe '#commit' do
it 'returns raw compare head commit' do
expect(subject.commit.id).to eq(head_commit.id)
end
it 'returns nil if compare head commit is nil' do
expect(raw_compare).to receive(:head).and_return(nil)
expect(subject.commit).to eq(nil)
end
end
describe '#base_commit' do
let(:base_commit) { Commit.new(another_sample_commit, project) }
it 'returns project merge base commit' do
expect(project).to receive(:merge_base_commit).with(start_commit.id, head_commit.id).and_return(base_commit)
expect(subject.base_commit).to eq(base_commit)
end
it 'returns nil if there is no start_commit' do
expect(subject).to receive(:start_commit).and_return(nil)
expect(subject.base_commit).to eq(nil)
end
it 'returns nil if there is no head commit' do
expect(subject).to receive(:head_commit).and_return(nil)
expect(subject.base_commit).to eq(nil)
end
end
describe '#diff_refs' do
it 'uses base_commit sha as base_sha' do
expect(subject).to receive(:base_commit).at_least(:once).and_call_original
expect(subject.diff_refs.base_sha).to eq(subject.base_commit.id)
end
it 'uses start_commit sha as start_sha' do
expect(subject.diff_refs.start_sha).to eq(start_commit.id)
end
it 'uses commit sha as head sha' do
expect(subject.diff_refs.head_sha).to eq(head_commit.id)
end
end
end
......@@ -58,7 +58,7 @@ describe LegacyDiffNote, models: true do
# Generate a real line_code value so we know it will match. We use a
# random line from a random diff just for funsies.
diff = merge.diffs.to_a.sample
diff = merge.raw_diffs.to_a.sample
line = Gitlab::Diff::Parser.new.parse(diff.diff.each_line).to_a.sample
code = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
......
......@@ -128,7 +128,7 @@ describe MergeRequest, models: true do
end
end
describe '#diffs' do
describe '#raw_diffs' do
let(:merge_request) { build(:merge_request) }
let(:options) { { paths: ['a/b', 'b/a', 'c/*'] } }
......@@ -138,6 +138,31 @@ describe MergeRequest, models: true do
expect(merge_request.merge_request_diff).to receive(:diffs).with(options)
merge_request.raw_diffs(options)
end
end
context 'when there are no MR diffs' do
it 'delegates to the compare object' do
merge_request.compare = double(:compare)
expect(merge_request.compare).to receive(:raw_diffs).with(options)
merge_request.raw_diffs(options)
end
end
end
describe '#diffs' do
let(:merge_request) { build(:merge_request) }
let(:options) { { paths: ['a/b', 'b/a', 'c/*'] } }
context 'when there are MR diffs' do
it 'delegates to the MR diffs' do
merge_request.merge_request_diff = MergeRequestDiff.new
expect(merge_request.merge_request_diff).to receive(:diffs).with(hash_including(options))
merge_request.diffs(options)
end
end
......@@ -660,6 +685,12 @@ describe MergeRequest, models: true do
subject.reload_diff
end
it "executs diff cache service" do
expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject)
subject.reload_diff
end
it "updates diff note positions" do
old_diff_refs = subject.diff_refs
......
......@@ -1154,7 +1154,7 @@ describe Repository, models: true do
it 'does not flush the cache if the commit does not change any logos' do
diff = double(:diff, new_path: 'test.txt')
expect(commit).to receive(:diffs).and_return([diff])
expect(commit).to receive(:raw_diffs).and_return([diff])
expect(cache).not_to receive(:expire)
repository.expire_avatar_cache(repository.root_ref, '123')
......@@ -1163,7 +1163,7 @@ describe Repository, models: true do
it 'flushes the cache if the commit changes any of the logos' do
diff = double(:diff, new_path: Repository::AVATAR_FILES[0])
expect(commit).to receive(:diffs).and_return([diff])
expect(commit).to receive(:raw_diffs).and_return([diff])
expect(cache).to receive(:expire).with(:avatar)
repository.expire_avatar_cache(repository.root_ref, '123')
......
......@@ -173,10 +173,10 @@ describe API::API, api: true do
end
it 'should return the inline comment' do
post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.diffs.first.new_path, line: 7, line_type: 'new'
post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 7, line_type: 'new'
expect(response).to have_http_status(201)
expect(json_response['note']).to eq('My comment')
expect(json_response['path']).to eq(project.repository.commit.diffs.first.new_path)
expect(json_response['path']).to eq(project.repository.commit.raw_diffs.first.new_path)
expect(json_response['line']).to eq(7)
expect(json_response['line_type']).to eq('new')
end
......
......@@ -61,7 +61,7 @@ describe MergeRequests::BuildService, services: true do
end
context 'one commit in the diff' do
let(:commits) { [commit_1] }
let(:commits) { Commit.decorate([commit_1], project) }
it 'allows the merge request to be created' do
expect(merge_request.can_be_created).to eq(true)
......@@ -84,7 +84,7 @@ describe MergeRequests::BuildService, services: true do
end
context 'commit has no description' do
let(:commits) { [commit_2] }
let(:commits) { Commit.decorate([commit_2], project) }
it 'uses the title of the commit as the title of the merge request' do
expect(merge_request.title).to eq(commit_2.safe_message)
......@@ -111,7 +111,7 @@ describe MergeRequests::BuildService, services: true do
end
context 'commit has no description' do
let(:commits) { [commit_2] }
let(:commits) { Commit.decorate([commit_2], project) }
it 'sets the description to "Closes #$issue-iid"' do
expect(merge_request.description).to eq("Closes ##{issue.iid}")
......@@ -121,7 +121,7 @@ describe MergeRequests::BuildService, services: true do
end
context 'more than one commit in the diff' do
let(:commits) { [commit_1, commit_2] }
let(:commits) { Commit.decorate([commit_1, commit_2], project) }
it 'allows the merge request to be created' do
expect(merge_request.can_be_created).to eq(true)
......
require 'spec_helper'
describe MergeRequests::MergeRequestDiffCacheService do
let(:subject) { MergeRequests::MergeRequestDiffCacheService.new }
describe '#execute' do
it 'retrieves the diff files to cache the highlighted result' do
merge_request = create(:merge_request)
cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequest.default_options]
expect(Rails.cache).to receive(:read).with(cache_key).and_return({})
expect(Rails.cache).to receive(:write).with(cache_key, anything)
subject.execute(merge_request)
end
end
end
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