1# frozen_string_literal: true 2 3module MergeRequests 4 class CreateFromIssueService < MergeRequests::CreateService 5 # TODO: This constructor does not use the "params:" argument from the superclass, 6 # but instead has a custom "mr_params:" argument. This is because historically, 7 # prior to named arguments being introduced to the constructor, it never passed 8 # along the third positional argument when calling `super`. 9 # This should be changed, in order to be consistent (all subclasses should pass 10 # along all of the arguments to the superclass, otherwise it is probably not an 11 # "is a" relationship). However, we need to be sure that passing the params 12 # argument to `super` (especially target_project_id) will not cause any unexpected 13 # behavior in the superclass. Since the addition of the named arguments is 14 # intended to be a low-risk pure refactor, we will defer this fix 15 # to this follow-on issue: 16 # https://gitlab.com/gitlab-org/gitlab/-/issues/328726 17 def initialize(project:, current_user:, mr_params: {}) 18 # branch - the name of new branch 19 # ref - the source of new branch. 20 21 @branch_name = mr_params[:branch_name] 22 @issue_iid = mr_params[:issue_iid] 23 @ref = mr_params[:ref] 24 @target_project_id = mr_params[:target_project_id] 25 26 super(project: project, current_user: current_user) 27 end 28 29 def execute 30 return error('Project not found') if target_project.blank? 31 return error('Not allowed to create merge request') unless can_create_merge_request? 32 return error('Invalid issue iid') unless @issue_iid.present? && issue.present? 33 34 result = ::Branches::CreateService.new(target_project, current_user).execute(branch_name, ref) 35 return result if result[:status] == :error 36 37 new_merge_request = create(merge_request) 38 39 if new_merge_request.valid? 40 merge_request_activity_counter.track_mr_create_from_issue(user: current_user) 41 SystemNoteService.new_merge_request(issue, project, current_user, new_merge_request) 42 43 success(new_merge_request) 44 else 45 SystemNoteService.new_issue_branch(issue, project, current_user, branch_name, branch_project: target_project) 46 47 error(new_merge_request.errors) 48 end 49 end 50 51 private 52 53 def can_create_merge_request? 54 can?(current_user, :create_merge_request_from, target_project) 55 end 56 57 # rubocop: disable CodeReuse/ActiveRecord 58 def issue 59 @issue ||= IssuesFinder.new(current_user, project_id: project.id).find_by(iid: @issue_iid) 60 end 61 # rubocop: enable CodeReuse/ActiveRecord 62 63 def branch_name 64 @branch ||= @branch_name || issue.to_branch_name 65 end 66 67 def ref 68 if valid_ref? 69 @ref 70 else 71 default_branch 72 end 73 end 74 75 def valid_ref? 76 ref_is_branch? || ref_is_tag? 77 end 78 79 def ref_is_branch? 80 target_project.repository.branch_exists?(@ref) 81 end 82 83 def ref_is_tag? 84 target_project.repository.tag_exists?(@ref) 85 end 86 87 def default_branch 88 target_project.default_branch_or_main 89 end 90 91 def merge_request 92 MergeRequests::BuildService.new(project: target_project, current_user: current_user, params: merge_request_params).execute 93 end 94 95 def merge_request_params 96 { 97 issue_iid: @issue_iid, 98 source_project_id: target_project.id, 99 source_branch: branch_name, 100 target_project_id: target_project.id, 101 target_branch: target_branch, 102 assignee_ids: [current_user.id] 103 } 104 end 105 106 def target_branch 107 if ref_is_branch? 108 @ref 109 else 110 default_branch 111 end 112 end 113 114 def success(merge_request) 115 super().merge(merge_request: merge_request) 116 end 117 118 def target_project 119 @target_project ||= 120 if @target_project_id.present? 121 project.forks.find_by_id(@target_project_id) 122 else 123 project 124 end 125 end 126 end 127end 128