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:
commit
eacda4cc98
29 changed files with 197 additions and 17 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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' })
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue