331 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			331 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Ruby
		
	
	
	
| # frozen_string_literal: true
 | |
| 
 | |
| module Gitlab
 | |
|   module QuickActions
 | |
|     module MergeRequestActions
 | |
|       extend ActiveSupport::Concern
 | |
|       include Gitlab::QuickActions::Dsl
 | |
| 
 | |
|       included do
 | |
|         # MergeRequest only quick actions definitions
 | |
|         desc do
 | |
|           if preferred_strategy = preferred_auto_merge_strategy(quick_action_target)
 | |
|             _("Merge automatically (%{strategy})") % { strategy: preferred_strategy.humanize }
 | |
|           else
 | |
|             _("Merge immediately")
 | |
|           end
 | |
|         end
 | |
|         explanation do
 | |
|           if preferred_strategy = preferred_auto_merge_strategy(quick_action_target)
 | |
|             _("Schedules to merge this merge request (%{strategy}).") % { strategy: preferred_strategy.humanize }
 | |
|           else
 | |
|             _('Merges this merge request immediately.')
 | |
|           end
 | |
|         end
 | |
|         execution_message do
 | |
|           if params[:merge_request_diff_head_sha].blank?
 | |
|             _("The `/merge` quick action requires the SHA of the head of the branch.")
 | |
|           elsif params[:merge_request_diff_head_sha] != quick_action_target.diff_head_sha
 | |
|             _("Branch has been updated since the merge was requested.")
 | |
|           elsif preferred_strategy = preferred_auto_merge_strategy(quick_action_target)
 | |
|             _("Scheduled to merge this merge request (%{strategy}).") % { strategy: preferred_strategy.humanize }
 | |
|           else
 | |
|             _('Merged this merge request.')
 | |
|           end
 | |
|         end
 | |
|         types MergeRequest
 | |
|         condition do
 | |
|           quick_action_target.persisted? &&
 | |
|             merge_orchestration_service.can_merge?(quick_action_target)
 | |
|         end
 | |
|         command :merge do
 | |
|           next unless params[:merge_request_diff_head_sha].present?
 | |
| 
 | |
|           next unless params[:merge_request_diff_head_sha] == quick_action_target.diff_head_sha
 | |
| 
 | |
|           @updates[:merge] = params[:merge_request_diff_head_sha]
 | |
|         end
 | |
| 
 | |
|         types MergeRequest
 | |
|         desc do
 | |
|           _('Rebase source branch')
 | |
|         end
 | |
|         explanation do
 | |
|           _('Rebase source branch on the target branch.')
 | |
|         end
 | |
|         condition do
 | |
|           merge_request = quick_action_target
 | |
| 
 | |
|           next false unless merge_request.open?
 | |
|           next false unless merge_request.source_branch_exists?
 | |
| 
 | |
|           access_check = ::Gitlab::UserAccess
 | |
|                            .new(current_user, container: merge_request.source_project)
 | |
| 
 | |
|           access_check.can_push_to_branch?(merge_request.source_branch)
 | |
|         end
 | |
|         command :rebase do
 | |
|           unless quick_action_target.permits_force_push?
 | |
|             @execution_message[:rebase] = _('This merge request branch is protected from force push.')
 | |
|             next
 | |
|           end
 | |
| 
 | |
|           if quick_action_target.cannot_be_merged?
 | |
|             @execution_message[:rebase] = _('This merge request cannot be rebased while there are conflicts.')
 | |
|             next
 | |
|           end
 | |
| 
 | |
|           if quick_action_target.rebase_in_progress?
 | |
|             @execution_message[:rebase] = _('A rebase is already in progress.')
 | |
|             next
 | |
|           end
 | |
| 
 | |
|           # This will be used to avoid simultaneous "/merge" and "/rebase" actions
 | |
|           @updates[:rebase] = true
 | |
| 
 | |
|           branch = quick_action_target.source_branch
 | |
| 
 | |
|           @execution_message[:rebase] = _('Scheduled a rebase of branch %{branch}.') % { branch: branch }
 | |
|         end
 | |
| 
 | |
|         desc { _('Set the Draft status') }
 | |
|         explanation do
 | |
|           draft_action_message(_("Marks"))
 | |
|         end
 | |
|         execution_message do
 | |
|           draft_action_message(_("Marked"))
 | |
|         end
 | |
| 
 | |
|         types MergeRequest
 | |
|         condition do
 | |
|           quick_action_target.respond_to?(:draft?) &&
 | |
|             (quick_action_target.new_record? || current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target))
 | |
|         end
 | |
|         command :draft do
 | |
|           @updates[:wip_event] = 'draft'
 | |
|         end
 | |
| 
 | |
|         desc { _('Set the Ready status') }
 | |
|         explanation do
 | |
|           noun = quick_action_target.to_ability_name.humanize(capitalize: false)
 | |
|           if quick_action_target.draft?
 | |
|             _("Marks this %{noun} as ready.")
 | |
|           else
 | |
|             _("No change to this %{noun}'s draft status.")
 | |
|           end % { noun: noun }
 | |
|         end
 | |
|         execution_message do
 | |
|           noun = quick_action_target.to_ability_name.humanize(capitalize: false)
 | |
|           if quick_action_target.draft?
 | |
|             _("Marked this %{noun} as ready.")
 | |
|           else
 | |
|             _("No change to this %{noun}'s draft status.")
 | |
|           end % { noun: noun }
 | |
|         end
 | |
| 
 | |
|         types MergeRequest
 | |
|         condition do
 | |
|           # Allow it to mark as draft on MR creation page or through MR notes
 | |
|           #
 | |
|           quick_action_target.respond_to?(:draft?) &&
 | |
|             (quick_action_target.new_record? || current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target))
 | |
|         end
 | |
|         command :ready do
 | |
|           @updates[:wip_event] = 'ready' if quick_action_target.draft?
 | |
|         end
 | |
| 
 | |
|         desc { _('Set target branch') }
 | |
|         explanation do |branch_name|
 | |
|           _('Sets target branch to %{branch_name}.') % { branch_name: branch_name }
 | |
|         end
 | |
|         execution_message do |branch_name|
 | |
|           _('Set target branch to %{branch_name}.') % { branch_name: branch_name }
 | |
|         end
 | |
|         params '<Local branch name>'
 | |
|         types MergeRequest
 | |
|         condition do
 | |
|           quick_action_target.respond_to?(:target_branch) &&
 | |
|             (current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) ||
 | |
|               quick_action_target.new_record?)
 | |
|         end
 | |
|         parse_params do |target_branch_param|
 | |
|           target_branch_param.strip
 | |
|         end
 | |
|         command :target_branch do |branch_name|
 | |
|           @updates[:target_branch] = branch_name if project.repository.branch_exists?(branch_name)
 | |
|         end
 | |
| 
 | |
|         desc { _('Submit a review') }
 | |
|         explanation { _('Submit the current review.') }
 | |
|         types MergeRequest
 | |
|         condition do
 | |
|           quick_action_target.persisted?
 | |
|         end
 | |
|         command :submit_review do
 | |
|           next if params[:review_id]
 | |
| 
 | |
|           result = DraftNotes::PublishService.new(quick_action_target, current_user).execute
 | |
|           @execution_message[:submit_review] = if result[:status] == :success
 | |
|                                                  _('Submitted the current review.')
 | |
|                                                else
 | |
|                                                  result[:message]
 | |
|                                                end
 | |
|         end
 | |
| 
 | |
|         desc { _('Approve a merge request') }
 | |
|         explanation { _('Approve the current merge request.') }
 | |
|         types MergeRequest
 | |
|         condition do
 | |
|           quick_action_target.persisted? && quick_action_target.eligible_for_approval_by?(current_user)
 | |
|         end
 | |
|         command :approve do
 | |
|           success = ::MergeRequests::ApprovalService.new(project: quick_action_target.project, current_user: current_user).execute(quick_action_target)
 | |
| 
 | |
|           next unless success
 | |
| 
 | |
|           @execution_message[:approve] = _('Approved the current merge request.')
 | |
|         end
 | |
| 
 | |
|         desc { _('Unapprove a merge request') }
 | |
|         explanation { _('Unapprove the current merge request.') }
 | |
|         types MergeRequest
 | |
|         condition do
 | |
|           quick_action_target.persisted? && quick_action_target.eligible_for_unapproval_by?(current_user)
 | |
|         end
 | |
|         command :unapprove do
 | |
|           success = ::MergeRequests::RemoveApprovalService.new(project: quick_action_target.project, current_user: current_user).execute(quick_action_target)
 | |
| 
 | |
|           next unless success
 | |
| 
 | |
|           ::MergeRequests::UpdateReviewerStateService
 | |
|             .new(project: quick_action_target.project, current_user: current_user)
 | |
|             .execute(quick_action_target, "unreviewed")
 | |
| 
 | |
|           @execution_message[:unapprove] = _('Unapproved the current merge request.')
 | |
|         end
 | |
| 
 | |
|         desc do
 | |
|           if quick_action_target.allows_multiple_reviewers?
 | |
|             _('Assign reviewers')
 | |
|           else
 | |
|             _('Assign reviewer')
 | |
|           end
 | |
|         end
 | |
|         explanation do |users|
 | |
|           reviewers = reviewers_to_add(users)
 | |
|           _('Assigns %{reviewer_users_sentence} as %{reviewer_text}.') % { reviewer_users_sentence: reviewer_users_sentence(users),
 | |
|                                                                            reviewer_text: 'reviewer'.pluralize(reviewers.size) }
 | |
|         end
 | |
|         execution_message do |users = nil|
 | |
|           reviewers = reviewers_to_add(users)
 | |
|           if reviewers.blank?
 | |
|             _("Failed to assign a reviewer because no user was specified.")
 | |
|           else
 | |
|             _('Assigned %{reviewer_users_sentence} as %{reviewer_text}.') % { reviewer_users_sentence: reviewer_users_sentence(users),
 | |
|                                                                               reviewer_text: 'reviewer'.pluralize(reviewers.size) }
 | |
|           end
 | |
|         end
 | |
|         params do
 | |
|           quick_action_target.allows_multiple_reviewers? ? '@user1 @user2' : '@user'
 | |
|         end
 | |
|         types MergeRequest
 | |
|         condition do
 | |
|           current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
 | |
|         end
 | |
|         parse_params do |reviewer_param|
 | |
|           extract_users(reviewer_param)
 | |
|         end
 | |
|         command :assign_reviewer, :reviewer, :request_review do |users|
 | |
|           next if users.empty?
 | |
| 
 | |
|           if quick_action_target.allows_multiple_reviewers?
 | |
|             @updates[:reviewer_ids] ||= quick_action_target.reviewers.map(&:id)
 | |
|             @updates[:reviewer_ids] |= users.map(&:id)
 | |
|           else
 | |
|             @updates[:reviewer_ids] = [users.first.id]
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         desc do
 | |
|           if quick_action_target.allows_multiple_reviewers?
 | |
|             _('Remove all or specific reviewers')
 | |
|           else
 | |
|             _('Remove reviewer')
 | |
|           end
 | |
|         end
 | |
|         explanation do |users = nil|
 | |
|           reviewers = reviewers_for_removal(users)
 | |
|           _("Removes %{reviewer_text} %{reviewer_references}.") %
 | |
|             { reviewer_text: 'reviewer'.pluralize(reviewers.size), reviewer_references: reviewers.map(&:to_reference).to_sentence }
 | |
|         end
 | |
|         execution_message do |users = nil|
 | |
|           reviewers = reviewers_for_removal(users)
 | |
|           _("Removed %{reviewer_text} %{reviewer_references}.") %
 | |
|             { reviewer_text: 'reviewer'.pluralize(reviewers.size), reviewer_references: reviewers.map(&:to_reference).to_sentence }
 | |
|         end
 | |
|         params do
 | |
|           quick_action_target.allows_multiple_reviewers? ? '@user1 @user2' : ''
 | |
|         end
 | |
|         types MergeRequest
 | |
|         condition do
 | |
|           quick_action_target.persisted? &&
 | |
|             quick_action_target.reviewers.any? &&
 | |
|             current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
 | |
|         end
 | |
|         parse_params do |unassign_reviewer_param|
 | |
|           # When multiple users are assigned, all will be unassigned if multiple reviewers are no longer allowed
 | |
|           extract_users(unassign_reviewer_param) if quick_action_target.allows_multiple_reviewers?
 | |
|         end
 | |
|         command :unassign_reviewer, :remove_reviewer do |users = nil|
 | |
|           if quick_action_target.allows_multiple_reviewers? && users&.any?
 | |
|             @updates[:reviewer_ids] ||= quick_action_target.reviewers.map(&:id)
 | |
|             @updates[:reviewer_ids] -= users.map(&:id)
 | |
|           else
 | |
|             @updates[:reviewer_ids] = []
 | |
|           end
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def reviewer_users_sentence(users)
 | |
|         reviewers_to_add(users).map(&:to_reference).to_sentence
 | |
|       end
 | |
| 
 | |
|       def reviewers_for_removal(users)
 | |
|         reviewers = quick_action_target.reviewers
 | |
|         if users.present? && quick_action_target.allows_multiple_reviewers?
 | |
|           users
 | |
|         else
 | |
|           reviewers
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def reviewers_to_add(users)
 | |
|         return if users.blank?
 | |
| 
 | |
|         if quick_action_target.allows_multiple_reviewers?
 | |
|           users
 | |
|         else
 | |
|           [users.first]
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def draft_action_message(verb)
 | |
|         noun = quick_action_target.to_ability_name.humanize(capitalize: false)
 | |
|         if !quick_action_target.draft?
 | |
|           _("%{verb} this %{noun} as a draft.")
 | |
|         else
 | |
|           _("No change to this %{noun}'s draft status.")
 | |
|         end % { verb: verb, noun: noun }
 | |
|       end
 | |
| 
 | |
|       def merge_orchestration_service
 | |
|         @merge_orchestration_service ||= ::MergeRequests::MergeOrchestrationService.new(project, current_user)
 | |
|       end
 | |
| 
 | |
|       def preferred_auto_merge_strategy(merge_request)
 | |
|         merge_orchestration_service.preferred_auto_merge_strategy(merge_request)
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 |