Commit 87d74366 authored by Shinya Maeda's avatar Shinya Maeda

Slash command support for merge train

This commit adds slash command support for merge train
parent 0a0077c3
...@@ -49,6 +49,14 @@ module AutoMerge ...@@ -49,6 +49,14 @@ module AutoMerge
end end
end end
def available_for?(merge_request)
strong_memoize("available_for_#{merge_request.id}") do
merge_request.can_be_merged_by?(current_user) &&
merge_request.mergeable_state?(skip_ci_check: true) &&
yield
end
end
private private
def strategy def strategy
......
...@@ -30,7 +30,9 @@ module AutoMerge ...@@ -30,7 +30,9 @@ module AutoMerge
end end
def available_for?(merge_request) def available_for?(merge_request)
merge_request.actual_head_pipeline&.active? super do
merge_request.actual_head_pipeline&.active?
end
end end
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
class AutoMergeService < BaseService class AutoMergeService < BaseService
include Gitlab::Utils::StrongMemoize
STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS = 'merge_when_pipeline_succeeds' STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS = 'merge_when_pipeline_succeeds'
STRATEGIES = [STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS].freeze STRATEGIES = [STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS].freeze
class << self class << self
def all_strategies def all_strategies_ordered_by_preference
STRATEGIES STRATEGIES
end end
def get_service_class(strategy) def get_service_class(strategy)
return unless all_strategies.include?(strategy) return unless all_strategies_ordered_by_preference.include?(strategy)
"::AutoMerge::#{strategy.camelize}Service".constantize "::AutoMerge::#{strategy.camelize}Service".constantize
end end
end end
def execute(merge_request, strategy) def execute(merge_request, strategy = nil)
service = get_service_instance(strategy) strategy ||= preferred_strategy(merge_request)
service = get_service_instance(merge_request, strategy)
return :failed unless service&.available_for?(merge_request) return :failed unless service&.available_for?(merge_request)
...@@ -27,37 +30,47 @@ class AutoMergeService < BaseService ...@@ -27,37 +30,47 @@ class AutoMergeService < BaseService
def update(merge_request) def update(merge_request)
return :failed unless merge_request.auto_merge_enabled? return :failed unless merge_request.auto_merge_enabled?
get_service_instance(merge_request.auto_merge_strategy).update(merge_request) strategy = merge_request.auto_merge_strategy
get_service_instance(merge_request, strategy).update(merge_request)
end end
def process(merge_request) def process(merge_request)
return unless merge_request.auto_merge_enabled? return unless merge_request.auto_merge_enabled?
get_service_instance(merge_request.auto_merge_strategy).process(merge_request) strategy = merge_request.auto_merge_strategy
get_service_instance(merge_request, strategy).process(merge_request)
end end
def cancel(merge_request) def cancel(merge_request)
return error("Can't cancel the automatic merge", 406) unless merge_request.auto_merge_enabled? return error("Can't cancel the automatic merge", 406) unless merge_request.auto_merge_enabled?
get_service_instance(merge_request.auto_merge_strategy).cancel(merge_request) strategy = merge_request.auto_merge_strategy
get_service_instance(merge_request, strategy).cancel(merge_request)
end end
def abort(merge_request, reason) def abort(merge_request, reason)
return error("Can't abort the automatic merge", 406) unless merge_request.auto_merge_enabled? return error("Can't abort the automatic merge", 406) unless merge_request.auto_merge_enabled?
get_service_instance(merge_request.auto_merge_strategy).abort(merge_request, reason) strategy = merge_request.auto_merge_strategy
get_service_instance(merge_request, strategy).abort(merge_request, reason)
end end
def available_strategies(merge_request) def available_strategies(merge_request)
self.class.all_strategies.select do |strategy| self.class.all_strategies_ordered_by_preference.select do |strategy|
get_service_instance(strategy).available_for?(merge_request) get_service_instance(merge_request, strategy).available_for?(merge_request)
end end
end end
def preferred_strategy(merge_request)
available_strategies(merge_request).first
end
private private
def get_service_instance(strategy) def get_service_instance(merge_request, strategy)
self.class.get_service_class(strategy)&.new(project, current_user, params) strong_memoize("service_instance_#{merge_request.id}_#{strategy}") do
self.class.get_service_class(strategy)&.new(project, current_user, params)
end
end end
end end
......
# frozen_string_literal: true
module MergeRequests
class MergeOrchestrationService < ::BaseService
def execute(merge_request)
return unless can_merge?(merge_request)
merge_request.update(merge_error: nil)
if can_merge_automatically?(merge_request)
auto_merge_service.execute(merge_request)
else
merge_request.merge_async(current_user.id, params)
end
end
def can_merge?(merge_request)
can_merge_automatically?(merge_request) || can_merge_immediately?(merge_request)
end
def preferred_auto_merge_strategy(merge_request)
auto_merge_service.preferred_strategy(merge_request)
end
private
def can_merge_immediately?(merge_request)
merge_request.can_be_merged_by?(current_user) &&
merge_request.mergeable_state?
end
def can_merge_automatically?(merge_request)
auto_merge_service.available_strategies(merge_request).any?
end
def auto_merge_service
@auto_merge_service ||= AutoMergeService.new(project, current_user, params)
end
end
end
...@@ -79,14 +79,21 @@ module MergeRequests ...@@ -79,14 +79,21 @@ module MergeRequests
def merge_from_quick_action(merge_request) def merge_from_quick_action(merge_request)
last_diff_sha = params.delete(:merge) last_diff_sha = params.delete(:merge)
return unless merge_request.mergeable_with_quick_action?(current_user, last_diff_sha: last_diff_sha)
merge_request.update(merge_error: nil) if Feature.enabled?(:merge_orchestration_service, merge_request.project, default_enabled: true)
MergeRequests::MergeOrchestrationService
if merge_request.head_pipeline_active? .new(project, current_user, { sha: last_diff_sha })
AutoMergeService.new(project, current_user, { sha: last_diff_sha }).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS) .execute(merge_request)
else else
merge_request.merge_async(current_user.id, { sha: last_diff_sha }) return unless merge_request.mergeable_with_quick_action?(current_user, last_diff_sha: last_diff_sha)
merge_request.update(merge_error: nil)
if merge_request.head_pipeline_active?
AutoMergeService.new(project, current_user, { sha: last_diff_sha }).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
else
merge_request.merge_async(current_user.id, { sha: last_diff_sha })
end
end end
end end
......
---
title: Add slash command support for merge train
merge_request: 28532
author:
type: added
...@@ -70,7 +70,7 @@ The following quick actions are applicable to descriptions, discussions and thre ...@@ -70,7 +70,7 @@ The following quick actions are applicable to descriptions, discussions and thre
| `/wip` | | ✓ | | Toggle the Work In Progress status | | `/wip` | | ✓ | | Toggle the Work In Progress status |
| `/approve` | | ✓ | | Approve the merge request **(STARTER)** | | `/approve` | | ✓ | | Approve the merge request **(STARTER)** |
| `/submit_review` | | ✓ | | Submit a pending review. ([Introduced in GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/issues/8041)) **(PREMIUM)** | | `/submit_review` | | ✓ | | Submit a pending review. ([Introduced in GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/issues/8041)) **(PREMIUM)** |
| `/merge` | | ✓ | | Merge (when pipeline succeeds) | | `/merge` | | ✓ | | Merge changes. Depending on the project setting, this may be [when the pipeline succeeds](merge_requests/merge_when_pipeline_succeeds.md), adding to a [Merge Train](../../ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md), etc). |
| `/child_epic <epic>` | | | ✓ | Add child epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. ([Introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab/issues/7330)) **(ULTIMATE)** | | `/child_epic <epic>` | | | ✓ | Add child epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. ([Introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab/issues/7330)) **(ULTIMATE)** |
| `/remove_child_epic <epic>` | | | ✓ | Remove child epic from `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. ([Introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab/issues/7330)) **(ULTIMATE)** | | `/remove_child_epic <epic>` | | | ✓ | Remove child epic from `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. ([Introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab/issues/7330)) **(ULTIMATE)** |
| `/parent_epic <epic>` | | | ✓ | Set parent epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab/issues/10556)) **(ULTIMATE)** | | `/parent_epic <epic>` | | | ✓ | Set parent epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab/issues/10556)) **(ULTIMATE)** |
......
...@@ -31,10 +31,11 @@ module AutoMerge ...@@ -31,10 +31,11 @@ module AutoMerge
end end
def available_for?(merge_request) def available_for?(merge_request)
merge_request.project.merge_trains_enabled? && super do
!merge_request.for_fork? && merge_request.project.merge_trains_enabled? &&
merge_request.actual_head_pipeline&.active? && !merge_request.for_fork? &&
merge_request.mergeable_state?(skip_ci_check: true) merge_request.actual_head_pipeline&.active?
end
end end
end end
end end
...@@ -45,12 +45,11 @@ module AutoMerge ...@@ -45,12 +45,11 @@ module AutoMerge
end end
def available_for?(merge_request) def available_for?(merge_request)
return false unless merge_request.project.merge_trains_enabled? super do
return false if merge_request.for_fork? merge_request.project.merge_trains_enabled? &&
return false unless merge_request.actual_head_pipeline&.complete? !merge_request.for_fork? &&
return false unless merge_request.mergeable_state?(skip_ci_check: true) merge_request.actual_head_pipeline&.complete?
end
true
end end
end end
end end
...@@ -12,10 +12,10 @@ module EE ...@@ -12,10 +12,10 @@ module EE
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
override :all_strategies override :all_strategies_ordered_by_preference
def all_strategies def all_strategies_ordered_by_preference
strong_memoize(:all_strategies) do strong_memoize(:all_strategies_ordered_by_preference) do
super + EE_STRATEGIES EE_STRATEGIES + super
end end
end end
end end
......
...@@ -41,6 +41,10 @@ describe AutoMerge::AddToMergeTrainWhenPipelineSucceedsService do ...@@ -41,6 +41,10 @@ describe AutoMerge::AddToMergeTrainWhenPipelineSucceedsService do
describe '#process' do describe '#process' do
subject { service.process(merge_request) } subject { service.process(merge_request) }
before do
service.execute(merge_request)
end
context 'when the latest pipeline in the merge request has succeeded' do context 'when the latest pipeline in the merge request has succeeded' do
before do before do
pipeline.succeed! pipeline.succeed!
...@@ -119,6 +123,12 @@ describe AutoMerge::AddToMergeTrainWhenPipelineSucceedsService do ...@@ -119,6 +123,12 @@ describe AutoMerge::AddToMergeTrainWhenPipelineSucceedsService do
it { is_expected.to eq(true) } it { is_expected.to eq(true) }
it 'memoizes the result' do
expect(merge_request).to receive(:can_be_merged_by?).once.and_call_original
2.times { is_expected.to be_truthy }
end
context 'when merge trains option is disabled' do context 'when merge trains option is disabled' do
before do before do
expect(merge_request.project).to receive(:merge_trains_enabled?) { false } expect(merge_request.project).to receive(:merge_trains_enabled?) { false }
...@@ -150,5 +160,13 @@ describe AutoMerge::AddToMergeTrainWhenPipelineSucceedsService do ...@@ -150,5 +160,13 @@ describe AutoMerge::AddToMergeTrainWhenPipelineSucceedsService do
it { is_expected.to eq(false) } it { is_expected.to eq(false) }
end end
context 'when the user does not have permission to merge' do
before do
allow(merge_request).to receive(:can_be_merged_by?) { false }
end
it { is_expected.to be_falsy }
end
end end
end end
...@@ -262,6 +262,12 @@ describe AutoMerge::MergeTrainService do ...@@ -262,6 +262,12 @@ describe AutoMerge::MergeTrainService do
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
it 'memoizes the result' do
expect(merge_request).to receive(:can_be_merged_by?).once.and_call_original
2.times { is_expected.to be_truthy }
end
context 'when merge trains project option is disabled' do context 'when merge trains project option is disabled' do
before do before do
stub_feature_flags(disable_merge_trains: true) stub_feature_flags(disable_merge_trains: true)
...@@ -278,6 +284,14 @@ describe AutoMerge::MergeTrainService do ...@@ -278,6 +284,14 @@ describe AutoMerge::MergeTrainService do
it { is_expected.to be_falsy } it { is_expected.to be_falsy }
end end
context 'when the user does not have permission to merge' do
before do
allow(merge_request).to receive(:can_be_merged_by?) { false }
end
it { is_expected.to be_falsy }
end
context 'when merge request is submitted from a forked project' do context 'when merge request is submitted from a forked project' do
before do before do
allow(merge_request).to receive(:for_fork?) { true } allow(merge_request).to receive(:for_fork?) { true }
......
...@@ -3,12 +3,13 @@ ...@@ -3,12 +3,13 @@
require 'spec_helper' require 'spec_helper'
describe AutoMergeService do describe AutoMergeService do
describe '.all_strategies' do describe '.all_strategies_ordered_by_preference' do
subject { described_class.all_strategies } subject { described_class.all_strategies_ordered_by_preference }
it 'includes all strategies' do it 'returns all strategies in preference order' do
is_expected.to include(AutoMergeService::STRATEGY_MERGE_TRAIN, is_expected.to eq([AutoMergeService::STRATEGY_MERGE_TRAIN,
AutoMergeService::STRATEGY_ADD_TO_MERGE_TRAIN_WHEN_PIPELINE_SUCCEEDS) AutoMergeService::STRATEGY_ADD_TO_MERGE_TRAIN_WHEN_PIPELINE_SUCCEEDS,
AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS])
end end
end end
end end
...@@ -8,14 +8,49 @@ module Gitlab ...@@ -8,14 +8,49 @@ module Gitlab
included do included do
# MergeRequest only quick actions definitions # MergeRequest only quick actions definitions
desc _('Merge (when the pipeline succeeds)') desc do
explanation _('Merges this merge request when the pipeline succeeds.') if Feature.enabled?(:merge_orchestration_service, quick_action_target.project, default_enabled: true)
execution_message _('Scheduled to merge this merge request when the pipeline succeeds.') if preferred_strategy = preferred_auto_merge_strategy(quick_action_target)
_("Merge automatically (%{strategy})") % { strategy: preferred_strategy.humanize }
else
_("Merge immediately")
end
else
_('Merge (when the pipeline succeeds)')
end
end
explanation do
if Feature.enabled?(:merge_orchestration_service, quick_action_target.project, default_enabled: true)
if preferred_strategy = preferred_auto_merge_strategy(quick_action_target)
_("Schedules to merge this merge request (%{strategy}).") % { strategy: preferred_strategy.humanize }
else
_('Merges this merge request immediately.')
end
else
_('Merges this merge request when the pipeline succeeds.')
end
end
execution_message do
if Feature.enabled?(:merge_orchestration_service, quick_action_target.project, default_enabled: true)
if preferred_strategy = preferred_auto_merge_strategy(quick_action_target)
_("Scheduled to merge this merge request (%{strategy}).") % { strategy: preferred_strategy.humanize }
else
_('Merged this merge request.')
end
else
_('Scheduled to merge this merge request when the pipeline succeeds.')
end
end
types MergeRequest types MergeRequest
condition do condition do
last_diff_sha = params && params[:merge_request_diff_head_sha] if Feature.enabled?(:merge_orchestration_service, quick_action_target.project, default_enabled: true)
quick_action_target.persisted? && quick_action_target.persisted? &&
quick_action_target.mergeable_with_quick_action?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha) merge_orchestration_service.can_merge?(quick_action_target)
else
last_diff_sha = params && params[:merge_request_diff_head_sha]
quick_action_target.persisted? &&
quick_action_target.mergeable_with_quick_action?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha)
end
end end
command :merge do command :merge do
@updates[:merge] = params[:merge_request_diff_head_sha] @updates[:merge] = params[:merge_request_diff_head_sha]
...@@ -70,6 +105,14 @@ module Gitlab ...@@ -70,6 +105,14 @@ module Gitlab
@updates[:target_branch] = branch_name if project.repository.branch_exists?(branch_name) @updates[:target_branch] = branch_name if project.repository.branch_exists?(branch_name)
end end
end end
def merge_orchestration_service
@merge_orchestration_service ||= MergeRequests::MergeOrchestrationService.new(project, current_user)
end
def preferred_auto_merge_strategy(merge_request)
merge_orchestration_service.preferred_auto_merge_strategy(merge_request)
end
end end
end end
end end
...@@ -12704,6 +12704,9 @@ msgstr "" ...@@ -12704,6 +12704,9 @@ msgstr ""
msgid "Merge Requests in Review" msgid "Merge Requests in Review"
msgstr "" msgstr ""
msgid "Merge automatically (%{strategy})"
msgstr ""
msgid "Merge commit message" msgid "Merge commit message"
msgstr "" msgstr ""
...@@ -12857,6 +12860,12 @@ msgstr "" ...@@ -12857,6 +12860,12 @@ msgstr ""
msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes." msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr "" msgstr ""
msgid "Merged this merge request."
msgstr ""
msgid "Merges this merge request immediately."
msgstr ""
msgid "Merges this merge request when the pipeline succeeds." msgid "Merges this merge request when the pipeline succeeds."
msgstr "" msgstr ""
...@@ -17681,12 +17690,18 @@ msgstr "" ...@@ -17681,12 +17690,18 @@ msgstr ""
msgid "Scheduled" msgid "Scheduled"
msgstr "" msgstr ""
msgid "Scheduled to merge this merge request (%{strategy})."
msgstr ""
msgid "Scheduled to merge this merge request when the pipeline succeeds." msgid "Scheduled to merge this merge request when the pipeline succeeds."
msgstr "" msgstr ""
msgid "Schedules" msgid "Schedules"
msgstr "" msgstr ""
msgid "Schedules to merge this merge request (%{strategy})."
msgstr ""
msgid "Scheduling" msgid "Scheduling"
msgstr "" msgstr ""
......
...@@ -94,6 +94,10 @@ describe MergeRequestPollWidgetEntity do ...@@ -94,6 +94,10 @@ describe MergeRequestPollWidgetEntity do
end end
describe 'auto merge' do describe 'auto merge' do
before do
project.add_maintainer(user)
end
context 'when auto merge is enabled' do context 'when auto merge is enabled' do
let(:resource) { create(:merge_request, :merge_when_pipeline_succeeds) } let(:resource) { create(:merge_request, :merge_when_pipeline_succeeds) }
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
require 'spec_helper' require 'spec_helper'
describe AutoMerge::MergeWhenPipelineSucceedsService do describe AutoMerge::MergeWhenPipelineSucceedsService do
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let(:mr_merge_if_green_enabled) do let(:mr_merge_if_green_enabled) do
create(:merge_request, merge_when_pipeline_succeeds: true, merge_user: user, create(:merge_request, merge_when_pipeline_succeeds: true, merge_user: user,
...@@ -20,6 +20,10 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do ...@@ -20,6 +20,10 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do
described_class.new(project, user, commit_message: 'Awesome message') described_class.new(project, user, commit_message: 'Awesome message')
end end
before_all do
project.add_maintainer(user)
end
describe "#available_for?" do describe "#available_for?" do
subject { service.available_for?(mr_merge_if_green_enabled) } subject { service.available_for?(mr_merge_if_green_enabled) }
...@@ -34,11 +38,25 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do ...@@ -34,11 +38,25 @@ describe AutoMerge::MergeWhenPipelineSucceedsService do
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
it 'memoizes the result' do
expect(mr_merge_if_green_enabled).to receive(:can_be_merged_by?).once.and_call_original
2.times { is_expected.to be_truthy }
end
context 'when the head pipeline succeeded' do context 'when the head pipeline succeeded' do
let(:pipeline_status) { :success } let(:pipeline_status) { :success }
it { is_expected.to be_falsy } it { is_expected.to be_falsy }
end end
context 'when the user does not have permission to merge' do
before do
allow(mr_merge_if_green_enabled).to receive(:can_be_merged_by?) { false }
end
it { is_expected.to be_falsy }
end
end end
describe "#execute" do describe "#execute" do
......
...@@ -3,22 +3,36 @@ ...@@ -3,22 +3,36 @@
require 'spec_helper' require 'spec_helper'
describe AutoMergeService do describe AutoMergeService do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:service) { described_class.new(project, user) } let(:service) { described_class.new(project, user) }
describe '.all_strategies' do before_all do
subject { described_class.all_strategies } project.add_maintainer(user)
end
it 'includes merge when pipeline succeeds' do describe '.all_strategies_ordered_by_preference' do
is_expected.to include(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS) subject { described_class.all_strategies_ordered_by_preference }
it 'returns all strategies in preference order' do
if Gitlab.ee?
is_expected.to eq(
[AutoMergeService::STRATEGY_MERGE_TRAIN,
AutoMergeService::STRATEGY_ADD_TO_MERGE_TRAIN_WHEN_PIPELINE_SUCCEEDS,
AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS])
else
is_expected.to eq([AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS])
end
end end
end end
describe '#available_strategies' do describe '#available_strategies' do
subject { service.available_strategies(merge_request) } subject { service.available_strategies(merge_request) }
let(:merge_request) { create(:merge_request) } let(:merge_request) do
create(:merge_request, source_project: project)
end
let(:pipeline_status) { :running } let(:pipeline_status) { :running }
before do before do
...@@ -42,6 +56,36 @@ describe AutoMergeService do ...@@ -42,6 +56,36 @@ describe AutoMergeService do
end end
end end
describe '#preferred_strategy' do
subject { service.preferred_strategy(merge_request) }
let(:merge_request) do
create(:merge_request, source_project: project)
end
let(:pipeline_status) { :running }
before do
create(:ci_pipeline, pipeline_status, ref: merge_request.source_branch,
sha: merge_request.diff_head_sha,
project: merge_request.source_project)
merge_request.update_head_pipeline
end
it 'returns preferred strategy' do
is_expected.to eq('merge_when_pipeline_succeeds')
end
context 'when the head piipeline succeeded' do
let(:pipeline_status) { :success }
it 'returns available strategies' do
is_expected.to be_nil
end
end
end
describe '.get_service_class' do describe '.get_service_class' do
subject { described_class.get_service_class(strategy) } subject { described_class.get_service_class(strategy) }
...@@ -63,7 +107,10 @@ describe AutoMergeService do ...@@ -63,7 +107,10 @@ describe AutoMergeService do
describe '#execute' do describe '#execute' do
subject { service.execute(merge_request, strategy) } subject { service.execute(merge_request, strategy) }
let(:merge_request) { create(:merge_request) } let(:merge_request) do
create(:merge_request, source_project: project)
end
let(:pipeline_status) { :running } let(:pipeline_status) { :running }
let(:strategy) { AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS } let(:strategy) { AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS }
...@@ -90,6 +137,14 @@ describe AutoMergeService do ...@@ -90,6 +137,14 @@ describe AutoMergeService do
is_expected.to eq(:failed) is_expected.to eq(:failed)
end end
end end
context 'when strategy is not specified' do
let(:strategy) { }
it 'chooses the most preferred strategy' do
is_expected.to eq(:merge_when_pipeline_succeeds)
end
end
end end
describe '#update' do describe '#update' do
......
# frozen_string_literal: true
require 'spec_helper'
describe MergeRequests::MergeOrchestrationService do
let_it_be(:maintainer) { create(:user) }
let(:merge_params) { { sha: merge_request.diff_head_sha } }
let(:user) { maintainer }
let(:service) { described_class.new(project, user, merge_params) }
let!(:merge_request) do
create(:merge_request, source_project: project, source_branch: 'feature',
target_project: project, target_branch: 'master')
end
shared_context 'fresh repository' do
let_it_be(:project) { create(:project, :repository) }
before_all do
project.add_maintainer(maintainer)
end
end
describe '#execute' do
subject { service.execute(merge_request) }
include_context 'fresh repository'
context 'when merge request is mergeable' do
context 'when merge request can be merged automatically' do
before do
create(:ci_pipeline, :detached_merge_request_pipeline, project: project, merge_request: merge_request)
merge_request.update_head_pipeline
end
it 'schedules auto merge' do
expect_next_instance_of(AutoMergeService, project, user, merge_params) do |service|
expect(service).to receive(:execute).with(merge_request).and_call_original
end
subject
expect(merge_request).to be_auto_merge_enabled
expect(merge_request.auto_merge_strategy).to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
expect(merge_request).not_to be_merged
end
end
context 'when merge request cannot be merged automatically' do
it 'merges immediately', :sidekiq_inline do
expect(merge_request)
.to receive(:merge_async).with(user.id, merge_params)
.and_call_original
subject
merge_request.reset
expect(merge_request).to be_merged
expect(merge_request).not_to be_auto_merge_enabled
end
end
end
context 'when merge request is not mergeable' do
before do
allow(merge_request).to receive(:mergeable_state?) { false }
end
it 'does nothing' do
subject
expect(merge_request).not_to be_auto_merge_enabled
expect(merge_request).not_to be_merged
end
end
end
describe '#can_merge?' do
subject { service.can_merge?(merge_request) }
include_context 'fresh repository'
context 'when merge request is mergeable' do
it { is_expected.to eq(true) }
end
context 'when merge request is not mergeable' do
before do
allow(merge_request).to receive(:mergeable_state?) { false }
end
it { is_expected.to eq(false) }
end
end
describe '#preferred_auto_merge_strategy' do
subject { service.preferred_auto_merge_strategy(merge_request) }
include_context 'fresh repository'
context 'when merge request can be merged automatically' do
before do
create(:ci_pipeline, :detached_merge_request_pipeline, project: project, merge_request: merge_request)
merge_request.update_head_pipeline
end
it 'fetches perferred auto merge strategy' do
is_expected.to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
end
end
context 'when merge request cannot be merged automatically' do
it { is_expected.to be_nil }
end
end
end
...@@ -208,7 +208,7 @@ describe MergeRequests::UpdateService, :mailer do ...@@ -208,7 +208,7 @@ describe MergeRequests::UpdateService, :mailer do
end end
end end
context 'merge' do shared_examples_for 'correct merge behavior' do
let(:opts) do let(:opts) do
{ {
merge: merge_request.diff_head_sha merge: merge_request.diff_head_sha
...@@ -311,6 +311,18 @@ describe MergeRequests::UpdateService, :mailer do ...@@ -311,6 +311,18 @@ describe MergeRequests::UpdateService, :mailer do
end end
end end
describe 'merge' do
it_behaves_like 'correct merge behavior'
context 'when merge_orchestration_service feature flag is disabled' do
before do
stub_feature_flags(merge_orchestration_service: false)
end
it_behaves_like 'correct merge behavior'
end
end
context 'todos' do context 'todos' do
let!(:pending_todo) { create(:todo, :assigned, user: user, project: project, target: merge_request, author: user2) } let!(:pending_todo) { create(:todo, :assigned, user: user, project: project, target: merge_request, author: user2) }
......
...@@ -492,7 +492,7 @@ describe QuickActions::InterpretService do ...@@ -492,7 +492,7 @@ describe QuickActions::InterpretService do
end end
end end
shared_examples 'merge command' do shared_examples 'merge immediately command' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
it 'runs merge command if content contains /merge' do it 'runs merge command if content contains /merge' do
...@@ -504,7 +504,18 @@ describe QuickActions::InterpretService do ...@@ -504,7 +504,18 @@ describe QuickActions::InterpretService do
it 'returns them merge message' do it 'returns them merge message' do
_, _, message = service.execute(content, issuable) _, _, message = service.execute(content, issuable)
expect(message).to eq('Scheduled to merge this merge request when the pipeline succeeds.') expect(message).to eq('Merged this merge request.')
end
end
shared_examples 'merge automatically command' do
let(:project) { create(:project, :repository) }
it 'runs merge command if content contains /merge and returns merge message' do
_, updates, message = service.execute(content, issuable)
expect(updates).to eq(merge: merge_request.diff_head_sha)
expect(message).to eq('Scheduled to merge this merge request (Merge when pipeline succeeds).')
end end
end end
...@@ -675,11 +686,23 @@ describe QuickActions::InterpretService do ...@@ -675,11 +686,23 @@ describe QuickActions::InterpretService do
context 'merge command' do context 'merge command' do
let(:service) { described_class.new(project, developer, { merge_request_diff_head_sha: merge_request.diff_head_sha }) } let(:service) { described_class.new(project, developer, { merge_request_diff_head_sha: merge_request.diff_head_sha }) }
it_behaves_like 'merge command' do it_behaves_like 'merge immediately command' do
let(:content) { '/merge' } let(:content) { '/merge' }
let(:issuable) { merge_request } let(:issuable) { merge_request }
end end
context 'when the head pipeline of merge request is running' do
before do
create(:ci_pipeline, :detached_merge_request_pipeline, merge_request: merge_request)
merge_request.update_head_pipeline
end
it_behaves_like 'merge automatically command' do
let(:content) { '/merge' }
let(:issuable) { merge_request }
end
end
context 'can not be merged when logged user does not have permissions' do context 'can not be merged when logged user does not have permissions' do
let(:service) { described_class.new(project, create(:user)) } let(:service) { described_class.new(project, create(:user)) }
......
...@@ -10,10 +10,27 @@ RSpec.shared_examples 'merge quick action' do ...@@ -10,10 +10,27 @@ RSpec.shared_examples 'merge quick action' do
it 'merges the MR', :sidekiq_might_not_need_inline do it 'merges the MR', :sidekiq_might_not_need_inline do
add_note("/merge") add_note("/merge")
expect(page).to have_content 'Scheduled to merge this merge request when the pipeline succeeds.' expect(page).to have_content 'Merged this merge request.'
expect(merge_request.reload).to be_merged expect(merge_request.reload).to be_merged
end end
context 'when auto merge is avialable' do
before do
create(:ci_pipeline, :detached_merge_request_pipeline,
project: project, merge_request: merge_request)
merge_request.update_head_pipeline
end
it 'schedules to merge the MR' do
add_note("/merge")
expect(page).to have_content "Scheduled to merge this merge request (Merge when pipeline succeeds)."
expect(merge_request.reload).to be_auto_merge_enabled
expect(merge_request.reload).not_to be_merged
end
end
end end
context 'when the head diff changes in the meanwhile' do context 'when the head diff changes in the meanwhile' do
......
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