diff options
author | Luke Duncalfe <lduncalfe@eml.cc> | 2019-03-29 14:07:03 +1300 |
---|---|---|
committer | Luke Duncalfe <lduncalfe@eml.cc> | 2019-04-09 09:36:42 +1200 |
commit | aa352a95df665ded5178c1b26d4492433e47714e (patch) | |
tree | 47e60787a36f6ab36d4649cef763f16b636583d8 /app | |
parent | 225edb0d2d7737cf52ef5cd358082d08e20feaa4 (diff) | |
download | gitlab-ce-aa352a95df665ded5178c1b26d4492433e47714e.tar.gz |
Support merge request create with push options
To create a new merge request:
git push -u origin -o merge_request.create
To create a new merge request setting target branch:
git push -u origin -o merge_request.create \
-o merge_request.target=123
To update an existing merge request with a new target branch:
git push -u origin -o merge_request.target=123
A new Gitlab::PushOptions class handles parsing and validating the push
options array. This can be the start of the standard of GitLab accepting
push options that follow namespacing rules. Rules are discussed in issue
https://gitlab.com/gitlab-org/gitlab-ce/issues/43263.
E.g. these push options:
-o merge_request.create -o merge_request.target=123
Become parsed as:
{
merge_request: {
create: true,
target: '123',
}
}
And are fetched with the class via:
push_options.get(:merge_request)
push_options.get(:merge_request, :create)
push_options.get(:merge_request, :target)
A new MergeRequests::PushOptionsHandlerService takes the `merge_request`
namespaced push options and handles creating and updating
merge requests.
Any errors encountered are passed to the existing `output` Hash in
Api::Internal's `post_receive` endpoint, and passed to gitlab-shell
where they're output to the user.
Issue https://gitlab.com/gitlab-org/gitlab-ce/issues/43263
Diffstat (limited to 'app')
-rw-r--r-- | app/services/merge_requests/push_options_handler_service.rb | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/app/services/merge_requests/push_options_handler_service.rb b/app/services/merge_requests/push_options_handler_service.rb new file mode 100644 index 00000000000..c810340c636 --- /dev/null +++ b/app/services/merge_requests/push_options_handler_service.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +module MergeRequests + class PushOptionsHandlerService + Error = Class.new(StandardError) + + LIMIT = 10 + + attr_reader :branches, :changes_by_branch, :current_user, :errors, + :project, :push_options, :target + + def initialize(project, current_user, changes, push_options) + @project = project + @current_user = current_user + @changes_by_branch = parse_changes(changes) + @push_options = push_options + @errors = [] + @branches = @changes_by_branch.keys + @target = @push_options[:target] || @project.default_branch + + 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?(@target) + raise Error, "Branch #{@target} does not exist" + end + end + + def execute + branches.each do |branch| + execute_for_branch(branch) + end + + self + end + + private + + # Parses changes in the push. + # Returns a hash of branch => changes_list + def parse_changes(raw_changes) + Gitlab::ChangesList.new(raw_changes).each_with_object({}) do |change, changes| + next unless Gitlab::Git.branch_ref?(change[:ref]) + + # Deleted branch + next if Gitlab::Git.blank_ref?(change[:newrev]) + + # Default branch + branch_name = Gitlab::Git.branch_name(change[:ref]) + next if branch_name == project.default_branch + + changes[branch_name] = change + end + end + + def merge_requests + @merge_requests ||= MergeRequest.from_project(@project) + .opened + .from_source_branches(@branches) + .to_a # fetch now + end + + def execute_for_branch(branch) + merge_request = merge_requests.find { |mr| mr.source_branch == branch } + + if merge_request + update!(merge_request) + else + create!(branch) + end + end + + def create!(branch) + unless push_options[:create] + errors << "A merge_request.create push option is required to create a merge request for branch #{branch}" + return + end + + merge_request = ::MergeRequests::CreateService.new( + project, + current_user, + create_params(branch) + ).execute + + collect_errors_from_merge_request(merge_request) unless merge_request.persisted? + end + + def update!(merge_request) + return if target == merge_request.target_branch + + merge_request = ::MergeRequests::UpdateService.new( + project, + current_user, + { target_branch: target } + ).execute(merge_request) + + collect_errors_from_merge_request(merge_request) unless merge_request.valid? + end + + 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 + + { + assignee: current_user, + source_branch: branch, + target_branch: target, + title: commit&.title&.strip || 'New Merge Request', + description: commit&.description&.strip + } + end + + def collect_errors_from_merge_request(merge_request) + errors << merge_request.errors.full_messages.to_sentence + end + end +end |