diff --git a/app/controllers/projects/settings/merge_requests_controller.rb b/app/controllers/projects/settings/merge_requests_controller.rb index f09e324f574ee10a6c02d8119ab230332157512f..2724e2d9eecede70e1037452e1c99b4b1e180e25 100644 --- a/app/controllers/projects/settings/merge_requests_controller.rb +++ b/app/controllers/projects/settings/merge_requests_controller.rb @@ -52,6 +52,7 @@ def project_params_attributes :resolve_outdated_diff_discussions, :only_allow_merge_if_all_discussions_are_resolved, :only_allow_merge_if_pipeline_succeeds, + :allow_merge_without_pipeline, :printing_merge_request_link_enabled, :remove_source_branch_after_merge, :merge_method, diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index cee56dca538045fed252543714001b6f52c3e564..e8c6d6b1f9efe6712615d9e91e70551a7deec844 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -497,6 +497,7 @@ def project_params_attributes :name, :only_allow_merge_if_all_discussions_are_resolved, :only_allow_merge_if_pipeline_succeeds, + :allow_merge_without_pipeline, :path, :printing_merge_request_link_enabled, :public_builds, diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 524a9b8074b100ed3d81bf8e8e7926ac3a7bfbfa..051568a567429a6a211c8b9ec11f463adf84ea86 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1572,6 +1572,10 @@ def only_allow_merge_if_pipeline_succeeds? project.only_allow_merge_if_pipeline_succeeds?(inherit_group_setting: true) end + def allow_merge_without_pipeline? + project.allow_merge_without_pipeline?(inherit_group_setting: true) + end + def only_allow_merge_if_all_discussions_are_resolved? project.only_allow_merge_if_all_discussions_are_resolved?(inherit_group_setting: true) end @@ -1579,6 +1583,7 @@ def only_allow_merge_if_all_discussions_are_resolved? def mergeable_ci_state? return true unless only_allow_merge_if_pipeline_succeeds? return false unless actual_head_pipeline + return true if project.allow_merge_on_skipped_pipeline?(inherit_group_setting: true) && actual_head_pipeline.skipped? actual_head_pipeline.success? diff --git a/app/models/project.rb b/app/models/project.rb index 0d103094aec97c3ee377653d9910cc46aac1849f..4aec4207ece0a6bb85bc4918e808ad15d60daed3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -539,6 +539,7 @@ def self.integration_association_name(name) with_options to: :project_setting do delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?, :allow_merge_on_skipped_pipeline= + delegate :allow_merge_without_pipeline, :allow_merge_without_pipeline?, :allow_merge_without_pipeline= delegate :has_confluence? delegate :has_shimo? delegate :show_diff_preview_in_email, :show_diff_preview_in_email=, :show_diff_preview_in_email? @@ -858,6 +859,7 @@ def self.cascading_with_parent_namespace(attribute) 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 + cascading_with_parent_namespace :allow_merge_without_pipeline def self.with_feature_available_for_user(feature, user) with_project_feature.merge(ProjectFeature.with_feature_available_for_user(feature, user)) diff --git a/db/migrate/20231026172345_add_allow_merge_without_pipeline_to_namespace_settings.rb b/db/migrate/20231026172345_add_allow_merge_without_pipeline_to_namespace_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..e2f919acbc0399b144ecc881f73e23697e315065 --- /dev/null +++ b/db/migrate/20231026172345_add_allow_merge_without_pipeline_to_namespace_settings.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddAllowMergeWithoutPipelineToNamespaceSettings < Gitlab::Database::Migration[2.2] + enable_lock_retries! + milestone '16.6' + + def change + add_column :namespace_settings, :allow_merge_without_pipeline, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20231026175042_add_allow_merge_without_pipeline_to_project_settings.rb b/db/migrate/20231026175042_add_allow_merge_without_pipeline_to_project_settings.rb new file mode 100644 index 0000000000000000000000000000000000000000..c9d786bc8019439087b5b7e7e2e62440bfda254f --- /dev/null +++ b/db/migrate/20231026175042_add_allow_merge_without_pipeline_to_project_settings.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddAllowMergeWithoutPipelineToProjectSettings < Gitlab::Database::Migration[2.2] + enable_lock_retries! + milestone '16.6' + + def change + add_column :project_settings, :allow_merge_without_pipeline, :boolean, default: false, null: false + end +end diff --git a/db/schema_migrations/20231026172345 b/db/schema_migrations/20231026172345 new file mode 100644 index 0000000000000000000000000000000000000000..805a3790bebc42c3befbaddf30af386c305dc059 --- /dev/null +++ b/db/schema_migrations/20231026172345 @@ -0,0 +1 @@ +45048beec6a1167b9a3925c99dcc97067d3bbeb3716276c3afd180b2806018a4 \ No newline at end of file diff --git a/db/schema_migrations/20231026175042 b/db/schema_migrations/20231026175042 new file mode 100644 index 0000000000000000000000000000000000000000..fd4bf10c3beb304c079d9169c3d844bc3181f5e1 --- /dev/null +++ b/db/schema_migrations/20231026175042 @@ -0,0 +1 @@ +34f2680296663a5a929da3e43b45d320a8c811d5191e1901f52dbacf2a097e59 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 650a45e39a28d4596264299876fb83517ddd79a8..5ea1c69abc56322049be6bc6855f3b1ab35f7278 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -19471,6 +19471,7 @@ CREATE TABLE namespace_settings ( default_branch_protection_defaults jsonb DEFAULT '{}'::jsonb NOT NULL, service_access_tokens_expiration_enforced boolean DEFAULT true NOT NULL, product_analytics_enabled boolean DEFAULT false NOT NULL, + allow_merge_without_pipeline boolean DEFAULT false NOT NULL, CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)), CONSTRAINT namespace_settings_unique_project_download_limit_alertlist_size CHECK ((cardinality(unique_project_download_limit_alertlist) <= 100)), CONSTRAINT namespace_settings_unique_project_download_limit_allowlist_size CHECK ((cardinality(unique_project_download_limit_allowlist) <= 100)) @@ -22054,6 +22055,7 @@ CREATE TABLE project_settings ( encrypted_product_analytics_configurator_connection_string bytea, encrypted_product_analytics_configurator_connection_string_iv bytea, pages_multiple_versions_enabled boolean DEFAULT false NOT NULL, + allow_merge_without_pipeline boolean DEFAULT false NOT NULL, CONSTRAINT check_1a30456322 CHECK ((char_length(pages_unique_domain) <= 63)), CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)), CONSTRAINT check_3ca5cbffe6 CHECK ((char_length(issue_branch_template) <= 255)), diff --git a/ee/app/controllers/groups/settings/merge_requests_controller.rb b/ee/app/controllers/groups/settings/merge_requests_controller.rb index a86a9b9f1d11a8f788755ef8d41d4c1eba1059f6..d186cf3f833ad7bbe413ec1677d0708a4a12d7f7 100644 --- a/ee/app/controllers/groups/settings/merge_requests_controller.rb +++ b/ee/app/controllers/groups/settings/merge_requests_controller.rb @@ -36,6 +36,7 @@ def group_settings_params only_allow_merge_if_pipeline_succeeds allow_merge_on_skipped_pipeline only_allow_merge_if_all_discussions_are_resolved + allow_merge_without_pipeline ] ) end diff --git a/ee/app/helpers/merge_checks_helper.rb b/ee/app/helpers/merge_checks_helper.rb index 274ccd144dc6996f654d3ebe741142c7c02534b7..28cd87ac89a5cca9d7ed660534901197c6180dcd 100644 --- a/ee/app/helpers/merge_checks_helper.rb +++ b/ee/app/helpers/merge_checks_helper.rb @@ -28,6 +28,10 @@ def merge_checks(source) only_allow_merge_if_all_resolved: { locked: target.only_allow_merge_if_all_discussions_are_resolved_locked?, value: target.only_allow_merge_if_all_discussions_are_resolved?(inherit_group_setting: true) + }, + allow_merge_without_pipeline: { + locked: target.allow_merge_without_pipeline_locked?, + value: target.allow_merge_without_pipeline?(inherit_group_setting: true) } }.to_json, parent_group_name: parent_group_name diff --git a/ee/app/models/ee/namespace_setting.rb b/ee/app/models/ee/namespace_setting.rb index e7a7c59a2a70286c01e35ef0c9ef4e6d8b08af3b..e93d97bfdc87b724bd641f4953fa3be04d2ff83d 100644 --- a/ee/app/models/ee/namespace_setting.rb +++ b/ee/app/models/ee/namespace_setting.rb @@ -68,6 +68,7 @@ def self.cascading_with_parent_namespace(attribute) 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 + cascading_with_parent_namespace :allow_merge_without_pipeline def unique_project_download_limit_alertlist self[:unique_project_download_limit_alertlist].presence || active_owner_ids @@ -152,6 +153,7 @@ def product_analytics_allowed auto_ban_user_on_excessive_projects_download default_compliance_framework_id only_allow_merge_if_pipeline_succeeds + allow_merge_without_pipeline allow_merge_on_skipped_pipeline only_allow_merge_if_all_discussions_are_resolved experiment_features_enabled diff --git a/ee/app/models/ee/project.rb b/ee/app/models/ee/project.rb index 92854dd51d329319a488ae54eecb3a2f43e36895..7bf7d28f85bf7951fb3e99be23a1fe5501fd4a99 100644 --- a/ee/app/models/ee/project.rb +++ b/ee/app/models/ee/project.rb @@ -456,6 +456,7 @@ def self.cascading_with_parent_namespace(attribute) 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 + cascading_with_parent_namespace :allow_merge_without_pipeline def mirror_last_update_succeeded? !!import_state&.last_update_succeeded? diff --git a/ee/spec/helpers/merge_checks_helper_spec.rb b/ee/spec/helpers/merge_checks_helper_spec.rb index d044a91be1ef21358ab5010207bba3030b007740..b58aa5520169e87d3b4e8701cf52d0225a111ae5 100644 --- a/ee/spec/helpers/merge_checks_helper_spec.rb +++ b/ee/spec/helpers/merge_checks_helper_spec.rb @@ -30,6 +30,10 @@ only_allow_merge_if_all_resolved: { locked: false, value: false + }, + allow_merge_without_pipeline: { + locked: false, + value: false } }.to_json, parent_group_name: '' @@ -56,6 +60,10 @@ only_allow_merge_if_all_resolved: { locked: false, value: false + }, + allow_merge_without_pipeline: { + locked: false, + value: false } }.to_json, parent_group_name: parent_group.name @@ -81,6 +89,10 @@ only_allow_merge_if_all_resolved: { locked: false, value: false + }, + allow_merge_without_pipeline: { + locked: false, + value: false } }.to_json, parent_group_name: group.name diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index d3c32da284232f6a430771a10926865db986b878..d6efdc954e864d2ddcabc989ae10b477c1f79921 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -6133,4 +6133,29 @@ def transition! it { is_expected.to eq(false) } end end + + describe '#allow_merge_without_pipeline?' do + let(:merge_request) { build_stubbed(:merge_request) } + + subject(:result) { merge_request.allow_merge_without_pipeline? } + + before do + allow(merge_request.project) + .to receive(:allow_merge_without_pipeline?) + .with(inherit_group_setting: true) + .and_return(allow_merge_without_pipeline?) + end + + context 'when associated project allow_merge_without_pipeline? returns true' do + let(:allow_merge_without_pipeline?) { true } + + it { is_expected.to eq(true) } + end + + context 'when associated project allow_merge_without_pipeline? returns false' do + let(:allow_merge_without_pipeline?) { false } + + it { is_expected.to eq(false) } + end + end end diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml index 165ea7bf66e36a6543159f351ab872b44f07e276..96c711738c4d5fde617d4b8b4bcaa6cfe9c12e36 100644 --- a/spec/requests/api/project_attributes.yml +++ b/spec/requests/api/project_attributes.yml @@ -151,6 +151,7 @@ project_setting: - legacy_open_source_license_available - prevent_merge_without_jira_issue - only_allow_merge_if_all_status_checks_passed + - allow_merge_without_pipeline - warn_about_potentially_unwanted_characters - previous_default_branch - project_id