Commit 57903a24 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'do-not-abort-merge-trains-on-ff' into 'master'

Abort only MWPS when FF only merge is impossible

Closes #34117

See merge request gitlab-org/gitlab!18591
parents 35ebb17e 31029c04
...@@ -209,14 +209,20 @@ class MergeRequest < ApplicationRecord ...@@ -209,14 +209,20 @@ class MergeRequest < ApplicationRecord
scope :by_target_branch, ->(branch_name) { where(target_branch: branch_name) } scope :by_target_branch, ->(branch_name) { where(target_branch: branch_name) }
scope :preload_source_project, -> { preload(:source_project) } scope :preload_source_project, -> { preload(:source_project) }
scope :with_open_merge_when_pipeline_succeeds, -> do scope :with_auto_merge_enabled, -> do
with_state(:opened).where(merge_when_pipeline_succeeds: true) with_state(:opened).where(auto_merge_enabled: true)
end end
after_save :keep_around_commit after_save :keep_around_commit
alias_attribute :project, :target_project alias_attribute :project, :target_project
alias_attribute :project_id, :target_project_id alias_attribute :project_id, :target_project_id
# Currently, `merge_when_pipeline_succeeds` column is used as a flag
# to check if _any_ auto merge strategy is activated on the merge request.
# Today, we have multiple strategies and MWPS is one of them.
# we'd eventually rename the column for avoiding confusions, but in the mean time
# please use `auto_merge_enabled` alias instead of `merge_when_pipeline_succeeds`.
alias_attribute :auto_merge_enabled, :merge_when_pipeline_succeeds alias_attribute :auto_merge_enabled, :merge_when_pipeline_succeeds
alias_method :issuing_parent, :target_project alias_method :issuing_parent, :target_project
......
...@@ -152,7 +152,8 @@ module MergeRequests ...@@ -152,7 +152,8 @@ module MergeRequests
def abort_ff_merge_requests_with_when_pipeline_succeeds def abort_ff_merge_requests_with_when_pipeline_succeeds
return unless @project.ff_merge_must_be_possible? return unless @project.ff_merge_must_be_possible?
requests_with_auto_merge_enabled_to(@push.branch_name).each do |merge_request| merge_requests_with_auto_merge_enabled_to(@push.branch_name).each do |merge_request|
next unless merge_request.auto_merge_strategy == AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS
next unless merge_request.should_be_rebased? next unless merge_request.should_be_rebased?
abort_auto_merge_with_todo(merge_request, 'target branch was updated') abort_auto_merge_with_todo(merge_request, 'target branch was updated')
...@@ -167,11 +168,11 @@ module MergeRequests ...@@ -167,11 +168,11 @@ module MergeRequests
todo_service.merge_request_became_unmergeable(merge_request) todo_service.merge_request_became_unmergeable(merge_request)
end end
def requests_with_auto_merge_enabled_to(target_branch) def merge_requests_with_auto_merge_enabled_to(target_branch)
@project @project
.merge_requests .merge_requests
.by_target_branch(target_branch) .by_target_branch(target_branch)
.with_open_merge_when_pipeline_succeeds .with_auto_merge_enabled
end end
def mark_pending_todos_done def mark_pending_todos_done
......
---
title: Abort only MWPS when FF only merge is impossible
merge_request: 18591
author:
type: fixed
# frozen_string_literal: true
class AddMergeRequestsIndexOnTargetProjectAndBranch < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :merge_requests, [:target_project_id, :target_branch],
where: "state_id = 1 AND merge_when_pipeline_succeeds = true"
end
def down
remove_concurrent_index :merge_requests, [:target_project_id, :target_branch]
end
end
...@@ -2342,6 +2342,7 @@ ActiveRecord::Schema.define(version: 2019_10_26_041447) do ...@@ -2342,6 +2342,7 @@ ActiveRecord::Schema.define(version: 2019_10_26_041447) do
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid_opened", where: "((state)::text = 'opened'::text)" t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid_opened", where: "((state)::text = 'opened'::text)"
t.index ["target_project_id", "merge_commit_sha", "id"], name: "index_merge_requests_on_tp_id_and_merge_commit_sha_and_id" t.index ["target_project_id", "merge_commit_sha", "id"], name: "index_merge_requests_on_tp_id_and_merge_commit_sha_and_id"
t.index ["target_project_id", "target_branch"], name: "index_merge_requests_on_target_project_id_and_target_branch", where: "((state_id = 1) AND (merge_when_pipeline_succeeds = true))"
t.index ["title"], name: "index_merge_requests_on_title" t.index ["title"], name: "index_merge_requests_on_title"
t.index ["title"], name: "index_merge_requests_on_title_trigram", opclass: :gin_trgm_ops, using: :gin t.index ["title"], name: "index_merge_requests_on_title_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["updated_by_id"], name: "index_merge_requests_on_updated_by_id", where: "(updated_by_id IS NOT NULL)" t.index ["updated_by_id"], name: "index_merge_requests_on_updated_by_id", where: "(updated_by_id IS NOT NULL)"
......
...@@ -4,6 +4,7 @@ require 'spec_helper' ...@@ -4,6 +4,7 @@ require 'spec_helper'
describe MergeRequests::RefreshService do describe MergeRequests::RefreshService do
include ProjectForksHelper include ProjectForksHelper
include ProjectHelpers
let(:group) { create(:group) } let(:group) { create(:group) }
let(:project) { create(:project, :repository, namespace: group, approvals_before_merge: 1, reset_approvals_on_push: true) } let(:project) { create(:project, :repository, namespace: group, approvals_before_merge: 1, reset_approvals_on_push: true) }
...@@ -147,4 +148,59 @@ describe MergeRequests::RefreshService do ...@@ -147,4 +148,59 @@ describe MergeRequests::RefreshService do
end end
end end
end end
describe '#abort_ff_merge_requests_with_when_pipeline_succeeds' do
let_it_be(:project) { create(:project, :repository, merge_method: 'ff') }
let_it_be(:author) { create_user_from_membership(project, :developer) }
let_it_be(:user) { create(:user) }
let_it_be(:merge_request, refind: true) do
create(:merge_request,
author: author,
source_project: project,
source_branch: 'feature',
target_branch: 'master',
target_project: project,
auto_merge_enabled: true,
merge_user: user)
end
let_it_be(:newrev) do
project
.repository
.create_file(user, 'test1.txt', 'Test data',
message: 'Test commit', branch_name: 'master')
end
let_it_be(:oldrev) do
project
.repository
.commit(newrev)
.parent_id
end
let(:refresh_service) { described_class.new(project, user) }
before do
merge_request.auto_merge_strategy = auto_merge_strategy
merge_request.save!
refresh_service.execute(oldrev, newrev, 'refs/heads/master')
merge_request.reload
end
context 'with add to merge train when pipeline succeeds strategy' do
let(:auto_merge_strategy) do
AutoMergeService::STRATEGY_ADD_TO_MERGE_TRAIN_WHEN_PIPELINE_SUCCEEDS
end
it_behaves_like 'maintained merge requests for MWPS'
end
context 'with merge train strategy' do
let(:auto_merge_strategy) { AutoMergeService::STRATEGY_MERGE_TRAIN }
it_behaves_like 'maintained merge requests for MWPS'
end
end
end end
...@@ -3368,7 +3368,7 @@ describe MergeRequest do ...@@ -3368,7 +3368,7 @@ describe MergeRequest do
end end
end end
describe '.with_open_merge_when_pipeline_succeeds' do describe '.with_auto_merge_enabled' do
let!(:project) { create(:project) } let!(:project) { create(:project) }
let!(:fork) { fork_project(project) } let!(:fork) { fork_project(project) }
let!(:merge_request1) do let!(:merge_request1) do
...@@ -3380,15 +3380,6 @@ describe MergeRequest do ...@@ -3380,15 +3380,6 @@ describe MergeRequest do
source_branch: 'feature-1') source_branch: 'feature-1')
end end
let!(:merge_request2) do
create(:merge_request,
:merge_when_pipeline_succeeds,
target_project: project,
target_branch: 'master',
source_project: fork,
source_branch: 'fork-feature-1')
end
let!(:merge_request4) do let!(:merge_request4) do
create(:merge_request, create(:merge_request,
target_project: project, target_project: project,
...@@ -3397,9 +3388,9 @@ describe MergeRequest do ...@@ -3397,9 +3388,9 @@ describe MergeRequest do
source_branch: 'fork-feature-2') source_branch: 'fork-feature-2')
end end
let(:query) { described_class.with_open_merge_when_pipeline_succeeds } let(:query) { described_class.with_auto_merge_enabled }
it { expect(query).to contain_exactly(merge_request1, merge_request2) } it { expect(query).to contain_exactly(merge_request1) }
end end
it_behaves_like 'versioned description' it_behaves_like 'versioned description'
......
...@@ -769,7 +769,7 @@ describe MergeRequests::RefreshService do ...@@ -769,7 +769,7 @@ describe MergeRequests::RefreshService do
fork_project(target_project, author, repository: true) fork_project(target_project, author, repository: true)
end end
let_it_be(:merge_request) do let_it_be(:merge_request, refind: true) do
create(:merge_request, create(:merge_request,
author: author, author: author,
source_project: source_project, source_project: source_project,
...@@ -795,88 +795,58 @@ describe MergeRequests::RefreshService do ...@@ -795,88 +795,58 @@ describe MergeRequests::RefreshService do
.parent_id .parent_id
end end
let(:auto_merge_strategy) { AutoMergeService::STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS }
let(:refresh_service) { service.new(project, user) } let(:refresh_service) { service.new(project, user) }
before do before do
target_project.merge_method = merge_method target_project.merge_method = merge_method
target_project.save! target_project.save!
merge_request.auto_merge_strategy = auto_merge_strategy
merge_request.save!
refresh_service.execute(oldrev, newrev, 'refs/heads/master') refresh_service.execute(oldrev, newrev, 'refs/heads/master')
merge_request.reload merge_request.reload
end end
let(:aborted_message) do
/aborted the automatic merge because target branch was updated/
end
shared_examples 'aborted MWPS' do
it 'aborts auto_merge' do
expect(merge_request.auto_merge_enabled?).to be_falsey
expect(merge_request.notes.last.note).to match(aborted_message)
end
it 'removes merge_user' do
expect(merge_request.merge_user).to be_nil
end
it 'does not add todos for merge user' do
expect(user.todos.for_target(merge_request)).to be_empty
end
it 'adds todos for merge author' do
expect(author.todos.for_target(merge_request)).to be_present.and be_all(&:pending?)
end
end
context 'when Project#merge_method is set to FF' do context 'when Project#merge_method is set to FF' do
let(:merge_method) { :ff } let(:merge_method) { :ff }
it_behaves_like 'aborted MWPS' it_behaves_like 'aborted merge requests for MWPS'
context 'with forked project' do context 'with forked project' do
let(:source_project) { forked_project } let(:source_project) { forked_project }
it_behaves_like 'aborted MWPS' it_behaves_like 'aborted merge requests for MWPS'
end
context 'with bogus auto merge strategy' do
let(:auto_merge_strategy) { 'bogus' }
it_behaves_like 'maintained merge requests for MWPS'
end end
end end
context 'when Project#merge_method is set to rebase_merge' do context 'when Project#merge_method is set to rebase_merge' do
let(:merge_method) { :rebase_merge } let(:merge_method) { :rebase_merge }
it_behaves_like 'aborted MWPS' it_behaves_like 'aborted merge requests for MWPS'
context 'with forked project' do context 'with forked project' do
let(:source_project) { forked_project } let(:source_project) { forked_project }
it_behaves_like 'aborted MWPS' it_behaves_like 'aborted merge requests for MWPS'
end end
end end
context 'when Project#merge_method is set to merge' do context 'when Project#merge_method is set to merge' do
let(:merge_method) { :merge } let(:merge_method) { :merge }
shared_examples 'maintained MWPS' do it_behaves_like 'maintained merge requests for MWPS'
it 'does not cancel auto merge' do
expect(merge_request.auto_merge_enabled?).to be_truthy
expect(merge_request.notes).to be_empty
end
it 'does not change merge_user' do
expect(merge_request.merge_user).to eq(user)
end
it 'does not add todos' do
expect(author.todos.for_target(merge_request)).to be_empty
expect(user.todos.for_target(merge_request)).to be_empty
end
end
it_behaves_like 'maintained MWPS'
context 'with forked project' do context 'with forked project' do
let(:source_project) { forked_project } let(:source_project) { forked_project }
it_behaves_like 'maintained MWPS' it_behaves_like 'maintained merge requests for MWPS'
end end
end end
end end
......
# frozen_string_literal: true
shared_examples 'aborted merge requests for MWPS' do
let(:aborted_message) do
/aborted the automatic merge because target branch was updated/
end
it 'aborts auto_merge' do
expect(merge_request.auto_merge_enabled?).to be_falsey
expect(merge_request.notes.last.note).to match(aborted_message)
end
it 'removes merge_user' do
expect(merge_request.merge_user).to be_nil
end
it 'does not add todos for merge user' do
expect(user.todos.for_target(merge_request)).to be_empty
end
it 'adds todos for merge author' do
expect(author.todos.for_target(merge_request)).to be_present.and be_all(&:pending?)
end
end
shared_examples 'maintained merge requests for MWPS' do
it 'does not cancel auto merge' do
expect(merge_request.auto_merge_enabled?).to be_truthy
expect(merge_request.notes).to be_empty
end
it 'does not change merge_user' do
expect(merge_request.merge_user).to eq(user)
end
it 'does not add todos' do
expect(author.todos.for_target(merge_request)).to be_empty
expect(user.todos.for_target(merge_request)).to be_empty
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