gitlab-org--gitlab-foss/app/controllers/projects/merge_requests_controller.rb
Yorick Peterse d470f3d195
Support for rendering/redacting multiple documents
This commit changes the way certain documents are rendered (currently
only Notes) and how documents are redacted. Previously both rendering
and redacting would run on a per document basis. The result of this was
that for every document we'd have to run countless queries just to
figure out if we could display a set of links or not.

This commit changes things around so that redacting Markdown documents
is no longer tied into the html-pipeline Gem. This in turn allows it to
redact multiple documents in a single pass, thus reducing the number of
queries needed.

In turn rendering issue/merge request notes has been adjusted to take
advantage of this new setup. Instead of rendering Markdown somewhere
deep down in a view the Markdown is rendered and redacted in the
controller (taking the current user and all that into account). This has
been done in such a way that the "markdown()" helper method can still be
used on its own.

This particular commit also paves the way for caching rendered HTML on
object level. Right now there's an accessor method Note#note_html which
is used for setting/getting the rendered HTML. Once we cache HTML on row
level we can simply change this field to be a column and call a "save"
whenever needed and we're pretty much done.
2016-06-24 11:46:39 +02:00

398 lines
12 KiB
Ruby

class Projects::MergeRequestsController < Projects::ApplicationController
include ToggleSubscriptionAction
include DiffHelper
include IssuableActions
include ToggleAwardEmoji
before_action :module_enabled
before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
:ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip
]
before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds]
before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds]
before_action :define_show_vars, only: [:show, :diffs, :commits, :builds]
before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds]
# Allow read any merge_request
before_action :authorize_read_merge_request!
# Allow write(create) merge_request
before_action :authorize_create_merge_request!, only: [:new, :create]
# Allow modify merge_request
before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort]
def index
terms = params['issue_search']
@merge_requests = get_merge_requests_collection
if terms.present?
if terms =~ /\A[#!](\d+)\z/
@merge_requests = @merge_requests.where(iid: $1)
else
@merge_requests = @merge_requests.full_search(terms)
end
end
@merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:target_project)
@labels = @project.labels.where(title: params[:label_name])
respond_to do |format|
format.html
format.json do
render json: {
html: view_to_html_string("projects/merge_requests/_merge_requests"),
labels: @labels.as_json(methods: :text_color)
}
end
end
end
def show
@note_counts = Note.where(commit_id: @merge_request.commits.map(&:id)).
group(:commit_id).count
respond_to do |format|
format.html
format.json { render json: @merge_request }
format.patch { render text: @merge_request.to_patch }
format.diff do
return render_404 unless @merge_request.diff_refs
send_git_diff @project.repository, @merge_request.diff_refs
end
end
end
def diffs
apply_diff_view_cookie!
@commit = @merge_request.last_commit
@base_commit = @merge_request.diff_base_commit
# MRs created before 8.4 don't have a diff_base_commit,
# but we need it for the "View file @ ..." link by deleted files
@base_commit ||= @merge_request.first_commit.parent || @merge_request.first_commit
@comments_target = {
noteable_type: 'MergeRequest',
noteable_id: @merge_request.id
}
@grouped_diff_notes = @merge_request.notes.grouped_diff_notes
Banzai::NoteRenderer.render(
@grouped_diff_notes.values.flatten,
@project,
current_user,
@path,
@project_wiki,
@ref
)
respond_to do |format|
format.html
format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } }
end
end
def commits
respond_to do |format|
format.html { render 'show' }
format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_commits') } }
end
end
def builds
respond_to do |format|
format.html { render 'show' }
format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_builds') } }
end
end
def new
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
@merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
@noteable = @merge_request
@target_branches = if @merge_request.target_project
@merge_request.target_project.repository.branch_names
else
[]
end
@target_project = merge_request.target_project
@source_project = merge_request.source_project
@commits = @merge_request.compare_commits.reverse
@commit = @merge_request.last_commit
@base_commit = @merge_request.diff_base_commit
@diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
@diff_notes_disabled = true
@pipeline = @merge_request.pipeline
@statuses = @pipeline.statuses if @pipeline
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
end
def create
@target_branches ||= []
@merge_request = MergeRequests::CreateService.new(project, current_user, merge_request_params).execute
if @merge_request.valid?
redirect_to(merge_request_path(@merge_request))
else
@source_project = @merge_request.source_project
@target_project = @merge_request.target_project
render action: "new"
end
end
def edit
@source_project = @merge_request.source_project
@target_project = @merge_request.target_project
@target_branches = @merge_request.target_project.repository.branch_names
end
def update
@merge_request = MergeRequests::UpdateService.new(project, current_user, merge_request_params).execute(@merge_request)
if @merge_request.valid?
respond_to do |format|
format.html do
redirect_to([@merge_request.target_project.namespace.becomes(Namespace),
@merge_request.target_project, @merge_request])
end
format.json do
render json: @merge_request.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
end
else
render "edit"
end
end
def remove_wip
MergeRequests::UpdateService.new(project, current_user, title: @merge_request.wipless_title).execute(@merge_request)
redirect_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request),
notice: "The merge request can now be merged."
end
def merge_check
@merge_request.check_if_can_be_merged
render partial: "projects/merge_requests/widget/show.html.haml", layout: false
end
def cancel_merge_when_build_succeeds
return access_denied! unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user)
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user).cancel(@merge_request)
end
def merge
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
unless @merge_request.mergeable?
@status = :failed
return
end
if params[:sha] != @merge_request.source_sha
@status = :sha_mismatch
return
end
TodoService.new.merge_merge_request(merge_request, current_user)
@merge_request.update(merge_error: nil)
if params[:merge_when_build_succeeds].present?
unless @merge_request.pipeline
@status = :failed
return
end
if @merge_request.pipeline.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
.execute(@merge_request)
@status = :merge_when_build_succeeds
elsif @merge_request.pipeline.success?
# This can be triggered when a user clicks the auto merge button while
# the tests finish at about the same time
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = :success
else
@status = :failed
end
else
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = :success
end
end
def branch_from
# This is always source
@source_project = @merge_request.nil? ? @project : @merge_request.source_project
@commit = @repository.commit(params[:ref]) if params[:ref].present?
render layout: false
end
def branch_to
@target_project = selected_target_project
@commit = @target_project.commit(params[:ref]) if params[:ref].present?
render layout: false
end
def update_branches
@target_project = selected_target_project
@target_branches = @target_project.repository.branch_names
render layout: false
end
def ci_status
pipeline = @merge_request.pipeline
if pipeline
status = pipeline.status
coverage = pipeline.try(:coverage)
status ||= "preparing"
else
ci_service = @merge_request.source_project.ci_service
status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch) if ci_service
if ci_service.respond_to?(:commit_coverage)
coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch)
end
end
response = {
title: merge_request.title,
sha: merge_request.last_commit_short_sha,
status: status,
coverage: coverage
}
render json: response
end
protected
def selected_target_project
if @project.id.to_s == params[:target_project_id] || @project.forked_project_link.nil?
@project
else
@project.forked_project_link.forked_from_project
end
end
def merge_request
@merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
end
alias_method :subscribable_resource, :merge_request
alias_method :issuable, :merge_request
alias_method :awardable, :merge_request
def closes_issues
@closes_issues ||= @merge_request.closes_issues
end
def authorize_update_merge_request!
return render_404 unless can?(current_user, :update_merge_request, @merge_request)
end
def authorize_admin_merge_request!
return render_404 unless can?(current_user, :admin_merge_request, @merge_request)
end
def module_enabled
return render_404 unless @project.merge_requests_enabled
end
def validates_merge_request
# If source project was removed (Ex. mr from fork to origin)
return invalid_mr unless @merge_request.source_project
# Show git not found page
# if there is no saved commits between source & target branch
if @merge_request.commits.blank?
# and if target branch doesn't exist
return invalid_mr unless @merge_request.target_branch_exists?
# or if source branch doesn't exist
return invalid_mr unless @merge_request.source_branch_exists?
end
end
def define_show_vars
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@discussions = @merge_request.mr_and_commit_notes.
inc_author_project_award_emoji.
fresh.
discussions
@notes = Banzai::NoteRenderer.render(
@discussions.flatten,
@project,
current_user,
@path,
@project_wiki,
@ref
)
@noteable = @merge_request
# Get commits from repository
# or from cache if already merged
@commits = @merge_request.commits
@merge_request_diff = @merge_request.merge_request_diff
@pipeline = @merge_request.pipeline
@statuses = @pipeline.statuses if @pipeline
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@merge_request.close
end
end
def define_widget_vars
@pipeline = @merge_request.pipeline
@pipelines = [@pipeline].compact
closes_issues
end
def invalid_mr
# Render special view for MR with removed source or target branch
render 'invalid'
end
def merge_request_params
params.require(:merge_request).permit(
:title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id,
:state_event, :description, :task_num, :force_remove_source_branch,
label_ids: []
)
end
def merge_params
params.permit(:should_remove_source_branch, :commit_message)
end
# Make sure merge requests created before 8.0
# have head file in refs/merge-requests/
def ensure_ref_fetched
@merge_request.ensure_ref_fetched
end
end