提交 b6b26692 编写于 作者: S Sean McGivern

Collapse large diffs by default

When rendering a list of diff files, skip those where the diff is over
10 KB and provide an endpoint to render individually instead.
上级 2c650b6f
......@@ -32,6 +32,8 @@ v 8.10.0 (unreleased)
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- PipelinesFinder uses git cache data
- Throttle the update of `project.pushes_since_gc` to 1 minute.
- Allow expanding and collapsing files in diff view (!4990)
- Collapse large diffs by default (!4990)
- Check for conflicts with existing Project's wiki path when creating a new project.
- Show last push widget in upstream after push to fork
- Don't instantiate a git tree on Projects show default view
......
......@@ -19,7 +19,7 @@ class Projects::CommitController < Projects::ApplicationController
@grouped_diff_notes = commit.notes.grouped_diff_notes
@notes = commit.notes.non_diff_notes.fresh
Banzai::NoteRenderer.render(
@grouped_diff_notes.values.flatten + @notes,
@project,
......@@ -41,6 +41,24 @@ class Projects::CommitController < Projects::ApplicationController
end
end
def diff_for_path
return git_not_found! unless commit
opts = diff_options
opts[:ignore_whitespace_change] = true if params[:format] == 'diff'
diffs = commit.diffs(opts.merge(paths: [params[:path]]))
diff_refs = [commit.parent || commit, commit]
@comments_target = {
noteable_type: 'Commit',
commit_id: @commit.id
}
@grouped_diff_notes = {}
render_diff_for_path(diffs, diff_refs, @project)
end
def builds
end
......
......@@ -6,7 +6,7 @@ class Projects::CompareController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
before_action :assign_ref_vars, only: [:index, :show]
before_action :assign_ref_vars, only: [:index, :show, :diff_for_path]
before_action :merge_request, only: [:index, :show]
def index
......@@ -35,6 +35,22 @@ class Projects::CompareController < Projects::ApplicationController
end
end
def diff_for_path
compare = CompareService.new.
execute(@project, @head_ref, @project, @base_ref, diff_options)
return render_404 unless compare
@commit = @project.commit(@head_ref)
@base_commit = @project.merge_base_commit(@base_ref, @head_ref)
diffs = compare.diffs(diff_options.merge(paths: [params[:path]]))
@diff_notes_disabled = true
@grouped_diff_notes = {}
render_diff_for_path(diffs, [@base_commit, @commit], @project)
end
def create
redirect_to namespace_project_compare_path(@project.namespace, @project,
params[:from], params[:to])
......
......@@ -13,6 +13,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds]
before_action :define_show_vars, only: [:show, :diffs, :commits, :builds]
before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check]
before_action :define_commit_vars, only: [:diffs]
before_action :define_diff_comment_vars, only: [:diffs]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds]
# Allow read any merge_request
......@@ -58,7 +60,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
render json: @merge_request
end
......@@ -80,32 +82,32 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def diffs
apply_diff_view_cookie!
@commit = @merge_request.diff_head_commit
@base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit
@comments_target = {
noteable_type: 'MergeRequest',
noteable_id: @merge_request.id
}
@use_legacy_diff_notes = !@merge_request.support_new_diff_notes?
@grouped_diff_notes = @merge_request.notes.grouped_diff_notes
Banzai::NoteRenderer.render(
@grouped_diff_notes.values.flatten,
@project,
current_user,
@path,
@project_wiki,
@ref
)
respond_to do |format|
format.html
format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } }
end
end
# With an ID param, loads the MR at that ID. Otherwise, accepts the same params as #new
# and uses that (unsaved) MR.
#
def diff_for_path
if params[:id]
merge_request
define_diff_comment_vars
else
build_merge_request
@diff_notes_disabled = true
@grouped_diff_notes = {}
end
define_commit_vars
diffs = @merge_request.diffs(diff_options.merge(paths: [params[:path]]))
diff_refs = @merge_request.diff_refs
render_diff_for_path(diffs, diff_refs, @merge_request.project)
end
def commits
respond_to do |format|
format.html { render 'show' }
......@@ -121,8 +123,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def new
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
@merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
build_merge_request
@noteable = @merge_request
@target_branches = if @merge_request.target_project
......@@ -380,6 +381,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController
closes_issues
end
def define_commit_vars
@commit = @merge_request.diff_head_commit
@base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit
end
def define_diff_comment_vars
@comments_target = {
noteable_type: 'MergeRequest',
noteable_id: @merge_request.id
}
@use_legacy_diff_notes = !@merge_request.support_new_diff_notes?
@grouped_diff_notes = @merge_request.notes.grouped_diff_notes
Banzai::NoteRenderer.render(
@grouped_diff_notes.values.flatten,
@project,
current_user,
@path,
@project_wiki,
@ref
)
end
def invalid_mr
# Render special view for MR with removed source or target branch
render 'invalid'
......@@ -408,4 +433,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
params[:merge_when_build_succeeds].present? &&
@merge_request.pipeline && @merge_request.pipeline.active?
end
def build_merge_request
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
@merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
end
end
......@@ -8,6 +8,25 @@ module DiffHelper
[marked_old_line, marked_new_line]
end
def render_diff_for_path(diffs, diff_refs, project)
diff_file = safe_diff_files(diffs, diff_refs).first
return render_404 unless diff_file
diff_commit = commit_for_diff(diff_file)
blob = project.repository.blob_for_diff(diff_commit, diff_file)
locals = {
diff_file: diff_file,
diff_commit: diff_commit,
diff_refs: diff_refs,
blob: blob,
project: project
}
render json: { html: view_to_html_string('projects/diffs/_content', locals) }
end
def diff_view
diff_views = %w(inline parallel)
......
......@@ -19,7 +19,7 @@ class MergeRequest < ActiveRecord::Base
after_create :create_merge_request_diff, unless: :importing?
after_update :update_merge_request_diff
delegate :commits, :diffs, :real_size, to: :merge_request_diff, prefix: nil
delegate :commits, :real_size, to: :merge_request_diff, prefix: nil
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
......@@ -164,6 +164,10 @@ 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)
end
def diff_size
merge_request_diff.size
end
......
......@@ -144,6 +144,12 @@ class MergeRequestDiff < ActiveRecord::Base
def load_diffs(raw, options)
if raw.respond_to?(:each)
if options[:paths]
raw = raw.select do |diff|
options[:paths].include?(diff[:new_path])
end
end
Gitlab::Git::DiffCollection.new(raw, options)
else
Gitlab::Git::DiffCollection.new([])
......
.diff-content.diff-wrap-lines
- # Skip all non non-supported blobs
- return unless blob.respond_to?(:text?)
- if diff_file.too_large?
.nothing-here-block This diff could not be displayed because it is too large.
- elsif blob.only_display_raw?
.nothing-here-block This file is too large to display.
- elsif blob_text_viewable?(blob)
- if !project.repository.diffable?(blob)
.nothing-here-block This diff was suppressed by a .gitattributes entry.
- elsif diff_file.diff_lines.length > 0
- if diff_view == 'parallel'
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob
- else
= render "projects/diffs/text_file", diff_file: diff_file
- else
- if diff_file.mode_changed?
.nothing-here-block File mode changed
- elsif diff_file.renamed_file
.nothing-here-block File moved
- elsif blob.image?
- old_blob = diff_file.old_blob(diff_commit)
= render "projects/diffs/image", diff_file: diff_file, old_file: old_blob, file: blob
- else
.nothing-here-block No preview for this file type
......@@ -16,28 +16,9 @@
= view_file_btn(diff_commit.id, diff_file, project)
.diff-content.diff-wrap-lines
- # Skip all non non-supported blobs
- return unless blob.respond_to?(:text?)
- if diff_file.too_large?
.nothing-here-block This diff could not be displayed because it is too large.
- elsif blob.only_display_raw?
.nothing-here-block This file is too large to display.
- elsif blob_text_viewable?(blob)
- if !project.repository.diffable?(blob)
.nothing-here-block This diff was suppressed by a .gitattributes entry.
- elsif diff_file.diff_lines.length > 0
- if diff_view == 'parallel'
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else
= render "projects/diffs/text_file", diff_file: diff_file, index: i
- else
- if diff_file.mode_changed?
.nothing-here-block File mode changed
- elsif diff_file.renamed_file
.nothing-here-block File moved
- elsif blob.image?
- old_blob = diff_file.old_blob(diff_commit)
= render "projects/diffs/image", diff_file: diff_file, old_file: old_blob, file: blob, index: i
- else
.nothing-here-block No preview for this file type
- if diff_file.collapsed_by_default?
- url = url_for(params.merge(action: :diff_for_path, path: diff_file.file_path, format: nil))
.diff-content.diff-wrap-lines{data: { diff_for_path: url }}
.nothing-here-block File hidden by default; content for this element available at #{url}
- else
= render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, diff_refs: diff_refs, blob: blob, project: project
......@@ -615,10 +615,18 @@ Rails.application.routes.draw do
post :retry_builds
post :revert
post :cherry_pick
get '/diffs/*path', action: :diff_for_path, constraints: { format: false }
end
end
resources :compare, only: [:index, :create]
resources :compare, only: [:index, :create] do
collection do
get '/diffs/*path', action: :diff_for_path, constraints: { format: false }
end
end
get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ }
resources :network, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }
resources :graphs, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } do
......@@ -629,9 +637,6 @@ Rails.application.routes.draw do
end
end
get '/compare/:from...:to' => 'compare#show', :as => 'compare',
:constraints => { from: /.+/, to: /.+/ }
resources :snippets, constraints: { id: /\d+/ } do
member do
get 'raw'
......@@ -706,12 +711,14 @@ Rails.application.routes.draw do
post :toggle_subscription
post :toggle_award_emoji
post :remove_wip
get '/diffs/*path', action: :diff_for_path, constraints: { format: false }
end
collection do
get :branch_from
get :branch_to
get :update_branches
get '/diffs/*path', action: :diff_for_path, constraints: { format: false }
end
end
......
......@@ -68,6 +68,10 @@ module Gitlab
@lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a
end
def collapsed_by_default?
diff.diff.bytesize > 10240 # 10 KB
end
def highlighted_diff_lines
@highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight
end
......
require 'spec_helper'
describe Projects::CommitController do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:commit) { project.commit("master") }
let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
let(:master_pickable_commit) { project.commit(master_pickable_sha) }
before do
sign_in(user)
project.team << [user, :master]
end
describe "#show" do
shared_examples "export as" do |format|
it "should generally work" do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: commit.id,
format: format)
expect(response).to be_success
end
it "should generate it" do
expect_any_instance_of(Commit).to receive(:"to_#{format}")
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: commit.id, format: format)
end
it "should render it" do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: commit.id, format: format)
expect(response.body).to eq(commit.send(:"to_#{format}"))
end
it "should not escape Html" do
allow_any_instance_of(Commit).to receive(:"to_#{format}").
and_return('HTML entities &<>" ')
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: commit.id, format: format)
expect(response.body).not_to include('&amp;')
expect(response.body).not_to include('&gt;')
expect(response.body).not_to include('&lt;')
expect(response.body).not_to include('&quot;')
end
end
describe "as diff" do
include_examples "export as", :diff
let(:format) { :diff }
it "should really only be a git diff" do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: commit.id,
format: format)
expect(response.body).to start_with("diff --git")
end
it "should really only be a git diff without whitespace changes" do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: '66eceea0db202bb39c4e445e8ca28689645366c5',
# id: commit.id,
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")
expect(diff_splits.length).to be <= 2
end
end
describe "as patch" do
include_examples "export as", :patch
let(:format) { :patch }
it "should really be a git email patch" do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: commit.id,
format: format)
expect(response.body).to start_with("From #{commit.id}")
end
it "should contain a git diff" do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: commit.id,
format: format)
expect(response.body).to match(/^diff --git/)
end
end
context 'commit that removes a submodule' do
render_views
let(:fork_project) { create(:forked_project_with_submodules) }
let(:commit) { fork_project.commit('remove-submodule') }
before do
fork_project.team << [user, :master]
end
it 'renders it' do
get(:show,
namespace_id: fork_project.namespace.to_param,
project_id: fork_project.to_param,
id: commit.id)
expect(response).to be_success
end
end
end
describe "#branches" do
it "contains branch and tags information" do
get(:branches,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: commit.id)
expect(assigns(:branches)).to include("master", "feature_conflict")
expect(assigns(:tags)).to include("v1.1.0")
end
end
describe '#revert' do
context 'when target branch is not provided' do
it 'should render the 404 page' do
post(:revert,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: commit.id)
expect(response).not_to be_success
expect(response).to have_http_status(404)
end
end
context 'when the revert was successful' do
it 'should redirect to the commits page' do
post(:revert,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
target_branch: 'master',
id: commit.id)
expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
expect(flash[:notice]).to eq('The commit has been successfully reverted.')
end
end
context 'when the revert failed' do
before do
post(:revert,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
target_branch: 'master',
id: commit.id)
end
it 'should redirect to the commit page' do
# Reverting a commit that has been already reverted.
post(:revert,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
target_branch: 'master',
id: commit.id)
expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id)
expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.')
end
end
end
describe '#cherry_pick' do
context 'when target branch is not provided' do
it 'should render the 404 page' do
post(:cherry_pick,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: master_pickable_commit.id)
expect(response).not_to be_success
expect(response).to have_http_status(404)
end
end
context 'when the cherry-pick was successful' do
it 'should redirect to the commits page' do
post(:cherry_pick,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
target_branch: 'master',
id: master_pickable_commit.id)
expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.')
end
end
context 'when the cherry_pick failed' do
before do
post(:cherry_pick,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
target_branch: 'master',
id: master_pickable_commit.id)
end
it 'should redirect to the commit page' do
# Cherry-picking a commit that has been already cherry-picked.
post(:cherry_pick,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
target_branch: 'master',
id: master_pickable_commit.id)
expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.')
end
end
end
end
require 'rails_helper'
require 'spec_helper'
describe Projects::CommitController do
describe 'GET show' do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:commit) { project.commit("master") }
let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
let(:master_pickable_commit) { project.commit(master_pickable_sha) }
before do
sign_in(user)
project.team << [user, :master]
end
describe 'GET #show' do
render_views
def go(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
project_id: project.to_param
}
get :show, params.merge(extra_params)
end
let(:project) { create(:project) }
before do
......@@ -15,7 +35,7 @@ describe Projects::CommitController do
context 'with valid id' do
it 'responds with 200' do
go id: project.commit.id
go(id: commit.id)
expect(response).to be_ok
end
......@@ -23,27 +43,274 @@ describe Projects::CommitController do
context 'with invalid id' do
it 'responds with 404' do
go id: project.commit.id.reverse
go(id: commit.id.reverse)
expect(response).to be_not_found
end
end
it 'handles binary files' do
get(:show,
go(id: TestEnv::BRANCH_SHA['binary-encoding'], format: 'html')
expect(response).to be_success
end
shared_examples "export as" do |format|
it "should generally work" do
go(id: commit.id, format: format)
expect(response).to be_success
end
it "should generate it" do
expect_any_instance_of(Commit).to receive(:"to_#{format}")
go(id: commit.id, format: format)
end
it "should render it" do
go(id: commit.id, format: format)
expect(response.body).to eq(commit.send(:"to_#{format}"))
end
it "should not escape Html" do
allow_any_instance_of(Commit).to receive(:"to_#{format}").
and_return('HTML entities &<>" ')
go(id: commit.id, format: format)
expect(response.body).not_to include('&amp;')
expect(response.body).not_to include('&gt;')
expect(response.body).not_to include('&lt;')
expect(response.body).not_to include('&quot;')
end
end
describe "as diff" do
include_examples "export as", :diff
let(:format) { :diff }
it "should really only be a git diff" do
go(id: commit.id, format: format)
expect(response.body).to start_with("diff --git")
end
it "should really only be a git diff without whitespace changes" 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")
expect(diff_splits.length).to be <= 2
end
end
describe "as patch" do
include_examples "export as", :patch
let(:format) { :patch }
it "should really be a git email patch" do
go(id: commit.id, format: format)
expect(response.body).to start_with("From #{commit.id}")
end
it "should contain a git diff" do
go(id: commit.id, format: format)
expect(response.body).to match(/^diff --git/)
end
end
context 'commit that removes a submodule' do
render_views
let(:fork_project) { create(:forked_project_with_submodules, visibility_level: 20) }
let(:commit) { fork_project.commit('remove-submodule') }
it 'renders it' do
get(:show,
namespace_id: fork_project.namespace.to_param,
project_id: fork_project.to_param,
id: commit.id)
expect(response).to be_success
end
end
end
describe "#branches" do
it "contains branch and tags information" do
get(:branches,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: TestEnv::BRANCH_SHA['binary-encoding'],
format: "html")
id: commit.id)
expect(response).to be_success
expect(assigns(:branches)).to include("master", "feature_conflict")
expect(assigns(:tags)).to include("v1.1.0")
end
end
describe '#revert' do
context 'when target branch is not provided' do
it 'should render the 404 page' do
post(:revert,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: commit.id)
expect(response).not_to be_success
expect(response).to have_http_status(404)
end
end
context 'when the revert was successful' do
it 'should redirect to the commits page' do
post(:revert,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
target_branch: 'master',
id: commit.id)
expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
expect(flash[:notice]).to eq('The commit has been successfully reverted.')
end
end
context 'when the revert failed' do
before do
post(:revert,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
target_branch: 'master',
id: commit.id)
end
it 'should redirect to the commit page' do
# Reverting a commit that has been already reverted.
post(:revert,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
target_branch: 'master',
id: commit.id)
expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id)
expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.')
end
end
end
describe '#cherry_pick' do
context 'when target branch is not provided' do
it 'should render the 404 page' do
post(:cherry_pick,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: master_pickable_commit.id)
expect(response).not_to be_success
expect(response).to have_http_status(404)
end
end
context 'when the cherry-pick was successful' do
it 'should redirect to the commits page' do
post(:cherry_pick,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
target_branch: 'master',
id: master_pickable_commit.id)
expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.')
end
end
def go(id:)
get :show,
context 'when the cherry_pick failed' do
before do
post(:cherry_pick,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
target_branch: 'master',
id: master_pickable_commit.id)
end
it 'should redirect to the commit page' do
# Cherry-picking a commit that has been already cherry-picked.
post(:cherry_pick,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
target_branch: 'master',
id: master_pickable_commit.id)
expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.')
end
end
end
describe 'GET #diff_for_path' do
def diff_for_path(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: id
project_id: project.to_param
}
get :diff_for_path, params.merge(extra_params)
end
let(:existing_path) { '.gitmodules' }
context 'when the commit exists' do
context 'when the user has access to the project' do
context 'when the path exists in the diff' do
it 'enables diff notes' do
diff_for_path(id: commit.id, path: existing_path)
expect(assigns(:diff_notes_disabled)).to be_falsey
expect(assigns(:comments_target)).to eq(noteable_type: 'Commit',
commit_id: commit.id)
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)
end
diff_for_path(id: commit.id, path: existing_path)
end
end
context 'when the path does not exist in the diff' do
before { diff_for_path(id: commit.id, path: existing_path.succ) }
it 'returns a 404' do
expect(response).to have_http_status(404)
end
end
end
context 'when the user does not have access to the project' do
before do
project.team.truncate
diff_for_path(id: commit.id, path: existing_path)
end
it 'returns a 404' do
expect(response).to have_http_status(404)
end
end
end
context 'when the commit does not exist' do
before { diff_for_path(id: commit.id.succ, path: existing_path) }
it 'returns a 404' do
expect(response).to have_http_status(404)
end
end
end
end
......@@ -64,4 +64,73 @@ describe Projects::CompareController do
expect(assigns(:commits)).to eq(nil)
end
end
describe 'GET #diff_for_path' do
def diff_for_path(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
project_id: project.to_param
}
get :diff_for_path, params.merge(extra_params)
end
let(:existing_path) { 'files/ruby/feature.rb' }
context 'when the from and to refs exist' do
context 'when the user has access to the project' do
context 'when the path exists in the diff' do
it 'disables diff notes' do
diff_for_path(from: ref_from, to: ref_to, path: existing_path)
expect(assigns(:diff_notes_disabled)).to be_truthy
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)
end
diff_for_path(from: ref_from, to: ref_to, path: existing_path)
end
end
context 'when the path does not exist in the diff' do
before { diff_for_path(from: ref_from, to: ref_to, path: existing_path.succ) }
it 'returns a 404' do
expect(response).to have_http_status(404)
end
end
end
context 'when the user does not have access to the project' do
before do
project.team.truncate
diff_for_path(from: ref_from, to: ref_to, path: existing_path)
end
it 'returns a 404' do
expect(response).to have_http_status(404)
end
end
end
context 'when the from ref does not exist' do
before { diff_for_path(from: ref_from.succ, to: ref_to, path: existing_path) }
it 'returns a 404' do
expect(response).to have_http_status(404)
end
end
context 'when the to ref does not exist' do
before { diff_for_path(from: ref_from, to: ref_to.succ, path: existing_path) }
it 'returns a 404' do
expect(response).to have_http_status(404)
end
end
end
end
......@@ -289,101 +289,215 @@ describe Projects::MergeRequestsController do
end
end
describe 'GET diffs' do
def go(format: 'html')
get :diffs,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid,
format: format
describe 'GET #diffs' do
def go(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid
}
get :diffs, params.merge(extra_params)
end
context 'as html' do
it 'renders the diff template' do
go
context 'with default params' do
context 'as html' do
before { go(format: 'html') }
expect(response).to render_template('diffs')
it 'renders the diff template' do
expect(response).to render_template('diffs')
end
end
end
context 'as json' do
it 'renders the diffs template to a string' do
go format: 'json'
context 'as json' do
before { go(format: 'json') }
expect(response).to render_template('projects/merge_requests/show/_diffs')
expect(JSON.parse(response.body)).to have_key('html')
it 'renders the diffs template to a string' do
expect(response).to render_template('projects/merge_requests/show/_diffs')
expect(JSON.parse(response.body)).to have_key('html')
end
end
end
context 'with forked projects with submodules' do
render_views
let(:project) { create(:project) }
let(:fork_project) { create(:forked_project_with_submodules) }
let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
context 'with forked projects with submodules' do
render_views
before do
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
fork_project.save
merge_request.reload
end
let(:project) { create(:project) }
let(:fork_project) { create(:forked_project_with_submodules) }
let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
it 'renders' do
go format: 'json'
before do
fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
fork_project.save
merge_request.reload
go(format: 'json')
end
expect(response).to be_success
expect(response.body).to have_content('Subproject commit')
it 'renders' do
expect(response).to be_success
expect(response.body).to have_content('Subproject commit')
end
end
end
end
describe 'GET diffs with ignore_whitespace_change' do
def go(format: 'html')
get :diffs,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid,
format: format,
w: 1
end
context 'with ignore_whitespace_change' do
context 'as html' do
before { go(format: 'html', w: 1) }
context 'as html' do
it 'renders the diff template' do
go
it 'renders the diff template' do
expect(response).to render_template('diffs')
end
end
context 'as json' do
before { go(format: 'json', w: 1) }
expect(response).to render_template('diffs')
it 'renders the diffs template to a string' do
expect(response).to render_template('projects/merge_requests/show/_diffs')
expect(JSON.parse(response.body)).to have_key('html')
end
end
end
context 'as json' do
it 'renders the diffs template to a string' do
go format: 'json'
context 'with view' do
before { go(view: 'parallel') }
expect(response).to render_template('projects/merge_requests/show/_diffs')
expect(JSON.parse(response.body)).to have_key('html')
it 'saves the preferred diff view in a cookie' do
expect(response.cookies['diff_view']).to eq('parallel')
end
end
end
describe 'GET diffs with view' do
def go(extra_params = {})
describe 'GET #diff_for_path' do
def diff_for_path(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid
project_id: project.to_param
}
get :diffs, params.merge(extra_params)
get :diff_for_path, params.merge(extra_params)
end
it 'saves the preferred diff view in a cookie' do
go view: 'parallel'
context 'when an ID param is passed' do
let(:existing_path) { 'files/ruby/popen.rb' }
expect(response.cookies['diff_view']).to eq('parallel')
context 'when the merge request exists' do
context 'when the user can view the merge request' do
context 'when the path exists in the diff' do
it 'enables diff notes' do
diff_for_path(id: merge_request.iid, path: existing_path)
expect(assigns(:diff_notes_disabled)).to be_falsey
expect(assigns(:comments_target)).to eq(noteable_type: 'MergeRequest',
noteable_id: merge_request.id)
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)
end
diff_for_path(id: merge_request.iid, path: existing_path)
end
end
context 'when the path does not exist in the diff' do
before { diff_for_path(id: merge_request.iid, path: 'files/ruby/nopen.rb') }
it 'returns a 404' do
expect(response).to have_http_status(404)
end
end
end
context 'when the user cannot view the merge request' do
before do
project.team.truncate
diff_for_path(id: merge_request.iid, path: existing_path)
end
it 'returns a 404' do
expect(response).to have_http_status(404)
end
end
end
context 'when the merge request does not exist' do
before { diff_for_path(id: merge_request.iid.succ, path: existing_path) }
it 'returns a 404' do
expect(response).to have_http_status(404)
end
end
context 'when the merge request belongs to a different project' do
let(:other_project) { create(:empty_project) }
before do
other_project.team << [user, :master]
diff_for_path(id: merge_request.iid, path: existing_path, project_id: other_project.to_param)
end
it 'returns a 404' do
expect(response).to have_http_status(404)
end
end
end
context 'when source and target params are passed' do
let(:existing_path) { 'files/ruby/feature.rb' }
context 'when both branches are in the same project' do
it 'disables diff notes' do
diff_for_path(path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
expect(assigns(:diff_notes_disabled)).to be_truthy
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)
end
diff_for_path(path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
end
end
context 'when the source branch is in a different project to the target' do
let(:other_project) { create(:project) }
before { other_project.team << [user, :master] }
context 'when the path exists in the diff' do
it 'disables diff notes' do
diff_for_path(path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
expect(assigns(:diff_notes_disabled)).to be_truthy
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)
end
diff_for_path(path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
end
end
context 'when the path does not exist in the diff' do
before { diff_for_path(path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) }
it 'returns a 404' do
expect(response).to have_http_status(404)
end
end
end
end
end
describe 'GET commits' do
describe 'GET #commits' do
def go(format: 'html')
get :commits,
namespace_id: project.namespace.to_param,
......
require 'spec_helper'
describe MergeRequestDiff, models: true do
describe '#diffs' do
let(:mr) { create(:merge_request, :with_diffs) }
let(:mr_diff) { mr.merge_request_diff }
context 'when the :ignore_whitespace_change option is set' do
it 'creates a new compare object instead of loading from the DB' do
expect(mr_diff).not_to receive(:load_diffs)
expect(Gitlab::Git::Compare).to receive(:new).and_call_original
mr_diff.diffs(ignore_whitespace_change: true)
end
end
context 'when the raw diffs are empty' do
before { mr_diff.update_attributes(st_diffs: '') }
it 'returns an empty DiffCollection' do
expect(mr_diff.diffs).to be_a(Gitlab::Git::DiffCollection)
expect(mr_diff.diffs).to be_empty
end
end
context 'when the raw diffs exist' do
it 'returns the diffs' do
expect(mr_diff.diffs).to be_a(Gitlab::Git::DiffCollection)
expect(mr_diff.diffs).not_to be_empty
end
context 'when the :paths option is set' do
let(:diffs) { mr_diff.diffs(paths: ['.gitignore', 'files/ruby/popen.rb', 'files/ruby/string.rb']) }
it 'only returns diffs that match the paths given' do
expect(diffs.map(&:new_path)).to contain_exactly('.gitignore', 'files/ruby/popen.rb')
end
it 'uses the diffs from the DB' do
expect(mr_diff).to receive(:load_diffs)
diffs
end
end
end
end
end
......@@ -116,6 +116,31 @@ describe MergeRequest, models: true do
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(options)
merge_request.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(:diffs).with(options)
merge_request.diffs(options)
end
end
end
describe "#mr_and_commit_notes" do
let!(:merge_request) { create(:merge_request) }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册