Commit 5b050cc4 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'add-add-to-merge-train-when-pipeline-succeeds-strategy' into 'master'

[New Auto Merge Strategy] Add To Merge Train When Pipeline Succeeds

See merge request gitlab-org/gitlab-ee!13767
parents c64f7473 48e461e2
# frozen_string_literal: true
module AutoMerge
class AddToMergeTrainWhenPipelineSucceedsService < AutoMerge::BaseService
def execute(merge_request)
super do
SystemNoteService.add_to_merge_train_when_pipeline_succeeds(merge_request, project, current_user, merge_request.diff_head_commit)
end
end
def process(merge_request)
return unless merge_request.actual_head_pipeline&.success?
merge_train_service = AutoMerge::MergeTrainService.new(project, merge_request.merge_user)
##
# We are currently abusing `#cancel` method to cancel the auto merge when
# a system failure happens. We should split the interfaces into two
# for explicitly telling that the cancel action is not triggered by the merge user directly.
# https://gitlab.com/gitlab-org/gitlab-ee/issues/12134
return cancel(merge_request) unless merge_train_service.available_for?(merge_request)
merge_train_service.execute(merge_request)
end
def cancel(merge_request)
super(merge_request) do
SystemNoteService.cancel_add_to_merge_train_when_pipeline_succeeds(merge_request, project, current_user)
end
end
def available_for?(merge_request)
merge_request.project.merge_trains_enabled? &&
!merge_request.for_fork? &&
merge_request.actual_head_pipeline&.active? &&
merge_request.mergeable_state?(skip_ci_check: true)
end
end
end
......@@ -5,7 +5,8 @@ module EE
extend ActiveSupport::Concern
STRATEGY_MERGE_TRAIN = 'merge_train'.freeze
EE_STRATEGIES = [STRATEGY_MERGE_TRAIN].freeze
STRATEGY_ADD_TO_MERGE_TRAIN_WHEN_PIPELINE_SUCCEEDS = 'add_to_merge_train_when_pipeline_succeeds'.freeze
EE_STRATEGIES = [STRATEGY_MERGE_TRAIN, STRATEGY_ADD_TO_MERGE_TRAIN_WHEN_PIPELINE_SUCCEEDS].freeze
class_methods do
extend ::Gitlab::Utils::Override
......
......@@ -210,5 +210,19 @@ module EE
create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
end
# Called when 'add to merge train when pipeline succeeds' is executed
def add_to_merge_train_when_pipeline_succeeds(noteable, project, author, last_commit)
body = "enabled automatic add to merge train when the pipeline for #{last_commit.to_reference(project)} succeeds"
create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
end
# Called when 'add to merge train when pipeline succeeds' is canceled
def cancel_add_to_merge_train_when_pipeline_succeeds(noteable, project, author)
body = 'cancelled automatic add to merge train'
create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
end
end
end
---
title: "[New Auto Merge Strategy] Add To Merge Train When Pipeline Succeeds"
merge_request: 13767
author:
type: added
......@@ -24,6 +24,12 @@ FactoryBot.modify do
end
end
trait :add_to_merge_train_when_pipeline_succeeds do
auto_merge_enabled true
auto_merge_strategy AutoMergeService::STRATEGY_ADD_TO_MERGE_TRAIN_WHEN_PIPELINE_SUCCEEDS
merge_user { author }
end
transient do
approval_groups []
approval_users []
......
# frozen_string_literal: true
require 'spec_helper'
describe AutoMerge::AddToMergeTrainWhenPipelineSucceedsService do
set(:project) { create(:project, :repository) }
set(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
let(:merge_request) do
create(:merge_request, :with_merge_request_pipeline,
source_project: project, source_branch: 'feature',
target_project: project, target_branch: 'master')
end
let(:pipeline) { merge_request.reload.all_pipelines.first }
before do
stub_licensed_features(merge_trains: true, merge_pipelines: true)
project.add_maintainer(user)
project.update!(merge_trains_enabled: true, merge_pipelines_enabled: true)
allow(AutoMergeProcessWorker).to receive(:perform_async) { }
merge_request.update_head_pipeline
end
describe '#execute' do
subject { service.execute(merge_request) }
it 'enables auto merge' do
expect(SystemNoteService)
.to receive(:add_to_merge_train_when_pipeline_succeeds)
.with(merge_request, project, user, merge_request.diff_head_commit)
subject
expect(merge_request).to be_auto_merge_enabled
end
end
describe '#process' do
subject { service.process(merge_request) }
context 'when the latest pipeline in the merge request has succeeded' do
before do
pipeline.succeed!
end
it 'executes MergeTrainService' do
expect_next_instance_of(AutoMerge::MergeTrainService) do |train_service|
expect(train_service).to receive(:execute).with(merge_request)
end
subject
end
context 'when merge train strategy is not available for the merge request' do
before do
train_service = double
allow(train_service).to receive(:available_for?) { false }
allow(AutoMerge::MergeTrainService).to receive(:new) { train_service }
end
it 'cancels auto merge' do
expect(service).to receive(:cancel).once
subject
end
end
end
context 'when the latest pipeline in the merge request is running' do
it 'does not initialize MergeTrainService' do
expect(AutoMerge::MergeTrainService).not_to receive(:new)
subject
end
end
end
describe '#cancel' do
subject { service.cancel(merge_request) }
let(:merge_request) { create(:merge_request, :add_to_merge_train_when_pipeline_succeeds, merge_user: user) }
it 'cancels auto merge' do
expect(SystemNoteService)
.to receive(:cancel_add_to_merge_train_when_pipeline_succeeds)
.with(merge_request, project, user)
subject
expect(merge_request).not_to be_auto_merge_enabled
end
end
describe '#available_for?' do
subject { service.available_for?(merge_request) }
it { is_expected.to eq(true) }
context 'when merge trains option is disabled' do
before do
expect(merge_request.project).to receive(:merge_trains_enabled?) { false }
end
it { is_expected.to eq(false) }
end
context 'when merge request is submitted from a forked project' do
before do
allow(merge_request).to receive(:for_fork?) { true }
end
it { is_expected.to eq(false) }
end
context 'when the latest pipeline in the merge request is not active' do
before do
pipeline.succeed!
end
it { is_expected.to eq(false) }
end
context 'when merge request is not mergeable' do
before do
merge_request.update!(title: merge_request.wip_title)
end
it { is_expected.to eq(false) }
end
end
end
......@@ -7,7 +7,8 @@ describe AutoMergeService do
subject { described_class.all_strategies }
it 'includes all strategies' do
is_expected.to include(AutoMergeService::STRATEGY_MERGE_TRAIN)
is_expected.to include(AutoMergeService::STRATEGY_MERGE_TRAIN,
AutoMergeService::STRATEGY_ADD_TO_MERGE_TRAIN_WHEN_PIPELINE_SUCCEEDS)
end
end
end
......@@ -370,4 +370,38 @@ describe SystemNoteService do
end
end
end
describe '.add_to_merge_train_when_pipeline_succeeds' do
subject { described_class.add_to_merge_train_when_pipeline_succeeds(noteable, project, author, noteable.diff_head_commit) }
let(:pipeline) { build(:ci_pipeline_without_jobs) }
let(:noteable) do
create(:merge_request, source_project: project, target_project: project)
end
it_behaves_like 'a system note' do
let(:action) { 'merge' }
end
it "posts the 'add to merge train when pipeline succeeds' system note" do
expect(subject.note).to match(%r{enabled automatic add to merge train when the pipeline for (\w+/\w+@)?\h{40} succeeds})
end
end
describe '.cancel_add_to_merge_train_when_pipeline_succeeds' do
subject { described_class.cancel_add_to_merge_train_when_pipeline_succeeds(noteable, project, author) }
let(:noteable) do
create(:merge_request, source_project: project, target_project: project)
end
it_behaves_like 'a system note' do
let(:action) { 'merge' }
end
it "posts the 'add to merge train when pipeline succeeds' system note" do
expect(subject.note).to eq 'cancelled automatic add to merge train'
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