2018-07-24 10:00:56 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-05-09 04:15:34 +00:00
|
|
|
class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
|
|
|
|
include ActionView::Helpers::UrlHelper
|
|
|
|
include GitlabRoutingHelper
|
|
|
|
include MarkupHelper
|
|
|
|
include TreeHelper
|
2018-04-06 14:02:36 +00:00
|
|
|
include ChecksCollaboration
|
2018-02-07 16:32:32 +00:00
|
|
|
include Gitlab::Utils::StrongMemoize
|
2017-05-09 04:15:34 +00:00
|
|
|
|
|
|
|
presents :merge_request
|
|
|
|
|
|
|
|
def ci_status
|
|
|
|
if pipeline
|
|
|
|
status = pipeline.status
|
2019-04-12 12:39:10 +00:00
|
|
|
status = "success-with-warnings" if pipeline.success? && pipeline.has_warnings?
|
2017-05-09 04:15:34 +00:00
|
|
|
|
|
|
|
status || "preparing"
|
|
|
|
else
|
|
|
|
ci_service = source_project.try(:ci_service)
|
|
|
|
ci_service&.commit_status(diff_head_sha, source_branch)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-05-22 11:45:27 +00:00
|
|
|
def cancel_auto_merge_path
|
|
|
|
if can_cancel_auto_merge?(current_user)
|
|
|
|
cancel_auto_merge_project_merge_request_path(project, merge_request)
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def create_issue_to_resolve_discussions_path
|
|
|
|
if can?(current_user, :create_issue, project) && project.issues_enabled?
|
2017-06-29 17:06:35 +00:00
|
|
|
new_project_issue_path(project, merge_request_to_resolve_discussions_of: iid)
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def remove_wip_path
|
2017-10-03 21:13:13 +00:00
|
|
|
if work_in_progress? && can?(current_user, :update_merge_request, merge_request.project)
|
2017-06-29 17:06:35 +00:00
|
|
|
remove_wip_project_merge_request_path(project, merge_request)
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def merge_path
|
|
|
|
if can_be_merged_by?(current_user)
|
2017-06-29 17:06:35 +00:00
|
|
|
merge_project_merge_request_path(project, merge_request)
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def revert_in_fork_path
|
2018-02-07 17:30:41 +00:00
|
|
|
if user_can_fork_project? && cached_can_be_reverted?
|
2017-05-09 04:15:34 +00:00
|
|
|
continue_params = {
|
|
|
|
to: merge_request_path(merge_request),
|
2019-04-16 10:32:05 +00:00
|
|
|
notice: _('%{edit_in_new_fork_notice} Try to cherry-pick this commit again.') % { edit_in_new_fork_notice: edit_in_new_fork_notice },
|
2017-05-09 04:15:34 +00:00
|
|
|
notice_now: edit_in_new_fork_notice_now
|
|
|
|
}
|
|
|
|
|
2017-06-29 17:06:35 +00:00
|
|
|
project_forks_path(merge_request.project,
|
2017-05-09 04:15:34 +00:00
|
|
|
namespace_key: current_user.namespace.id,
|
|
|
|
continue: continue_params)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def cherry_pick_in_fork_path
|
|
|
|
if user_can_fork_project? && can_be_cherry_picked?
|
|
|
|
continue_params = {
|
|
|
|
to: merge_request_path(merge_request),
|
2019-04-16 10:32:05 +00:00
|
|
|
notice: _('%{edit_in_new_fork_notice} Try to revert this commit again.') % { edit_in_new_fork_notice: edit_in_new_fork_notice },
|
2017-05-09 04:15:34 +00:00
|
|
|
notice_now: edit_in_new_fork_notice_now
|
|
|
|
}
|
|
|
|
|
2017-06-29 17:06:35 +00:00
|
|
|
project_forks_path(project,
|
2017-05-09 04:15:34 +00:00
|
|
|
namespace_key: current_user.namespace.id,
|
|
|
|
continue: continue_params)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def conflict_resolution_path
|
2017-05-11 15:23:02 +00:00
|
|
|
if conflicts.can_be_resolved_in_ui? && conflicts.can_be_resolved_by?(current_user)
|
2017-06-29 17:06:35 +00:00
|
|
|
conflicts_project_merge_request_path(project, merge_request)
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-12-20 09:01:21 +00:00
|
|
|
def rebase_path
|
2018-02-28 08:06:18 +00:00
|
|
|
if !rebase_in_progress? && should_be_rebased? && can_push_to_source_branch?
|
2017-12-20 09:01:21 +00:00
|
|
|
rebase_project_merge_request_path(project, merge_request)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-07-11 14:48:59 +00:00
|
|
|
def target_branch_tree_path
|
2017-05-09 04:15:34 +00:00
|
|
|
if target_branch_exists?
|
2017-07-11 14:48:59 +00:00
|
|
|
project_tree_path(project, target_branch)
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-07-11 16:49:22 +00:00
|
|
|
def target_branch_commits_path
|
|
|
|
if target_branch_exists?
|
|
|
|
project_commits_path(project, target_branch)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-03-01 10:05:04 +00:00
|
|
|
def target_branch_path
|
|
|
|
if target_branch_exists?
|
|
|
|
project_branch_path(project, target_branch)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-03-06 08:34:38 +00:00
|
|
|
def source_branch_commits_path
|
|
|
|
if source_branch_exists?
|
|
|
|
project_commits_path(source_project, source_branch)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-09 04:15:34 +00:00
|
|
|
def source_branch_path
|
|
|
|
if source_branch_exists?
|
2017-06-29 17:06:35 +00:00
|
|
|
project_branch_path(source_project, source_branch)
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def source_branch_with_namespace_link
|
|
|
|
namespace = source_project_namespace
|
|
|
|
branch = source_branch
|
|
|
|
|
2018-10-12 21:39:13 +00:00
|
|
|
namespace_link = source_branch_exists? ? link_to(namespace, project_path(source_project)) : ERB::Util.html_escape(namespace)
|
|
|
|
branch_link = source_branch_exists? ? link_to(branch, project_tree_path(source_project, source_branch)) : ERB::Util.html_escape(branch)
|
2017-05-09 04:15:34 +00:00
|
|
|
|
2018-10-12 21:39:13 +00:00
|
|
|
for_fork? ? "#{namespace_link}:#{branch_link}" : branch_link
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def closing_issues_links
|
2017-06-08 16:09:04 +00:00
|
|
|
markdown(
|
|
|
|
issues_sentence(project, closing_issues),
|
|
|
|
pipeline: :gfm,
|
|
|
|
author: author,
|
|
|
|
project: project,
|
|
|
|
issuable_state_filter_enabled: true
|
|
|
|
)
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def mentioned_issues_links
|
|
|
|
mentioned_issues = issues_mentioned_but_not_closing(current_user)
|
2017-06-08 16:09:04 +00:00
|
|
|
markdown(
|
|
|
|
issues_sentence(project, mentioned_issues),
|
|
|
|
pipeline: :gfm,
|
|
|
|
author: author,
|
|
|
|
project: project,
|
|
|
|
issuable_state_filter_enabled: true
|
|
|
|
)
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def assign_to_closing_issues_link
|
2018-08-27 15:31:01 +00:00
|
|
|
# rubocop: disable CodeReuse/ServiceClass
|
2017-05-09 04:15:34 +00:00
|
|
|
issues = MergeRequests::AssignIssuesService.new(project,
|
|
|
|
current_user,
|
|
|
|
merge_request: merge_request,
|
|
|
|
closes_issues: closing_issues
|
|
|
|
).assignable_issues
|
2017-06-29 17:06:35 +00:00
|
|
|
path = assign_related_issues_project_merge_request_path(project, merge_request)
|
2017-05-09 04:15:34 +00:00
|
|
|
if issues.present?
|
2019-04-16 10:32:05 +00:00
|
|
|
if issues.count > 1
|
|
|
|
link_to _('Assign yourself to these issues'), path, method: :post
|
|
|
|
else
|
|
|
|
link_to _('Assign yourself to this issue'), path, method: :post
|
|
|
|
end
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
2018-08-27 15:31:01 +00:00
|
|
|
# rubocop: enable CodeReuse/ServiceClass
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def can_revert_on_current_merge_request?
|
2018-04-06 14:02:36 +00:00
|
|
|
can_collaborate_with_project?(project) && cached_can_be_reverted?
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def can_cherry_pick_on_current_merge_request?
|
2018-04-06 14:02:36 +00:00
|
|
|
can_collaborate_with_project?(project) && can_be_cherry_picked?
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
|
2017-12-20 09:01:21 +00:00
|
|
|
def can_push_to_source_branch?
|
2018-02-28 08:06:18 +00:00
|
|
|
return false unless source_branch_exists?
|
|
|
|
|
|
|
|
!!::Gitlab::UserAccess
|
|
|
|
.new(current_user, project: source_project)
|
|
|
|
.can_push_to_branch?(source_branch)
|
2017-12-20 09:01:21 +00:00
|
|
|
end
|
|
|
|
|
2018-06-25 08:59:00 +00:00
|
|
|
def can_remove_source_branch?
|
|
|
|
source_branch_exists? && merge_request.can_remove_source_branch?(current_user)
|
|
|
|
end
|
|
|
|
|
2019-01-28 12:12:30 +00:00
|
|
|
def can_read_pipeline?
|
|
|
|
pipeline && can?(current_user, :read_pipeline, pipeline)
|
|
|
|
end
|
|
|
|
|
2018-05-21 07:52:24 +00:00
|
|
|
def mergeable_discussions_state
|
|
|
|
# This avoids calling MergeRequest#mergeable_discussions_state without
|
|
|
|
# considering the state of the MR first. If a MR isn't mergeable, we can
|
|
|
|
# safely short-circuit it.
|
|
|
|
if merge_request.mergeable_state?(skip_ci_check: true, skip_discussions_check: true)
|
|
|
|
merge_request.mergeable_discussions_state?
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def web_url
|
|
|
|
Gitlab::UrlBuilder.build(merge_request)
|
|
|
|
end
|
|
|
|
|
|
|
|
def subscribed?
|
|
|
|
merge_request.subscribed?(current_user, merge_request.target_project)
|
|
|
|
end
|
|
|
|
|
2018-12-21 10:47:03 +00:00
|
|
|
def conflicts_docs_path
|
|
|
|
help_page_path('user/project/merge_requests/resolve_conflicts.md')
|
|
|
|
end
|
|
|
|
|
2019-04-15 13:25:48 +00:00
|
|
|
def merge_request_pipelines_docs_path
|
|
|
|
help_page_path('ci/merge_request_pipelines/index.md')
|
|
|
|
end
|
|
|
|
|
2019-04-29 07:29:54 +00:00
|
|
|
def source_branch_link
|
|
|
|
if source_branch_exists?
|
|
|
|
link_to(source_branch, source_branch_commits_path, class: 'ref-name')
|
|
|
|
else
|
|
|
|
content_tag(:span, source_branch, class: 'ref-name')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def target_branch_link
|
|
|
|
if target_branch_exists?
|
|
|
|
link_to(target_branch, target_branch_commits_path, class: 'ref-name')
|
|
|
|
else
|
|
|
|
content_tag(:span, target_branch, class: 'ref-name')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-09 04:15:34 +00:00
|
|
|
private
|
|
|
|
|
2018-02-07 17:30:41 +00:00
|
|
|
def cached_can_be_reverted?
|
2018-02-07 16:32:32 +00:00
|
|
|
strong_memoize(:can_be_reverted) do
|
|
|
|
can_be_reverted?(current_user)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-11 15:23:02 +00:00
|
|
|
def conflicts
|
2018-08-27 15:31:01 +00:00
|
|
|
# rubocop: disable CodeReuse/ServiceClass
|
2017-05-11 15:23:02 +00:00
|
|
|
@conflicts ||= MergeRequests::Conflicts::ListService.new(merge_request)
|
2018-08-27 15:31:01 +00:00
|
|
|
# rubocop: enable CodeReuse/ServiceClass
|
2017-05-11 15:23:02 +00:00
|
|
|
end
|
|
|
|
|
2017-05-09 04:15:34 +00:00
|
|
|
def closing_issues
|
2018-07-30 18:52:42 +00:00
|
|
|
@closing_issues ||= visible_closing_issues_for(current_user)
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def pipeline
|
2017-12-01 13:26:51 +00:00
|
|
|
@pipeline ||= actual_head_pipeline
|
2017-05-09 04:15:34 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def issues_sentence(project, issues)
|
|
|
|
# Sorting based on the `#123` or `group/project#123` reference will sort
|
|
|
|
# local issues first.
|
|
|
|
issues.map do |issue|
|
|
|
|
issue.to_reference(project)
|
|
|
|
end.sort.to_sentence
|
|
|
|
end
|
|
|
|
|
|
|
|
def user_can_fork_project?
|
|
|
|
can?(current_user, :fork_project, project)
|
|
|
|
end
|
|
|
|
end
|