diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 9b7c14c6a0e22fd9eef3aea497174fa0e19f3b97..e17956256fc8eae0d74b72817cf5766c6b08b9b5 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1271,7 +1271,7 @@ def commit_notes end def mergeable_discussions_state? - return true unless project.only_allow_merge_if_all_discussions_are_resolved? + return true unless project.only_allow_merge_if_all_discussions_are_resolved?(inherit_group_setting: true) unresolved_notes.none?(&:to_be_resolved?) end @@ -1452,9 +1452,9 @@ def can_be_merged_via_command_line_by?(user) end def mergeable_ci_state? - return true unless project.only_allow_merge_if_pipeline_succeeds? + return true unless project.only_allow_merge_if_pipeline_succeeds?(inherit_group_setting: true) return false unless actual_head_pipeline - return true if project.allow_merge_on_skipped_pipeline? && actual_head_pipeline.skipped? + return true if project.allow_merge_on_skipped_pipeline?(inherit_group_setting: true) && actual_head_pipeline.skipped? actual_head_pipeline.success? end diff --git a/app/models/project.rb b/app/models/project.rb index d07e9ff759ec08b10caddbb4f0eaa74321dd98b2..521c5da40e7a5cf632da5e49132d1e6da935ff88 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -742,6 +742,29 @@ def self.public_or_visible_to_user(user = nil, min_access_level = nil) end end + # Defines instance methods: + # + # - only_allow_merge_if_pipeline_succeeds?(inherit_group_setting: false) + # - allow_merge_on_skipped_pipeline?(inherit_group_setting: false) + # - only_allow_merge_if_all_discussions_are_resolved?(inherit_group_setting: false) + # - only_allow_merge_if_pipeline_succeeds_locked? + # - allow_merge_on_skipped_pipeline_locked? + # - only_allow_merge_if_all_discussions_are_resolved_locked? + def self.cascading_with_parent_namespace(attribute) + # method overriden in EE + define_method("#{attribute}?") do |inherit_group_setting: false| + self.public_send(attribute) # rubocop:disable GitlabSecurity/PublicSend + end + + define_method("#{attribute}_locked?") do + false + end + end + + cascading_with_parent_namespace :only_allow_merge_if_pipeline_succeeds + cascading_with_parent_namespace :allow_merge_on_skipped_pipeline + cascading_with_parent_namespace :only_allow_merge_if_all_discussions_are_resolved + def self.with_feature_available_for_user(feature, user) with_project_feature.merge(ProjectFeature.with_feature_available_for_user(feature, user)) end diff --git a/app/serializers/merge_request_poll_widget_entity.rb b/app/serializers/merge_request_poll_widget_entity.rb index ab180b35b29a93da81434d97d09b06f76a80358b..cef3f4555df72dae418ac1a05efcfa5e75d31a33 100644 --- a/app/serializers/merge_request_poll_widget_entity.rb +++ b/app/serializers/merge_request_poll_widget_entity.rb @@ -31,7 +31,7 @@ class MergeRequestPollWidgetEntity < Grape::Entity end expose :only_allow_merge_if_pipeline_succeeds do |merge_request| - merge_request.project.only_allow_merge_if_pipeline_succeeds? + merge_request.project.only_allow_merge_if_pipeline_succeeds?(inherit_group_setting: true) end # CI related diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 9373556a37e0f40a70fe08765082f75ab85974be..5020b58c169dccad80ac3eef2ade6deb4962aa23 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -8380,6 +8380,70 @@ def has_external_wiki it { is_expected.to be(false) } end + describe '.cascading_with_parent_namespace' do + let_it_be_with_reload(:group) { create(:group, :with_root_storage_statistics) } + let_it_be_with_reload(:subgroup) { create(:group, parent: group) } + let_it_be_with_reload(:project) { create(:project, group: subgroup) } + let_it_be_with_reload(:project_without_group) { create(:project) } + + shared_examples 'cascading settings' do |attribute| + it 'return self value when no parent' do + expect(project_without_group.group).to be_nil + + project_without_group.update!(attribute => true) + expect(project_without_group.public_send("#{attribute}?", inherit_group_setting: true)).to be_truthy + expect(project_without_group.public_send("#{attribute}_locked?")).to be_falsey + + project_without_group.update!(attribute => false) + expect(project_without_group.public_send("#{attribute}?", inherit_group_setting: true)).to be_falsey + expect(project_without_group.public_send("#{attribute}_locked?")).to be_falsey + end + + it 'return self value when unlocked' do + subgroup.namespace_settings.update!(attribute => false) + group.namespace_settings.update!(attribute => false) + + project.update!(attribute => true) + expect(project.public_send("#{attribute}?", inherit_group_setting: true)).to be_truthy + expect(project.public_send("#{attribute}_locked?")).to be_falsey + + project.update!(attribute => false) + expect(project.public_send("#{attribute}?", inherit_group_setting: true)).to be_falsey + expect(project.public_send("#{attribute}_locked?")).to be_falsey + end + + it 'still return self value when locked subgroup' do + subgroup.namespace_settings.update!(attribute => true) + group.namespace_settings.update!(attribute => false) + + project.update!(attribute => true) + expect(project.public_send("#{attribute}?", inherit_group_setting: true)).to be_truthy + expect(project.public_send("#{attribute}_locked?")).to be_falsey + + project.update!(attribute => false) + expect(project.public_send("#{attribute}?", inherit_group_setting: true)).to be_falsey + expect(project.public_send("#{attribute}_locked?")).to be_falsey + end + + it 'still return unlocked value when locked group' do + subgroup.namespace_settings.update!(attribute => false) + group.namespace_settings.update!(attribute => true) + + project.update!(attribute => true) + expect(project.public_send("#{attribute}?", inherit_group_setting: true)).to be_truthy + expect(project.public_send("#{attribute}_locked?")).to be_falsey + + project.update!(attribute => false) + expect(project.public_send("#{attribute}?", inherit_group_setting: true)).to be_falsey + expect(project.public_send("#{attribute}_locked?")).to be_falsey + end + end + + it_behaves_like 'cascading settings', :only_allow_merge_if_pipeline_succeeds + it_behaves_like 'cascading settings', :allow_merge_on_skipped_pipeline + it_behaves_like 'cascading settings', :only_allow_merge_if_all_discussions_are_resolved + end + private def finish_job(export_job)