1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
# frozen_string_literal: true
require 'gitlab/email/handler/base_handler'
require 'gitlab/email/handler/reply_processing'
# handles merge request creation emails with these formats:
# 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)
module Gitlab
module Email
module Handler
class CreateMergeRequestHandler < BaseHandler
include ReplyProcessing
HANDLER_REGEX = /\A.+-(?<project_id>.+)-(?<incoming_email_token>.+)-merge-request\z/.freeze
HANDLER_REGEX_LEGACY = /\A(?<project_path>[^\+]*)\+merge-request\+(?<incoming_email_token>.*)/.freeze
def initialize(mail, mail_key)
super(mail, mail_key)
if matched = HANDLER_REGEX.match(mail_key.to_s)
@project_id, @incoming_email_token = matched.captures
elsif matched = HANDLER_REGEX_LEGACY.match(mail_key.to_s)
@project_path, @incoming_email_token = matched.captures
end
end
def can_handle?
incoming_email_token && (project_id || project_path)
end
def execute
raise ProjectNotFound unless project
validate_permission!(:create_merge_request_in)
validate_permission!(:create_merge_request_from)
verify_record!(
record: create_merge_request,
invalid_exception: InvalidMergeRequestError,
record_name: 'merge_request')
end
# rubocop: disable CodeReuse/ActiveRecord
def author
@author ||= User.find_by(incoming_email_token: incoming_email_token)
end
# rubocop: enable CodeReuse/ActiveRecord
def project
@project ||= if project_id
Project.find_by_id(project_id)
else
Project.find_by_full_path(project_path)
end
end
def metrics_params
super.merge(includes_patches: patch_attachments.any?)
end
private
attr_reader :project_id, :project_path, :incoming_email_token
def build_merge_request
MergeRequests::BuildService.new(project, author, merge_request_params).execute
end
def create_merge_request
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
if merge_request.errors.any?
merge_request
else
MergeRequests::CreateService.new(project, author).create(merge_request)
end
end
def merge_request_params
params = {
source_project_id: project.id,
source_branch: source_branch,
target_project_id: project.id
}
params[:description] = message if message.present?
params
end
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) }
# reset the message, so it needs to be reprocessed when the attachments
# 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
end
end
end
end
|