diff --git a/app/models/project.rb b/app/models/project.rb index a63c40deeac50745b1e0d53f3add76bf6ee11db2..374b825a6e2708600f78a2f38736faeabc8315e3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -131,13 +131,15 @@ class Project < ApplicationRecord # Storage specific hooks after_initialize :use_hashed_storage after_initialize :set_project_feature_defaults, if: :new_record? - before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } + before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } before_validation :ensure_project_namespace_in_sync before_validation :set_package_registry_access_level, if: :packages_enabled_changed? before_validation :remove_leading_spaces_on_name before_validation :set_last_activity_at + after_validation :check_pending_delete + before_save :ensure_runners_token after_create -> { create_or_load_association(:project_feature) } diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 37588800439531ec16dd25a718abb309155abf5d..3dcc116b30530d9686d0e6e2508b0b199de15a6e 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -56,6 +56,9 @@ def execute namespace_id = params[:namespace_id] || current_user.namespace_id @project.namespace_id = namespace_id.to_i + organization_id = params[:organization_id] || @project.namespace.organization_id + @project.organization_id = organization_id.to_i + @project.check_personal_projects_limit return @project if @project.errors.any? diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb index 63a41d172eab3a72064b7f7b0749d1ba8afebb88..1bef3330599941f5445eef2e5c3ac1c54990a6c6 100644 --- a/app/services/projects/gitlab_projects_import_service.rb +++ b/app/services/projects/gitlab_projects_import_service.rb @@ -72,6 +72,8 @@ def prepare_import_params params[:import_type] = 'gitlab_project' end + params[:organization_id] = current_namespace.organization_id + params[:import_data] = { data: data } if data.present? end end diff --git a/ee/app/services/registrations/standard_namespace_create_service.rb b/ee/app/services/registrations/standard_namespace_create_service.rb index 5d51740f293ed733244fd1b7541484bef6ad8fc4..b207247028638d30a571630d998ec11f807fd8ce 100644 --- a/ee/app/services/registrations/standard_namespace_create_service.rb +++ b/ee/app/services/registrations/standard_namespace_create_service.rb @@ -47,7 +47,10 @@ def create_project_params end def project_params(*extra) - params.require(:project).permit(project_params_attributes + extra).merge(namespace_id: group.id) + params + .require(:project) + .permit(project_params_attributes + extra) + .merge(namespace_id: group.id, organization_id: group.organization_id) end def project_params_attributes diff --git a/ee/app/services/security/security_orchestration_policies/project_create_service.rb b/ee/app/services/security/security_orchestration_policies/project_create_service.rb index e29907872c135d3beb3b088b5414b29bed16599d..cc0a5caaf8955b199a3df9db33e4c31714f825a9 100644 --- a/ee/app/services/security/security_orchestration_policies/project_create_service.rb +++ b/ee/app/services/security/security_orchestration_policies/project_create_service.rb @@ -63,6 +63,7 @@ def create_project_params name: "#{container.name} - Security policy project", description: "This project is automatically generated to manage security policies for the project.", namespace_id: namespace_id, + organization_id: namespace.organization_id, initialize_with_readme: true, container_registry_enabled: false, packages_enabled: false, diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb index db75b6e519030e9a1a824937558625511096ee23..2a5c6951073468b3022debd616827debe6085a61 100644 --- a/lib/gitlab/bitbucket_import/project_creator.rb +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -20,6 +20,7 @@ def execute path: name, description: repo.description, namespace_id: namespace.id, + organization_id: namespace.organization_id, visibility_level: repo.visibility_level, import_type: 'bitbucket', import_source: repo.full_name, diff --git a/lib/gitlab/bitbucket_server_import/project_creator.rb b/lib/gitlab/bitbucket_server_import/project_creator.rb index e856484ca89e35b66ea54a342b67a2d3927aff73..2833b6fa2007f63333e5bb8a299aee80c8ae867d 100644 --- a/lib/gitlab/bitbucket_server_import/project_creator.rb +++ b/lib/gitlab/bitbucket_server_import/project_creator.rb @@ -26,6 +26,7 @@ def execute path: name, description: repo.description, namespace_id: namespace.id, + organization_id: namespace.organization_id, visibility_level: repo.visibility_level, import_type: 'bitbucket_server', import_source: repo.browse_url, diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb index a5e6356eb177cdaa2f21065b33024ed78a1933a1..b7a6467314a9000500f459b0c27679b371575a17 100644 --- a/lib/gitlab/fogbugz_import/project_creator.rb +++ b/lib/gitlab/fogbugz_import/project_creator.rb @@ -20,6 +20,7 @@ def execute name: name, path: name, namespace_id: namespace.id, + organization_id: namespace.organization_id, creator: current_user, visibility_level: Gitlab::VisibilityLevel::PRIVATE, import_type: 'fogbugz', diff --git a/lib/gitlab/git_access_project.rb b/lib/gitlab/git_access_project.rb index b007a957348332d250f35f599a286de2831a2335..a6c956ca0c35fcc7ebcad87891dd67ab94ac8e73 100644 --- a/lib/gitlab/git_access_project.rb +++ b/lib/gitlab/git_access_project.rb @@ -58,6 +58,7 @@ def ensure_project_on_push! project_params = { path: project_path, namespace_id: namespace.id, + organization_id: namespace.organization_id, visibility_level: Gitlab::VisibilityLevel::PRIVATE } diff --git a/lib/gitlab/legacy_github_import/project_creator.rb b/lib/gitlab/legacy_github_import/project_creator.rb index 01e04fa9c81ef4e99584d5963007fedf0c0b3da4..53c4d767ce39a54103f7106996dc3859686e08c5 100644 --- a/lib/gitlab/legacy_github_import/project_creator.rb +++ b/lib/gitlab/legacy_github_import/project_creator.rb @@ -20,6 +20,7 @@ def execute(extra_attrs = {}) path: name, description: repo[:description], namespace_id: namespace.id, + organization_id: namespace.organization_id, visibility_level: visibility_level, import_type: type, import_source: repo[:full_name], diff --git a/lib/gitlab/manifest_import/project_creator.rb b/lib/gitlab/manifest_import/project_creator.rb index 6637cbb9cc855a062f20aaf89f46304c0df50c66..70f99c34a0fc08dce33a9aec3dd788d63f3d4cf3 100644 --- a/lib/gitlab/manifest_import/project_creator.rb +++ b/lib/gitlab/manifest_import/project_creator.rb @@ -21,6 +21,7 @@ def execute import_source: repository[:url], import_type: 'manifest', namespace_id: group.id, + organization_id: group.organization_id, path: project_path, name: project_path, visibility_level: destination.visibility_level diff --git a/lib/gitlab/seeders/ci/catalog/resource_seeder.rb b/lib/gitlab/seeders/ci/catalog/resource_seeder.rb index c29075cff320626d79842ba7abe7be8e5e607a9f..1a14d027fe9f28b81d35eb28d3cc2e44624c1956 100644 --- a/lib/gitlab/seeders/ci/catalog/resource_seeder.rb +++ b/lib/gitlab/seeders/ci/catalog/resource_seeder.rb @@ -33,6 +33,7 @@ def create_project(name, index) description: "This is Catalog resource ##{index}", name: name, namespace_id: @group.id, + organization_id: @group.organization_id, path: name, visibility_level: @group.visibility_level ).execute diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index 00b8ca0a4ff319fd427d98227fb69bc03c71005c..b66ad8f1e3c2304d63cda9d57b1e475b09f30c22 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -65,7 +65,7 @@ merge_request_2 = create(:merge_request, source_project: project_2) create(:todo, project: project_2, author: author, user: user, target: merge_request_2) - expect { get :index }.not_to exceed_query_limit(control) + expect { get :index }.not_to exceed_query_limit(control).with_threshold(1) expect(response).to have_gitlab_http_status(:ok) end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index db2ec3b23d6e0ae9f086b55fb749c308e44e08a5..5e55ad239b7dbb9d8f6bb6ba4ce5c77d0cb25573 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -16,6 +16,7 @@ has_external_wiki { false } # Associations + organization { namespace&.organization } namespace creator { group ? association(:user) : namespace&.owner } @@ -99,6 +100,7 @@ name: evaluator.name, path: evaluator.path, parent: evaluator.namespace, + organization: evaluator.organization, shared_runners_enabled: evaluator.shared_runners_enabled, visibility_level: evaluator.visibility_level } diff --git a/spec/lib/gitlab/database/schema_cache_with_renamed_table_spec.rb b/spec/lib/gitlab/database/schema_cache_with_renamed_table_spec.rb index 0bea348e6b4ddefe4f7914eeecb12ea387f9b0f3..d9e6d1220bae7a6690235e5f4a1d0e025c58f7b5 100644 --- a/spec/lib/gitlab/database/schema_cache_with_renamed_table_spec.rb +++ b/spec/lib/gitlab/database/schema_cache_with_renamed_table_spec.rb @@ -68,7 +68,10 @@ describe 'when the table behind a model is actually a view' do let(:group) { create(:group) } - let(:attrs) { attributes_for(:project, namespace_id: group.id, project_namespace_id: group.id).except(:creator) } + let(:attrs) do + attributes_for(:project, namespace_id: group.id, project_namespace_id: group.id).except(:creator, :organization) + end + let(:record) { old_model.create!(attrs) } it 'can persist records' do diff --git a/spec/models/preloaders/users_max_access_level_by_project_preloader_spec.rb b/spec/models/preloaders/users_max_access_level_by_project_preloader_spec.rb index 73095560b97764df92e01f99bfe4e043a7907ac7..4129d14125207433a69c856840266014bff8773c 100644 --- a/spec/models/preloaders/users_max_access_level_by_project_preloader_spec.rb +++ b/spec/models/preloaders/users_max_access_level_by_project_preloader_spec.rb @@ -44,7 +44,7 @@ end end - expect(policy_queries).not_to exceed_query_limit(0) + expect(policy_queries).not_to exceed_query_limit(1) end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index c23b94a31e7b84217d7a8e98ebca4304154dd7d3..be486fb567606c428b925d6bc1ed769988411f77 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -672,10 +672,7 @@ end describe 'validation' do - let!(:project) { create(:project) } - it { is_expected.to validate_presence_of(:name) } - it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) } it { is_expected.to validate_length_of(:name).is_at_most(255) } it { is_expected.to allow_value('space last ').for(:name) } it { is_expected.not_to allow_value('colon:in:path').for(:path) } # This is to validate that a specially crafted name cannot bypass a pattern match. See !72555 @@ -692,6 +689,12 @@ it { is_expected.to validate_numericality_of(:max_artifacts_size).only_integer.is_greater_than(0) } it { is_expected.to validate_length_of(:suggestion_commit_message).is_at_most(255) } + it 'validates name is case-sensitively unique within the scope of namespace_id' do + project = create(:project) + + expect(project).to validate_uniqueness_of(:name).scoped_to(:namespace_id) + end + it 'validates build timeout constraints' do is_expected.to validate_numericality_of(:build_timeout) .only_integer @@ -9278,7 +9281,8 @@ def create_hook context 'with loose foreign key on organization_id' do it_behaves_like 'cleanup by a loose foreign key' do let_it_be(:parent) { create(:organization) } - let_it_be(:model) { create(:project, organization: parent) } + let_it_be(:group) { create(:group, organization: parent) } + let_it_be(:model) { create(:project, group: group, organization: parent) } end end diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb index dff3205f55d9dbe85fca9e9b84affe291d8f50f6..aa0ea390b14e0e7faa4d4fb9e10b5cca488826d4 100644 --- a/spec/requests/api/invitations_spec.rb +++ b/spec/requests/api/invitations_spec.rb @@ -435,7 +435,7 @@ def invite_member_by_email(source, source_type, email, created_by, access_level: emails = 'email3@example.com,email4@example.com,email5@example.com,email6@example.com,email7@example.com,' \ 'EMAIL8@EXamPle.com' - unresolved_n_plus_ones = 73 # currently there are 10 queries added per email + unresolved_n_plus_ones = 77 # currently there are 10 queries added per email expect do post invitations_url(project, maintainer), params: { email: emails, access_level: Member::DEVELOPER } diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index cb8dc38a8bfcf648c5143b09978fc41a1fdf6052..e53a1bebcf62ee7d923edba337b05efe25a2835f 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -1117,6 +1117,10 @@ def request it_behaves_like 'POST /:source_type/:id/members', 'project' do let(:source) { project } + + before do + allow(Gitlab::QueryLimiting::Transaction).to receive(:threshold).and_return(102) + end end it_behaves_like 'POST /:source_type/:id/members', 'group' do diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb index 9bc4be936955f41639289b59a91d291852f57705..a86e77319d4163721a9dcd9233aae737551edf3b 100644 --- a/spec/requests/api/project_import_spec.rb +++ b/spec/requests/api/project_import_spec.rb @@ -64,7 +64,7 @@ it 'executes a limited number of queries', :use_clean_rails_redis_caching do control = ActiveRecord::QueryRecorder.new { perform_archive_upload } - expect(control.count).to be <= 111 + expect(control.count).to be <= 113 end it 'schedules an import using a namespace' do diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 591ba49812194298127546a3c795a98a0542b979..68d18200df705e7e892d65ecc3956636c84f9273 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -229,6 +229,10 @@ let(:path) { "#{user.namespace.path}/new-project.git" } context 'when authenticated' do + before do + allow(Gitlab::QueryLimiting::Transaction).to receive(:threshold).and_return(102) + end + it 'creates a new project under the existing namespace' do # current scenario does not matter with the user activity case, # so stub/double it to escape more sql running times limit diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index a328cc905c5b8a6959f771ca294a326da841fd85..548a56d9260b9c0f9d4d271dd885a438f58aecc5 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Projects::CreateService, '#execute', feature_category: :groups_and_projects do include ExternalAuthorizationServiceHelpers - let(:user) { create :user } + let_it_be(:user) { create(:user) } let(:project_name) { 'GitLab' } let(:opts) do { @@ -86,6 +86,28 @@ end end + describe 'setting organization' do + subject(:project) { create_project(user, opts) } + + context 'with group namespace' do + let_it_be(:namespace) { create(:group) } + + before do + opts[:namespace_id] = namespace.id + end + + it 'sets correct organization' do + expect(project.organization).to eq(namespace.organization) + end + end + + context 'with user namespace' do + it 'sets correct organization' do + expect(project.organization).to eq(user.namespace.organization) + end + end + end + describe 'topics' do subject(:project) { create_project(user, opts) } diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb index 218c7f89e34c7b3e3b170e6219d29d7853ac02c7..fc9aa9275f9fa79baa6d682bf413115f0cc123d2 100644 --- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb @@ -10,8 +10,8 @@ let_it_be(:inherited_reporter) { create(:user) } let_it_be(:inherited_developer) { create(:user) } let_it_be(:inherited_maintainer) { create(:user) } - let_it_be(:owner) { create(:user) } let_it_be(:organization) { create(:organization, :default) } + let_it_be(:owner) { create(:user, namespace: create(:user_namespace, organization: organization)) } let_it_be(:organization_owner) { create(:user, :organization_owner) } let_it_be(:admin) { create(:admin) } let_it_be(:non_member) { create(:user) }