2018-08-23 09:22:23 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-08-24 06:20:36 +00:00
|
|
|
require 'gitlab/email/handler/base_handler'
|
|
|
|
require 'gitlab/email/handler/reply_processing'
|
|
|
|
|
2018-12-14 18:37:03 +00:00
|
|
|
# handles merge request creation emails with these formats:
|
2018-12-13 00:35:01 +00:00
|
|
|
# incoming+gitlab-org-gitlab-ce-20-Author_Token12345678-merge-request@incoming.gitlab.com
|
|
|
|
# incoming+gitlab-org/gitlab-ce+merge-request+Author_Token12345678@incoming.gitlab.com (legacy)
|
2017-08-24 06:20:36 +00:00
|
|
|
module Gitlab
|
|
|
|
module Email
|
|
|
|
module Handler
|
|
|
|
class CreateMergeRequestHandler < BaseHandler
|
|
|
|
include ReplyProcessing
|
2018-12-13 00:35:01 +00:00
|
|
|
|
2019-01-02 20:41:28 +00:00
|
|
|
HANDLER_REGEX = /\A#{HANDLER_ACTION_BASE_REGEX}-(?<incoming_email_token>.+)-merge-request\z/.freeze
|
2018-12-13 00:35:01 +00:00
|
|
|
HANDLER_REGEX_LEGACY = /\A(?<project_path>[^\+]*)\+merge-request\+(?<incoming_email_token>.*)/.freeze
|
2017-08-24 06:20:36 +00:00
|
|
|
|
|
|
|
def initialize(mail, mail_key)
|
|
|
|
super(mail, mail_key)
|
2018-01-11 16:34:01 +00:00
|
|
|
|
2018-12-28 18:53:39 +00:00
|
|
|
if !mail_key&.include?('/') && (matched = HANDLER_REGEX.match(mail_key.to_s))
|
|
|
|
@project_slug = matched[:project_slug]
|
|
|
|
@project_id = matched[:project_id]&.to_i
|
|
|
|
@incoming_email_token = matched[:incoming_email_token]
|
2018-12-13 00:35:01 +00:00
|
|
|
elsif matched = HANDLER_REGEX_LEGACY.match(mail_key.to_s)
|
2018-12-28 18:53:39 +00:00
|
|
|
@project_path = matched[:project_path]
|
|
|
|
@incoming_email_token = matched[:incoming_email_token]
|
2017-08-24 06:20:36 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def can_handle?
|
2018-12-13 00:35:01 +00:00
|
|
|
incoming_email_token && (project_id || project_path)
|
2017-08-24 06:20:36 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def execute
|
|
|
|
raise ProjectNotFound unless project
|
|
|
|
|
2018-04-06 17:58:53 +00:00
|
|
|
validate_permission!(:create_merge_request_in)
|
|
|
|
validate_permission!(:create_merge_request_from)
|
2017-08-24 06:20:36 +00:00
|
|
|
|
|
|
|
verify_record!(
|
|
|
|
record: create_merge_request,
|
|
|
|
invalid_exception: InvalidMergeRequestError,
|
|
|
|
record_name: 'merge_request')
|
|
|
|
end
|
|
|
|
|
2018-08-27 15:31:01 +00:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2017-08-24 06:20:36 +00:00
|
|
|
def author
|
|
|
|
@author ||= User.find_by(incoming_email_token: incoming_email_token)
|
|
|
|
end
|
2018-08-27 15:31:01 +00:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2017-08-24 06:20:36 +00:00
|
|
|
|
2018-10-24 16:01:44 +00:00
|
|
|
def metrics_params
|
|
|
|
super.merge(includes_patches: patch_attachments.any?)
|
|
|
|
end
|
|
|
|
|
2019-09-13 13:26:31 +00:00
|
|
|
def metrics_event
|
|
|
|
:receive_email_create_merge_request
|
|
|
|
end
|
|
|
|
|
2017-08-24 06:20:36 +00:00
|
|
|
private
|
|
|
|
|
2018-10-24 16:01:44 +00:00
|
|
|
def build_merge_request
|
2021-10-11 06:13:09 +00:00
|
|
|
::MergeRequests::BuildService.new(project: project, current_user: author, params: merge_request_params).execute
|
2018-10-24 16:01:44 +00:00
|
|
|
end
|
|
|
|
|
2017-08-24 06:20:36 +00:00
|
|
|
def create_merge_request
|
2018-10-24 16:01:44 +00:00
|
|
|
merge_request = build_merge_request
|
|
|
|
|
|
|
|
if patch_attachments.any?
|
|
|
|
apply_patches_to_source_branch(start_branch: merge_request.target_branch)
|
|
|
|
remove_patch_attachments
|
|
|
|
# Rebuild the merge request as the source branch might just have
|
|
|
|
# been created, so we should re-validate.
|
|
|
|
merge_request = build_merge_request
|
|
|
|
end
|
2017-08-24 06:20:36 +00:00
|
|
|
|
|
|
|
if merge_request.errors.any?
|
|
|
|
merge_request
|
|
|
|
else
|
2021-10-11 06:13:09 +00:00
|
|
|
::MergeRequests::CreateService.new(project: project, current_user: author).create(merge_request)
|
2017-08-24 06:20:36 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def merge_request_params
|
2017-12-11 09:14:11 +00:00
|
|
|
params = {
|
2017-08-24 06:20:36 +00:00
|
|
|
source_project_id: project.id,
|
2018-10-24 16:01:44 +00:00
|
|
|
source_branch: source_branch,
|
2017-08-24 06:20:36 +00:00
|
|
|
target_project_id: project.id
|
|
|
|
}
|
2017-12-11 09:14:11 +00:00
|
|
|
params[:description] = message if message.present?
|
|
|
|
params
|
2017-08-24 06:20:36 +00:00
|
|
|
end
|
2018-10-24 16:01:44 +00:00
|
|
|
|
|
|
|
def apply_patches_to_source_branch(start_branch:)
|
|
|
|
patches = patch_attachments.map { |patch| patch.body.decoded }
|
|
|
|
|
|
|
|
result = Commits::CommitPatchService
|
|
|
|
.new(project, author, branch_name: source_branch, patches: patches, start_branch: start_branch)
|
|
|
|
.execute
|
|
|
|
|
|
|
|
if result[:status] != :success
|
|
|
|
message = "Could not apply patches to #{source_branch}:\n#{result[:message]}"
|
|
|
|
raise InvalidAttachment, message
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def remove_patch_attachments
|
|
|
|
patch_attachments.each { |patch| mail.parts.delete(patch) }
|
2018-12-13 00:35:01 +00:00
|
|
|
# reset the message, so it needs to be reprocessed when the attachments
|
2018-10-24 16:01:44 +00:00
|
|
|
# have been modified
|
|
|
|
@message = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
def patch_attachments
|
|
|
|
@patches ||= mail.attachments
|
|
|
|
.select { |attachment| attachment.filename.ends_with?('.patch') }
|
|
|
|
.sort_by(&:filename)
|
|
|
|
end
|
|
|
|
|
|
|
|
def source_branch
|
|
|
|
@source_branch ||= mail.subject
|
|
|
|
end
|
2017-08-24 06:20:36 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|