Commit b9b67fab authored by Furkan Ayhan's avatar Furkan Ayhan Committed by Igor Drozdov

Prevent MR to be dropped from Merge Trains for open discussions

After adding an MR to Merge Train, new discussions and comments will not drop the MR from the train.
parent c61a6b09
...@@ -17,7 +17,7 @@ export default function deviseState() { ...@@ -17,7 +17,7 @@ export default function deviseState() {
return stateKey.pipelineFailed; return stateKey.pipelineFailed;
} else if (this.workInProgress) { } else if (this.workInProgress) {
return stateKey.workInProgress; return stateKey.workInProgress;
} else if (this.hasMergeableDiscussionsState) { } else if (this.hasMergeableDiscussionsState && !this.autoMergeEnabled) {
return stateKey.unresolvedDiscussions; return stateKey.unresolvedDiscussions;
} else if (this.isPipelineBlocked) { } else if (this.isPipelineBlocked) {
return stateKey.pipelineBlocked; return stateKey.pipelineBlocked;
......
...@@ -955,8 +955,9 @@ class MergeRequest < ApplicationRecord ...@@ -955,8 +955,9 @@ class MergeRequest < ApplicationRecord
self.class.wip_title(self.title) self.class.wip_title(self.title)
end end
def mergeable?(skip_ci_check: false) def mergeable?(skip_ci_check: false, skip_discussions_check: false)
return false unless mergeable_state?(skip_ci_check: skip_ci_check) return false unless mergeable_state?(skip_ci_check: skip_ci_check,
skip_discussions_check: skip_discussions_check)
check_mergeability check_mergeability
......
...@@ -10,13 +10,14 @@ module MergeRequests ...@@ -10,13 +10,14 @@ module MergeRequests
class MergeService < MergeRequests::MergeBaseService class MergeService < MergeRequests::MergeBaseService
delegate :merge_jid, :state, to: :@merge_request delegate :merge_jid, :state, to: :@merge_request
def execute(merge_request) def execute(merge_request, options = {})
if project.merge_requests_ff_only_enabled && !self.is_a?(FfMergeService) if project.merge_requests_ff_only_enabled && !self.is_a?(FfMergeService)
FfMergeService.new(project, current_user, params).execute(merge_request) FfMergeService.new(project, current_user, params).execute(merge_request)
return return
end end
@merge_request = merge_request @merge_request = merge_request
@options = options
validate! validate!
...@@ -55,7 +56,7 @@ module MergeRequests ...@@ -55,7 +56,7 @@ module MergeRequests
error = error =
if @merge_request.should_be_rebased? if @merge_request.should_be_rebased?
'Only fast-forward merge is allowed for your project. Please update your source branch' 'Only fast-forward merge is allowed for your project. Please update your source branch'
elsif !@merge_request.mergeable? elsif !@merge_request.mergeable?(skip_discussions_check: @options[:skip_discussions_check])
'Merge request is not mergeable' 'Merge request is not mergeable'
elsif !@merge_request.squash && project.squash_always? elsif !@merge_request.squash && project.squash_always?
'This project requires squashing commits when merge requests are accepted.' 'This project requires squashing commits when merge requests are accepted.'
......
---
title: Prevent MRs to be dropped from Merge Trains for open discussions
merge_request: 39957
author:
type: changed
...@@ -103,7 +103,7 @@ module EE ...@@ -103,7 +103,7 @@ module EE
end end
override :mergeable? override :mergeable?
def mergeable?(skip_ci_check: false) def mergeable?(skip_ci_check: false, skip_discussions_check: false)
return false unless approved? return false unless approved?
return false if has_denied_policies? return false if has_denied_policies?
return false if merge_blocked_by_other_mrs? return false if merge_blocked_by_other_mrs?
......
...@@ -33,7 +33,7 @@ module MergeTrains ...@@ -33,7 +33,7 @@ module MergeTrains
raise ProcessError, 'merge request is not on a merge train' raise ProcessError, 'merge request is not on a merge train'
end end
unless merge_request.mergeable_state?(skip_ci_check: true) unless merge_request.mergeable_state?(skip_ci_check: true, skip_discussions_check: true)
raise ProcessError, 'merge request is not mergeable' raise ProcessError, 'merge request is not mergeable'
end end
...@@ -70,7 +70,7 @@ module MergeTrains ...@@ -70,7 +70,7 @@ module MergeTrains
merge_train.start_merge! merge_train.start_merge!
MergeRequests::MergeService.new(project, merge_user, merge_request.merge_params) MergeRequests::MergeService.new(project, merge_user, merge_request.merge_params)
.execute(merge_request) .execute(merge_request, skip_discussions_check: true)
raise ProcessError, "failed to merge. #{merge_request.merge_error}" unless merge_request.merged? raise ProcessError, "failed to merge. #{merge_request.merge_error}" unless merge_request.merged?
......
...@@ -203,7 +203,7 @@ RSpec.describe MergeTrains::RefreshMergeRequestService do ...@@ -203,7 +203,7 @@ RSpec.describe MergeTrains::RefreshMergeRequestService do
expect(merge_request.merge_train).to receive(:start_merge!).and_call_original expect(merge_request.merge_train).to receive(:start_merge!).and_call_original
expect(merge_request.merge_train).to receive(:finish_merge!).and_call_original expect(merge_request.merge_train).to receive(:finish_merge!).and_call_original
expect_next_instance_of(MergeRequests::MergeService, project, maintainer, anything) do |service| expect_next_instance_of(MergeRequests::MergeService, project, maintainer, anything) do |service|
expect(service).to receive(:execute).with(merge_request).and_call_original expect(service).to receive(:execute).with(merge_request, skip_discussions_check: true).and_call_original
end end
expect { subject } expect { subject }
......
...@@ -30,6 +30,7 @@ describe('getStateKey', () => { ...@@ -30,6 +30,7 @@ describe('getStateKey', () => {
expect(bound()).toEqual('notAllowedToMerge'); expect(bound()).toEqual('notAllowedToMerge');
context.autoMergeEnabled = true; context.autoMergeEnabled = true;
context.hasMergeableDiscussionsState = true;
expect(bound()).toEqual('autoMergeEnabled'); expect(bound()).toEqual('autoMergeEnabled');
...@@ -44,6 +45,7 @@ describe('getStateKey', () => { ...@@ -44,6 +45,7 @@ describe('getStateKey', () => {
expect(bound()).toEqual('pipelineBlocked'); expect(bound()).toEqual('pipelineBlocked');
context.hasMergeableDiscussionsState = true; context.hasMergeableDiscussionsState = true;
context.autoMergeEnabled = false;
expect(bound()).toEqual('unresolvedDiscussions'); expect(bound()).toEqual('unresolvedDiscussions');
......
...@@ -2467,6 +2467,57 @@ RSpec.describe MergeRequest, factory_default: :keep do ...@@ -2467,6 +2467,57 @@ RSpec.describe MergeRequest, factory_default: :keep do
expect(subject.mergeable?).to be_truthy expect(subject.mergeable?).to be_truthy
end end
context 'with skip_ci_check option' do
using RSpec::Parameterized::TableSyntax
before do
allow(subject).to receive_messages(check_mergeability: nil,
can_be_merged?: true,
broken?: false)
end
where(:mergeable_ci_state, :skip_ci_check, :expected_mergeable) do
false | false | false
false | true | true
true | false | true
true | true | true
end
with_them do
it 'overrides mergeable_ci_state?' do
allow(subject).to receive(:mergeable_ci_state?) { mergeable_ci_state }
expect(subject.mergeable?(skip_ci_check: skip_ci_check)).to eq(expected_mergeable)
end
end
end
context 'with skip_discussions_check option' do
using RSpec::Parameterized::TableSyntax
before do
allow(subject).to receive_messages(mergeable_ci_state?: true,
check_mergeability: nil,
can_be_merged?: true,
broken?: false)
end
where(:mergeable_discussions_state, :skip_discussions_check, :expected_mergeable) do
false | false | false
false | true | true
true | false | true
true | true | true
end
with_them do
it 'overrides mergeable_discussions_state?' do
allow(subject).to receive(:mergeable_discussions_state?) { mergeable_discussions_state }
expect(subject.mergeable?(skip_discussions_check: skip_discussions_check)).to eq(expected_mergeable)
end
end
end
end end
describe '#check_mergeability' do describe '#check_mergeability' do
...@@ -2570,6 +2621,10 @@ RSpec.describe MergeRequest, factory_default: :keep do ...@@ -2570,6 +2621,10 @@ RSpec.describe MergeRequest, factory_default: :keep do
it 'returns false' do it 'returns false' do
expect(subject.mergeable_state?).to be_falsey expect(subject.mergeable_state?).to be_falsey
end end
it 'returns true when skipping ci check' do
expect(subject.mergeable_state?(skip_ci_check: true)).to be(true)
end
end end
context 'when #mergeable_discussions_state? is false' do context 'when #mergeable_discussions_state? is false' do
......
...@@ -435,6 +435,43 @@ RSpec.describe MergeRequests::MergeService do ...@@ -435,6 +435,43 @@ RSpec.describe MergeRequests::MergeService do
end end
end end
end end
context 'when not mergeable' do
let!(:error_message) { 'Merge request is not mergeable' }
context 'with failing CI' do
before do
allow(merge_request).to receive(:mergeable_ci_state?) { false }
end
it 'logs and saves error' do
service.execute(merge_request)
expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
end
end
context 'with unresolved discussions' do
before do
allow(merge_request).to receive(:mergeable_discussions_state?) { false }
end
it 'logs and saves error' do
service.execute(merge_request)
expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message))
end
context 'when passing `skip_discussions_check: true` as `options` parameter' do
it 'merges the merge request' do
service.execute(merge_request, skip_discussions_check: true)
expect(merge_request).to be_valid
expect(merge_request).to be_merged
end
end
end
end
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment