Commit eacda4cc authored by Douwe Maan's avatar Douwe Maan

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
parents f7c8434c a882026b
...@@ -222,6 +222,7 @@ class Project < ActiveRecord::Base ...@@ -222,6 +222,7 @@ class Project < ActiveRecord::Base
validates :import_url, importable_url: true, if: [:external_import?, :import_url_changed?] validates :import_url, importable_url: true, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 } validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create validate :check_limit, on: :create
validate :can_create_repository?, on: [:create, :update], if: ->(project) { !project.persisted? || project.renamed? }
validate :avatar_type, validate :avatar_type,
if: ->(project) { project.avatar.present? && project.avatar_changed? } if: ->(project) { project.avatar.present? && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
...@@ -468,7 +469,7 @@ class Project < ActiveRecord::Base ...@@ -468,7 +469,7 @@ class Project < ActiveRecord::Base
end end
def repository_storage_path def repository_storage_path
Gitlab.config.repositories.storages[repository_storage]['path'] Gitlab.config.repositories.storages[repository_storage].try(:[], 'path')
end end
def team def team
...@@ -583,7 +584,7 @@ class Project < ActiveRecord::Base ...@@ -583,7 +584,7 @@ class Project < ActiveRecord::Base
end end
def valid_import_url? def valid_import_url?
valid? || errors.messages[:import_url].nil? valid?(:import_url) || errors.messages[:import_url].nil?
end end
def create_or_update_import_data(data: nil, credentials: nil) def create_or_update_import_data(data: nil, credentials: nil)
...@@ -1000,6 +1001,20 @@ class Project < ActiveRecord::Base ...@@ -1000,6 +1001,20 @@ class Project < ActiveRecord::Base
end end
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) def create_repository(force: false)
# Forked import is handled asynchronously # Forked import is handled asynchronously
return if forked? && !force return if forked? && !force
...@@ -1486,6 +1501,10 @@ class Project < ActiveRecord::Base ...@@ -1486,6 +1501,10 @@ class Project < ActiveRecord::Base
self.storage_version.nil? self.storage_version.nil?
end end
def renamed?
persisted? && path_changed?
end
private private
def storage def storage
......
...@@ -36,14 +36,12 @@ module SharedGroup ...@@ -36,14 +36,12 @@ module SharedGroup
protected protected
def is_member_of(username, groupname, role) def is_member_of(username, groupname, role)
@project_count ||= 0
user = User.find_by(name: username) || create(:user, name: username) user = User.find_by(name: username) || create(:user, name: username)
group = Group.find_by(name: groupname) || create(:group, name: groupname) group = Group.find_by(name: groupname) || create(:group, name: groupname)
group.add_user(user, role) 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) create(:closed_issue_event, project: project)
project.team << [user, :master] project.team << [user, :master]
@project_count += 1
end end
def owned_group def owned_group
......
...@@ -101,8 +101,6 @@ FactoryGirl.define do ...@@ -101,8 +101,6 @@ FactoryGirl.define do
# Test repository - https://gitlab.com/gitlab-org/gitlab-test # Test repository - https://gitlab.com/gitlab-org/gitlab-test
trait :repository do trait :repository do
path { 'gitlabhq' }
test_repo test_repo
transient do transient do
......
...@@ -95,13 +95,13 @@ describe BlobHelper do ...@@ -95,13 +95,13 @@ describe BlobHelper do
it 'returns a link with the proper route' do it 'returns a link with the proper route' do
link = edit_blob_link(project, 'master', 'README.md') 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 end
it 'returns a link with the passed link_opts on the expected route' do 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 }) 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
end end
......
...@@ -17,6 +17,10 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do ...@@ -17,6 +17,10 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
sign_in(admin) sign_in(admin)
end end
after do
remove_repository(project)
end
it 'blob/show.html.raw' do |example| it 'blob/show.html.raw' do |example|
get(:show, get(:show,
namespace_id: project.namespace, namespace_id: project.namespace,
......
...@@ -17,6 +17,10 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle ...@@ -17,6 +17,10 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle
sign_in(admin) sign_in(admin)
end end
after do
remove_repository(project)
end
it 'branches/new_branch.html.raw' do |example| it 'branches/new_branch.html.raw' do |example|
get :new, get :new,
namespace_id: project.namespace.to_param, namespace_id: project.namespace.to_param,
......
...@@ -17,6 +17,10 @@ describe Dashboard::ProjectsController, '(JavaScript fixtures)', type: :controll ...@@ -17,6 +17,10 @@ describe Dashboard::ProjectsController, '(JavaScript fixtures)', type: :controll
sign_in(admin) sign_in(admin)
end end
after do
remove_repository(project)
end
it 'dashboard/user-callout.html.raw' do |example| it 'dashboard/user-callout.html.raw' do |example|
rendered = render_template('shared/_user_callout') rendered = render_template('shared/_user_callout')
store_frontend_fixture(rendered, example.description) store_frontend_fixture(rendered, example.description)
......
...@@ -16,6 +16,10 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control ...@@ -16,6 +16,10 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control
sign_in(admin) sign_in(admin)
end end
after do
remove_repository(project)
end
render_views render_views
it 'deploy_keys/keys.json' do |example| it 'deploy_keys/keys.json' do |example|
......
...@@ -17,6 +17,10 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller ...@@ -17,6 +17,10 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
sign_in(admin) sign_in(admin)
end end
after do
remove_repository(project)
end
it 'issues/open-issue.html.raw' do |example| it 'issues/open-issue.html.raw' do |example|
render_issue(example.description, create(:issue, project: project)) render_issue(example.description, create(:issue, project: project))
end end
......
...@@ -21,6 +21,10 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do ...@@ -21,6 +21,10 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
sign_in(admin) sign_in(admin)
end end
after do
remove_repository(project)
end
it 'builds/build-with-artifacts.html.raw' do |example| it 'builds/build-with-artifacts.html.raw' do |example|
get :show, get :show,
namespace_id: project.namespace.to_param, namespace_id: project.namespace.to_param,
......
...@@ -19,6 +19,10 @@ describe 'Labels (JavaScript fixtures)' do ...@@ -19,6 +19,10 @@ describe 'Labels (JavaScript fixtures)' do
clean_frontend_fixtures('labels/') clean_frontend_fixtures('labels/')
end end
after do
remove_repository(project)
end
describe Groups::LabelsController, '(JavaScript fixtures)', type: :controller do describe Groups::LabelsController, '(JavaScript fixtures)', type: :controller do
render_views render_views
......
...@@ -37,6 +37,10 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont ...@@ -37,6 +37,10 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
sign_in(admin) sign_in(admin)
end end
after do
remove_repository(project)
end
it 'merge_requests/merge_request_with_task_list.html.raw' do |example| it 'merge_requests/merge_request_with_task_list.html.raw' do |example|
create(:ci_build, :pending, pipeline: pipeline) create(:ci_build, :pending, pipeline: pipeline)
......
...@@ -29,6 +29,10 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type ...@@ -29,6 +29,10 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type
sign_in(admin) sign_in(admin)
end end
after do
remove_repository(project)
end
it 'merge_request_diffs/inline_changes_tab_with_comments.json' do |example| 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(: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) create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
......
...@@ -17,6 +17,10 @@ describe ProjectsController, '(JavaScript fixtures)', type: :controller do ...@@ -17,6 +17,10 @@ describe ProjectsController, '(JavaScript fixtures)', type: :controller do
sign_in(admin) sign_in(admin)
end end
after do
remove_repository(project)
end
it 'projects/dashboard.html.raw' do |example| it 'projects/dashboard.html.raw' do |example|
get :show, get :show,
namespace_id: project.namespace.to_param, namespace_id: project.namespace.to_param,
......
...@@ -18,6 +18,10 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle ...@@ -18,6 +18,10 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
sign_in(admin) sign_in(admin)
end end
after do
remove_repository(project)
end
it 'services/prometheus/prometheus_service.html.raw' do |example| it 'services/prometheus/prometheus_service.html.raw' do |example|
get :edit, get :edit,
namespace_id: namespace, namespace_id: namespace,
......
...@@ -10,6 +10,10 @@ describe 'Raw files', '(JavaScript fixtures)', type: :controller do ...@@ -10,6 +10,10 @@ describe 'Raw files', '(JavaScript fixtures)', type: :controller do
clean_frontend_fixtures('blob/notebook/') clean_frontend_fixtures('blob/notebook/')
end end
after do
remove_repository(project)
end
it 'blob/notebook/basic.json' do |example| it 'blob/notebook/basic.json' do |example|
blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb') blob = project.repository.blob_at('6d85bb69', 'files/ipython/basic.ipynb')
......
...@@ -18,6 +18,10 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle ...@@ -18,6 +18,10 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
sign_in(admin) sign_in(admin)
end end
after do
remove_repository(project)
end
it 'services/edit_service.html.raw' do |example| it 'services/edit_service.html.raw' do |example|
get :edit, get :edit,
namespace_id: namespace, namespace_id: namespace,
......
...@@ -18,6 +18,10 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do ...@@ -18,6 +18,10 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do
sign_in(admin) sign_in(admin)
end end
after do
remove_repository(project)
end
it 'snippets/show.html.raw' do |example| it 'snippets/show.html.raw' do |example|
get(:show, id: snippet.to_param) get(:show, id: snippet.to_param)
......
...@@ -15,6 +15,10 @@ describe 'Todos (JavaScript fixtures)' do ...@@ -15,6 +15,10 @@ describe 'Todos (JavaScript fixtures)' do
clean_frontend_fixtures('todos/') clean_frontend_fixtures('todos/')
end end
after do
remove_repository(project)
end
describe Dashboard::TodosController, '(JavaScript fixtures)', type: :controller do describe Dashboard::TodosController, '(JavaScript fixtures)', type: :controller do
render_views render_views
......
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe ContainerRegistry::Tag do describe ContainerRegistry::Tag do
let(:group) { create(:group, name: 'group') } 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 let(:repository) do
create(:container_repository, name: '', project: project) create(:container_repository, name: '', project: project)
......
...@@ -13,7 +13,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do ...@@ -13,7 +13,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
let(:email_raw) { fixture_file('emails/valid_new_issue.eml') } let(:email_raw) { fixture_file('emails/valid_new_issue.eml') }
let(:namespace) { create(:namespace, path: 'gitlabhq') } 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 let!(:user) do
create( create(
:user, :user,
......
...@@ -4,7 +4,7 @@ describe Gitlab::Email::Message::RepositoryPush do ...@@ -4,7 +4,7 @@ describe Gitlab::Email::Message::RepositoryPush do
include RepoHelpers include RepoHelpers
let!(:group) { create(:group, name: 'my_group') } 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!(:author) { create(:author, name: 'Author') }
let(:message) do let(:message) do
...@@ -38,7 +38,7 @@ describe Gitlab::Email::Message::RepositoryPush do ...@@ -38,7 +38,7 @@ describe Gitlab::Email::Message::RepositoryPush do
describe '#project_name_with_namespace' do describe '#project_name_with_namespace' do
subject { message.project_name_with_namespace } 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 end
describe '#author' do describe '#author' do
......
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe ContainerRepository do describe ContainerRepository do
let(:group) { create(:group, name: 'group') } 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 let(:repository) do
create(:container_repository, name: 'my_image', project: project) create(:container_repository, name: 'my_image', project: project)
......
...@@ -181,7 +181,7 @@ describe Project do ...@@ -181,7 +181,7 @@ describe Project do
end end
end end
context 'repository storages inclussion' do context 'repository storages inclusion' do
let(:project2) { build(:project, repository_storage: 'missing') } let(:project2) { build(:project, repository_storage: 'missing') }
before do before do
......
require 'spec_helper' require 'spec_helper'
describe Projects::CreateService, '#execute' do describe Projects::CreateService, '#execute' do
let(:gitlab_shell) { Gitlab::Shell.new }
let(:user) { create :user } let(:user) { create :user }
let(:opts) do let(:opts) do
{ {
name: "GitLab", name: 'GitLab',
namespace: user.namespace namespace_id: user.namespace.id
} }
end end
...@@ -146,6 +147,41 @@ describe Projects::CreateService, '#execute' do ...@@ -146,6 +147,41 @@ describe Projects::CreateService, '#execute' do
expect(project.owner).to eq(user) expect(project.owner).to eq(user)
expect(project.namespace).to eq(user.namespace) expect(project.namespace).to eq(user.namespace)
end 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 end
context 'when there is an active service template' do context 'when there is an active service template' do
......
require 'spec_helper' require 'spec_helper'
describe Projects::ForkService do describe Projects::ForkService do
let(:gitlab_shell) { Gitlab::Shell.new }
describe 'fork by user' do describe 'fork by user' do
before do before do
@from_user = create(:user) @from_user = create(:user)
...@@ -73,6 +75,26 @@ describe Projects::ForkService do ...@@ -73,6 +75,26 @@ describe Projects::ForkService do
end end
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 context 'GitLab CI is enabled' do
it "forks and enables CI for fork" do it "forks and enables CI for fork" do
@from_project.enable_ci @from_project.enable_ci
......
require 'spec_helper' require 'spec_helper'
describe Projects::TransferService do describe Projects::TransferService do
let(:gitlab_shell) { Gitlab::Shell.new }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
let(:project) { create(:project, :repository, namespace: user.namespace) } let(:project) { create(:project, :repository, namespace: user.namespace) }
...@@ -119,6 +120,25 @@ describe Projects::TransferService do ...@@ -119,6 +120,25 @@ describe Projects::TransferService do
it { expect(project.namespace).to eq(user.namespace) } it { expect(project.namespace).to eq(user.namespace) }
end 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) def transfer_project(project, user, new_namespace)
service = Projects::TransferService.new(project, user) service = Projects::TransferService.new(project, user)
......
require 'spec_helper' require 'spec_helper'
describe Projects::UpdateService, '#execute' do describe Projects::UpdateService, '#execute' do
let(:gitlab_shell) { Gitlab::Shell.new }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
...@@ -132,6 +133,28 @@ describe Projects::UpdateService, '#execute' do ...@@ -132,6 +133,28 @@ describe Projects::UpdateService, '#execute' do
end end
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 context 'when passing invalid parameters' do
it 'returns an error result when record cannot be updated' do it 'returns an error result when record cannot be updated' do
result = update_project(project, admin, { name: 'foo&bar' }) result = update_project(project, admin, { name: 'foo&bar' })
......
...@@ -31,6 +31,10 @@ module JavaScriptFixturesHelpers ...@@ -31,6 +31,10 @@ module JavaScriptFixturesHelpers
File.write(fixture_file_name, fixture) File.write(fixture_file_name, fixture)
end end
def remove_repository(project)
Gitlab::Shell.new.remove_repository(project.repository_storage_path, project.disk_path)
end
private private
# Private: Prepare a response object for use as a frontend fixture # Private: Prepare a response object for use as a frontend fixture
......
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