Commit a7fd826a authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'abstract-auto-merge' into 'master'

Refactor and abstract Auto Merge Processes

See merge request gitlab-org/gitlab-ce!28595
parents b2d7b7ad 84e550fa
...@@ -57,7 +57,7 @@ export default { ...@@ -57,7 +57,7 @@ export default {
removeSourceBranch() { removeSourceBranch() {
const options = { const options = {
sha: this.mr.sha, sha: this.mr.sha,
merge_when_pipeline_succeeds: true, auto_merge_strategy: 'merge_when_pipeline_succeeds',
should_remove_source_branch: true, should_remove_source_branch: true,
}; };
...@@ -85,7 +85,7 @@ export default { ...@@ -85,7 +85,7 @@ export default {
<h4 class="d-flex align-items-start"> <h4 class="d-flex align-items-start">
<span class="append-right-10"> <span class="append-right-10">
{{ s__('mrWidget|Set by') }} {{ s__('mrWidget|Set by') }}
<mr-widget-author :author="mr.setToMWPSBy" /> <mr-widget-author :author="mr.setToAutoMergeBy" />
{{ s__('mrWidget|to be merged automatically when the pipeline succeeds') }} {{ s__('mrWidget|to be merged automatically when the pipeline succeeds') }}
</span> </span>
<a <a
......
...@@ -31,7 +31,7 @@ export default { ...@@ -31,7 +31,7 @@ export default {
return { return {
removeSourceBranch: this.mr.shouldRemoveSourceBranch, removeSourceBranch: this.mr.shouldRemoveSourceBranch,
mergeWhenBuildSucceeds: false, mergeWhenBuildSucceeds: false,
setToMergeWhenPipelineSucceeds: false, autoMergeStrategy: undefined,
isMakingRequest: false, isMakingRequest: false,
isMergingImmediately: false, isMergingImmediately: false,
commitMessage: this.mr.commitMessage, commitMessage: this.mr.commitMessage,
...@@ -42,7 +42,7 @@ export default { ...@@ -42,7 +42,7 @@ export default {
}; };
}, },
computed: { computed: {
shouldShowMergeWhenPipelineSucceedsText() { shouldShowAutoMergeText() {
return this.mr.isPipelineActive; return this.mr.isPipelineActive;
}, },
status() { status() {
...@@ -87,7 +87,7 @@ export default { ...@@ -87,7 +87,7 @@ export default {
mergeButtonText() { mergeButtonText() {
if (this.isMergingImmediately) { if (this.isMergingImmediately) {
return __('Merge in progress'); return __('Merge in progress');
} else if (this.shouldShowMergeWhenPipelineSucceedsText) { } else if (this.shouldShowAutoMergeText) {
return __('Merge when pipeline succeeds'); return __('Merge when pipeline succeeds');
} }
...@@ -104,7 +104,7 @@ export default { ...@@ -104,7 +104,7 @@ export default {
return enableSquashBeforeMerge && commitsCount > 1; return enableSquashBeforeMerge && commitsCount > 1;
}, },
shouldShowMergeControls() { shouldShowMergeControls() {
return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText; return this.mr.isMergeAllowed || this.shouldShowAutoMergeText;
}, },
shouldShowSquashEdit() { shouldShowSquashEdit() {
return this.squashBeforeMerge && this.shouldShowSquashBeforeMerge; return this.squashBeforeMerge && this.shouldShowSquashBeforeMerge;
...@@ -126,12 +126,12 @@ export default { ...@@ -126,12 +126,12 @@ export default {
this.isMergingImmediately = true; this.isMergingImmediately = true;
} }
this.setToMergeWhenPipelineSucceeds = mergeWhenBuildSucceeds === true; this.autoMergeStrategy = mergeWhenBuildSucceeds ? 'merge_when_pipeline_succeeds' : undefined;
const options = { const options = {
sha: this.mr.sha, sha: this.mr.sha,
commit_message: this.commitMessage, commit_message: this.commitMessage,
merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds, auto_merge_strategy: this.autoMergeStrategy,
should_remove_source_branch: this.removeSourceBranch === true, should_remove_source_branch: this.removeSourceBranch === true,
squash: this.squashBeforeMerge, squash: this.squashBeforeMerge,
squash_commit_message: this.squashCommitMessage, squash_commit_message: this.squashCommitMessage,
......
...@@ -23,8 +23,8 @@ export default function deviseState(data) { ...@@ -23,8 +23,8 @@ export default function deviseState(data) {
return stateKey.pipelineBlocked; return stateKey.pipelineBlocked;
} else if (this.isSHAMismatch) { } else if (this.isSHAMismatch) {
return stateKey.shaMismatch; return stateKey.shaMismatch;
} else if (this.mergeWhenPipelineSucceeds) { } else if (this.autoMergeEnabled) {
return this.mergeError ? stateKey.autoMergeFailed : stateKey.mergeWhenPipelineSucceeds; return this.mergeError ? stateKey.autoMergeFailed : stateKey.autoMergeEnabled;
} else if (!this.canMerge) { } else if (!this.canMerge) {
return stateKey.notAllowedToMerge; return stateKey.notAllowedToMerge;
} else if (this.canBeMerged) { } else if (this.canBeMerged) {
......
...@@ -61,7 +61,7 @@ export default class MergeRequestStore { ...@@ -61,7 +61,7 @@ export default class MergeRequestStore {
this.updatedAt = data.updated_at; this.updatedAt = data.updated_at;
this.metrics = MergeRequestStore.buildMetrics(data.metrics); this.metrics = MergeRequestStore.buildMetrics(data.metrics);
this.setToMWPSBy = MergeRequestStore.formatUserObject(data.merge_user || {}); this.setToAutoMergeBy = MergeRequestStore.formatUserObject(data.merge_user || {});
this.mergeUserId = data.merge_user_id; this.mergeUserId = data.merge_user_id;
this.currentUserId = gon.current_user_id; this.currentUserId = gon.current_user_id;
this.sourceBranchPath = data.source_branch_path; this.sourceBranchPath = data.source_branch_path;
...@@ -70,12 +70,13 @@ export default class MergeRequestStore { ...@@ -70,12 +70,13 @@ export default class MergeRequestStore {
this.targetBranchPath = data.target_branch_commits_path; this.targetBranchPath = data.target_branch_commits_path;
this.targetBranchTreePath = data.target_branch_tree_path; this.targetBranchTreePath = data.target_branch_tree_path;
this.conflictResolutionPath = data.conflict_resolution_path; this.conflictResolutionPath = data.conflict_resolution_path;
this.cancelAutoMergePath = data.cancel_merge_when_pipeline_succeeds_path; this.cancelAutoMergePath = data.cancel_auto_merge_path;
this.removeWIPPath = data.remove_wip_path; this.removeWIPPath = data.remove_wip_path;
this.sourceBranchRemoved = !data.source_branch_exists; this.sourceBranchRemoved = !data.source_branch_exists;
this.shouldRemoveSourceBranch = data.remove_source_branch || false; this.shouldRemoveSourceBranch = data.remove_source_branch || false;
this.onlyAllowMergeIfPipelineSucceeds = data.only_allow_merge_if_pipeline_succeeds || false; this.onlyAllowMergeIfPipelineSucceeds = data.only_allow_merge_if_pipeline_succeeds || false;
this.mergeWhenPipelineSucceeds = data.merge_when_pipeline_succeeds || false; this.autoMergeEnabled = Boolean(data.auto_merge_enabled);
this.autoMergeStrategy = data.auto_merge_strategy;
this.mergePath = data.merge_path; this.mergePath = data.merge_path;
this.ffOnlyEnabled = data.ff_only_enabled; this.ffOnlyEnabled = data.ff_only_enabled;
this.shouldBeRebased = !!data.should_be_rebased; this.shouldBeRebased = !!data.should_be_rebased;
...@@ -93,7 +94,7 @@ export default class MergeRequestStore { ...@@ -93,7 +94,7 @@ export default class MergeRequestStore {
this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false; this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false;
this.canMerge = !!data.merge_path; this.canMerge = !!data.merge_path;
this.canCreateIssue = currentUser.can_create_issue || false; this.canCreateIssue = currentUser.can_create_issue || false;
this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path; this.canCancelAutomaticMerge = !!data.cancel_auto_merge_path;
this.isSHAMismatch = this.sha !== data.diff_head_sha; this.isSHAMismatch = this.sha !== data.diff_head_sha;
this.canBeMerged = data.can_be_merged || false; this.canBeMerged = data.can_be_merged || false;
this.isMergeAllowed = data.mergeable || false; this.isMergeAllowed = data.mergeable || false;
......
...@@ -13,7 +13,7 @@ const stateToComponentMap = { ...@@ -13,7 +13,7 @@ const stateToComponentMap = {
unresolvedDiscussions: 'mr-widget-unresolved-discussions', unresolvedDiscussions: 'mr-widget-unresolved-discussions',
pipelineBlocked: 'mr-widget-pipeline-blocked', pipelineBlocked: 'mr-widget-pipeline-blocked',
pipelineFailed: 'mr-widget-pipeline-failed', pipelineFailed: 'mr-widget-pipeline-failed',
mergeWhenPipelineSucceeds: 'mr-widget-merge-when-pipeline-succeeds', autoMergeEnabled: 'mr-widget-merge-when-pipeline-succeeds',
failedToMerge: 'mr-widget-failed-to-merge', failedToMerge: 'mr-widget-failed-to-merge',
autoMergeFailed: 'mr-widget-auto-merge-failed', autoMergeFailed: 'mr-widget-auto-merge-failed',
shaMismatch: 'sha-mismatch', shaMismatch: 'sha-mismatch',
...@@ -45,7 +45,7 @@ export const stateKey = { ...@@ -45,7 +45,7 @@ export const stateKey = {
pipelineBlocked: 'pipelineBlocked', pipelineBlocked: 'pipelineBlocked',
shaMismatch: 'shaMismatch', shaMismatch: 'shaMismatch',
autoMergeFailed: 'autoMergeFailed', autoMergeFailed: 'autoMergeFailed',
mergeWhenPipelineSucceeds: 'mergeWhenPipelineSucceeds', autoMergeEnabled: 'autoMergeEnabled',
notAllowedToMerge: 'notAllowedToMerge', notAllowedToMerge: 'notAllowedToMerge',
readyToMerge: 'readyToMerge', readyToMerge: 'readyToMerge',
rebase: 'rebase', rebase: 'rebase',
......
...@@ -145,14 +145,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -145,14 +145,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
render partial: 'projects/merge_requests/widget/commit_change_content', layout: false render partial: 'projects/merge_requests/widget/commit_change_content', layout: false
end end
def cancel_merge_when_pipeline_succeeds def cancel_auto_merge
unless @merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user) unless @merge_request.can_cancel_auto_merge?(current_user)
return access_denied! return access_denied!
end end
::MergeRequests::MergeWhenPipelineSucceedsService AutoMergeService.new(project, current_user).cancel(@merge_request)
.new(@project, current_user)
.cancel(@merge_request)
render json: serialize_widget(@merge_request) render json: serialize_widget(@merge_request)
end end
...@@ -229,12 +227,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -229,12 +227,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end end
def merge_params_attributes def merge_params_attributes
[:should_remove_source_branch, :commit_message, :squash_commit_message, :squash] [:should_remove_source_branch, :commit_message, :squash_commit_message, :squash, :auto_merge_strategy]
end end
def merge_when_pipeline_succeeds_active? def auto_merge_requested?
params[:merge_when_pipeline_succeeds].present? && # Support params[:merge_when_pipeline_succeeds] during the transition period
@merge_request.head_pipeline && @merge_request.head_pipeline.active? params[:auto_merge_strategy].present? || params[:merge_when_pipeline_succeeds].present?
end end
def close_merge_request_if_no_source_project def close_merge_request_if_no_source_project
...@@ -258,9 +256,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -258,9 +256,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end end
def merge! def merge!
# Disable the CI check if merge_when_pipeline_succeeds is enabled since we have # Disable the CI check if auto_merge_strategy is specified since we have
# to wait until CI completes to know # to wait until CI completes to know
unless @merge_request.mergeable?(skip_ci_check: merge_when_pipeline_succeeds_active?) unless @merge_request.mergeable?(skip_ci_check: auto_merge_requested?)
return :failed return :failed
end end
...@@ -274,24 +272,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -274,24 +272,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@merge_request.update(merge_error: nil, squash: merge_params.fetch(:squash, false)) @merge_request.update(merge_error: nil, squash: merge_params.fetch(:squash, false))
if params[:merge_when_pipeline_succeeds].present? if auto_merge_requested?
return :failed unless @merge_request.actual_head_pipeline AutoMergeService.new(project, current_user, merge_params)
.execute(merge_request,
if @merge_request.actual_head_pipeline.active? params[:auto_merge_strategy] || AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
::MergeRequests::MergeWhenPipelineSucceedsService
.new(@project, current_user, merge_params)
.execute(@merge_request)
:merge_when_pipeline_succeeds
elsif @merge_request.actual_head_pipeline.success?
# This can be triggered when a user clicks the auto merge button while
# the tests finish at about the same time
@merge_request.merge_async(current_user.id, merge_params)
:success
else
:failed
end
else else
@merge_request.merge_async(current_user.id, merge_params) @merge_request.merge_async(current_user.id, merge_params)
......
...@@ -103,7 +103,7 @@ module MergeRequestsHelper ...@@ -103,7 +103,7 @@ module MergeRequestsHelper
def merge_params(merge_request) def merge_params(merge_request)
{ {
merge_when_pipeline_succeeds: true, auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
should_remove_source_branch: true, should_remove_source_branch: true,
sha: merge_request.diff_head_sha, sha: merge_request.diff_head_sha,
squash: merge_request.squash squash: merge_request.squash
......
...@@ -165,7 +165,7 @@ class MergeRequest < ApplicationRecord ...@@ -165,7 +165,7 @@ class MergeRequest < ApplicationRecord
validates :source_branch, presence: true validates :source_branch, presence: true
validates :target_project, presence: true validates :target_project, presence: true
validates :target_branch, presence: true validates :target_branch, presence: true
validates :merge_user, presence: true, if: :merge_when_pipeline_succeeds?, unless: :importing? validates :merge_user, presence: true, if: :auto_merge_enabled?, unless: :importing?
validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?] validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
validate :validate_fork, unless: :closed_without_fork? validate :validate_fork, unless: :closed_without_fork?
validate :validate_target_project, on: :create validate :validate_target_project, on: :create
...@@ -196,6 +196,7 @@ class MergeRequest < ApplicationRecord ...@@ -196,6 +196,7 @@ class MergeRequest < ApplicationRecord
alias_attribute :project, :target_project alias_attribute :project, :target_project
alias_attribute :project_id, :target_project_id alias_attribute :project_id, :target_project_id
alias_attribute :auto_merge_enabled, :merge_when_pipeline_succeeds
def self.reference_prefix def self.reference_prefix
'!' '!'
...@@ -391,7 +392,7 @@ class MergeRequest < ApplicationRecord ...@@ -391,7 +392,7 @@ class MergeRequest < ApplicationRecord
def merge_participants def merge_participants
participants = [author] participants = [author]
if merge_when_pipeline_succeeds? && !participants.include?(merge_user) if auto_merge_enabled? && !participants.include?(merge_user)
participants << merge_user participants << merge_user
end end
...@@ -782,7 +783,7 @@ class MergeRequest < ApplicationRecord ...@@ -782,7 +783,7 @@ class MergeRequest < ApplicationRecord
project.ff_merge_must_be_possible? && !ff_merge_possible? project.ff_merge_must_be_possible? && !ff_merge_possible?
end end
def can_cancel_merge_when_pipeline_succeeds?(current_user) def can_cancel_auto_merge?(current_user)
can_be_merged_by?(current_user) || self.author == current_user can_be_merged_by?(current_user) || self.author == current_user
end end
...@@ -801,6 +802,16 @@ class MergeRequest < ApplicationRecord ...@@ -801,6 +802,16 @@ class MergeRequest < ApplicationRecord
Gitlab::Utils.to_boolean(merge_params['force_remove_source_branch']) Gitlab::Utils.to_boolean(merge_params['force_remove_source_branch'])
end end
def auto_merge_strategy
return unless auto_merge_enabled?
merge_params['auto_merge_strategy'] || AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
end
def auto_merge_strategy=(strategy)
merge_params['auto_merge_strategy'] = strategy
end
def remove_source_branch? def remove_source_branch?
should_remove_source_branch? || force_remove_source_branch? should_remove_source_branch? || force_remove_source_branch?
end end
...@@ -973,15 +984,16 @@ class MergeRequest < ApplicationRecord ...@@ -973,15 +984,16 @@ class MergeRequest < ApplicationRecord
end end
end end
def reset_merge_when_pipeline_succeeds def reset_auto_merge
return unless merge_when_pipeline_succeeds? return unless auto_merge_enabled?
self.merge_when_pipeline_succeeds = false self.auto_merge_enabled = false
self.merge_user = nil self.merge_user = nil
if merge_params if merge_params
merge_params.delete('should_remove_source_branch') merge_params.delete('should_remove_source_branch')
merge_params.delete('commit_message') merge_params.delete('commit_message')
merge_params.delete('squash_commit_message') merge_params.delete('squash_commit_message')
merge_params.delete('auto_merge_strategy')
end end
self.save self.save
......
...@@ -22,9 +22,9 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -22,9 +22,9 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end end
end end
def cancel_merge_when_pipeline_succeeds_path def cancel_auto_merge_path
if can_cancel_merge_when_pipeline_succeeds?(current_user) if can_cancel_auto_merge?(current_user)
cancel_merge_when_pipeline_succeeds_project_merge_request_path(project, merge_request) cancel_auto_merge_project_merge_request_path(project, merge_request)
end end
end end
......
...@@ -9,7 +9,11 @@ class MergeRequestWidgetEntity < IssuableEntity ...@@ -9,7 +9,11 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_params expose :merge_params
expose :merge_status expose :merge_status
expose :merge_user_id expose :merge_user_id
expose :merge_when_pipeline_succeeds expose :auto_merge_enabled
expose :auto_merge_strategy
expose :available_auto_merge_strategies do |merge_request|
AutoMergeService.new(merge_request.project, current_user).available_strategies(merge_request) # rubocop: disable CodeReuse/ServiceClass
end
expose :source_branch expose :source_branch
expose :source_branch_protected do |merge_request| expose :source_branch_protected do |merge_request|
merge_request.source_project.present? && ProtectedBranch.protected?(merge_request.source_project, merge_request.source_branch) merge_request.source_project.present? && ProtectedBranch.protected?(merge_request.source_project, merge_request.source_branch)
...@@ -182,8 +186,8 @@ class MergeRequestWidgetEntity < IssuableEntity ...@@ -182,8 +186,8 @@ class MergeRequestWidgetEntity < IssuableEntity
presenter(merge_request).remove_wip_path presenter(merge_request).remove_wip_path
end end
expose :cancel_merge_when_pipeline_succeeds_path do |merge_request| expose :cancel_auto_merge_path do |merge_request|
presenter(merge_request).cancel_merge_when_pipeline_succeeds_path presenter(merge_request).cancel_auto_merge_path
end end
expose :create_issue_to_resolve_discussions_path do |merge_request| expose :create_issue_to_resolve_discussions_path do |merge_request|
......
# frozen_string_literal: true
module AutoMerge
class MergeWhenPipelineSucceedsService < BaseService
def execute(merge_request)
return :failed unless merge_request.actual_head_pipeline
if merge_request.actual_head_pipeline.active?
merge_request.merge_params.merge!(params)
unless merge_request.auto_merge_enabled?
merge_request.auto_merge_enabled = true
merge_request.merge_user = @current_user
merge_request.auto_merge_strategy = AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
SystemNoteService.merge_when_pipeline_succeeds(merge_request, @project, @current_user, merge_request.diff_head_commit)
end
return :failed unless merge_request.save
:merge_when_pipeline_succeeds
elsif merge_request.actual_head_pipeline.success?
# This can be triggered when a user clicks the auto merge button while
# the tests finish at about the same time
merge_request.merge_async(current_user.id, merge_params)
:success
else
:failed
end
end
def process(merge_request)
return unless merge_request.actual_head_pipeline&.success?
return unless merge_request.mergeable?
merge_request.merge_async(merge_request.merge_user_id, merge_request.merge_params)
end
def cancel(merge_request)
if merge_request.reset_auto_merge
SystemNoteService.cancel_merge_when_pipeline_succeeds(merge_request, @project, @current_user)
success
else
error("Can't cancel the automatic merge", 406)
end
end
def available_for?(merge_request)
merge_request.actual_head_pipeline&.active?
end
end
end
# frozen_string_literal: true
class AutoMergeService < BaseService
STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS = 'merge_when_pipeline_succeeds'.freeze
STRATEGIES = [STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS].freeze
class << self
def all_strategies
STRATEGIES
end
def get_service_class(strategy)
return unless all_strategies.include?(strategy)
"::AutoMerge::#{strategy.camelize}Service".constantize
end
end
def execute(merge_request, strategy)
service = get_service_instance(strategy)
return :failed unless service&.available_for?(merge_request)
service.execute(merge_request)
end
def process(merge_request)
return unless merge_request.auto_merge_enabled?
get_service_instance(merge_request.auto_merge_strategy).process(merge_request)
end
def cancel(merge_request)
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)
end
def available_strategies(merge_request)
self.class.all_strategies.select do |strategy|
get_service_instance(strategy).available_for?(merge_request)
end
end
private
def get_service_instance(strategy)
self.class.get_service_class(strategy)&.new(project, current_user, params)
end
end
# frozen_string_literal: true
module MergeRequests
class MergeWhenPipelineSucceedsService < MergeRequests::BaseService
# Marks the passed `merge_request` to be merged when the pipeline succeeds or
# updates the params for the automatic merge
def execute(merge_request)
merge_request.merge_params.merge!(params)
# The service is also called when the merge params are updated.
already_approved = merge_request.merge_when_pipeline_succeeds?
unless already_approved
merge_request.merge_when_pipeline_succeeds = true
merge_request.merge_user = @current_user
SystemNoteService.merge_when_pipeline_succeeds(merge_request, @project, @current_user, merge_request.diff_head_commit)
end
merge_request.save
end
# Triggers the automatic merge of merge_request once the pipeline succeeds
def trigger(pipeline)
return unless pipeline.success?
pipeline_merge_requests(pipeline) do |merge_request|
next unless merge_request.merge_when_pipeline_succeeds?
next unless merge_request.mergeable?
merge_request.merge_async(merge_request.merge_user_id, merge_request.merge_params)
end
end
# Cancels the automatic merge
def cancel(merge_request)
if merge_request.merge_when_pipeline_succeeds? && merge_request.open?
merge_request.reset_merge_when_pipeline_succeeds
SystemNoteService.cancel_merge_when_pipeline_succeeds(merge_request, @project, @current_user)
success
else
error("Can't cancel the automatic merge", 406)
end
end
end
end
...@@ -24,7 +24,7 @@ module MergeRequests ...@@ -24,7 +24,7 @@ module MergeRequests
reload_merge_requests reload_merge_requests
outdate_suggestions outdate_suggestions
refresh_pipelines_on_merge_requests refresh_pipelines_on_merge_requests
reset_merge_when_pipeline_succeeds cancel_auto_merge
mark_pending_todos_done mark_pending_todos_done
cache_merge_requests_closing_issues cache_merge_requests_closing_issues
...@@ -142,8 +142,10 @@ module MergeRequests ...@@ -142,8 +142,10 @@ module MergeRequests
end end
end end
def reset_merge_when_pipeline_succeeds def cancel_auto_merge
merge_requests_for_source_branch.each(&:reset_merge_when_pipeline_succeeds) merge_requests_for_source_branch.each do |merge_request|
AutoMergeService.new(project, current_user).cancel(merge_request)
end
end end
def mark_pending_todos_done def mark_pending_todos_done
......
...@@ -89,7 +89,7 @@ module MergeRequests ...@@ -89,7 +89,7 @@ module MergeRequests
merge_request.update(merge_error: nil) merge_request.update(merge_error: nil)
if merge_request.head_pipeline && merge_request.head_pipeline.active? if merge_request.head_pipeline && merge_request.head_pipeline.active?
MergeRequests::MergeWhenPipelineSucceedsService.new(project, current_user).execute(merge_request) AutoMergeService.new(project, current_user).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
else else
merge_request.merge_async(current_user.id, {}) merge_request.merge_async(current_user.id, {})
end end
......
...@@ -238,7 +238,7 @@ class NotificationService ...@@ -238,7 +238,7 @@ class NotificationService
merge_request, merge_request,
current_user, current_user,
:merged_merge_request_email, :merged_merge_request_email,
skip_current_user: !merge_request.merge_when_pipeline_succeeds? skip_current_user: !merge_request.auto_merge_enabled?
) )
end end
......
...@@ -9,9 +9,10 @@ class PipelineSuccessWorker ...@@ -9,9 +9,10 @@ class PipelineSuccessWorker
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id) def perform(pipeline_id)
Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline|
MergeRequests::MergeWhenPipelineSucceedsService pipeline.all_merge_requests.preload(:merge_user).each do |merge_request|
.new(pipeline.project, nil) AutoMergeService.new(pipeline.project, merge_request.merge_user)
.trigger(pipeline) .process(merge_request)
end
end end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
---
title: Refactor and abstract Auto Merge Processes
merge_request: 28595
author:
type: other
...@@ -208,7 +208,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -208,7 +208,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
member do member do
get :commit_change_content get :commit_change_content
post :merge post :merge
post :cancel_merge_when_pipeline_succeeds post :cancel_auto_merge
get :pipeline_status get :pipeline_status
get :ci_environments_status get :ci_environments_status
post :toggle_subscription post :toggle_subscription
......
...@@ -386,9 +386,8 @@ module API ...@@ -386,9 +386,8 @@ module API
) )
if merge_when_pipeline_succeeds && merge_request.head_pipeline && merge_request.head_pipeline.active? if merge_when_pipeline_succeeds && merge_request.head_pipeline && merge_request.head_pipeline.active?
::MergeRequests::MergeWhenPipelineSucceedsService AutoMergeService.new(merge_request.target_project, current_user, merge_params)
.new(merge_request.target_project, current_user, merge_params) .execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
.execute(merge_request)
else else
::MergeRequests::MergeService ::MergeRequests::MergeService
.new(merge_request.target_project, current_user, merge_params) .new(merge_request.target_project, current_user, merge_params)
...@@ -429,11 +428,9 @@ module API ...@@ -429,11 +428,9 @@ module API
post ':id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do post ':id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do
merge_request = find_project_merge_request(params[:merge_request_iid]) merge_request = find_project_merge_request(params[:merge_request_iid])
unauthorized! unless merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user) unauthorized! unless merge_request.can_cancel_auto_merge?(current_user)
::MergeRequests::MergeWhenPipelineSucceedsService AutoMergeService.new(merge_request.target_project, current_user).cancel(merge_request)
.new(merge_request.target_project, current_user)
.cancel(merge_request)
end end
desc 'Rebase the merge request against its target branch' do desc 'Rebase the merge request against its target branch' do
......
...@@ -429,8 +429,9 @@ describe Projects::MergeRequestsController do ...@@ -429,8 +429,9 @@ describe Projects::MergeRequestsController do
it 'sets the MR to merge when the pipeline succeeds' do it 'sets the MR to merge when the pipeline succeeds' do
service = double(:merge_when_pipeline_succeeds_service) service = double(:merge_when_pipeline_succeeds_service)
allow(service).to receive(:available_for?) { true }
expect(MergeRequests::MergeWhenPipelineSucceedsService) expect(AutoMerge::MergeWhenPipelineSucceedsService)
.to receive(:new).with(project, anything, anything) .to receive(:new).with(project, anything, anything)
.and_return(service) .and_return(service)
expect(service).to receive(:execute).with(merge_request) expect(service).to receive(:execute).with(merge_request)
...@@ -713,9 +714,9 @@ describe Projects::MergeRequestsController do ...@@ -713,9 +714,9 @@ describe Projects::MergeRequestsController do
end end
end end
describe 'POST cancel_merge_when_pipeline_succeeds' do describe 'POST cancel_auto_merge' do
subject do subject do
post :cancel_merge_when_pipeline_succeeds, post :cancel_auto_merge,
params: { params: {
format: :json, format: :json,
namespace_id: merge_request.project.namespace.to_param, namespace_id: merge_request.project.namespace.to_param,
...@@ -725,14 +726,15 @@ describe Projects::MergeRequestsController do ...@@ -725,14 +726,15 @@ describe Projects::MergeRequestsController do
xhr: true xhr: true
end end
it 'calls MergeRequests::MergeWhenPipelineSucceedsService' do it 'calls AutoMergeService' do
mwps_service = double auto_merge_service = double
allow(MergeRequests::MergeWhenPipelineSucceedsService) allow(AutoMergeService)
.to receive(:new) .to receive(:new)
.and_return(mwps_service) .and_return(auto_merge_service)
expect(mwps_service).to receive(:cancel).with(merge_request) allow(auto_merge_service).to receive(:available_strategies).with(merge_request)
expect(auto_merge_service).to receive(:cancel).with(merge_request)
subject subject
end end
......
...@@ -95,7 +95,8 @@ FactoryBot.define do ...@@ -95,7 +95,8 @@ FactoryBot.define do
end end
trait :merge_when_pipeline_succeeds do trait :merge_when_pipeline_succeeds do
merge_when_pipeline_succeeds true auto_merge_enabled true
auto_merge_strategy AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
merge_user { author } merge_user { author }
end end
......
...@@ -74,11 +74,12 @@ describe 'Merge request > User merges when pipeline succeeds', :js do ...@@ -74,11 +74,12 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
source_project: project, source_project: project,
title: 'Bug NS-04', title: 'Bug NS-04',
author: user, author: user,
merge_user: user, merge_user: user)
merge_params: { force_remove_source_branch: '1' })
end end
before do before do
merge_request.merge_params['force_remove_source_branch'] = '0'
merge_request.save!
click_link "Cancel automatic merge" click_link "Cancel automatic merge"
end end
...@@ -102,11 +103,11 @@ describe 'Merge request > User merges when pipeline succeeds', :js do ...@@ -102,11 +103,11 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
context 'when merge when pipeline succeeds is enabled' do context 'when merge when pipeline succeeds is enabled' do
let(:merge_request) do let(:merge_request) do
create(:merge_request_with_diffs, :simple, source_project: project, create(:merge_request_with_diffs, :simple, :merge_when_pipeline_succeeds,
author: user, source_project: project,
merge_user: user, author: user,
title: 'MepMep', merge_user: user,
merge_when_pipeline_succeeds: true) title: 'MepMep')
end end
let!(:build) do let!(:build) do
create(:ci_build, pipeline: pipeline) create(:ci_build, pipeline: pipeline)
......
...@@ -314,7 +314,8 @@ describe 'Merge request > User sees merge widget', :js do ...@@ -314,7 +314,8 @@ describe 'Merge request > User sees merge widget', :js do
context 'view merge request with MWPS enabled but automatically merge fails' do context 'view merge request with MWPS enabled but automatically merge fails' do
before do before do
merge_request.update( merge_request.update(
merge_when_pipeline_succeeds: true, auto_merge_enabled: true,
auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
merge_user: merge_request.author, merge_user: merge_request.author,
merge_error: 'Something went wrong' merge_error: 'Something went wrong'
) )
......
...@@ -4,7 +4,7 @@ describe('getStateKey', () => { ...@@ -4,7 +4,7 @@ describe('getStateKey', () => {
it('should return proper state name', () => { it('should return proper state name', () => {
const context = { const context = {
mergeStatus: 'checked', mergeStatus: 'checked',
mergeWhenPipelineSucceeds: false, autoMergeEnabled: false,
canMerge: true, canMerge: true,
onlyAllowMergeIfPipelineSucceeds: false, onlyAllowMergeIfPipelineSucceeds: false,
isPipelineFailed: false, isPipelineFailed: false,
...@@ -31,9 +31,9 @@ describe('getStateKey', () => { ...@@ -31,9 +31,9 @@ describe('getStateKey', () => {
expect(bound()).toEqual('notAllowedToMerge'); expect(bound()).toEqual('notAllowedToMerge');
context.mergeWhenPipelineSucceeds = true; context.autoMergeEnabled = true;
expect(bound()).toEqual('mergeWhenPipelineSucceeds'); expect(bound()).toEqual('autoMergeEnabled');
context.isSHAMismatch = true; context.isSHAMismatch = true;
...@@ -80,7 +80,7 @@ describe('getStateKey', () => { ...@@ -80,7 +80,7 @@ describe('getStateKey', () => {
it('returns rebased state key', () => { it('returns rebased state key', () => {
const context = { const context = {
mergeStatus: 'checked', mergeStatus: 'checked',
mergeWhenPipelineSucceeds: false, autoMergeEnabled: false,
canMerge: true, canMerge: true,
onlyAllowMergeIfPipelineSucceeds: true, onlyAllowMergeIfPipelineSucceeds: true,
isPipelineFailed: true, isPipelineFailed: true,
......
...@@ -21,7 +21,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => { ...@@ -21,7 +21,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
canCancelAutomaticMerge: true, canCancelAutomaticMerge: true,
mergeUserId: 1, mergeUserId: 1,
currentUserId: 1, currentUserId: 1,
setToMWPSBy: {}, setToAutoMergeBy: {},
sha, sha,
targetBranchPath, targetBranchPath,
targetBranch, targetBranch,
...@@ -106,7 +106,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => { ...@@ -106,7 +106,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
expect(vm.service.merge).toHaveBeenCalledWith({ expect(vm.service.merge).toHaveBeenCalledWith({
sha, sha,
merge_when_pipeline_succeeds: true, auto_merge_strategy: 'merge_when_pipeline_succeeds',
should_remove_source_branch: true, should_remove_source_branch: true,
}); });
done(); done();
......
...@@ -80,7 +80,7 @@ describe('ReadyToMerge', () => { ...@@ -80,7 +80,7 @@ describe('ReadyToMerge', () => {
it('should have default data', () => { it('should have default data', () => {
expect(vm.mergeWhenBuildSucceeds).toBeFalsy(); expect(vm.mergeWhenBuildSucceeds).toBeFalsy();
expect(vm.useCommitMessageWithDescription).toBeFalsy(); expect(vm.useCommitMessageWithDescription).toBeFalsy();
expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy(); expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.showCommitMessageEditor).toBeFalsy(); expect(vm.showCommitMessageEditor).toBeFalsy();
expect(vm.isMakingRequest).toBeFalsy(); expect(vm.isMakingRequest).toBeFalsy();
expect(vm.isMergingImmediately).toBeFalsy(); expect(vm.isMergingImmediately).toBeFalsy();
...@@ -91,17 +91,17 @@ describe('ReadyToMerge', () => { ...@@ -91,17 +91,17 @@ describe('ReadyToMerge', () => {
}); });
describe('computed', () => { describe('computed', () => {
describe('shouldShowMergeWhenPipelineSucceedsText', () => { describe('shouldShowAutoMergeText', () => {
it('should return true with active pipeline', () => { it('should return true with active pipeline', () => {
vm.mr.isPipelineActive = true; vm.mr.isPipelineActive = true;
expect(vm.shouldShowMergeWhenPipelineSucceedsText).toBeTruthy(); expect(vm.shouldShowAutoMergeText).toBeTruthy();
}); });
it('should return false with inactive pipeline', () => { it('should return false with inactive pipeline', () => {
vm.mr.isPipelineActive = false; vm.mr.isPipelineActive = false;
expect(vm.shouldShowMergeWhenPipelineSucceedsText).toBeFalsy(); expect(vm.shouldShowAutoMergeText).toBeFalsy();
}); });
}); });
...@@ -325,16 +325,20 @@ describe('ReadyToMerge', () => { ...@@ -325,16 +325,20 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick(true); vm.handleMergeButtonClick(true);
setTimeout(() => { setTimeout(() => {
expect(vm.setToMergeWhenPipelineSucceeds).toBeTruthy(); expect(vm.autoMergeStrategy).toBe('merge_when_pipeline_succeeds');
expect(vm.isMakingRequest).toBeTruthy(); expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
const params = vm.service.merge.calls.argsFor(0)[0]; const params = vm.service.merge.calls.argsFor(0)[0];
expect(params.sha).toEqual(vm.mr.sha); expect(params).toEqual(
expect(params.commit_message).toEqual(vm.mr.commitMessage); jasmine.objectContaining({
expect(params.should_remove_source_branch).toBeFalsy(); sha: vm.mr.sha,
expect(params.merge_when_pipeline_succeeds).toBeTruthy(); commit_message: vm.mr.commitMessage,
should_remove_source_branch: false,
auto_merge_strategy: 'merge_when_pipeline_succeeds',
}),
);
done(); done();
}, 333); }, 333);
}); });
...@@ -345,7 +349,7 @@ describe('ReadyToMerge', () => { ...@@ -345,7 +349,7 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick(false, true); vm.handleMergeButtonClick(false, true);
setTimeout(() => { setTimeout(() => {
expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy(); expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.isMakingRequest).toBeTruthy(); expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined); expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined);
...@@ -363,7 +367,7 @@ describe('ReadyToMerge', () => { ...@@ -363,7 +367,7 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick(); vm.handleMergeButtonClick();
setTimeout(() => { setTimeout(() => {
expect(vm.setToMergeWhenPipelineSucceeds).toBeFalsy(); expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.isMakingRequest).toBeTruthy(); expect(vm.isMakingRequest).toBeTruthy();
expect(vm.initiateMergePolling).toHaveBeenCalled(); expect(vm.initiateMergePolling).toHaveBeenCalled();
......
...@@ -1038,14 +1038,28 @@ describe MergeRequest do ...@@ -1038,14 +1038,28 @@ describe MergeRequest do
end end
end end
describe "#reset_merge_when_pipeline_succeeds" do describe "#auto_merge_strategy" do
subject { merge_request.auto_merge_strategy }
let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
it { is_expected.to eq('merge_when_pipeline_succeeds') }
context 'when auto merge is disabled' do
let(:merge_request) { create(:merge_request) }
it { is_expected.to be_nil }
end
end
describe "#reset_auto_merge" do
let(:merge_if_green) do let(:merge_if_green) do
create :merge_request, merge_when_pipeline_succeeds: true, merge_user: create(:user), create :merge_request, merge_when_pipeline_succeeds: true, merge_user: create(:user),
merge_params: { "should_remove_source_branch" => "1", "commit_message" => "msg" } merge_params: { "should_remove_source_branch" => "1", "commit_message" => "msg" }
end end
it "sets the item to false" do it "sets the item to false" do
merge_if_green.reset_merge_when_pipeline_succeeds merge_if_green.reset_auto_merge
merge_if_green.reload merge_if_green.reload
expect(merge_if_green.merge_when_pipeline_succeeds).to be_falsey expect(merge_if_green.merge_when_pipeline_succeeds).to be_falsey
......
...@@ -207,25 +207,25 @@ describe MergeRequestPresenter do ...@@ -207,25 +207,25 @@ describe MergeRequestPresenter do
end end
end end
describe '#cancel_merge_when_pipeline_succeeds_path' do describe '#cancel_auto_merge_path' do
subject do subject do
described_class.new(resource, current_user: user) described_class.new(resource, current_user: user)
.cancel_merge_when_pipeline_succeeds_path .cancel_auto_merge_path
end end
context 'when can cancel mwps' do context 'when can cancel mwps' do
it 'returns path' do it 'returns path' do
allow(resource).to receive(:can_cancel_merge_when_pipeline_succeeds?) allow(resource).to receive(:can_cancel_auto_merge?)
.with(user) .with(user)
.and_return(true) .and_return(true)
is_expected.to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}/cancel_merge_when_pipeline_succeeds") is_expected.to eq("/#{resource.project.full_path}/merge_requests/#{resource.iid}/cancel_auto_merge")
end end
end end
context 'when cannot cancel mwps' do context 'when cannot cancel mwps' do
it 'returns nil' do it 'returns nil' do
allow(resource).to receive(:can_cancel_merge_when_pipeline_succeeds?) allow(resource).to receive(:can_cancel_auto_merge?)
.with(user) .with(user)
.and_return(false) .and_return(false)
......
...@@ -1473,7 +1473,7 @@ describe API::MergeRequests do ...@@ -1473,7 +1473,7 @@ describe API::MergeRequests do
end end
it "enables merge when pipeline succeeds if the pipeline is active" do it "enables merge when pipeline succeeds if the pipeline is active" do
allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) allow_any_instance_of(MergeRequest).to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
allow(pipeline).to receive(:active?).and_return(true) allow(pipeline).to receive(:active?).and_return(true)
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { merge_when_pipeline_succeeds: true } put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { merge_when_pipeline_succeeds: true }
...@@ -1484,7 +1484,7 @@ describe API::MergeRequests do ...@@ -1484,7 +1484,7 @@ describe API::MergeRequests do
end end
it "enables merge when pipeline succeeds if the pipeline is active and only_allow_merge_if_pipeline_succeeds is true" do it "enables merge when pipeline succeeds if the pipeline is active and only_allow_merge_if_pipeline_succeeds is true" do
allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) allow_any_instance_of(MergeRequest).to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
allow(pipeline).to receive(:active?).and_return(true) allow(pipeline).to receive(:active?).and_return(true)
project.update_attribute(:only_allow_merge_if_pipeline_succeeds, true) project.update_attribute(:only_allow_merge_if_pipeline_succeeds, true)
...@@ -1950,7 +1950,7 @@ describe API::MergeRequests do ...@@ -1950,7 +1950,7 @@ describe API::MergeRequests do
describe 'POST :id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do describe 'POST :id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do
before do before do
::MergeRequests::MergeWhenPipelineSucceedsService.new(merge_request.target_project, user).execute(merge_request) ::AutoMergeService.new(merge_request.target_project, user).execute(merge_request, AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
end end
it 'removes the merge_when_pipeline_succeeds status' do it 'removes the merge_when_pipeline_succeeds status' do
......
...@@ -297,4 +297,50 @@ describe MergeRequestWidgetEntity do ...@@ -297,4 +297,50 @@ describe MergeRequestWidgetEntity do
end end
end end
end end
describe 'auto merge' do
context 'when auto merge is enabled' do
let(:resource) { create(:merge_request, :merge_when_pipeline_succeeds) }
it 'returns auto merge related information' do
expect(subject[:auto_merge_enabled]).to be_truthy
expect(subject[:auto_merge_strategy]).to eq('merge_when_pipeline_succeeds')
end
end
context 'when auto merge is not enabled' do
let(:resource) { create(:merge_request) }
it 'returns auto merge related information' do
expect(subject[:auto_merge_enabled]).to be_falsy
expect(subject[:auto_merge_strategy]).to be_nil
end
end
context 'when head pipeline is running' do
before do
create(:ci_pipeline, :running, project: project,
ref: resource.source_branch,
sha: resource.diff_head_sha)
resource.update_head_pipeline
end
it 'returns available auto merge strategies' do
expect(subject[:available_auto_merge_strategies]).to eq(%w[merge_when_pipeline_succeeds])
end
end
context 'when head pipeline is finished' do
before do
create(:ci_pipeline, :success, project: project,
ref: resource.source_branch,
sha: resource.diff_head_sha)
resource.update_head_pipeline
end
it 'returns available auto merge strategies' do
expect(subject[:available_auto_merge_strategies]).to be_empty
end
end
end
end end
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
describe MergeRequests::MergeWhenPipelineSucceedsService do describe AutoMerge::MergeWhenPipelineSucceedsService do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
...@@ -21,6 +21,27 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do ...@@ -21,6 +21,27 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
described_class.new(project, user, commit_message: 'Awesome message') described_class.new(project, user, commit_message: 'Awesome message')
end end
describe "#available_for?" do
subject { service.available_for?(mr_merge_if_green_enabled) }
let(:pipeline_status) { :running }
before do
create(:ci_pipeline, pipeline_status, ref: mr_merge_if_green_enabled.source_branch,
sha: mr_merge_if_green_enabled.diff_head_sha,
project: mr_merge_if_green_enabled.source_project)
mr_merge_if_green_enabled.update_head_pipeline
end
it { is_expected.to be_truthy }
context 'when the head piipeline succeeded' do
let(:pipeline_status) { :success }
it { is_expected.to be_falsy }
end
end
describe "#execute" do describe "#execute" do
let(:merge_request) do let(:merge_request) do
create(:merge_request, target_project: project, source_project: project, create(:merge_request, target_project: project, source_project: project,
...@@ -30,8 +51,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do ...@@ -30,8 +51,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
context 'first time enabling' do context 'first time enabling' do
before do before do
allow(merge_request) allow(merge_request)
.to receive(:head_pipeline) .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
.and_return(pipeline)
service.execute(merge_request) service.execute(merge_request)
end end
...@@ -39,7 +59,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do ...@@ -39,7 +59,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
it 'sets the params, merge_user, and flag' do it 'sets the params, merge_user, and flag' do
expect(merge_request).to be_valid expect(merge_request).to be_valid
expect(merge_request.merge_when_pipeline_succeeds).to be_truthy expect(merge_request.merge_when_pipeline_succeeds).to be_truthy
expect(merge_request.merge_params).to eq commit_message: 'Awesome message' expect(merge_request.merge_params).to include commit_message: 'Awesome message'
expect(merge_request.merge_user).to be user expect(merge_request.merge_user).to be user
end end
...@@ -54,8 +74,8 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do ...@@ -54,8 +74,8 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) } let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch) }
before do before do
allow(mr_merge_if_green_enabled).to receive(:head_pipeline) allow(mr_merge_if_green_enabled)
.and_return(pipeline) .to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
allow(mr_merge_if_green_enabled).to receive(:mergeable?) allow(mr_merge_if_green_enabled).to receive(:mergeable?)
.and_return(true) .and_return(true)
...@@ -72,7 +92,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do ...@@ -72,7 +92,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
end end
end end
describe "#trigger" do describe "#process" do
let(:merge_request_ref) { mr_merge_if_green_enabled.source_branch } let(:merge_request_ref) { mr_merge_if_green_enabled.source_branch }
let(:merge_request_head) do let(:merge_request_head) do
project.commit(mr_merge_if_green_enabled.source_branch).id project.commit(mr_merge_if_green_enabled.source_branch).id
...@@ -86,8 +106,11 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do ...@@ -86,8 +106,11 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
end end
it "merges all merge requests with merge when the pipeline succeeds enabled" do it "merges all merge requests with merge when the pipeline succeeds enabled" do
allow(mr_merge_if_green_enabled)
.to receive_messages(head_pipeline: triggering_pipeline, actual_head_pipeline: triggering_pipeline)
expect(MergeWorker).to receive(:perform_async) expect(MergeWorker).to receive(:perform_async)
service.trigger(triggering_pipeline) service.process(mr_merge_if_green_enabled)
end end
end end
...@@ -99,7 +122,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do ...@@ -99,7 +122,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
it 'does not merge request' do it 'does not merge request' do
expect(MergeWorker).not_to receive(:perform_async) expect(MergeWorker).not_to receive(:perform_async)
service.trigger(old_pipeline) service.process(mr_merge_if_green_enabled)
end end
end end
...@@ -111,7 +134,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do ...@@ -111,7 +134,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
it 'does not merge request' do it 'does not merge request' do
expect(MergeWorker).not_to receive(:perform_async) expect(MergeWorker).not_to receive(:perform_async)
service.trigger(unrelated_pipeline) service.process(mr_merge_if_green_enabled)
end end
end end
...@@ -125,8 +148,11 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do ...@@ -125,8 +148,11 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
end end
it 'merges the associated merge request' do it 'merges the associated merge request' do
allow(mr_merge_if_green_enabled)
.to receive_messages(head_pipeline: pipeline, actual_head_pipeline: pipeline)
expect(MergeWorker).to receive(:perform_async) expect(MergeWorker).to receive(:perform_async)
service.trigger(pipeline) service.process(mr_merge_if_green_enabled)
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe AutoMergeService do
set(:project) { create(:project) }
set(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
describe '.all_strategies' do
subject { described_class.all_strategies }
it 'returns all strategies' do
is_expected.to eq(AutoMergeService::STRATEGIES)
end
end
describe '#available_strategies' do
subject { service.available_strategies(merge_request) }
let(:merge_request) { create(:merge_request) }
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 available strategies' do
is_expected.to include('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_empty
end
end
end
describe '.get_service_class' do
subject { described_class.get_service_class(strategy) }
let(:strategy) { AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS }
it 'returns service instance' do
is_expected.to eq(AutoMerge::MergeWhenPipelineSucceedsService)
end
context 'when strategy is not present' do
let(:strategy) { }
it 'returns nil' do
is_expected.to be_nil
end
end
end
describe '#execute' do
subject { service.execute(merge_request, strategy) }
let(:merge_request) { create(:merge_request) }
let(:pipeline_status) { :running }
let(:strategy) { AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS }
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 'delegates to a relevant service instance' do
expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service|
expect(service).to receive(:execute).with(merge_request)
end
subject
end
context 'when the head piipeline succeeded' do
let(:pipeline_status) { :success }
it 'returns failed' do
is_expected.to eq(:failed)
end
end
end
describe '#process' do
subject { service.process(merge_request) }
let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
it 'delegates to a relevant service instance' do
expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service|
expect(service).to receive(:process).with(merge_request)
end
subject
end
context 'when auto merge is not enabled' do
let(:merge_request) { create(:merge_request) }
it 'returns nil' do
is_expected.to be_nil
end
end
end
describe '#cancel' do
subject { service.cancel(merge_request) }
let(:merge_request) { create(:merge_request, :merge_when_pipeline_succeeds) }
it 'delegates to a relevant service instance' do
expect_next_instance_of(AutoMerge::MergeWhenPipelineSucceedsService) do |service|
expect(service).to receive(:cancel).with(merge_request)
end
subject
end
context 'when auto merge is not enabled' do
let(:merge_request) { create(:merge_request) }
it 'returns error' do
expect(subject[:message]).to eq("Can't cancel the automatic merge")
expect(subject[:status]).to eq(:error)
expect(subject[:http_status]).to eq(406)
end
end
end
end
...@@ -76,10 +76,11 @@ describe MergeRequests::PushOptionsHandlerService do ...@@ -76,10 +76,11 @@ describe MergeRequests::PushOptionsHandlerService do
shared_examples_for 'a service that can set the merge request to merge when pipeline succeeds' do shared_examples_for 'a service that can set the merge request to merge when pipeline succeeds' do
subject(:last_mr) { MergeRequest.last } subject(:last_mr) { MergeRequest.last }
it 'sets merge_when_pipeline_succeeds' do it 'sets auto_merge_enabled' do
service.execute service.execute
expect(last_mr.merge_when_pipeline_succeeds).to eq(true) expect(last_mr.auto_merge_enabled).to eq(true)
expect(last_mr.auto_merge_strategy).to eq(AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS)
end end
it 'sets merge_user to the user' do it 'sets merge_user to the user' do
......
...@@ -23,7 +23,8 @@ describe MergeRequests::RefreshService do ...@@ -23,7 +23,8 @@ describe MergeRequests::RefreshService do
source_branch: 'master', source_branch: 'master',
target_branch: 'feature', target_branch: 'feature',
target_project: @project, target_project: @project,
merge_when_pipeline_succeeds: true, auto_merge_enabled: true,
auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
merge_user: @user) merge_user: @user)
@another_merge_request = create(:merge_request, @another_merge_request = create(:merge_request,
...@@ -31,7 +32,8 @@ describe MergeRequests::RefreshService do ...@@ -31,7 +32,8 @@ describe MergeRequests::RefreshService do
source_branch: 'master', source_branch: 'master',
target_branch: 'test', target_branch: 'test',
target_project: @project, target_project: @project,
merge_when_pipeline_succeeds: true, auto_merge_enabled: true,
auto_merge_strategy: AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS,
merge_user: @user) merge_user: @user)
@fork_merge_request = create(:merge_request, @fork_merge_request = create(:merge_request,
...@@ -83,7 +85,7 @@ describe MergeRequests::RefreshService do ...@@ -83,7 +85,7 @@ describe MergeRequests::RefreshService do
expect(@merge_request.notes).not_to be_empty expect(@merge_request.notes).not_to be_empty
expect(@merge_request).to be_open expect(@merge_request).to be_open
expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey expect(@merge_request.auto_merge_enabled).to be_falsey
expect(@merge_request.diff_head_sha).to eq(@newrev) expect(@merge_request.diff_head_sha).to eq(@newrev)
expect(@fork_merge_request).to be_open expect(@fork_merge_request).to be_open
expect(@fork_merge_request.notes).to be_empty expect(@fork_merge_request.notes).to be_empty
...@@ -292,7 +294,7 @@ describe MergeRequests::RefreshService do ...@@ -292,7 +294,7 @@ describe MergeRequests::RefreshService do
expect(@merge_request.notes).not_to be_empty expect(@merge_request.notes).not_to be_empty
expect(@merge_request).to be_open expect(@merge_request).to be_open
expect(@merge_request.merge_when_pipeline_succeeds).to be_falsey expect(@merge_request.auto_merge_enabled).to be_falsey
expect(@merge_request.diff_head_sha).to eq(@newrev) expect(@merge_request.diff_head_sha).to eq(@newrev)
expect(@fork_merge_request).to be_open expect(@fork_merge_request).to be_open
expect(@fork_merge_request.notes).to be_empty expect(@fork_merge_request.notes).to be_empty
......
...@@ -217,8 +217,9 @@ describe MergeRequests::UpdateService, :mailer do ...@@ -217,8 +217,9 @@ describe MergeRequests::UpdateService, :mailer do
head_pipeline_of: merge_request head_pipeline_of: merge_request
) )
expect(MergeRequests::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user) expect(AutoMerge::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user, {})
.and_return(service_mock) .and_return(service_mock)
allow(service_mock).to receive(:available_for?) { true }
expect(service_mock).to receive(:execute).with(merge_request) expect(service_mock).to receive(:execute).with(merge_request)
end end
......
...@@ -5,12 +5,13 @@ require 'spec_helper' ...@@ -5,12 +5,13 @@ require 'spec_helper'
describe PipelineSuccessWorker do describe PipelineSuccessWorker do
describe '#perform' do describe '#perform' do
context 'when pipeline exists' do context 'when pipeline exists' do
let(:pipeline) { create(:ci_pipeline, status: 'success') } let(:pipeline) { create(:ci_pipeline, status: 'success', ref: merge_request.source_branch, project: merge_request.source_project) }
let(:merge_request) { create(:merge_request) }
it 'performs "merge when pipeline succeeds"' do it 'performs "merge when pipeline succeeds"' do
expect_any_instance_of( expect_next_instance_of(AutoMergeService) do |auto_merge|
MergeRequests::MergeWhenPipelineSucceedsService expect(auto_merge).to receive(:process)
).to receive(:trigger) end
described_class.new.perform(pipeline.id) described_class.new.perform(pipeline.id)
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