diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 10c7b4032cf938d64b9f542007cc3e2283b57423..7bf3cb6230b7ea91539fdd45a7e823638b382f0d 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -190,12 +190,8 @@ def diff_file_changed_icon_color(diff_file) def render_overflow_warning?(diffs_collection) diff_files = diffs_collection.raw_diff_files - if diff_files.any?(&:too_large?) - Gitlab::Metrics.add_event(:diffs_overflow_single_file_limits) - end - diff_files.overflow?.tap do |overflown| - Gitlab::Metrics.add_event(:diffs_overflow_collection_limits) if overflown + log_overflow_limits(diff_files) end end @@ -286,4 +282,18 @@ def conflicts conflicts_service.conflicts.files.index_by(&:our_path) end + + def log_overflow_limits(diff_files) + if diff_files.any?(&:too_large?) + Gitlab::Metrics.add_event(:diffs_overflow_single_file_limits) + end + + Gitlab::Metrics.add_event(:diffs_overflow_collection_limits) if diff_files.overflow? + Gitlab::Metrics.add_event(:diffs_overflow_max_bytes_limits) if diff_files.overflow_max_bytes? + Gitlab::Metrics.add_event(:diffs_overflow_max_files_limits) if diff_files.overflow_max_files? + Gitlab::Metrics.add_event(:diffs_overflow_max_lines_limits) if diff_files.overflow_max_lines? + Gitlab::Metrics.add_event(:diffs_overflow_collapsed_bytes_limits) if diff_files.collapsed_safe_bytes? + Gitlab::Metrics.add_event(:diffs_overflow_collapsed_files_limits) if diff_files.collapsed_safe_files? + Gitlab::Metrics.add_event(:diffs_overflow_collapsed_lines_limits) if diff_files.collapsed_safe_lines? + end end diff --git a/changelogs/unreleased/31063-ensure-diff-collection-limits-are-configurable.yml b/changelogs/unreleased/31063-ensure-diff-collection-limits-are-configurable.yml new file mode 100644 index 0000000000000000000000000000000000000000..9e8918810c41d98544b2cfebd2d049d7e7b0fa36 --- /dev/null +++ b/changelogs/unreleased/31063-ensure-diff-collection-limits-are-configurable.yml @@ -0,0 +1,5 @@ +--- +title: Track the different overflows for diff collections +merge_request: 57790 +author: +type: other diff --git a/lib/gitlab/git/diff_collection.rb b/lib/gitlab/git/diff_collection.rb index 19462e6cb023d9dae211f57875bfa213fd3116c5..b7b6343aec041226d6b7b4297aeadaf26e072b1b 100644 --- a/lib/gitlab/git/diff_collection.rb +++ b/lib/gitlab/git/diff_collection.rb @@ -82,6 +82,30 @@ def overflow? !!@overflow end + def overflow_max_lines? + !!@overflow_max_lines + end + + def overflow_max_bytes? + !!@overflow_max_bytes + end + + def overflow_max_files? + !!@overflow_max_files + end + + def collapsed_safe_lines? + !!@collapsed_safe_lines + end + + def collapsed_safe_files? + !!@collapsed_safe_files + end + + def collapsed_safe_bytes? + !!@collapsed_safe_bytes + end + def size @size ||= count # forces a loop using each method end @@ -121,7 +145,15 @@ def populate! end def over_safe_limits?(files) - files >= safe_max_files || @line_count > safe_max_lines || @byte_count >= safe_max_bytes + if files >= safe_max_files + @collapsed_safe_files = true + elsif @line_count > safe_max_lines + @collapsed_safe_lines = true + elsif @byte_count >= safe_max_bytes + @collapsed_safe_bytes = true + end + + @collapsed_safe_files || @collapsed_safe_lines || @collapsed_safe_bytes end def expand_diff? @@ -154,6 +186,7 @@ def each_serialized_patch if @enforce_limits && i >= max_files @overflow = true + @overflow_max_files = true break end @@ -166,10 +199,19 @@ def each_serialized_patch @line_count += diff.line_count @byte_count += diff.diff.bytesize - if @enforce_limits && (@line_count >= max_lines || @byte_count >= max_bytes) + if @enforce_limits && @line_count >= max_lines + # This last Diff instance pushes us over the lines limit. We stop and + # discard it. + @overflow = true + @overflow_max_lines = true + break + end + + if @enforce_limits && @byte_count >= max_bytes # This last Diff instance pushes us over the lines limit. We stop and # discard it. @overflow = true + @overflow_max_bytes = true break end diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 20fa8d628841aa793b5e353fcafdc09c2ca62a2a..dfea1020c5294fbcf440423d5db4a0de8f11fd17 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -291,6 +291,8 @@ end describe '#render_overflow_warning?' do + using RSpec::Parameterized::TableSyntax + let(:diffs_collection) { instance_double(Gitlab::Diff::FileCollection::MergeRequestDiff, raw_diff_files: diff_files) } let(:diff_files) { Gitlab::Git::DiffCollection.new(files) } let(:safe_file) { { too_large: false, diff: '' } } @@ -299,13 +301,42 @@ before do allow(diff_files).to receive(:overflow?).and_return(false) + allow(diff_files).to receive(:overflow_max_bytes?).and_return(false) + allow(diff_files).to receive(:overflow_max_files?).and_return(false) + allow(diff_files).to receive(:overflow_max_lines?).and_return(false) + allow(diff_files).to receive(:collapsed_safe_bytes?).and_return(false) + allow(diff_files).to receive(:collapsed_safe_files?).and_return(false) + allow(diff_files).to receive(:collapsed_safe_lines?).and_return(false) end - context 'when neither collection nor individual file hit the limit' do + context 'when no limits are hit' do it 'returns false and does not log any overflow events' do expect(Gitlab::Metrics).not_to receive(:add_event).with(:diffs_overflow_collection_limits) expect(Gitlab::Metrics).not_to receive(:add_event).with(:diffs_overflow_single_file_limits) + expect(Gitlab::Metrics).not_to receive(:add_event).with(:diffs_overflow_max_bytes_limits) + expect(Gitlab::Metrics).not_to receive(:add_event).with(:diffs_overflow_max_files_limits) + expect(Gitlab::Metrics).not_to receive(:add_event).with(:diffs_overflow_max_lines_limits) + expect(Gitlab::Metrics).not_to receive(:add_event).with(:diffs_overflow_collapsed_bytes_limits) + expect(Gitlab::Metrics).not_to receive(:add_event).with(:diffs_overflow_collapsed_files_limits) + expect(Gitlab::Metrics).not_to receive(:add_event).with(:diffs_overflow_collapsed_lines_limits) + + expect(render_overflow_warning?(diffs_collection)).to be false + end + end + + where(:overflow_method, :event_name) do + :overflow_max_bytes? | :diffs_overflow_max_bytes_limits + :overflow_max_files? | :diffs_overflow_max_files_limits + :overflow_max_lines? | :diffs_overflow_max_lines_limits + :collapsed_safe_bytes? | :diffs_overflow_collapsed_bytes_limits + :collapsed_safe_files? | :diffs_overflow_collapsed_files_limits + :collapsed_safe_lines? | :diffs_overflow_collapsed_lines_limits + end + with_them do + it 'returns false and only logs the correct collection overflow event' do + allow(diff_files).to receive(overflow_method).and_return(true) + expect(Gitlab::Metrics).to receive(:add_event).with(event_name).once expect(render_overflow_warning?(diffs_collection)).to be false end end @@ -315,9 +346,8 @@ allow(diff_files).to receive(:overflow?).and_return(true) end - it 'returns false and only logs collection overflow event' do - expect(Gitlab::Metrics).to receive(:add_event).with(:diffs_overflow_collection_limits).exactly(:once) - expect(Gitlab::Metrics).not_to receive(:add_event).with(:diffs_overflow_single_file_limits) + it 'returns true and only logs all the correct collection overflow event' do + expect(Gitlab::Metrics).to receive(:add_event).with(:diffs_overflow_collection_limits).once expect(render_overflow_warning?(diffs_collection)).to be true end diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb index 1a3c332a21bc5b89fb50ac5351e920027f7f60ea..114b3d01952e314c3c058aa8efb123e2a70c4cbb 100644 --- a/spec/lib/gitlab/git/diff_collection_spec.rb +++ b/spec/lib/gitlab/git/diff_collection_spec.rb @@ -31,6 +31,19 @@ def each end end + let(:overflow_max_bytes) { false } + let(:overflow_max_files) { false } + let(:overflow_max_lines) { false } + + shared_examples 'overflow stuff' do + it 'returns the expected overflow values' do + subject.overflow? + expect(subject.overflow_max_bytes?).to eq(overflow_max_bytes) + expect(subject.overflow_max_files?).to eq(overflow_max_files) + expect(subject.overflow_max_lines?).to eq(overflow_max_lines) + end + end + subject do Gitlab::Git::DiffCollection.new( iterator, @@ -76,12 +89,19 @@ def each end context 'overflow handling' do + subject { super() } + + let(:collapsed_safe_files) { false } + let(:collapsed_safe_lines) { false } + context 'adding few enough files' do let(:file_count) { 3 } context 'and few enough lines' do let(:line_count) { 10 } + it_behaves_like 'overflow stuff' + describe '#overflow?' do subject { super().overflow? } @@ -117,6 +137,11 @@ def each context 'when limiting is disabled' do let(:limits) { false } + let(:overflow_max_bytes) { false } + let(:overflow_max_files) { false } + let(:overflow_max_lines) { false } + + it_behaves_like 'overflow stuff' describe '#overflow?' do subject { super().overflow? } @@ -155,6 +180,9 @@ def each context 'and too many lines' do let(:line_count) { 1000 } + let(:overflow_max_lines) { true } + + it_behaves_like 'overflow stuff' describe '#overflow?' do subject { super().overflow? } @@ -184,6 +212,11 @@ def each context 'when limiting is disabled' do let(:limits) { false } + let(:overflow_max_bytes) { false } + let(:overflow_max_files) { false } + let(:overflow_max_lines) { false } + + it_behaves_like 'overflow stuff' describe '#overflow?' do subject { super().overflow? } @@ -216,10 +249,13 @@ def each context 'adding too many files' do let(:file_count) { 11 } + let(:overflow_max_files) { true } context 'and few enough lines' do let(:line_count) { 1 } + it_behaves_like 'overflow stuff' + describe '#overflow?' do subject { super().overflow? } @@ -248,6 +284,11 @@ def each context 'when limiting is disabled' do let(:limits) { false } + let(:overflow_max_bytes) { false } + let(:overflow_max_files) { false } + let(:overflow_max_lines) { false } + + it_behaves_like 'overflow stuff' describe '#overflow?' do subject { super().overflow? } @@ -279,6 +320,10 @@ def each context 'and too many lines' do let(:line_count) { 30 } + let(:overflow_max_lines) { true } + let(:overflow_max_files) { false } + + it_behaves_like 'overflow stuff' describe '#overflow?' do subject { super().overflow? } @@ -308,6 +353,11 @@ def each context 'when limiting is disabled' do let(:limits) { false } + let(:overflow_max_bytes) { false } + let(:overflow_max_files) { false } + let(:overflow_max_lines) { false } + + it_behaves_like 'overflow stuff' describe '#overflow?' do subject { super().overflow? } @@ -344,6 +394,8 @@ def each context 'and few enough lines' do let(:line_count) { 1 } + it_behaves_like 'overflow stuff' + describe '#overflow?' do subject { super().overflow? } @@ -375,6 +427,9 @@ def each context 'adding too many bytes' do let(:file_count) { 10 } let(:line_length) { 5200 } + let(:overflow_max_bytes) { true } + + it_behaves_like 'overflow stuff' describe '#overflow?' do subject { super().overflow? } @@ -404,6 +459,11 @@ def each context 'when limiting is disabled' do let(:limits) { false } + let(:overflow_max_bytes) { false } + let(:overflow_max_files) { false } + let(:overflow_max_lines) { false } + + it_behaves_like 'overflow stuff' describe '#overflow?' do subject { super().overflow? } @@ -437,6 +497,8 @@ def each describe 'empty collection' do subject { Gitlab::Git::DiffCollection.new([]) } + it_behaves_like 'overflow stuff' + describe '#overflow?' do subject { super().overflow? } @@ -555,7 +617,7 @@ def each .and_return({ max_files: 2, max_lines: max_lines }) end - it 'prunes diffs by default even little ones' do + it 'prunes diffs by default even little ones and sets collapsed_safe_files true' do subject.each_with_index do |d, i| if i < 2 expect(d.diff).not_to eq('') @@ -563,6 +625,8 @@ def each expect(d.diff).to eq('') end end + + expect(subject.collapsed_safe_files?).to eq(true) end end @@ -582,7 +646,7 @@ def each .and_return({ max_files: max_files, max_lines: 80 }) end - it 'prunes diffs by default even little ones' do + it 'prunes diffs by default even little ones and sets collapsed_safe_lines true' do subject.each_with_index do |d, i| if i < 2 expect(d.diff).not_to eq('') @@ -590,26 +654,30 @@ def each expect(d.diff).to eq('') end end + + expect(subject.collapsed_safe_lines?).to eq(true) end end context 'when go over safe limits on bytes' do let(:iterator) do [ - fake_diff(1, 45), - fake_diff(1, 45), - fake_diff(1, 20480), - fake_diff(1, 1) + fake_diff(5, 10), + fake_diff(5000, 10), + fake_diff(5, 10), + fake_diff(5, 10) ] end before do + allow(Gitlab::CurrentSettings).to receive(:diff_max_patch_bytes).and_return(1.megabyte) + allow(Gitlab::Git::DiffCollection) .to receive(:default_limits) - .and_return({ max_files: max_files, max_lines: 80 }) + .and_return({ max_files: 4, max_lines: 3000 }) end - it 'prunes diffs by default even little ones' do + it 'prunes diffs by default even little ones and sets collapsed_safe_bytes true' do subject.each_with_index do |d, i| if i < 2 expect(d.diff).not_to eq('') @@ -617,6 +685,8 @@ def each expect(d.diff).to eq('') end end + + expect(subject.collapsed_safe_bytes?).to eq(true) end end end