9a73b634ab
This adds an ID-less table containing one row per file, per merge request diff. It has a column for each attribute on Gitlab::Git::Diff that is serialised currently, with the advantage that we can easily query the attributes of this new table. It does not migrate existing data, so we have fallback code when the legacy st_diffs column is present instead. For a merge request diff to be valid, it should have at most one of: * Rows in this new table, with the correct merge_request_diff_id. * A non-NULL st_diffs column. It may have neither, if the diff is empty.
354 lines
9.7 KiB
Ruby
354 lines
9.7 KiB
Ruby
require "spec_helper"
|
|
|
|
describe Gitlab::Git::Diff, seed_helper: true do
|
|
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
|
|
|
|
before do
|
|
@raw_diff_hash = {
|
|
diff: <<EOT.gsub(/^ {8}/, "").sub(/\n$/, ""),
|
|
--- a/.gitmodules
|
|
+++ b/.gitmodules
|
|
@@ -4,3 +4,6 @@
|
|
[submodule "gitlab-shell"]
|
|
\tpath = gitlab-shell
|
|
\turl = https://github.com/gitlabhq/gitlab-shell.git
|
|
+[submodule "gitlab-grack"]
|
|
+ path = gitlab-grack
|
|
+ url = https://gitlab.com/gitlab-org/gitlab-grack.git
|
|
|
|
EOT
|
|
new_path: ".gitmodules",
|
|
old_path: ".gitmodules",
|
|
a_mode: '100644',
|
|
b_mode: '100644',
|
|
new_file: false,
|
|
renamed_file: false,
|
|
deleted_file: false,
|
|
too_large: false
|
|
}
|
|
|
|
@rugged_diff = repository.rugged.diff("5937ac0a7beb003549fc5fd26fc247adbce4a52e^", "5937ac0a7beb003549fc5fd26fc247adbce4a52e", paths:
|
|
[".gitmodules"]).patches.first
|
|
end
|
|
|
|
describe 'size limit feature toggles' do
|
|
context 'when the feature gitlab_git_diff_size_limit_increase is enabled' do
|
|
before do
|
|
Feature.enable('gitlab_git_diff_size_limit_increase')
|
|
end
|
|
|
|
it 'returns 200 KB for size_limit' do
|
|
expect(described_class.size_limit).to eq(200.kilobytes)
|
|
end
|
|
|
|
it 'returns 100 KB for collapse_limit' do
|
|
expect(described_class.collapse_limit).to eq(100.kilobytes)
|
|
end
|
|
end
|
|
|
|
context 'when the feature gitlab_git_diff_size_limit_increase is disabled' do
|
|
before do
|
|
Feature.disable('gitlab_git_diff_size_limit_increase')
|
|
end
|
|
|
|
it 'returns 100 KB for size_limit' do
|
|
expect(described_class.size_limit).to eq(100.kilobytes)
|
|
end
|
|
|
|
it 'returns 10 KB for collapse_limit' do
|
|
expect(described_class.collapse_limit).to eq(10.kilobytes)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.new' do
|
|
context 'using a Hash' do
|
|
context 'with a small diff' do
|
|
let(:diff) { described_class.new(@raw_diff_hash) }
|
|
|
|
it 'initializes the diff' do
|
|
expect(diff.to_hash).to eq(@raw_diff_hash)
|
|
end
|
|
|
|
it 'does not prune the diff' do
|
|
expect(diff).not_to be_too_large
|
|
end
|
|
end
|
|
|
|
context 'using a diff that is too large' do
|
|
it 'prunes the diff' do
|
|
diff = described_class.new(diff: 'a' * (described_class.size_limit + 1))
|
|
|
|
expect(diff.diff).to be_empty
|
|
expect(diff).to be_too_large
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'using a Rugged::Patch' do
|
|
context 'with a small diff' do
|
|
let(:diff) { described_class.new(@rugged_diff) }
|
|
|
|
it 'initializes the diff' do
|
|
expect(diff.to_hash).to eq(@raw_diff_hash)
|
|
end
|
|
|
|
it 'does not prune the diff' do
|
|
expect(diff).not_to be_too_large
|
|
end
|
|
end
|
|
|
|
context 'using a diff that is too large' do
|
|
it 'prunes the diff' do
|
|
expect_any_instance_of(String).to receive(:bytesize).
|
|
and_return(1024 * 1024 * 1024)
|
|
|
|
diff = described_class.new(@rugged_diff)
|
|
|
|
expect(diff.diff).to be_empty
|
|
expect(diff).to be_too_large
|
|
end
|
|
end
|
|
|
|
context 'using a collapsable diff that is too large' do
|
|
before do
|
|
# The patch total size is 200, with lines between 21 and 54.
|
|
# This is a quick-and-dirty way to test this. Ideally, a new patch is
|
|
# added to the test repo with a size that falls between the real limits.
|
|
allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(150)
|
|
allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(100)
|
|
end
|
|
|
|
it 'prunes the diff as a large diff instead of as a collapsed diff' do
|
|
diff = described_class.new(@rugged_diff, expanded: false)
|
|
|
|
expect(diff.diff).to be_empty
|
|
expect(diff).to be_too_large
|
|
expect(diff).not_to be_collapsed
|
|
end
|
|
end
|
|
|
|
context 'using a large binary diff' do
|
|
it 'does not prune the diff' do
|
|
expect_any_instance_of(Rugged::Diff::Delta).to receive(:binary?).
|
|
and_return(true)
|
|
|
|
diff = described_class.new(@rugged_diff)
|
|
|
|
expect(diff.diff).not_to be_empty
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'using a GitalyClient::Diff' do
|
|
let(:diff) do
|
|
described_class.new(
|
|
Gitlab::GitalyClient::Diff.new(
|
|
to_path: ".gitmodules",
|
|
from_path: ".gitmodules",
|
|
old_mode: 0100644,
|
|
new_mode: 0100644,
|
|
from_id: '357406f3075a57708d0163752905cc1576fceacc',
|
|
to_id: '8e5177d718c561d36efde08bad36b43687ee6bf0',
|
|
patch: raw_patch
|
|
)
|
|
)
|
|
end
|
|
|
|
context 'with a small diff' do
|
|
let(:raw_patch) { @raw_diff_hash[:diff] }
|
|
|
|
it 'initializes the diff' do
|
|
expect(diff.to_hash).to eq(@raw_diff_hash)
|
|
end
|
|
|
|
it 'does not prune the diff' do
|
|
expect(diff).not_to be_too_large
|
|
end
|
|
end
|
|
|
|
context 'using a diff that is too large' do
|
|
let(:raw_patch) { 'a' * 204800 }
|
|
|
|
it 'prunes the diff' do
|
|
expect(diff.diff).to be_empty
|
|
expect(diff).to be_too_large
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'straight diffs' do
|
|
let(:options) { { straight: true } }
|
|
let(:diffs) { described_class.between(repository, 'feature', 'master', options) }
|
|
|
|
it 'has the correct size' do
|
|
expect(diffs.size).to eq(24)
|
|
end
|
|
|
|
context 'diff' do
|
|
it 'is an instance of Diff' do
|
|
expect(diffs.first).to be_kind_of(described_class)
|
|
end
|
|
|
|
it 'has the correct new_path' do
|
|
expect(diffs.first.new_path).to eq('.DS_Store')
|
|
end
|
|
|
|
it 'has the correct diff' do
|
|
expect(diffs.first.diff).to include('Binary files /dev/null and b/.DS_Store differ')
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.between' do
|
|
let(:diffs) { described_class.between(repository, 'feature', 'master') }
|
|
subject { diffs }
|
|
|
|
it { is_expected.to be_kind_of Gitlab::Git::DiffCollection }
|
|
|
|
describe '#size' do
|
|
subject { super().size }
|
|
|
|
it { is_expected.to eq(1) }
|
|
end
|
|
|
|
context 'diff' do
|
|
subject { diffs.first }
|
|
|
|
it { is_expected.to be_kind_of described_class }
|
|
|
|
describe '#new_path' do
|
|
subject { super().new_path }
|
|
|
|
it { is_expected.to eq('files/ruby/feature.rb') }
|
|
end
|
|
|
|
describe '#diff' do
|
|
subject { super().diff }
|
|
|
|
it { is_expected.to include '+class Feature' }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.filter_diff_options' do
|
|
let(:options) { { max_size: 100, invalid_opt: true } }
|
|
|
|
context "without default options" do
|
|
let(:filtered_options) { described_class.filter_diff_options(options) }
|
|
|
|
it "should filter invalid options" do
|
|
expect(filtered_options).not_to have_key(:invalid_opt)
|
|
end
|
|
end
|
|
|
|
context "with default options" do
|
|
let(:filtered_options) do
|
|
default_options = { max_size: 5, bad_opt: 1, ignore_whitespace: true }
|
|
described_class.filter_diff_options(options, default_options)
|
|
end
|
|
|
|
it "should filter invalid options" do
|
|
expect(filtered_options).not_to have_key(:invalid_opt)
|
|
expect(filtered_options).not_to have_key(:bad_opt)
|
|
end
|
|
|
|
it "should merge with default options" do
|
|
expect(filtered_options).to have_key(:ignore_whitespace)
|
|
end
|
|
|
|
it "should override default options" do
|
|
expect(filtered_options).to have_key(:max_size)
|
|
expect(filtered_options[:max_size]).to eq(100)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#submodule?' do
|
|
before do
|
|
commit = repository.lookup('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
|
|
@diffs = commit.parents[0].diff(commit).patches
|
|
end
|
|
|
|
it { expect(described_class.new(@diffs[0]).submodule?).to eq(false) }
|
|
it { expect(described_class.new(@diffs[1]).submodule?).to eq(true) }
|
|
end
|
|
|
|
describe '#line_count' do
|
|
it 'returns the correct number of lines' do
|
|
diff = described_class.new(@rugged_diff)
|
|
|
|
expect(diff.line_count).to eq(9)
|
|
end
|
|
end
|
|
|
|
describe '#too_large?' do
|
|
it 'returns true for a diff that is too large' do
|
|
diff = described_class.new(diff: 'a' * 204800)
|
|
|
|
expect(diff.too_large?).to eq(true)
|
|
end
|
|
|
|
it 'returns false for a diff that is small enough' do
|
|
diff = described_class.new(diff: 'a')
|
|
|
|
expect(diff.too_large?).to eq(false)
|
|
end
|
|
|
|
it 'returns true for a diff that was explicitly marked as being too large' do
|
|
diff = described_class.new(diff: 'a')
|
|
|
|
diff.too_large!
|
|
|
|
expect(diff.too_large?).to eq(true)
|
|
end
|
|
end
|
|
|
|
describe '#collapsed?' do
|
|
it 'returns false by default even on quite big diff' do
|
|
diff = described_class.new(diff: 'a' * 20480)
|
|
|
|
expect(diff).not_to be_collapsed
|
|
end
|
|
|
|
it 'returns false by default for a diff that is small enough' do
|
|
diff = described_class.new(diff: 'a')
|
|
|
|
expect(diff).not_to be_collapsed
|
|
end
|
|
|
|
it 'returns true for a diff that was explicitly marked as being collapsed' do
|
|
diff = described_class.new(diff: 'a')
|
|
|
|
diff.collapse!
|
|
|
|
expect(diff).to be_collapsed
|
|
end
|
|
end
|
|
|
|
describe '#collapsed?' do
|
|
it 'returns true for a diff that is quite large' do
|
|
diff = described_class.new({ diff: 'a' * (described_class.collapse_limit + 1) }, expanded: false)
|
|
|
|
expect(diff).to be_collapsed
|
|
end
|
|
|
|
it 'returns false for a diff that is small enough' do
|
|
diff = described_class.new({ diff: 'a' }, expanded: false)
|
|
|
|
expect(diff).not_to be_collapsed
|
|
end
|
|
end
|
|
|
|
describe '#collapse!' do
|
|
it 'prunes the diff' do
|
|
diff = described_class.new(diff: "foo\nbar")
|
|
|
|
diff.collapse!
|
|
|
|
expect(diff.diff).to eq('')
|
|
expect(diff.line_count).to eq(0)
|
|
end
|
|
end
|
|
end
|