Commit 6e46fa00 authored by Patrick Bajao's avatar Patrick Bajao

Check push size based on quarantine size

Fallback to using the old approach when env var is
not present.
parent 7e7454ae
---
title: Use quarantine size to check push size against repository size limit
merge_request: 13460
author:
type: fixed
...@@ -84,20 +84,40 @@ module EE ...@@ -84,20 +84,40 @@ module EE
def check_push_size! def check_push_size!
return unless check_size_limit? return unless check_size_limit?
git_env = ::Gitlab::Git::HookEnv.all(repository.gl_repository)
# Use #quarantine_size to get correct push size whenever a lof of changes
# gets pushed at the same time containing the same blobs. This is only
# doable if GIT_OBJECT_DIRECTORY_RELATIVE env var is set and happens
# when git push comes from CLI (not via UI and API).
#
# Fallback to determining push size using the changes_list so we can still
# determine the push size if env var isn't set (e.g. changes are made
# via UI and API).
push_size = git_env['GIT_OBJECT_DIRECTORY_RELATIVE'].present? ? quarantine_size : changes_size
if project.changes_will_exceed_size_limit?(push_size)
raise ::Gitlab::GitAccess::UnauthorizedError, ::Gitlab::RepositorySizeError.new(project).new_changes_error
end
end
def quarantine_size
project.repository.object_directory_size
end
def changes_size
# If there are worktrees with a HEAD pointing to a non-existent object, # If there are worktrees with a HEAD pointing to a non-existent object,
# calls to `git rev-list --all` will fail in git 2.15+. This should also # calls to `git rev-list --all` will fail in git 2.15+. This should also
# clear stale lock files. # clear stale lock files.
project.repository.clean_stale_repository_files project.repository.clean_stale_repository_files
push_size_in_bytes = 0 size_in_bytes = 0
changes_list.each do |change| changes_list.each do |change|
push_size_in_bytes += repository.new_blobs(change[:newrev]).sum(&:size) # rubocop: disable CodeReuse/ActiveRecord size_in_bytes += repository.new_blobs(change[:newrev]).sum(&:size) # rubocop: disable CodeReuse/ActiveRecord
if project.changes_will_exceed_size_limit?(push_size_in_bytes)
raise ::Gitlab::GitAccess::UnauthorizedError, ::Gitlab::RepositorySizeError.new(project).new_changes_error
end
end end
size_in_bytes
end end
def check_size_limit? def check_size_limit?
......
...@@ -181,18 +181,13 @@ describe Gitlab::GitAccess do ...@@ -181,18 +181,13 @@ describe Gitlab::GitAccess do
# Delete branch so Repository#new_blobs can return results # Delete branch so Repository#new_blobs can return results
repository.delete_branch('2-mb-file') repository.delete_branch('2-mb-file')
repository.delete_branch('wip') repository.delete_branch('wip')
end
context 'when repository size is over limit' do
before do
allow(project).to receive(:repository_and_lfs_size).and_return(2.megabytes)
project.update_attribute(:repository_size_limit, 1.megabytes) allow(project).to receive(:repository_and_lfs_size).and_return(repository_size)
end project.update_attribute(:repository_size_limit, repository_size_limit)
end
shared_examples_for 'a push to repository over the limit' do
it 'rejects the push' do it 'rejects the push' do
expect(repository.new_blobs(sha_with_smallest_changes)).to be_present
expect do expect do
push_changes("#{Gitlab::Git::BLANK_SHA} #{sha_with_smallest_changes} refs/heads/master") push_changes("#{Gitlab::Git::BLANK_SHA} #{sha_with_smallest_changes} refs/heads/master")
end.to raise_error(described_class::UnauthorizedError, /Your push has been rejected/) end.to raise_error(described_class::UnauthorizedError, /Your push has been rejected/)
...@@ -207,13 +202,7 @@ describe Gitlab::GitAccess do ...@@ -207,13 +202,7 @@ describe Gitlab::GitAccess do
end end
end end
context 'when repository size is below the limit' do shared_examples_for 'a push to repository below the limit' do
before do
allow(project).to receive(:repository_and_lfs_size).and_return(1.megabyte)
project.update_attribute(:repository_size_limit, 2.megabytes)
end
context 'when trying to authenticate the user' do context 'when trying to authenticate the user' do
it 'does not raise an error' do it 'does not raise an error' do
expect { push_changes }.not_to raise_error expect { push_changes }.not_to raise_error
...@@ -229,24 +218,88 @@ describe Gitlab::GitAccess do ...@@ -229,24 +218,88 @@ describe Gitlab::GitAccess do
end.not_to raise_error end.not_to raise_error
end end
end end
end
context 'when new change exceeds the limit' do context 'when GIT_OBJECT_DIRECTORY_RELATIVE env var is set' do
it 'rejects the push' do let(:object_directory_size) { 1.megabyte }
expect(repository.new_blobs(sha_with_2_mb_file)).to be_present
expect do before do
push_changes("#{Gitlab::Git::BLANK_SHA} #{sha_with_2_mb_file} refs/heads/my_branch_2") allow(Gitlab::Git::HookEnv)
end.to raise_error(described_class::UnauthorizedError, /Your push to this repository would cause it to exceed the size limit/) .to receive(:all)
.with(repository.gl_repository)
.and_return({ 'GIT_OBJECT_DIRECTORY_RELATIVE' => 'objects' })
# Stub the object directory size to "simulate" quarantine size
allow(repository)
.to receive(:object_directory_size)
.and_return(object_directory_size)
end
context 'when repository size is over limit' do
let(:repository_size) { 2.megabytes }
let(:repository_size_limit) { 1.megabyte }
it_behaves_like 'a push to repository over the limit'
end
context 'when repository size is below the limit' do
let(:repository_size) { 1.megabyte }
let(:repository_size_limit) { 2.megabytes }
it_behaves_like 'a push to repository below the limit'
context 'when object directory (quarantine) size exceeds the limit' do
let(:object_directory_size) { 2.megabytes }
it 'rejects the push' do
expect do
push_changes("#{Gitlab::Git::BLANK_SHA} #{sha_with_2_mb_file} refs/heads/my_branch_2")
end.to raise_error(described_class::UnauthorizedError, /Your push to this repository would cause it to exceed the size limit/)
end
end
context 'when object directory (quarantine) size does not exceed the limit' do
it 'accepts the push' do
expect do
push_changes("#{Gitlab::Git::BLANK_SHA} #{sha_with_smallest_changes} refs/heads/my_branch_3")
end.not_to raise_error
end
end end
end end
end
context 'when new change does not exceeds the limit' do context 'when GIT_OBJECT_DIRECTORY_RELATIVE env var is not set' do
it 'accepts the push' do context 'when repository size is over limit' do
expect(repository.new_blobs(sha_with_smallest_changes)).to be_present let(:repository_size) { 2.megabytes }
let(:repository_size_limit) { 1.megabyte }
expect do it_behaves_like 'a push to repository over the limit'
push_changes("#{Gitlab::Git::BLANK_SHA} #{sha_with_smallest_changes} refs/heads/my_branch_3") end
end.not_to raise_error
context 'when repository size is below the limit' do
let(:repository_size) { 1.megabyte }
let(:repository_size_limit) { 2.megabytes }
it_behaves_like 'a push to repository below the limit'
context 'when new change exceeds the limit' do
it 'rejects the push' do
expect(repository.new_blobs(sha_with_2_mb_file)).to be_present
expect do
push_changes("#{Gitlab::Git::BLANK_SHA} #{sha_with_2_mb_file} refs/heads/my_branch_2")
end.to raise_error(described_class::UnauthorizedError, /Your push to this repository would cause it to exceed the size limit/)
end
end
context 'when new change does not exceed the limit' do
it 'accepts the push' do
expect(repository.new_blobs(sha_with_smallest_changes)).to be_present
expect do
push_changes("#{Gitlab::Git::BLANK_SHA} #{sha_with_smallest_changes} refs/heads/my_branch_3")
end.not_to raise_error
end
end end
end 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