debian-mirror-gitlab/spec/services/merge_requests/merge_service_spec.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

596 lines
20 KiB
Ruby
Raw Permalink Normal View History

2019-07-31 22:56:46 +05:30
# frozen_string_literal: true
2015-04-26 12:48:37 +05:30
require 'spec_helper'
2023-05-27 22:25:52 +05:30
RSpec.describe MergeRequests::MergeService, feature_category: :code_review_workflow do
2021-06-08 01:23:25 +05:30
include ExclusiveLeaseHelpers
2020-03-13 15:44:24 +05:30
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
2021-04-29 21:17:54 +05:30
2019-07-31 22:56:46 +05:30
let(:merge_request) { create(:merge_request, :simple, author: user2, assignees: [user2]) }
2015-04-26 12:48:37 +05:30
let(:project) { merge_request.project }
before do
2018-11-18 11:00:15 +05:30
project.add_maintainer(user)
2018-03-17 18:26:18 +05:30
project.add_developer(user2)
2015-04-26 12:48:37 +05:30
end
2016-08-24 12:49:21 +05:30
describe '#execute' do
2021-06-08 01:23:25 +05:30
let(:service) { described_class.new(project: project, current_user: user, params: merge_params) }
2019-12-26 22:10:19 +05:30
let(:merge_params) do
{ commit_message: 'Awesome message', sha: merge_request.diff_head_sha }
end
2015-04-26 12:48:37 +05:30
2021-06-08 01:23:25 +05:30
let(:lease_key) { "merge_requests_merge_service:#{merge_request.id}" }
let!(:lease) { stub_exclusive_lease(lease_key) }
2019-12-26 22:10:19 +05:30
context 'valid params' do
2015-04-26 12:48:37 +05:30
before do
allow(service).to receive(:execute_hooks)
2021-01-03 14:25:43 +05:30
expect(merge_request).to receive(:update_and_mark_in_progress_merge_commit_sha).twice.and_call_original
2015-04-26 12:48:37 +05:30
2015-12-23 02:04:40 +05:30
perform_enqueued_jobs do
service.execute(merge_request)
end
2015-04-26 12:48:37 +05:30
end
it { expect(merge_request).to be_valid }
it { expect(merge_request).to be_merged }
2020-03-13 15:44:24 +05:30
it 'persists merge_commit_sha and nullifies in_progress_merge_commit_sha' do
expect(merge_request.merge_commit_sha).not_to be_nil
expect(merge_request.in_progress_merge_commit_sha).to be_nil
end
2021-03-08 18:12:59 +05:30
it 'does not update squash_commit_sha if it is not a squash' do
expect(merge_request.squash_commit_sha).to be_nil
end
2016-09-13 17:45:13 +05:30
it 'sends email to user2 about merge of new merge_request' do
2015-04-26 12:48:37 +05:30
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(merge_request.title)
end
2020-06-23 00:09:42 +05:30
context 'note creation' do
2021-01-03 14:25:43 +05:30
it 'creates resource state event about merge_request merge' do
event = merge_request.resource_state_events.last
expect(event.state).to eq('merged')
2020-06-23 00:09:42 +05:30
end
2015-04-26 12:48:37 +05:30
end
2019-12-26 22:10:19 +05:30
context 'when squashing' do
let(:merge_params) do
{ commit_message: 'Merge commit message',
squash_commit_message: 'Squash commit message',
sha: merge_request.diff_head_sha }
end
let(:merge_request) do
2020-06-23 00:09:42 +05:30
# A merge request with 5 commits
2023-07-09 08:55:56 +05:30
create(
:merge_request,
:simple,
author: user2,
assignees: [user2],
squash: true,
source_branch: 'improve/awesome',
target_branch: 'fix'
)
2019-12-26 22:10:19 +05:30
end
it 'merges the merge request with squashed commits' do
expect(merge_request).to be_merged
merge_commit = merge_request.merge_commit
squash_commit = merge_request.merge_commit.parents.last
expect(merge_commit.message).to eq('Merge commit message')
expect(squash_commit.message).to eq("Squash commit message\n")
end
2021-03-08 18:12:59 +05:30
it 'persists squash_commit_sha' do
squash_commit = merge_request.merge_commit.parents.last
expect(merge_request.squash_commit_sha).to eq(squash_commit.id)
end
2019-12-26 22:10:19 +05:30
end
2015-04-26 12:48:37 +05:30
end
2015-10-24 18:46:33 +05:30
2022-11-25 23:54:43 +05:30
context 'running the service once' do
let(:ref) { merge_request.to_reference(full: true) }
let(:jid) { SecureRandom.hex }
let(:messages) do
[
/#{ref} - Git merge started on JID #{jid}/,
/#{ref} - Git merge finished on JID #{jid}/,
/#{ref} - Post merge started on JID #{jid}/,
/#{ref} - Post merge finished on JID #{jid}/,
/#{ref} - Merge process finished on JID #{jid}/
]
end
before do
merge_request.update!(merge_jid: jid)
::Gitlab::ApplicationContext.push(caller_id: 'MergeWorker')
end
it 'logs status messages' do
allow(Gitlab::AppLogger).to receive(:info).and_call_original
messages.each do |message|
expect(Gitlab::AppLogger).to receive(:info).with(
hash_including(
'meta.caller_id' => 'MergeWorker',
message: message,
merge_request_info: ref
)
).and_call_original
end
service.execute(merge_request)
end
end
2021-06-08 01:23:25 +05:30
context 'running the service multiple time' do
it 'is idempotent' do
2.times { service.execute(merge_request) }
expect(merge_request.merge_error).to be_falsey
expect(merge_request).to be_valid
expect(merge_request).to be_merged
commit_messages = project.repository.commits('master', limit: 2).map(&:message)
expect(commit_messages.uniq.size).to eq(2)
expect(merge_request.in_progress_merge_commit_sha).to be_nil
end
end
2019-12-26 22:10:19 +05:30
context 'when an invalid sha is passed' do
let(:merge_request) do
2023-07-09 08:55:56 +05:30
create(
:merge_request,
:simple,
author: user2,
assignees: [user2],
squash: true,
source_branch: 'improve/awesome',
target_branch: 'fix'
)
2019-12-26 22:10:19 +05:30
end
let(:merge_params) do
{ sha: merge_request.commits.second.sha }
end
it 'does not merge the MR' do
service.execute(merge_request)
expect(merge_request).not_to be_merged
expect(merge_request.merge_error).to match(/Branch has been updated/)
end
end
context 'when the `sha` param is missing' do
let(:merge_params) { {} }
it 'returns the error' do
merge_error = 'Branch has been updated since the merge was requested. '\
'Please review the changes.'
expect { service.execute(merge_request) }
.to change { merge_request.merge_error }
.from(nil).to(merge_error)
end
end
context 'closes related issues' do
2016-11-03 12:29:30 +05:30
before do
allow(project).to receive(:default_branch).and_return(merge_request.target_branch)
end
2022-07-16 23:28:13 +05:30
it 'closes GitLab issue tracker issues', :sidekiq_inline do
2016-11-03 12:29:30 +05:30
issue = create :issue, project: project
2022-01-26 12:08:38 +05:30
commit = double('commit', safe_message: "Fixes #{issue.to_reference}", date: Time.current, authored_date: Time.current)
2016-11-03 12:29:30 +05:30
allow(merge_request).to receive(:commits).and_return([commit])
2018-11-18 11:00:15 +05:30
merge_request.cache_merge_request_closes_issues!
2016-11-03 12:29:30 +05:30
service.execute(merge_request)
expect(issue.reload.closed?).to be_truthy
end
2019-09-30 21:07:59 +05:30
context 'with Jira integration' do
2022-07-23 23:45:48 +05:30
include JiraIntegrationHelpers
2016-11-03 12:29:30 +05:30
2021-09-30 23:02:18 +05:30
let(:jira_tracker) { project.create_jira_integration }
2017-08-17 22:00:37 +05:30
let(:jira_issue) { ExternalIssue.new('JIRA-123', project) }
let(:commit) { double('commit', safe_message: "Fixes #{jira_issue.to_reference}") }
2016-11-03 12:29:30 +05:30
before do
2021-09-30 23:02:18 +05:30
stub_jira_integration_test
2018-11-18 11:00:15 +05:30
project.update!(has_external_issue_tracker: true)
2021-09-30 23:02:18 +05:30
jira_integration_settings
2017-08-17 22:00:37 +05:30
stub_jira_urls(jira_issue.id)
allow(merge_request).to receive(:commits).and_return([commit])
2016-11-03 12:29:30 +05:30
end
2019-09-30 21:07:59 +05:30
it 'closes issues on Jira issue tracker' do
2016-11-03 12:29:30 +05:30
jira_issue = ExternalIssue.new('JIRA-123', project)
2017-08-17 22:00:37 +05:30
stub_jira_urls(jira_issue)
2016-11-03 12:29:30 +05:30
commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}")
allow(merge_request).to receive(:commits).and_return([commit])
2021-09-04 01:27:46 +05:30
expect_any_instance_of(Integrations::Jira).to receive(:close_issue).with(merge_request, jira_issue, user).once
2016-11-03 12:29:30 +05:30
service.execute(merge_request)
end
2019-12-26 22:10:19 +05:30
context 'wrong issue markdown' do
2019-09-30 21:07:59 +05:30
it 'does not close issues on Jira issue tracker' do
2017-08-17 22:00:37 +05:30
jira_issue = ExternalIssue.new('#JIRA-123', project)
stub_jira_urls(jira_issue)
2016-11-03 12:29:30 +05:30
commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}")
allow(merge_request).to receive(:commits).and_return([commit])
2021-09-04 01:27:46 +05:30
expect_any_instance_of(Integrations::Jira).not_to receive(:close_issue)
2016-11-03 12:29:30 +05:30
service.execute(merge_request)
end
end
end
end
context 'closes related todos' do
2019-07-31 22:56:46 +05:30
let(:merge_request) { create(:merge_request, assignees: [user], author: user) }
2016-11-03 12:29:30 +05:30
let(:project) { merge_request.project }
2019-12-26 22:10:19 +05:30
2016-11-03 12:29:30 +05:30
let!(:todo) do
create(:todo, :assigned,
project: project,
author: user,
user: user,
target: merge_request)
end
before do
allow(service).to receive(:execute_hooks)
perform_enqueued_jobs do
service.execute(merge_request)
todo.reload
end
end
it { expect(todo).to be_done }
end
2017-09-10 17:25:29 +05:30
context 'source branch removal' do
context 'when the source branch is protected' do
let(:service) do
2021-06-08 01:23:25 +05:30
described_class.new(project: project, current_user: user, params: merge_params.merge('should_remove_source_branch' => true))
2017-09-10 17:25:29 +05:30
end
before do
create(:protected_branch, project: project, name: merge_request.source_branch)
end
it 'does not delete the source branch' do
2020-01-01 13:55:28 +05:30
expect(::Branches::DeleteService).not_to receive(:new)
2017-09-10 17:25:29 +05:30
service.execute(merge_request)
end
2016-06-02 11:05:42 +05:30
end
2017-09-10 17:25:29 +05:30
context 'when the source branch is the default branch' do
let(:service) do
2021-06-08 01:23:25 +05:30
described_class.new(project: project, current_user: user, params: merge_params.merge('should_remove_source_branch' => true))
2017-09-10 17:25:29 +05:30
end
before do
allow(project).to receive(:root_ref?).with(merge_request.source_branch).and_return(true)
end
it 'does not delete the source branch' do
2020-01-01 13:55:28 +05:30
expect(::Branches::DeleteService).not_to receive(:new)
2017-09-10 17:25:29 +05:30
service.execute(merge_request)
end
end
context 'when the source branch can be removed' do
context 'when MR author set the source branch to be removed' do
2018-03-17 18:26:18 +05:30
before do
merge_request.update_attribute(:merge_params, { 'force_remove_source_branch' => '1' })
2017-09-10 17:25:29 +05:30
end
2022-07-23 23:45:48 +05:30
# Not a real use case. When a merger merges a MR , merge param 'should_remove_source_branch' is defined
2017-09-10 17:25:29 +05:30
it 'removes the source branch using the author user' do
2021-04-17 20:07:23 +05:30
expect(::MergeRequests::DeleteSourceBranchWorker).to receive(:perform_async).with(merge_request.id, merge_request.source_branch_sha, merge_request.author.id)
2017-09-10 17:25:29 +05:30
service.execute(merge_request)
2022-07-23 23:45:48 +05:30
expect(merge_request.reload.should_remove_source_branch?).to be nil
2017-09-10 17:25:29 +05:30
end
2018-03-17 18:26:18 +05:30
context 'when the merger set the source branch not to be removed' do
2021-06-08 01:23:25 +05:30
let(:service) { described_class.new(project: project, current_user: user, params: merge_params.merge('should_remove_source_branch' => false)) }
2018-03-17 18:26:18 +05:30
it 'does not delete the source branch' do
2021-04-17 20:07:23 +05:30
expect(::MergeRequests::DeleteSourceBranchWorker).not_to receive(:perform_async)
2018-03-17 18:26:18 +05:30
service.execute(merge_request)
2022-07-23 23:45:48 +05:30
expect(merge_request.reload.should_remove_source_branch?).to be false
2018-03-17 18:26:18 +05:30
end
end
2017-09-10 17:25:29 +05:30
end
context 'when MR merger set the source branch to be removed' do
let(:service) do
2021-06-08 01:23:25 +05:30
described_class.new(project: project, current_user: user, params: merge_params.merge('should_remove_source_branch' => true))
2017-09-10 17:25:29 +05:30
end
it 'removes the source branch using the current user' do
2021-04-17 20:07:23 +05:30
expect(::MergeRequests::DeleteSourceBranchWorker).to receive(:perform_async).with(merge_request.id, merge_request.source_branch_sha, user.id)
2017-09-10 17:25:29 +05:30
service.execute(merge_request)
2022-07-23 23:45:48 +05:30
expect(merge_request.reload.should_remove_source_branch?).to be true
2017-09-10 17:25:29 +05:30
end
end
2016-06-02 11:05:42 +05:30
end
end
2019-12-26 22:10:19 +05:30
context 'error handling' do
2017-08-17 22:00:37 +05:30
before do
2020-06-23 00:09:42 +05:30
allow(Gitlab::AppLogger).to receive(:error)
2017-08-17 22:00:37 +05:30
end
2015-10-24 18:46:33 +05:30
2019-09-30 21:07:59 +05:30
context 'when source is missing' do
it 'logs and saves error' do
allow(merge_request).to receive(:diff_head_sha) { nil }
error_message = 'No source for merge'
service.execute(merge_request)
expect(merge_request.merge_error).to eq(error_message)
2023-07-09 08:55:56 +05:30
expect(Gitlab::AppLogger).to have_received(:error).with(
hash_including(
merge_request_info: merge_request.to_reference(full: true),
message: a_string_matching(error_message)
)
)
2019-09-30 21:07:59 +05:30
end
end
2017-08-17 22:00:37 +05:30
it 'logs and saves error if there is an exception' do
error_message = 'error message'
2021-03-11 19:13:27 +05:30
allow(service).to receive(:repository).and_raise(error_message)
2015-10-24 18:46:33 +05:30
allow(service).to receive(:execute_hooks)
2015-12-23 02:04:40 +05:30
service.execute(merge_request)
2015-10-24 18:46:33 +05:30
2021-03-11 19:13:27 +05:30
expect(merge_request.merge_error).to eq(described_class::GENERIC_ERROR_MESSAGE)
2023-07-09 08:55:56 +05:30
expect(Gitlab::AppLogger).to have_received(:error).with(
hash_including(
merge_request_info: merge_request.to_reference(full: true),
message: a_string_matching(error_message)
)
)
2015-10-24 18:46:33 +05:30
end
2016-08-24 12:49:21 +05:30
2019-07-07 11:18:12 +05:30
it 'logs and saves error if user is not authorized' do
2021-06-08 01:23:25 +05:30
stub_exclusive_lease
2019-07-07 11:18:12 +05:30
unauthorized_user = create(:user)
project.add_reporter(unauthorized_user)
2021-06-08 01:23:25 +05:30
service = described_class.new(project: project, current_user: unauthorized_user)
2019-07-07 11:18:12 +05:30
service.execute(merge_request)
expect(merge_request.merge_error)
.to eq('You are not allowed to merge this merge request')
end
2017-08-17 22:00:37 +05:30
it 'logs and saves error if there is an PreReceiveError exception' do
error_message = 'error message'
2016-08-24 12:49:21 +05:30
2019-07-07 11:18:12 +05:30
allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "GitLab: #{error_message}")
2016-08-24 12:49:21 +05:30
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
2018-10-15 14:42:47 +05:30
expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook')
2023-07-09 08:55:56 +05:30
expect(Gitlab::AppLogger).to have_received(:error).with(
hash_including(
merge_request_info: merge_request.to_reference(full: true),
message: a_string_matching(error_message)
)
)
2016-08-24 12:49:21 +05:30
end
2021-03-11 19:13:27 +05:30
it 'logs and saves error if commit is not created' do
2016-08-24 12:49:21 +05:30
allow_any_instance_of(Repository).to receive(:merge).and_return(false)
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
2017-08-17 22:00:37 +05:30
expect(merge_request).to be_open
2016-08-24 12:49:21 +05:30
expect(merge_request.merge_commit_sha).to be_nil
2021-03-11 19:13:27 +05:30
expect(merge_request.merge_error).to include(described_class::GENERIC_ERROR_MESSAGE)
2023-07-09 08:55:56 +05:30
expect(Gitlab::AppLogger).to have_received(:error).with(
hash_including(
merge_request_info: merge_request.to_reference(full: true),
message: a_string_matching(described_class::GENERIC_ERROR_MESSAGE)
)
)
2016-08-24 12:49:21 +05:30
end
2018-03-17 18:26:18 +05:30
2020-07-28 23:09:34 +05:30
context 'when squashing is required' do
before do
merge_request.update!(source_branch: 'master', target_branch: 'feature')
merge_request.target_project.project_setting.squash_always!
end
it 'raises an error if squashing is not done' do
error_message = 'requires squashing commits'
service.execute(merge_request)
expect(merge_request).to be_open
expect(merge_request.merge_commit_sha).to be_nil
2021-03-08 18:12:59 +05:30
expect(merge_request.squash_commit_sha).to be_nil
2020-07-28 23:09:34 +05:30
expect(merge_request.merge_error).to include(error_message)
2023-07-09 08:55:56 +05:30
expect(Gitlab::AppLogger).to have_received(:error).with(
hash_including(
merge_request_info: merge_request.to_reference(full: true),
message: a_string_matching(error_message)
)
)
2020-07-28 23:09:34 +05:30
end
end
2018-11-08 19:23:39 +05:30
context 'when squashing' do
2018-03-17 18:26:18 +05:30
before do
2018-11-08 19:23:39 +05:30
merge_request.update!(source_branch: 'master', target_branch: 'feature')
2018-03-17 18:26:18 +05:30
end
2018-11-08 19:23:39 +05:30
it 'logs and saves error if there is an error when squashing' do
2022-04-04 11:22:00 +05:30
error_message = 'Squashing failed: Squash the commits locally, resolve any conflicts, then push the branch.'
2018-03-17 18:26:18 +05:30
2019-03-02 22:35:43 +05:30
allow_any_instance_of(MergeRequests::SquashService).to receive(:squash!).and_return(nil)
2020-11-24 15:15:51 +05:30
merge_request.update!(squash: true)
2018-11-08 19:23:39 +05:30
service.execute(merge_request)
expect(merge_request).to be_open
expect(merge_request.merge_commit_sha).to be_nil
2021-03-08 18:12:59 +05:30
expect(merge_request.squash_commit_sha).to be_nil
2018-11-08 19:23:39 +05:30
expect(merge_request.merge_error).to include(error_message)
2023-07-09 08:55:56 +05:30
expect(Gitlab::AppLogger).to have_received(:error).with(
hash_including(
merge_request_info: merge_request.to_reference(full: true),
message: a_string_matching(error_message)
)
)
2018-11-08 19:23:39 +05:30
end
2021-03-08 18:12:59 +05:30
it 'logs and saves error if there is an PreReceiveError exception' do
error_message = 'error message'
allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "GitLab: #{error_message}")
allow(service).to receive(:execute_hooks)
merge_request.update!(squash: true)
service.execute(merge_request)
expect(merge_request).to be_open
expect(merge_request.merge_commit_sha).to be_nil
expect(merge_request.squash_commit_sha).to be_nil
expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook')
2023-07-09 08:55:56 +05:30
expect(Gitlab::AppLogger).to have_received(:error).with(
hash_including(
merge_request_info: merge_request.to_reference(full: true),
message: a_string_matching(error_message)
)
)
2021-03-08 18:12:59 +05:30
end
2019-12-26 22:10:19 +05:30
context 'when fast-forward merge is not allowed' do
2018-11-08 19:23:39 +05:30
before do
allow_any_instance_of(Repository).to receive(:ancestor?).and_return(nil)
end
%w(semi-linear ff).each do |merge_method|
it "logs and saves error if merge is #{merge_method} only" do
merge_method = 'rebase_merge' if merge_method == 'semi-linear'
2020-11-24 15:15:51 +05:30
merge_request.project.update!(merge_method: merge_method)
2018-11-08 19:23:39 +05:30
error_message = 'Only fast-forward merge is allowed for your project. Please update your source branch'
allow(service).to receive(:execute_hooks)
2021-06-08 01:23:25 +05:30
expect(lease).to receive(:cancel)
2018-11-08 19:23:39 +05:30
service.execute(merge_request)
expect(merge_request).to be_open
expect(merge_request.merge_commit_sha).to be_nil
2021-03-08 18:12:59 +05:30
expect(merge_request.squash_commit_sha).to be_nil
2018-11-08 19:23:39 +05:30
expect(merge_request.merge_error).to include(error_message)
2023-07-09 08:55:56 +05:30
expect(Gitlab::AppLogger).to have_received(:error).with(
hash_including(
merge_request_info: merge_request.to_reference(full: true),
message: a_string_matching(error_message)
)
)
2018-11-08 19:23:39 +05:30
end
2018-03-17 18:26:18 +05:30
end
end
end
2020-11-24 15:15:51 +05:30
context 'when not mergeable' do
let!(:error_message) { 'Merge request is not mergeable' }
context 'with failing CI' do
before do
allow(merge_request).to receive(:mergeable_ci_state?) { false }
end
it 'logs and saves error' do
service.execute(merge_request)
2023-07-09 08:55:56 +05:30
expect(Gitlab::AppLogger).to have_received(:error).with(
hash_including(
merge_request_info: merge_request.to_reference(full: true),
message: a_string_matching(error_message)
)
)
2020-11-24 15:15:51 +05:30
end
end
context 'with unresolved discussions' do
before do
allow(merge_request).to receive(:mergeable_discussions_state?) { false }
end
it 'logs and saves error' do
service.execute(merge_request)
2023-07-09 08:55:56 +05:30
expect(Gitlab::AppLogger).to have_received(:error).with(
hash_including(
merge_request_info: merge_request.to_reference(full: true),
message: a_string_matching(error_message)
)
)
2020-11-24 15:15:51 +05:30
end
context 'when passing `skip_discussions_check: true` as `options` parameter' do
it 'merges the merge request' do
service.execute(merge_request, skip_discussions_check: true)
expect(merge_request).to be_valid
expect(merge_request).to be_merged
end
end
end
end
2015-10-24 18:46:33 +05:30
end
2021-06-08 01:23:25 +05:30
context 'when the other sidekiq worker has already been running' do
before do
stub_exclusive_lease_taken(lease_key)
end
it 'does not execute service' do
expect(service).not_to receive(:commit)
service.execute(merge_request)
end
end
2015-04-26 12:48:37 +05:30
end
end