84dd31493e
Turns out push rules to validate commit message does not apply in the context of automatic merge to the refs/merge-requests/:iid/merge. Mainly because if it fails to merge to it, we currently can't give enough preemptive feedback to the user and it'll turn the merge request unmergeable (given we automatically mark it as unmergeable if we can't merge to the ref). In general, it's a systematic operation, which already bypasses user authorization and git hooks. Therefore, this commit makes it bypass the push rules at EE as well.
337 lines
12 KiB
Ruby
337 lines
12 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
describe MergeRequests::MergeService do
|
|
set(:user) { create(:user) }
|
|
set(:user2) { create(:user) }
|
|
let(:merge_request) { create(:merge_request, :simple, author: user2, assignees: [user2]) }
|
|
let(:project) { merge_request.project }
|
|
|
|
before do
|
|
project.add_maintainer(user)
|
|
project.add_developer(user2)
|
|
end
|
|
|
|
describe '#execute' do
|
|
context 'valid params' do
|
|
let(:service) { described_class.new(project, user, commit_message: 'Awesome message') }
|
|
|
|
before do
|
|
allow(service).to receive(:execute_hooks)
|
|
|
|
perform_enqueued_jobs do
|
|
service.execute(merge_request)
|
|
end
|
|
end
|
|
|
|
it { expect(merge_request).to be_valid }
|
|
it { expect(merge_request).to be_merged }
|
|
|
|
it 'sends email to user2 about merge of new merge_request' do
|
|
email = ActionMailer::Base.deliveries.last
|
|
expect(email.to.first).to eq(user2.email)
|
|
expect(email.subject).to include(merge_request.title)
|
|
end
|
|
|
|
it 'creates system note about merge_request merge' do
|
|
note = merge_request.notes.last
|
|
expect(note.note).to include 'merged'
|
|
end
|
|
end
|
|
|
|
context 'closes related issues' do
|
|
let(:service) { described_class.new(project, user, commit_message: 'Awesome message') }
|
|
|
|
before do
|
|
allow(project).to receive(:default_branch).and_return(merge_request.target_branch)
|
|
end
|
|
|
|
it 'closes GitLab issue tracker issues' do
|
|
issue = create :issue, project: project
|
|
commit = double('commit', safe_message: "Fixes #{issue.to_reference}")
|
|
allow(merge_request).to receive(:commits).and_return([commit])
|
|
merge_request.cache_merge_request_closes_issues!
|
|
|
|
service.execute(merge_request)
|
|
|
|
expect(issue.reload.closed?).to be_truthy
|
|
end
|
|
|
|
context 'with Jira integration' do
|
|
include JiraServiceHelper
|
|
|
|
let(:jira_tracker) { project.create_jira_service }
|
|
let(:jira_issue) { ExternalIssue.new('JIRA-123', project) }
|
|
let(:commit) { double('commit', safe_message: "Fixes #{jira_issue.to_reference}") }
|
|
|
|
before do
|
|
project.update!(has_external_issue_tracker: true)
|
|
jira_service_settings
|
|
stub_jira_urls(jira_issue.id)
|
|
allow(merge_request).to receive(:commits).and_return([commit])
|
|
end
|
|
|
|
it 'closes issues on Jira issue tracker' do
|
|
jira_issue = ExternalIssue.new('JIRA-123', project)
|
|
stub_jira_urls(jira_issue)
|
|
commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}")
|
|
allow(merge_request).to receive(:commits).and_return([commit])
|
|
|
|
expect_any_instance_of(JiraService).to receive(:close_issue).with(merge_request, jira_issue).once
|
|
|
|
service.execute(merge_request)
|
|
end
|
|
|
|
context "when jira_issue_transition_id is not present" do
|
|
before do
|
|
allow_any_instance_of(JIRA::Resource::Issue).to receive(:resolution).and_return(nil)
|
|
end
|
|
|
|
it "does not close issue" do
|
|
allow(jira_tracker).to receive_messages(jira_issue_transition_id: nil)
|
|
|
|
expect_any_instance_of(JiraService).not_to receive(:transition_issue)
|
|
|
|
service.execute(merge_request)
|
|
end
|
|
end
|
|
|
|
context "wrong issue markdown" do
|
|
it 'does not close issues on Jira issue tracker' do
|
|
jira_issue = ExternalIssue.new('#JIRA-123', project)
|
|
stub_jira_urls(jira_issue)
|
|
commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}")
|
|
allow(merge_request).to receive(:commits).and_return([commit])
|
|
|
|
expect_any_instance_of(JiraService).not_to receive(:close_issue)
|
|
|
|
service.execute(merge_request)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'closes related todos' do
|
|
let(:merge_request) { create(:merge_request, assignees: [user], author: user) }
|
|
let(:project) { merge_request.project }
|
|
let(:service) { described_class.new(project, user, commit_message: 'Awesome message') }
|
|
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
|
|
|
|
context 'source branch removal' do
|
|
context 'when the source branch is protected' do
|
|
let(:service) do
|
|
described_class.new(project, user, 'should_remove_source_branch' => true)
|
|
end
|
|
|
|
before do
|
|
create(:protected_branch, project: project, name: merge_request.source_branch)
|
|
end
|
|
|
|
it 'does not delete the source branch' do
|
|
expect(DeleteBranchService).not_to receive(:new)
|
|
service.execute(merge_request)
|
|
end
|
|
end
|
|
|
|
context 'when the source branch is the default branch' do
|
|
let(:service) do
|
|
described_class.new(project, user, 'should_remove_source_branch' => true)
|
|
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
|
|
expect(DeleteBranchService).not_to receive(:new)
|
|
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
|
|
let(:service) { described_class.new(project, user, commit_message: 'Awesome message') }
|
|
|
|
before do
|
|
merge_request.update_attribute(:merge_params, { 'force_remove_source_branch' => '1' })
|
|
end
|
|
|
|
it 'removes the source branch using the author user' do
|
|
expect(DeleteBranchService).to receive(:new)
|
|
.with(merge_request.source_project, merge_request.author)
|
|
.and_call_original
|
|
service.execute(merge_request)
|
|
end
|
|
|
|
context 'when the merger set the source branch not to be removed' do
|
|
let(:service) { described_class.new(project, user, commit_message: 'Awesome message', 'should_remove_source_branch' => false) }
|
|
|
|
it 'does not delete the source branch' do
|
|
expect(DeleteBranchService).not_to receive(:new)
|
|
service.execute(merge_request)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when MR merger set the source branch to be removed' do
|
|
let(:service) do
|
|
described_class.new(project, user, commit_message: 'Awesome message', 'should_remove_source_branch' => true)
|
|
end
|
|
|
|
it 'removes the source branch using the current user' do
|
|
expect(DeleteBranchService).to receive(:new)
|
|
.with(merge_request.source_project, user)
|
|
.and_call_original
|
|
service.execute(merge_request)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "error handling" do
|
|
let(:service) { described_class.new(project, user, commit_message: 'Awesome message') }
|
|
|
|
before do
|
|
allow(Rails.logger).to receive(:error)
|
|
end
|
|
|
|
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)
|
|
expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
|
|
end
|
|
end
|
|
|
|
it 'logs and saves error if there is an exception' do
|
|
error_message = 'error message'
|
|
|
|
allow(service).to receive(:repository).and_raise("error message")
|
|
allow(service).to receive(:execute_hooks)
|
|
|
|
service.execute(merge_request)
|
|
|
|
expect(merge_request.merge_error).to include('Something went wrong during merge')
|
|
expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
|
|
end
|
|
|
|
it 'logs and saves error if user is not authorized' do
|
|
unauthorized_user = create(:user)
|
|
project.add_reporter(unauthorized_user)
|
|
|
|
service = described_class.new(project, unauthorized_user)
|
|
|
|
service.execute(merge_request)
|
|
|
|
expect(merge_request.merge_error)
|
|
.to eq('You are not allowed to merge this merge request')
|
|
end
|
|
|
|
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)
|
|
|
|
service.execute(merge_request)
|
|
|
|
expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook')
|
|
expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
|
|
end
|
|
|
|
it 'logs and saves error if there is a merge conflict' do
|
|
error_message = 'Conflicts detected during merge'
|
|
|
|
allow_any_instance_of(Repository).to receive(:merge).and_return(false)
|
|
allow(service).to receive(:execute_hooks)
|
|
|
|
service.execute(merge_request)
|
|
|
|
expect(merge_request).to be_open
|
|
expect(merge_request.merge_commit_sha).to be_nil
|
|
expect(merge_request.merge_error).to include(error_message)
|
|
expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
|
|
end
|
|
|
|
context 'when squashing' do
|
|
before do
|
|
merge_request.update!(source_branch: 'master', target_branch: 'feature')
|
|
end
|
|
|
|
it 'logs and saves error if there is an error when squashing' do
|
|
error_message = 'Failed to squash. Should be done manually'
|
|
|
|
allow_any_instance_of(MergeRequests::SquashService).to receive(:squash!).and_return(nil)
|
|
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.merge_error).to include(error_message)
|
|
expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
|
|
end
|
|
|
|
it 'logs and saves error if there is a squash in progress' do
|
|
error_message = 'another squash is already in progress'
|
|
|
|
allow_any_instance_of(MergeRequest).to receive(:squash_in_progress?).and_return(true)
|
|
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.merge_error).to include(error_message)
|
|
expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
|
|
end
|
|
|
|
context "when fast-forward merge is not allowed" do
|
|
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'
|
|
merge_request.project.update(merge_method: merge_method)
|
|
error_message = 'Only fast-forward merge is allowed for your project. Please update your source branch'
|
|
allow(service).to receive(:execute_hooks)
|
|
|
|
service.execute(merge_request)
|
|
|
|
expect(merge_request).to be_open
|
|
expect(merge_request.merge_commit_sha).to be_nil
|
|
expect(merge_request.merge_error).to include(error_message)
|
|
expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|