Commit e797df72 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 1d64791c5762
# frozen_string_literal: true
module Gitlab
module ImportExport
class ProjectRelationFactory < BaseRelationFactory
prepend_if_ee('::EE::Gitlab::ImportExport::ProjectRelationFactory') # rubocop: disable Cop/InjectEnterpriseEditionModule
OVERRIDES = { snippets: :project_snippets,
ci_pipelines: 'Ci::Pipeline',
pipelines: 'Ci::Pipeline',
stages: 'Ci::Stage',
statuses: 'commit_status',
triggers: 'Ci::Trigger',
pipeline_schedules: 'Ci::PipelineSchedule',
builds: 'Ci::Build',
runners: 'Ci::Runner',
hooks: 'ProjectHook',
merge_access_levels: 'ProtectedBranch::MergeAccessLevel',
push_access_levels: 'ProtectedBranch::PushAccessLevel',
create_access_levels: 'ProtectedTag::CreateAccessLevel',
labels: :project_labels,
priorities: :label_priorities,
auto_devops: :project_auto_devops,
label: :project_label,
custom_attributes: 'ProjectCustomAttribute',
project_badges: 'Badge',
metrics: 'MergeRequest::Metrics',
ci_cd_settings: 'ProjectCiCdSetting',
error_tracking_setting: 'ErrorTracking::ProjectErrorTrackingSetting',
links: 'Releases::Link',
metrics_setting: 'ProjectMetricsSetting' }.freeze
BUILD_MODELS = %i[Ci::Build commit_status].freeze
GROUP_REFERENCES = %w[group_id].freeze
PROJECT_REFERENCES = %w[project_id source_project_id target_project_id].freeze
EXISTING_OBJECT_RELATIONS = %i[
milestone
milestones
label
labels
project_label
project_labels
group_label
group_labels
project_feature
merge_request
epic
ProjectCiCdSetting
container_expiration_policy
].freeze
def create
@object = super
# We preload the project, user, and group to re-use objects
@object = preload_keys(@object, PROJECT_REFERENCES, @importable)
@object = preload_keys(@object, GROUP_REFERENCES, @importable.group)
@object = preload_keys(@object, USER_REFERENCES, @user)
end
private
def invalid_relation?
# Do not create relation if it is:
# - An unknown service
# - A legacy trigger
unknown_service? ||
(!Feature.enabled?(:use_legacy_pipeline_triggers, @importable) && legacy_trigger?)
end
def setup_models
case @relation_name
when :merge_request_diff_files then setup_diff
when :notes then setup_note
when :'Ci::Pipeline' then setup_pipeline
when *BUILD_MODELS then setup_build
end
update_project_references
update_group_references
end
def generate_imported_object
if @relation_name == :merge_requests
MergeRequestParser.new(@importable, @relation_hash.delete('diff_head_sha'), super, @relation_hash).parse!
else
super
end
end
def update_project_references
# If source and target are the same, populate them with the new project ID.
if @relation_hash['source_project_id']
@relation_hash['source_project_id'] = same_source_and_target? ? @relation_hash['project_id'] : MergeRequestParser::FORKED_PROJECT_ID
end
@relation_hash['target_project_id'] = @relation_hash['project_id'] if @relation_hash['target_project_id']
end
def same_source_and_target?
@relation_hash['target_project_id'] && @relation_hash['target_project_id'] == @relation_hash['source_project_id']
end
def update_group_references
return unless existing_object?
return unless @relation_hash['group_id']
@relation_hash['group_id'] = @importable.namespace_id
end
# This code is a workaround for broken project exports that don't
# export merge requests with CI pipelines (i.e. exports that were
# generated from
# https://gitlab.com/gitlab-org/gitlab/merge_requests/17844).
# This method can be removed in GitLab 12.6.
def update_merge_request_references
# If a merge request was properly created, we don't need to fix
# up this export.
return if @relation_hash['merge_request']
merge_request_id = @relation_hash['merge_request_id']
return unless merge_request_id
new_merge_request_id = @merge_requests_mapping[merge_request_id]
return unless new_merge_request_id
@relation_hash['merge_request_id'] = new_merge_request_id
parsed_relation_hash['merge_request_id'] = new_merge_request_id
end
def setup_build
@relation_hash.delete('trace') # old export files have trace
@relation_hash.delete('token')
@relation_hash.delete('commands')
@relation_hash.delete('artifacts_file_store')
@relation_hash.delete('artifacts_metadata_store')
@relation_hash.delete('artifacts_size')
end
def setup_diff
@relation_hash['diff'] = @relation_hash.delete('utf8_diff')
end
def setup_pipeline
update_merge_request_references
@relation_hash.fetch('stages', []).each do |stage|
stage.statuses.each do |status|
status.pipeline = imported_object
end
end
end
def unknown_service?
@relation_name == :services && parsed_relation_hash['type'] &&
!Object.const_defined?(parsed_relation_hash['type'])
end
def legacy_trigger?
@relation_name == :'Ci::Trigger' && @relation_hash['owner_id'].nil?
end
def preload_keys(object, references, value)
return object unless value
references.each do |key|
attribute = "#{key.delete_suffix('_id')}=".to_sym
next unless object.respond_to?(key) && object.respond_to?(attribute)
if object.read_attribute(key) == value&.id
object.public_send(attribute, value) # rubocop:disable GitlabSecurity/PublicSend
end
end
object
end
end
end
end
......@@ -48,6 +48,7 @@ def relation_tree_restorer
shared: @shared,
importable: @project,
tree_hash: @tree_hash,
object_builder: object_builder,
members_mapper: members_mapper,
relation_factory: relation_factory,
reader: reader
......@@ -60,8 +61,12 @@ def members_mapper
importable: @project)
end
def object_builder
Gitlab::ImportExport::GroupProjectObjectBuilder
end
def relation_factory
Gitlab::ImportExport::RelationFactory
Gitlab::ImportExport::ProjectRelationFactory
end
def reader
......
......@@ -11,12 +11,13 @@ class RelationTreeRestorer
attr_reader :importable
attr_reader :tree_hash
def initialize(user:, shared:, importable:, tree_hash:, members_mapper:, relation_factory:, reader:)
def initialize(user:, shared:, importable:, tree_hash:, members_mapper:, object_builder:, relation_factory:, reader:)
@user = user
@shared = shared
@importable = importable
@tree_hash = tree_hash
@members_mapper = members_mapper
@object_builder = object_builder
@relation_factory = relation_factory
@reader = reader
end
......@@ -221,15 +222,16 @@ def merge_requests_mapping
def relation_factory_params(relation_key, data_hash)
base_params = {
relation_sym: relation_key.to_sym,
relation_hash: data_hash,
relation_sym: relation_key.to_sym,
relation_hash: data_hash,
importable: @importable,
members_mapper: @members_mapper,
user: @user,
excluded_keys: excluded_keys_for_relation(relation_key)
object_builder: @object_builder,
user: @user,
excluded_keys: excluded_keys_for_relation(relation_key)
}
base_params[:merge_requests_mapping] = merge_requests_mapping if importable_class == Project
base_params[importable_class_sym] = @importable
base_params
end
end
......
......@@ -9235,6 +9235,9 @@ msgstr ""
msgid "Group ID: %{group_id}"
msgstr ""
msgid "Group Owner must have signed in with SAML before enabling Group Managed Accounts"
msgstr ""
msgid "Group Runners"
msgstr ""
......@@ -14360,6 +14363,9 @@ msgstr ""
msgid "ProjectSettings|All discussions must be resolved"
msgstr ""
msgid "ProjectSettings|Allow users to make copies of your repository to a new project"
msgstr ""
msgid "ProjectSettings|Allow users to request access"
msgstr ""
......@@ -14420,6 +14426,9 @@ msgstr ""
msgid "ProjectSettings|Fast-forward merges only"
msgstr ""
msgid "ProjectSettings|Forks"
msgstr ""
msgid "ProjectSettings|Git Large File Storage"
msgstr ""
......
......@@ -15,6 +15,10 @@ class Browser
CAPYBARA_MAX_WAIT_TIME = 10
class << self
attr_accessor :rspec_configured, :capybara_configured
end
def initialize
self.class.configure!
end
......@@ -45,11 +49,40 @@ def self.visit(address, page_class, &block)
end
def self.configure!
configure_rspec!
configure_capybara!
end
def self.configure_rspec!
# We don't want to enter this infinite loop:
# Runtime::Release.perform_before_hooks -> `QA::Runtime::Browser.visit` -> configure! -> configure_rspec! -> Runtime::Release.perform_before_hooks
# So we make sure this method is called only once.
return if self.rspec_configured
browser = self
RSpec.configure do |config|
config.define_derived_metadata(file_path: %r{/qa/specs/features/}) do |metadata|
metadata[:type] = :feature
end
config.before do
unless browser.rspec_configured
browser.rspec_configured = true
##
# Perform before hooks, which are different for CE and EE
#
Runtime::Release.perform_before_hooks
end
end
end
end
def self.configure_capybara!
return if self.capybara_configured
self.capybara_configured = true
Capybara.server_port = 9887 + ENV['TEST_ENV_NUMBER'].to_i
......
......@@ -14,11 +14,9 @@ module Logger
attr_writer :logger
def logger
return @logger if @logger
@logger = ::Logger.new Runtime::Env.log_destination
@logger.level = Runtime::Env.debug? ? ::Logger::DEBUG : ::Logger::ERROR
@logger
@logger ||= ::Logger.new(Runtime::Env.log_destination).tap do |logger|
logger.level = Runtime::Env.debug? ? ::Logger::DEBUG : ::Logger::ERROR
end
end
end
end
......
......@@ -23,11 +23,6 @@ def focus
def perform(options, *args)
extract_address(:gitlab_address, options, args)
##
# Perform before hooks, which are different for CE and EE
#
Runtime::Release.perform_before_hooks
Runtime::Feature.enable(options[:enable_feature]) if options.key?(:enable_feature)
Specs::Runner.perform do |specs|
......
......@@ -20,11 +20,6 @@ def self.perform(*args)
def self.do_perform(address, *rspec_options)
Runtime::Scenario.define(:gitlab_address, address)
##
# Perform before hooks, which are different for CE and EE
#
Runtime::Release.perform_before_hooks
Specs::Runner.perform do |specs|
specs.tty = true
specs.options = rspec_options if rspec_options.any?
......
......@@ -16,7 +16,7 @@ def refresh
super
end
def wait_until(max_duration: 60, sleep_interval: 0.1, reload: true)
def wait_until(max_duration: 60, sleep_interval: 0.1, reload: true, raise_on_failure: false)
log("next wait uses reload: #{reload}")
# Logging of wait start/end/duration is handled by QA::Support::Waiter
......
......@@ -62,7 +62,7 @@
end
end
describe '#wait' do
describe '#wait_until' do
subject { Class.new(described_class).new }
context 'when the condition is true' do
......
......@@ -12,9 +12,9 @@
QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) if QA::Runtime::Env.runtime_scenario_attributes
%w[helpers shared_examples].each do |d|
Dir[::File.join(__dir__, d, '**', '*.rb')].each { |f| require f }
end
Dir[::File.join(__dir__, "support/helpers/*.rb")].each { |f| require f }
Dir[::File.join(__dir__, "support/shared_contexts/*.rb")].each { |f| require f }
Dir[::File.join(__dir__, "support/shared_examples/*.rb")].each { |f| require f }
RSpec.configure do |config|
QA::Specs::Helpers::Quarantine.configure_rspec
......
......@@ -31,12 +31,6 @@
expect(attributes).to have_received(:define).with(:gitlab_address, 'http://gitlab_address').at_least(:once)
end
it 'performs before hooks' do
subject.perform(args)
expect(release).to have_received(:perform_before_hooks)
end
it 'sets tags on runner' do
subject.perform(args)
......
......@@ -12,6 +12,21 @@
group.add_owner(user)
end
shared_examples 'forking disabled' do
let(:project) { create(:project, :private, :repository, :forking_disabled) }
before do
project.add_developer(user)
sign_in(user)
end
it 'returns with 404' do
subject
expect(response).to have_gitlab_http_status(404)
end
end
describe 'GET index' do
def get_forks(search: nil)
get :index,
......@@ -138,19 +153,19 @@ def get_forks(search: nil)
end
describe 'GET new' do
def get_new
subject do
get :new,
params: {
namespace_id: project.namespace,
project_id: project
}
params: {
namespace_id: project.namespace,
project_id: project
}
end
context 'when user is signed in' do
it 'responds with status 200' do
sign_in(user)
get_new
subject
expect(response).to have_gitlab_http_status(200)
end
......@@ -160,21 +175,26 @@ def get_new
it 'redirects to the sign-in page' do
sign_out(user)
get_new
subject
expect(response).to redirect_to(new_user_session_path)
end
end
it_behaves_like 'forking disabled'
end
describe 'POST create' do
def post_create(params = {})
post :create,
params: {
namespace_id: project.namespace,
project_id: project,
namespace_key: user.namespace.id
}.merge(params)
let(:params) do
{
namespace_id: project.namespace,
project_id: project,
namespace_key: user.namespace.id
}
end
subject do
post :create, params: params
end
context 'when user is signed in' do
......@@ -183,18 +203,34 @@ def post_create(params = {})
end
it 'responds with status 302' do
post_create
subject
expect(response).to have_gitlab_http_status(302)
expect(response).to redirect_to(namespace_project_import_path(user.namespace, project))
end
it 'passes continue params to the redirect' do
continue_params = { to: '/-/ide/project/path', notice: 'message' }
post_create continue: continue_params
context 'continue params' do
let(:params) do
{
namespace_id: project.namespace,
project_id: project,
namespace_key: user.namespace.id,
continue: continue_params
}
end
let(:continue_params) do
{
to: '/-/ide/project/path',
notice: 'message'
}
end
expect(response).to have_gitlab_http_status(302)
expect(response).to redirect_to(namespace_project_import_path(user.namespace, project, continue: continue_params))
it 'passes continue params to the redirect' do
subject
expect(response).to have_gitlab_http_status(302)
expect(response).to redirect_to(namespace_project_import_path(user.namespace, project, continue: continue_params))
end
end
end
......@@ -202,10 +238,12 @@ def post_create(params = {})
it 'redirects to the sign-in page' do
sign_out(user)
post_create
subject
expect(response).to redirect_to(new_user_session_path)
end
end
it_behaves_like 'forking disabled'
end
end
......@@ -25,6 +25,7 @@
builds_access_level { ProjectFeature::ENABLED }
snippets_access_level { ProjectFeature::ENABLED }
issues_access_level { ProjectFeature::ENABLED }
forking_access_level { ProjectFeature::ENABLED }
merge_requests_access_level { ProjectFeature::ENABLED }
repository_access_level { ProjectFeature::ENABLED }
pages_access_level do
......@@ -48,6 +49,7 @@
builds_access_level: builds_access_level,
snippets_access_level: evaluator.snippets_access_level,
issues_access_level: evaluator.issues_access_level,
forking_access_level: evaluator.forking_access_level,
merge_requests_access_level: merge_requests_access_level,
repository_access_level: evaluator.repository_access_level
}
......@@ -264,6 +266,9 @@
trait(:issues_disabled) { issues_access_level { ProjectFeature::DISABLED } }
trait(:issues_enabled) { issues_access_level { ProjectFeature::ENABLED } }
trait(:issues_private) { issues_access_level { ProjectFeature::PRIVATE } }
trait(:forking_disabled) { forking_access_level { ProjectFeature::DISABLED } }
trait(:forking_enabled) { forking_access_level { ProjectFeature::ENABLED } }
trait(:forking_private) { forking_access_level { ProjectFeature::PRIVATE } }
trait(:merge_requests_enabled) { merge_requests_access_level { ProjectFeature::ENABLED } }
trait(:merge_requests_disabled) { merge_requests_access_level { ProjectFeature::DISABLED } }
trait(:merge_requests_private) { merge_requests_access_level { ProjectFeature::PRIVATE } }
......
......@@ -186,7 +186,7 @@
click_button "Save changes"
end
expect(find(".sharing-permissions")).to have_selector(".project-feature-toggle.is-disabled", count: 2)
expect(find(".sharing-permissions")).to have_selector(".project-feature-toggle.is-disabled", count: 3)
end
it "shows empty features project homepage" do
......
......@@ -27,6 +27,89 @@
expect(page).to have_css('a.disabled', text: 'Fork')
end
context 'forking enabled / disabled in project settings' do
before do
project.project_feature.update_attribute(
:forking_access_level, forking_access_level)
end
context 'forking is enabled' do
let(:forking_access_level) { ProjectFeature::ENABLED }
it 'enables fork button' do
visit project_path(project)
expect(page).to have_css('a', text: 'Fork')
expect(page).not_to have_css('a.disabled', text: 'Fork')