Merge branch '36743-existing-repo-master' into 'master'

[master] Prevent project creation (blank, import or fork) when repository already exists on disk

See merge request gitlab/gitlabhq!2169
This commit is contained in:
Douwe Maan 2017-08-31 07:03:12 +00:00
commit eacda4cc98
29 changed files with 197 additions and 17 deletions

View file

@ -222,6 +222,7 @@ class Project < ActiveRecord::Base
validates :import_url, importable_url: true, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
validate :can_create_repository?, on: [:create, :update], if: ->(project) { !project.persisted? || project.renamed? }
validate :avatar_type,
if: ->(project) { project.avatar.present? && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
@ -468,7 +469,7 @@ class Project < ActiveRecord::Base
end
def repository_storage_path
Gitlab.config.repositories.storages[repository_storage]['path']
Gitlab.config.repositories.storages[repository_storage].try(:[], 'path')
end
def team
@ -583,7 +584,7 @@ class Project < ActiveRecord::Base
end
def valid_import_url?
valid? || errors.messages[:import_url].nil?
valid?(:import_url) || errors.messages[:import_url].nil?
end
def create_or_update_import_data(data: nil, credentials: nil)
@ -1000,6 +1001,20 @@ class Project < ActiveRecord::Base
end
end
# Check if repository already exists on disk
def can_create_repository?
return false unless repository_storage_path
expires_full_path_cache # we need to clear cache to validate renames correctly
if gitlab_shell.exists?(repository_storage_path, "#{disk_path}.git")
errors.add(:base, 'There is already a repository with that name on disk')
return false
end
true
end
def create_repository(force: false)
# Forked import is handled asynchronously
return if forked? && !force
@ -1486,6 +1501,10 @@ class Project < ActiveRecord::Base
self.storage_version.nil?
end
def renamed?
persisted? && path_changed?
end
private
def storage

View file

@ -36,14 +36,12 @@ module SharedGroup
protected
def is_member_of(username, groupname, role)
@project_count ||= 0
user = User.find_by(name: username) || create(:user, name: username)
group = Group.find_by(name: groupname) || create(:group, name: groupname)
group.add_user(user, role)
project ||= create(:project, :repository, namespace: group, path: "project#{@project_count}")
project ||= create(:project, :repository, namespace: group)
create(:closed_issue_event, project: project)
project.team << [user, :master]
@project_count += 1
end
def owned_group

View file

@ -101,8 +101,6 @@ FactoryGirl.define do
# Test repository - https://gitlab.com/gitlab-org/gitlab-test
trait :repository do
path { 'gitlabhq' }
test_repo
transient do

View file

@ -95,13 +95,13 @@ describe BlobHelper do
it 'returns a link with the proper route' do
link = edit_blob_link(project, 'master', 'README.md')
expect(Capybara.string(link).find_link('Edit')[:href]).to eq('/gitlab/gitlabhq/edit/master/README.md')
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md")
end
it 'returns a link with the passed link_opts on the expected route' do
link = edit_blob_link(project, 'master', 'README.md', link_opts: { mr_id: 10 })
expect(Capybara.string(link).find_link('Edit')[:href]).to eq('/gitlab/gitlabhq/edit/master/README.md?mr_id=10')
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md?mr_id=10")
end
end

View file

@ -17,6 +17,10 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
sign_in(admin)
end
after do
remove_repository(project)
end
it 'blob/show.html.raw' do |example|
get(:show,
namespace_id: project.namespace,

View file

@ -17,6 +17,10 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle
sign_in(admin)
end
after do
remove_repository(project)
end
it 'branches/new_branch.html.raw' do |example|
get :new,
namespace_id: project.namespace.to_param,

View file

@ -17,6 +17,10 @@ describe Dashboard::ProjectsController, '(JavaScript fixtures)', type: :controll
sign_in(admin)
end
after do
remove_repository(project)
end
it 'dashboard/user-callout.html.raw' do |example|
rendered = render_template('shared/_user_callout')
store_frontend_fixture(rendered, example.description)

View file

@ -16,6 +16,10 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control
sign_in(admin)
end
after do
remove_repository(project)
end
render_views
it 'deploy_keys/keys.json' do |example|

View file

@ -17,6 +17,10 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
sign_in(admin)
end
after do
remove_repository(project)
end
it 'issues/open-issue.html.raw' do |example|
render_issue(example.description, create(:issue, project: project))
end

View file

@ -21,6 +21,10 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
sign_in(admin)
end
after do
remove_repository(project)
end
it 'builds/build-with-artifacts.html.raw' do |example|
get :show,
namespace_id: project.namespace.to_param,

View file

@ -19,6 +19,10 @@ describe 'Labels (JavaScript fixtures)' do
clean_frontend_fixtures('labels/')
end
after do
remove_repository(project)
end
describe Groups::LabelsController, '(JavaScript fixtures)', type: :controller do
render_views

View file

@ -37,6 +37,10 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
sign_in(admin)
end
after do
remove_repository(project)
end
it 'merge_requests/merge_request_with_task_list.html.raw' do |example|
create(:ci_build, :pending, pipeline: pipeline)

View file

@ -29,6 +29,10 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type
sign_in(admin)
end
after do
remove_repository(project)
end
it 'merge_request_diffs/inline_changes_tab_with_comments.json' do |example|
create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)

View file

@ -17,6 +17,10 @@ describe ProjectsController, '(JavaScript fixtures)', type: :controller do
sign_in(admin)
end
after do
remove_repository(project)
end
it 'projects/dashboard.html.raw' do |example|
get :show,
namespace_id: project.namespace.to_param,

View file

@ -18,6 +18,10 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
sign_in(admin)
end
after do
remove_repository(project)
end
it 'services/prometheus/prometheus_service.html.raw' do |example|
get :edit,
namespace_id: namespace,

View file

@ -10,6 +10,10 @@ describe 'Raw files', '(JavaScript fixtures)', type: :controller do
clean_frontend_fixtures('blob/notebook/')
end
after do
remove_repository(project)
end
it 'blob/notebook/basic.json' do |example|
blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb')

View file

@ -18,6 +18,10 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
sign_in(admin)
end
after do
remove_repository(project)
end
it 'services/edit_service.html.raw' do |example|
get :edit,
namespace_id: namespace,

View file

@ -18,6 +18,10 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do
sign_in(admin)
end
after do
remove_repository(project)
end
it 'snippets/show.html.raw' do |example|
get(:show, id: snippet.to_param)

View file

@ -15,6 +15,10 @@ describe 'Todos (JavaScript fixtures)' do
clean_frontend_fixtures('todos/')
end
after do
remove_repository(project)
end
describe Dashboard::TodosController, '(JavaScript fixtures)', type: :controller do
render_views

View file

@ -2,7 +2,7 @@ require 'spec_helper'
describe ContainerRegistry::Tag do
let(:group) { create(:group, name: 'group') }
let(:project) { create(:project, :repository, path: 'test', group: group) }
let(:project) { create(:project, path: 'test', group: group) }
let(:repository) do
create(:container_repository, name: '', project: project)

View file

@ -13,7 +13,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
let(:email_raw) { fixture_file('emails/valid_new_issue.eml') }
let(:namespace) { create(:namespace, path: 'gitlabhq') }
let!(:project) { create(:project, :public, :repository, namespace: namespace) }
let!(:project) { create(:project, :public, namespace: namespace, path: 'gitlabhq') }
let!(:user) do
create(
:user,

View file

@ -4,7 +4,7 @@ describe Gitlab::Email::Message::RepositoryPush do
include RepoHelpers
let!(:group) { create(:group, name: 'my_group') }
let!(:project) { create(:project, :repository, name: 'my_project', namespace: group) }
let!(:project) { create(:project, :repository, namespace: group) }
let!(:author) { create(:author, name: 'Author') }
let(:message) do
@ -38,7 +38,7 @@ describe Gitlab::Email::Message::RepositoryPush do
describe '#project_name_with_namespace' do
subject { message.project_name_with_namespace }
it { is_expected.to eq 'my_group / my_project' }
it { is_expected.to eq "#{group.name} / #{project.path}" }
end
describe '#author' do

View file

@ -2,7 +2,7 @@ require 'spec_helper'
describe ContainerRepository do
let(:group) { create(:group, name: 'group') }
let(:project) { create(:project, :repository, path: 'test', group: group) }
let(:project) { create(:project, path: 'test', group: group) }
let(:repository) do
create(:container_repository, name: 'my_image', project: project)

View file

@ -181,7 +181,7 @@ describe Project do
end
end
context 'repository storages inclussion' do
context 'repository storages inclusion' do
let(:project2) { build(:project, repository_storage: 'missing') }
before do

View file

@ -1,11 +1,12 @@
require 'spec_helper'
describe Projects::CreateService, '#execute' do
let(:gitlab_shell) { Gitlab::Shell.new }
let(:user) { create :user }
let(:opts) do
{
name: "GitLab",
namespace: user.namespace
name: 'GitLab',
namespace_id: user.namespace.id
}
end
@ -146,6 +147,41 @@ describe Projects::CreateService, '#execute' do
expect(project.owner).to eq(user)
expect(project.namespace).to eq(user.namespace)
end
context 'when another repository already exists on disk' do
let(:opts) do
{
name: 'Existing',
namespace_id: user.namespace.id
}
end
let(:repository_storage_path) { Gitlab.config.repositories.storages['default']['path'] }
before do
gitlab_shell.add_repository(repository_storage_path, "#{user.namespace.full_path}/existing")
end
after do
gitlab_shell.remove_repository(repository_storage_path, "#{user.namespace.full_path}/existing")
end
it 'does not allow to create project with same path' do
project = create_project(user, opts)
expect(project).to respond_to(:errors)
expect(project.errors.messages).to have_key(:base)
expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
end
it 'does not allow to import a project with the same path' do
project = create_project(user, opts.merge({ import_url: 'https://gitlab.com/gitlab-org/gitlab-test.git' }))
expect(project).to respond_to(:errors)
expect(project.errors.messages).to have_key(:base)
expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
end
end
end
context 'when there is an active service template' do

View file

@ -1,6 +1,8 @@
require 'spec_helper'
describe Projects::ForkService do
let(:gitlab_shell) { Gitlab::Shell.new }
describe 'fork by user' do
before do
@from_user = create(:user)
@ -73,6 +75,26 @@ describe Projects::ForkService do
end
end
context 'repository already exists' do
let(:repository_storage_path) { Gitlab.config.repositories.storages['default']['path'] }
before do
gitlab_shell.add_repository(repository_storage_path, "#{@to_user.namespace.full_path}/#{@from_project.path}")
end
after do
gitlab_shell.remove_repository(repository_storage_path, "#{@to_user.namespace.full_path}/#{@from_project.path}")
end
it 'does not allow creation' do
to_project = fork_project(@from_project, @to_user)
expect(to_project).not_to be_persisted
expect(to_project.errors.messages).to have_key(:base)
expect(to_project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
end
end
context 'GitLab CI is enabled' do
it "forks and enables CI for fork" do
@from_project.enable_ci

View file

@ -1,6 +1,7 @@
require 'spec_helper'
describe Projects::TransferService do
let(:gitlab_shell) { Gitlab::Shell.new }
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, :repository, namespace: user.namespace) }
@ -119,6 +120,25 @@ describe Projects::TransferService do
it { expect(project.namespace).to eq(user.namespace) }
end
context 'namespace which contains orphan repository with same projects path name' do
let(:repository_storage_path) { Gitlab.config.repositories.storages['default']['path'] }
before do
group.add_owner(user)
gitlab_shell.add_repository(repository_storage_path, "#{group.full_path}/#{project.path}")
@result = transfer_project(project, user, group)
end
after do
gitlab_shell.remove_repository(repository_storage_path, "#{group.full_path}/#{project.path}")
end
it { expect(@result).to eq false }
it { expect(project.namespace).to eq(user.namespace) }
it { expect(project.errors[:new_namespace]).to include('Cannot move project') }
end
def transfer_project(project, user, new_namespace)
service = Projects::TransferService.new(project, user)

View file

@ -1,6 +1,7 @@
require 'spec_helper'
describe Projects::UpdateService, '#execute' do
let(:gitlab_shell) { Gitlab::Shell.new }
let(:user) { create(:user) }
let(:admin) { create(:admin) }
@ -132,6 +133,28 @@ describe Projects::UpdateService, '#execute' do
end
end
context 'when renaming a project' do
let(:repository_storage_path) { Gitlab.config.repositories.storages['default']['path'] }
before do
gitlab_shell.add_repository(repository_storage_path, "#{user.namespace.full_path}/existing")
end
after do
gitlab_shell.remove_repository(repository_storage_path, "#{user.namespace.full_path}/existing")
end
it 'does not allow renaming when new path matches existing repository on disk' do
result = update_project(project, admin, path: 'existing')
expect(result).to include(status: :error)
expect(result[:message]).to match('Project could not be updated!')
expect(project).not_to be_valid
expect(project.errors.messages).to have_key(:base)
expect(project.errors.messages[:base]).to include('There is already a repository with that name on disk')
end
end
context 'when passing invalid parameters' do
it 'returns an error result when record cannot be updated' do
result = update_project(project, admin, { name: 'foo&bar' })

View file

@ -31,6 +31,10 @@ module JavaScriptFixturesHelpers
File.write(fixture_file_name, fixture)
end
def remove_repository(project)
Gitlab::Shell.new.remove_repository(project.repository_storage_path, project.disk_path)
end
private
# Private: Prepare a response object for use as a frontend fixture