diff --git a/ee/app/models/ee/ci/pipeline.rb b/ee/app/models/ee/ci/pipeline.rb index ccb9b540abe2a3579d74592a7c11f1e6c3a9a50e..8b5072ceabd8e37fd4c2a15089528324e95567a4 100644 --- a/ee/app/models/ee/ci/pipeline.rb +++ b/ee/app/models/ee/ci/pipeline.rb @@ -173,6 +173,15 @@ def license_management_report end end + ## + # Check if it's a merge request pipeline with the HEAD of source and target branches + # TODO: Make `Ci::Pipeline#latest?` compatible with merge request pipelines and remove this method. + def latest_merge_request_pipeline? + merge_request_pipeline? && + source_sha == merge_request.diff_head_sha && + target_sha == merge_request.target_branch_sha + end + private def available_licensed_report_type?(file_type) diff --git a/ee/app/models/ee/merge_request.rb b/ee/app/models/ee/merge_request.rb index 0e0ecb90b06fc8d85537593f193c212b3d586eef..9146834dca09dd0362b8db76e32b5d01e9afaf5a 100644 --- a/ee/app/models/ee/merge_request.rb +++ b/ee/app/models/ee/merge_request.rb @@ -47,10 +47,23 @@ def mergeable?(skip_ci_check: false) super end + override :mergeable_ci_state? + def mergeable_ci_state? + return false unless validate_merge_request_pipelines + + super + end + def supports_weight? false end + def validate_merge_request_pipelines + return true unless project.merge_pipelines_enabled? + + actual_head_pipeline&.latest_merge_request_pipeline? + end + def validate_approvals_before_merge return true unless approvals_before_merge return true unless target_project diff --git a/ee/changelogs/unreleased/ignore-merge-when-merge-pipelines-is-stale.yml b/ee/changelogs/unreleased/ignore-merge-when-merge-pipelines-is-stale.yml new file mode 100644 index 0000000000000000000000000000000000000000..b54e200c4c6e41608b317f616a45eb99793cee74 --- /dev/null +++ b/ee/changelogs/unreleased/ignore-merge-when-merge-pipelines-is-stale.yml @@ -0,0 +1,5 @@ +--- +title: Prevent merge if the merge request pipeline is stale +merge_request: 9643 +author: +type: added diff --git a/ee/spec/models/ci/pipeline_spec.rb b/ee/spec/models/ci/pipeline_spec.rb index 149706cd48faae4c02218629b88f34a038303cf0..0d9cb6914aaef3f7f74461ad0b17deb09bbd24e4 100644 --- a/ee/spec/models/ci/pipeline_spec.rb +++ b/ee/spec/models/ci/pipeline_spec.rb @@ -508,4 +508,36 @@ end end end + + describe '#latest_merge_request_pipeline?' do + subject { pipeline.latest_merge_request_pipeline? } + + let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) } + let(:pipeline) { merge_request.all_pipelines.first } + let(:args) { {} } + + it { is_expected.to be_truthy } + + context 'when pipeline is not merge request pipeline' do + let(:pipeline) { build(:ci_pipeline) } + + it { is_expected.to be_falsy } + end + + context 'when source sha is outdated' do + before do + pipeline.source_sha = merge_request.diff_base_sha + end + + it { is_expected.to be_falsy } + end + + context 'when target sha is outdated' do + before do + pipeline.target_sha = 'old-sha' + end + + it { is_expected.to be_falsy } + end + end end diff --git a/ee/spec/models/merge_request_spec.rb b/ee/spec/models/merge_request_spec.rb index c5ce166f05cb5760ef4ce3aae5915616abd09601..c01fc5ff5e5ae609ab1e9356ff98705a3f9ce5d9 100644 --- a/ee/spec/models/merge_request_spec.rb +++ b/ee/spec/models/merge_request_spec.rb @@ -769,4 +769,62 @@ def create_pipeline(status) end end end + + describe '#mergeable_ci_state?' do + subject { merge_request.mergeable_ci_state? } + + let(:project) { create(:project, :repository) } + + let(:merge_request) do + create(:merge_request, + :with_merge_request_pipeline, + source_branch: 'feature', + source_project: project, + target_branch: 'master', + target_project: project) + end + + shared_examples_for 'merge pipelines project option is disabled' do + before do + project.merge_pipelines_enabled = false + end + + it { is_expected.to be_truthy } + end + + before do + stub_licensed_features(merge_pipelines: true) + project.merge_pipelines_enabled = true + merge_request.update_head_pipeline + end + + it { is_expected.to be_truthy } + + context 'when head pipeline is a detached merge request pipeline' do + before do + merge_request.head_pipeline.update_column(:target_sha, nil) + end + + it { is_expected.to be_falsy } + it_behaves_like 'merge pipelines project option is disabled' + end + + context 'when source sha of the merge request pipeline is not HEAD' do + before do + merge_request.head_pipeline.update_column(:source_sha, 'old-commit') + end + + it { is_expected.to be_falsy } + it_behaves_like 'merge pipelines project option is disabled' + end + + context 'when target sha of the merge request pipeline is not HEAD' do + before do + merge_request.head_pipeline.update_column(:target_sha, 'old-commit') + end + + it { is_expected.to be_falsy } + it_behaves_like 'merge pipelines project option is disabled' + end + end end