mirror of
https://gitlab.com/gitlab-org/gitlab-foss.git
synced 2025-08-13 13:31:19 +00:00
523 lines
20 KiB
Ruby
523 lines
20 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Gitlab
|
|
module QuickActions
|
|
module MergeRequestActions
|
|
extend ActiveSupport::Concern
|
|
include Gitlab::QuickActions::Dsl
|
|
|
|
REBASE_FAILURE_UNMERGEABLE = 'This merge request is currently in an unmergeable state, and cannot be rebased.'
|
|
REBASE_FAILURE_PROTECTED_BRANCH = 'This merge request branch is protected from force push.'
|
|
REBASE_FAILURE_REBASE_IN_PROGRESS = 'A rebase is already in progress.'
|
|
|
|
included do
|
|
# MergeRequest only quick actions definitions
|
|
#
|
|
|
|
########################################################################
|
|
#
|
|
# /merge
|
|
#
|
|
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
|
|
|
|
########################################################################
|
|
#
|
|
# /rebase
|
|
#
|
|
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] = _(REBASE_FAILURE_PROTECTED_BRANCH)
|
|
next
|
|
end
|
|
|
|
if quick_action_target.cannot_be_merged?
|
|
@execution_message[:rebase] = _(REBASE_FAILURE_UNMERGEABLE)
|
|
next
|
|
end
|
|
|
|
if quick_action_target.rebase_in_progress?
|
|
@execution_message[:rebase] = _(REBASE_FAILURE_REBASE_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
|
|
|
|
########################################################################
|
|
#
|
|
# /draft
|
|
#
|
|
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
|
|
|
|
########################################################################
|
|
#
|
|
# /ready
|
|
#
|
|
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
|
|
|
|
########################################################################
|
|
#
|
|
# /target_branch
|
|
#
|
|
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
|
|
|
|
########################################################################
|
|
#
|
|
# /submit_review
|
|
#
|
|
desc { _('Submit a review') }
|
|
explanation { _('Submit the current review.') }
|
|
types MergeRequest
|
|
condition do
|
|
quick_action_target.persisted?
|
|
end
|
|
command :submit_review do |state = "reviewed"|
|
|
next if params[:review_id]
|
|
|
|
result = DraftNotes::PublishService.new(quick_action_target, current_user).execute
|
|
|
|
reviewer_state = state.strip.presence
|
|
|
|
@execution_message[:submit_review] = if result[:status] == :success
|
|
[_('Submitted the current review.')]
|
|
else
|
|
[result[:message]]
|
|
end
|
|
|
|
if reviewer_state === 'approve'
|
|
approval_success = ::MergeRequests::ApprovalService
|
|
.new(project: quick_action_target.project, current_user: current_user)
|
|
.execute(quick_action_target)
|
|
|
|
@execution_message[:submit_review] << if approval_success
|
|
_('Approved the current merge request.')
|
|
else
|
|
_('Failed to approve the current merge request.')
|
|
end
|
|
elsif MergeRequestReviewer.states.key?(reviewer_state)
|
|
::MergeRequests::UpdateReviewerStateService
|
|
.new(project: quick_action_target.project, current_user: current_user)
|
|
.execute(quick_action_target, reviewer_state)
|
|
end
|
|
end
|
|
|
|
########################################################################
|
|
#
|
|
# /request_changes
|
|
#
|
|
desc { _('Request changes') }
|
|
explanation { _('Request changes to the current merge request.') }
|
|
types MergeRequest
|
|
condition do
|
|
quick_action_target.persisted? &&
|
|
quick_action_target.find_reviewer(current_user)
|
|
end
|
|
command :request_changes do
|
|
result = ::MergeRequests::UpdateReviewerStateService.new(project: quick_action_target.project, current_user: current_user)
|
|
.execute(quick_action_target, "requested_changes")
|
|
|
|
@execution_message[:request_changes] = if result[:status] == :success
|
|
_('Changes requested to the current merge request.')
|
|
else
|
|
result[:message]
|
|
end
|
|
end
|
|
|
|
########################################################################
|
|
#
|
|
# /approve
|
|
#
|
|
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) && !quick_action_target.merged?
|
|
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
|
|
|
|
########################################################################
|
|
#
|
|
# /unapprove
|
|
#
|
|
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) && !quick_action_target.merged?
|
|
end
|
|
command :unapprove do
|
|
success = ::MergeRequests::RemoveApprovalService.new(project: quick_action_target.project, current_user: current_user).execute(quick_action_target)
|
|
|
|
next unless success
|
|
|
|
@execution_message[:unapprove] = _('Unapproved the current merge request.')
|
|
end
|
|
|
|
########################################################################
|
|
#
|
|
# /assign_reviewer
|
|
#
|
|
desc do
|
|
if quick_action_target.allows_multiple_reviewers?
|
|
_('Assign reviewers')
|
|
else
|
|
_('Assign reviewer')
|
|
end
|
|
end
|
|
explanation do |users|
|
|
reviewers = reviewers_to_add(users)
|
|
if reviewers.blank?
|
|
_("Failed to assign a reviewer because no user was specified.")
|
|
else
|
|
_('Assigns %{reviewer_users_sentence} as %{reviewer_text}.') % { reviewer_users_sentence: reviewer_users_sentence(users),
|
|
reviewer_text: 'reviewer'.pluralize(reviewers.size) }
|
|
end
|
|
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
|
|
processed_users = process_reviewer_users(users)
|
|
processed_msg = process_reviewer_users_message
|
|
|
|
if processed_users.present?
|
|
[
|
|
processed_msg,
|
|
_('Assigned %{reviewer_users_sentence} as %{reviewer_text}.') % {
|
|
reviewer_users_sentence: reviewer_users_sentence(processed_users),
|
|
reviewer_text: 'reviewer'.pluralize(processed_users.size)
|
|
}
|
|
].compact.join(' ')
|
|
else
|
|
processed_msg
|
|
end
|
|
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 do |users|
|
|
processed_users = process_reviewer_users(users)
|
|
|
|
next if processed_users.empty?
|
|
|
|
if quick_action_target.allows_multiple_reviewers?
|
|
@updates[:reviewer_ids] ||= quick_action_target.reviewers.map(&:id)
|
|
@updates[:reviewer_ids] |= processed_users.map(&:id)
|
|
else
|
|
@updates[:reviewer_ids] = [processed_users.first.id]
|
|
end
|
|
end
|
|
|
|
########################################################################
|
|
#
|
|
# /request_review
|
|
#
|
|
desc do
|
|
_('Request a review')
|
|
end
|
|
explanation do |users|
|
|
if users.blank?
|
|
_("Failed to request a review because no user was specified.")
|
|
else
|
|
_('Requests a review from %{reviewer_users_sentence}.') % { reviewer_users_sentence: reviewer_users_sentence(users) }
|
|
end
|
|
end
|
|
execution_message do |users = nil|
|
|
if users.blank?
|
|
_("Failed to request a review because no user was specified.")
|
|
else
|
|
processed_users = process_reviewer_users(users)
|
|
processed_msg = process_reviewer_users_message
|
|
|
|
if processed_users.present?
|
|
[
|
|
processed_msg,
|
|
_('Requested a review from %{reviewer_users_sentence}.') % {
|
|
reviewer_users_sentence: reviewer_users_sentence(processed_users)
|
|
}
|
|
].compact.join(' ')
|
|
else
|
|
processed_msg
|
|
end
|
|
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 :request_review do |users|
|
|
processed_users = process_reviewer_users(users)
|
|
|
|
next if processed_users.empty?
|
|
|
|
@updates[:reviewer_ids] ||= quick_action_target.reviewers.map(&:id)
|
|
|
|
service = ::MergeRequests::RequestReviewService.new(
|
|
project: quick_action_target.project,
|
|
current_user: current_user
|
|
)
|
|
|
|
reviewers_to_add(processed_users).each do |user|
|
|
if @updates[:reviewer_ids].include?(user.id)
|
|
# Request a new review from the reviewer if they are already assigned
|
|
service.execute(quick_action_target, user)
|
|
else
|
|
# Assign the user as a reviewer if they are not already
|
|
@updates[:reviewer_ids] << user.id
|
|
end
|
|
end
|
|
end
|
|
|
|
########################################################################
|
|
#
|
|
# /unassign_reviewer
|
|
#
|
|
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
|
|
reviewers_to_remove?(@updates) &&
|
|
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|
|
|
current_reviewers = quick_action_target.reviewers
|
|
# if preceding commands have been executed already, we need to use the updated reviewer_ids
|
|
current_reviewers = User.find(@updates[:reviewer_ids]) if @updates[:reviewer_ids].present?
|
|
|
|
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
|
|
|
|
removed_reviewers = current_reviewers.select { |user| @updates[:reviewer_ids].exclude?(user.id) }
|
|
# only generate the message here if the change would not be traceable otherwise
|
|
# because all reviewers have been assigned and removed immediately
|
|
if removed_reviewers.present? && !reviewers_to_remove?(@updates)
|
|
@execution_message[:unassign_reviewer] = _("Removed %{reviewer_text} %{reviewer_references}.") %
|
|
{ reviewer_text: 'reviewer'.pluralize(removed_reviewers.size), reviewer_references: removed_reviewers.map(&:to_reference).to_sentence }
|
|
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 reviewers_to_remove?(updates)
|
|
quick_action_target.reviewers.any? || updates&.dig(:reviewer_ids)&.any?
|
|
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
|
|
|
|
# Overriden in EE
|
|
def process_reviewer_users(users)
|
|
users
|
|
end
|
|
|
|
# Overriden in EE
|
|
def process_reviewer_users_message
|
|
nil
|
|
end
|
|
end
|
|
end
|
|
end
|