diff --git a/CHANGELOG b/CHANGELOG index 88678b087c5..0995133d210 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 7.11.0 (unreleased) - Add "Reply quoting selected text" shortcut key (`r`) - Fix bug causing `@whatever` inside an issue's first code block to be picked up as a user mention. - Fix bug causing `@whatever` inside an inline code snippet (backtick-style) to be picked up as a user mention. + - Improve handling of large diffs - - Show Atom feed buttons everywhere where applicable. - Add project activity atom feed. diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 162778ade58..1b10795bb7b 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -7,14 +7,23 @@ module DiffHelper end end - def safe_diff_files(diffs) - diffs.first(allowed_diff_size).map do |diff| - Gitlab::Diff::File.new(diff) + def allowed_diff_lines + if diff_hard_limit_enabled? + Commit::DIFF_HARD_LIMIT_LINES + else + Commit::DIFF_SAFE_LINES end end - def show_diff_size_warning?(diffs) - diffs.size > allowed_diff_size + def safe_diff_files(diffs) + lines = 0 + safe_files = [] + diffs.first(allowed_diff_size).each do |diff| + lines += diff.diff.lines.count + break if lines > allowed_diff_lines + safe_files << Gitlab::Diff::File.new(diff) + end + safe_files end def diff_hard_limit_enabled? diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index b49aee504fe..ec8974c5475 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -5,11 +5,13 @@ = parallel_diff_btn = render 'projects/diffs/stats', diffs: diffs -- if show_diff_size_warning?(diffs) - = render 'projects/diffs/warning', diffs: diffs +- diff_files = safe_diff_files(diffs) + +- if diff_files.count < diffs.size + = render 'projects/diffs/warning', diffs: diffs, shown_files_count: diff_files.count .files - - safe_diff_files(diffs).each_with_index do |diff_file, index| + - diff_files.each_with_index do |diff_file, index| = render 'projects/diffs/file', diff_file: diff_file, i: index, project: project - if @diff_timeout diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml index 47abbba2eb2..bd0b7376ba7 100644 --- a/app/views/projects/diffs/_warning.html.haml +++ b/app/views/projects/diffs/_warning.html.haml @@ -14,6 +14,6 @@ = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-warning btn-sm" %p To preserve performance only - %strong #{allowed_diff_size} of #{diffs.size} + %strong #{shown_files_count} of #{diffs.size} files are displayed. diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 95719b4b49f..dd4c1d645e2 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -5,7 +5,8 @@ describe DiffHelper do let(:project) { create(:project) } let(:commit) { project.commit(sample_commit.id) } - let(:diff) { commit.diffs.first } + let(:diffs) { commit.diffs } + let(:diff) { diffs.first } let(:diff_file) { Gitlab::Diff::File.new(diff) } describe 'diff_hard_limit_enabled?' do @@ -30,6 +31,57 @@ describe DiffHelper do end end + describe 'allowed_diff_lines' do + it 'should return hard limit for number of lines in a diff if force diff is true' do + allow(controller).to receive(:params) { { force_show_diff: true } } + expect(allowed_diff_lines).to eq(50000) + end + + it 'should return safe limit for numbers of lines a diff if force diff is false' do + expect(allowed_diff_lines).to eq(5000) + end + end + + describe 'safe_diff_files' do + it 'should return all files from a commit that is smaller than safe limits' do + expect(safe_diff_files(diffs).length).to eq(2) + end + + it 'should return only the first file if the diff line count in the 2nd file takes the total beyond safe limits' do + diffs[1].diff.stub(lines: [""] * 4999) #simulate 4999 lines + expect(safe_diff_files(diffs).length).to eq(1) + end + + it 'should return all files from a commit that is beyond safe limit for numbers of lines if force diff is true' do + allow(controller).to receive(:params) { { force_show_diff: true } } + diffs[1].diff.stub(lines: [""] * 4999) #simulate 4999 lines + expect(safe_diff_files(diffs).length).to eq(2) + end + + it 'should return only the first file if the diff line count in the 2nd file takes the total beyond hard limits' do + allow(controller).to receive(:params) { { force_show_diff: true } } + diffs[1].diff.stub(lines: [""] * 49999) #simulate 49999 lines + expect(safe_diff_files(diffs).length).to eq(1) + end + + it 'should return only a safe number of file diffs if a commit touches more files than the safe limits' do + large_diffs = diffs * 100 #simulate 200 diffs + expect(safe_diff_files(large_diffs).length).to eq(100) + end + + it 'should return all file diffs if a commit touches more files than the safe limits but force diff is true' do + allow(controller).to receive(:params) { { force_show_diff: true } } + large_diffs = diffs * 100 #simulate 200 diffs + expect(safe_diff_files(large_diffs).length).to eq(200) + end + + it 'should return a limited file diffs if a commit touches more files than the hard limits and force diff is true' do + allow(controller).to receive(:params) { { force_show_diff: true } } + very_large_diffs = diffs * 1000 #simulate 2000 diffs + expect(safe_diff_files(very_large_diffs).length).to eq(1000) + end + end + describe 'parallel_diff' do it 'should return an array of arrays containing the parsed diff' do expect(parallel_diff(diff_file, 0)).