Commit e73f537c authored by Luke Duncalfe's avatar Luke Duncalfe

Refactor PushOptionsHandlerService from review

Exceptions are no longer raised, instead all errors encountered are
added to the errors property.

MergeRequests::BuildService is used to generate attributes of a new
merge request.

Code moved from Api::Internal to Api::Helpers::InternalHelpers.
parent 68f189ad
...@@ -2,39 +2,28 @@ ...@@ -2,39 +2,28 @@
module MergeRequests module MergeRequests
class PushOptionsHandlerService class PushOptionsHandlerService
Error = Class.new(StandardError)
LIMIT = 10 LIMIT = 10
attr_reader :branches, :changes_by_branch, :current_user, :errors, attr_reader :branches, :changes_by_branch, :current_user, :errors,
:project, :push_options :project, :push_options, :target_project
def initialize(project, current_user, changes, push_options) def initialize(project, current_user, changes, push_options)
@project = project @project = project
@target_project = @project.default_merge_request_target
@current_user = current_user @current_user = current_user
@changes_by_branch = parse_changes(changes) @branches = get_branches(changes)
@push_options = push_options @push_options = push_options
@errors = [] @errors = []
@branches = @changes_by_branch.keys
raise Error, 'User is required' if @current_user.nil?
unless @project.merge_requests_enabled?
raise Error, 'Merge requests are not enabled for project'
end
if @branches.size > LIMIT
raise Error, "Too many branches pushed (#{@branches.size} were pushed, limit is #{LIMIT})"
end
if @push_options[:target] && !@project.repository.branch_exists?(@push_options[:target])
raise Error, "Branch #{@push_options[:target]} does not exist"
end
end end
def execute def execute
validate_service
return self if errors.present?
branches.each do |branch| branches.each do |branch|
execute_for_branch(branch) execute_for_branch(branch)
rescue Gitlab::Access::AccessDeniedError
errors << 'User access was denied'
end end
self self
...@@ -42,32 +31,47 @@ module MergeRequests ...@@ -42,32 +31,47 @@ module MergeRequests
private private
# Parses changes in the push. def get_branches(raw_changes)
# Returns a hash of branch => changes_list Gitlab::ChangesList.new(raw_changes).map do |changes|
def parse_changes(raw_changes) next unless Gitlab::Git.branch_ref?(changes[:ref])
Gitlab::ChangesList.new(raw_changes).each_with_object({}) do |change, changes|
next unless Gitlab::Git.branch_ref?(change[:ref])
# Deleted branch # Deleted branch
next if Gitlab::Git.blank_ref?(change[:newrev]) next if Gitlab::Git.blank_ref?(changes[:newrev])
# Default branch # Default branch
branch_name = Gitlab::Git.branch_name(change[:ref]) branch_name = Gitlab::Git.branch_name(changes[:ref])
next if branch_name == project.default_branch next if branch_name == target_project.default_branch
changes[branch_name] = change branch_name
end.compact.uniq
end end
def validate_service
errors << 'User is required' if current_user.nil?
unless target_project.merge_requests_enabled?
errors << "Merge requests are not enabled for project #{target_project.full_path}"
end end
if branches.size > LIMIT
errors << "Too many branches pushed (#{branches.size} were pushed, limit is #{LIMIT})"
end
if push_options[:target] && !target_project.repository.branch_exists?(push_options[:target])
errors << "Branch #{push_options[:target]} does not exist"
end
end
# Returns a Hash of branch => MergeRequest
def merge_requests def merge_requests
@merge_requests ||= MergeRequest.from_project(@project) @merge_requests ||= MergeRequest.from_project(target_project)
.opened .opened
.from_source_branches(@branches) .from_source_branches(branches)
.to_a # fetch now .index_by(&:source_branch)
end end
def execute_for_branch(branch) def execute_for_branch(branch)
merge_request = merge_requests.find { |mr| mr.source_branch == branch } merge_request = merge_requests[branch]
if merge_request if merge_request
update!(merge_request) update!(merge_request)
...@@ -82,18 +86,27 @@ module MergeRequests ...@@ -82,18 +86,27 @@ module MergeRequests
return return
end end
merge_request = ::MergeRequests::CreateService.new( # Use BuildService to assign the standard attributes of a merge request
merge_request = ::MergeRequests::BuildService.new(
project, project,
current_user, current_user,
create_params(branch) create_params(branch)
).execute ).execute
unless merge_request.errors.present?
merge_request = ::MergeRequests::CreateService.new(
project,
current_user,
merge_request.attributes
).execute
end
collect_errors_from_merge_request(merge_request) unless merge_request.persisted? collect_errors_from_merge_request(merge_request) unless merge_request.persisted?
end end
def update!(merge_request) def update!(merge_request)
merge_request = ::MergeRequests::UpdateService.new( merge_request = ::MergeRequests::UpdateService.new(
project, target_project,
current_user, current_user,
update_params update_params
).execute(merge_request) ).execute(merge_request)
...@@ -102,18 +115,12 @@ module MergeRequests ...@@ -102,18 +115,12 @@ module MergeRequests
end end
def create_params(branch) def create_params(branch)
change = changes_by_branch.fetch(branch)
commits = project.repository.commits_between(project.default_branch, change[:newrev])
commits = CommitCollection.new(project, commits)
commit = commits.without_merge_commits.first
params = { params = {
assignee: current_user, assignee: current_user,
source_branch: branch, source_branch: branch,
target_branch: push_options[:target] || project.default_branch, source_project: project,
title: commit&.title&.strip || 'New Merge Request', target_branch: push_options[:target] || target_project.default_branch,
description: commit&.description&.strip target_project: target_project
} }
if push_options.key?(:merge_when_pipeline_succeeds) if push_options.key?(:merge_when_pipeline_succeeds)
...@@ -144,7 +151,9 @@ module MergeRequests ...@@ -144,7 +151,9 @@ module MergeRequests
end end
def collect_errors_from_merge_request(merge_request) def collect_errors_from_merge_request(merge_request)
errors << merge_request.errors.full_messages.to_sentence merge_request.errors.full_messages.each do |error|
errors << error
end
end end
end end
end end
...@@ -43,6 +43,23 @@ module API ...@@ -43,6 +43,23 @@ module API
::MergeRequests::GetUrlsService.new(project).execute(params[:changes]) ::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
end end
def process_mr_push_options(push_options, project, user, changes)
output = {}
service = ::MergeRequests::PushOptionsHandlerService.new(
project,
user,
changes,
push_options
).execute
if service.errors.present?
output[:warnings] = push_options_warning(service.errors.join("\n\n"))
end
output
end
def push_options_warning(warning) def push_options_warning(warning)
options = Array.wrap(params[:push_options]).map { |p| "'#{p}'" }.join(' ') options = Array.wrap(params[:push_options]).map { |p| "'#{p}'" }.join(' ')
"Error encountered with push options #{options}: #{warning}" "Error encountered with push options #{options}: #{warning}"
......
...@@ -264,24 +264,8 @@ module API ...@@ -264,24 +264,8 @@ module API
PostReceive.perform_async(params[:gl_repository], params[:identifier], PostReceive.perform_async(params[:gl_repository], params[:identifier],
params[:changes], push_options.as_json) params[:changes], push_options.as_json)
if (mr_options = push_options.get(:merge_request)) mr_options = push_options.get(:merge_request)
begin output.merge!(process_mr_push_options(mr_options, project, user, params[:changes])) if mr_options.present?
service = ::MergeRequests::PushOptionsHandlerService.new(
project,
user,
params[:changes],
mr_options
).execute
if service.errors.present?
output[:warnings] = push_options_warning(service.errors.join("\n\n"))
end
rescue ::MergeRequests::PushOptionsHandlerService::Error => e
output[:warnings] = push_options_warning(e.message)
rescue Gitlab::Access::AccessDeniedError
output[:warnings] = push_options_warning('User access was denied')
end
end
broadcast_message = BroadcastMessage.current&.last&.message broadcast_message = BroadcastMessage.current&.last&.message
reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease
......
...@@ -888,8 +888,10 @@ describe API::Internal do ...@@ -888,8 +888,10 @@ describe API::Internal do
} }
end end
let(:branch_name) { 'feature' }
let(:changes) do let(:changes) do
"#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{branch_name}"
end end
let(:push_options) do let(:push_options) do
...@@ -924,8 +926,8 @@ describe API::Internal do ...@@ -924,8 +926,8 @@ describe API::Internal do
post api('/internal/post_receive'), params: valid_params post api('/internal/post_receive'), params: valid_params
expect(json_response['merge_request_urls']).to match [{ expect(json_response['merge_request_urls']).to match [{
"branch_name" => "new_branch", "branch_name" => branch_name,
"url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch", "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name}",
"new_merge_request" => true "new_merge_request" => true
}] }]
end end
...@@ -955,26 +957,22 @@ describe API::Internal do ...@@ -955,26 +957,22 @@ describe API::Internal do
post api('/internal/post_receive'), params: valid_params post api('/internal/post_receive'), params: valid_params
end end
it 'creates a new merge request' do
expect do
post api('/internal/post_receive'), params: valid_params
end.to change { MergeRequest.count }.by(1)
end
it 'links to the newly created merge request' do it 'links to the newly created merge request' do
post api('/internal/post_receive'), params: valid_params post api('/internal/post_receive'), params: valid_params
expect(json_response['merge_request_urls']).to match [{ expect(json_response['merge_request_urls']).to match [{
'branch_name' => 'new_branch', 'branch_name' => branch_name,
'url' => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/1", 'url' => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/1",
'new_merge_request' => false 'new_merge_request' => false
}] }]
end end
it 'adds errors raised from MergeRequests::PushOptionsHandlerService to warnings' do
expect(MergeRequests::PushOptionsHandlerService).to receive(:new).and_raise(
MergeRequests::PushOptionsHandlerService::Error, 'my warning'
)
post api('/internal/post_receive'), params: valid_params
expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my warning')
end
it 'adds errors on the service instance to warnings' do it 'adds errors on the service instance to warnings' do
expect_any_instance_of( expect_any_instance_of(
MergeRequests::PushOptionsHandlerService MergeRequests::PushOptionsHandlerService
......
...@@ -3,10 +3,13 @@ ...@@ -3,10 +3,13 @@
require 'spec_helper' require 'spec_helper'
describe MergeRequests::PushOptionsHandlerService do describe MergeRequests::PushOptionsHandlerService do
include ProjectForksHelper
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
let(:forked_project) { fork_project(project, user, repository: true) }
let(:service) { described_class.new(project, user, changes, push_options) } let(:service) { described_class.new(project, user, changes, push_options) }
let(:source_branch) { 'test' } let(:source_branch) { 'fix' }
let(:target_branch) { 'feature' } let(:target_branch) { 'feature' }
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" } let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" } let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
...@@ -38,21 +41,25 @@ describe MergeRequests::PushOptionsHandlerService do ...@@ -38,21 +41,25 @@ describe MergeRequests::PushOptionsHandlerService do
expect(last_mr.assignee).to eq(user) expect(last_mr.assignee).to eq(user)
end end
it 'sets the title and description from the first non-merge commit' do context 'when project has been forked' do
commits = project.repository.commits('master', limit: 5) let(:forked_project) { fork_project(project, user, repository: true) }
let(:service) { described_class.new(forked_project, user, changes, push_options) }
expect(Gitlab::Git::Commit).to receive(:between).at_least(:once).and_return(commits) before do
allow(forked_project).to receive(:empty_repo?).and_return(false)
end
it 'sets the correct source project' do
service.execute service.execute
merge_commit = commits.first expect(last_mr.source_project).to eq(forked_project)
non_merge_commit = commits.second end
expect(merge_commit.merge_commit?).to eq(true) it 'sets the correct target project' do
expect(non_merge_commit.merge_commit?).to eq(false) service.execute
expect(last_mr.title).to eq(non_merge_commit.title) expect(last_mr.target_project).to eq(project)
expect(last_mr.description).to eq(non_merge_commit.description) end
end end
end end
...@@ -271,7 +278,7 @@ describe MergeRequests::PushOptionsHandlerService do ...@@ -271,7 +278,7 @@ describe MergeRequests::PushOptionsHandlerService do
let(:changes) do let(:changes) do
[ [
new_branch_changes, new_branch_changes,
"#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/second-branch" "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/feature_conflict"
] ]
end end
...@@ -287,11 +294,10 @@ describe MergeRequests::PushOptionsHandlerService do ...@@ -287,11 +294,10 @@ describe MergeRequests::PushOptionsHandlerService do
end end
end end
it 'throws an error' do it 'records an error' do
expect { service.execute }.to raise_error( service.execute
MergeRequests::PushOptionsHandlerService::Error,
"Too many branches pushed (#{limit + 1} were pushed, limit is #{limit})" expect(service.errors).to eq(["Too many branches pushed (#{limit + 1} were pushed, limit is #{limit})"])
)
end end
end end
end end
...@@ -308,11 +314,10 @@ describe MergeRequests::PushOptionsHandlerService do ...@@ -308,11 +314,10 @@ describe MergeRequests::PushOptionsHandlerService do
let(:push_options) { { create: true } } let(:push_options) { { create: true } }
let(:changes) { new_branch_changes } let(:changes) { new_branch_changes }
it 'throws an error' do it 'records an error' do
expect { service.execute }.to raise_error( service.execute
MergeRequests::PushOptionsHandlerService::Error,
'User is required' expect(service.errors).to eq(['User is required'])
)
end end
end end
...@@ -320,10 +325,12 @@ describe MergeRequests::PushOptionsHandlerService do ...@@ -320,10 +325,12 @@ describe MergeRequests::PushOptionsHandlerService do
let(:push_options) { { create: true } } let(:push_options) { { create: true } }
let(:changes) { new_branch_changes } let(:changes) { new_branch_changes }
it 'throws an error' do it 'records an error' do
Members::DestroyService.new(user).execute(ProjectMember.find_by!(user_id: user.id)) Members::DestroyService.new(user).execute(ProjectMember.find_by!(user_id: user.id))
expect { service.execute }.to raise_error(Gitlab::Access::AccessDeniedError) service.execute
expect(service.errors).to eq(['User access was denied'])
end end
end end
...@@ -331,11 +338,10 @@ describe MergeRequests::PushOptionsHandlerService do ...@@ -331,11 +338,10 @@ describe MergeRequests::PushOptionsHandlerService do
let(:push_options) { { create: true, target: 'my-branch' } } let(:push_options) { { create: true, target: 'my-branch' } }
let(:changes) { new_branch_changes } let(:changes) { new_branch_changes }
it 'throws an error' do it 'records an error' do
expect { service.execute }.to raise_error( service.execute
MergeRequests::PushOptionsHandlerService::Error,
'Branch my-branch does not exist' expect(service.errors).to eq(['Branch my-branch does not exist'])
)
end end
end end
...@@ -343,13 +349,12 @@ describe MergeRequests::PushOptionsHandlerService do ...@@ -343,13 +349,12 @@ describe MergeRequests::PushOptionsHandlerService do
let(:push_options) { { create: true } } let(:push_options) { { create: true } }
let(:changes) { new_branch_changes } let(:changes) { new_branch_changes }
it 'throws an error' do it 'records an error' do
expect(project).to receive(:merge_requests_enabled?).and_return(false) expect(project).to receive(:merge_requests_enabled?).and_return(false)
expect { service.execute }.to raise_error( service.execute
MergeRequests::PushOptionsHandlerService::Error,
'Merge requests are not enabled for project' expect(service.errors).to eq(["Merge requests are not enabled for project #{project.full_path}"])
)
end end
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