1
1
mirror of https://github.com/github/semantic.git synced 2024-12-22 14:21:31 +03:00
semantic/temp.rb
2017-02-09 21:31:55 -08:00

1117 lines
36 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# rubocop:disable Style/FrozenStringLiteralComment
# rubocop:disable Rails/Render
class PullRequestsController < AbstractRepositoryController
areas_of_responsibility :code_collab, :pull_requests
include ShowPartial, PrefetchHelper, ProgressiveTimeline, DiscussionPreloadHelper,
ControllerMethods::Diffs
helper :compare
around_filter :select_up_to_date_database, only: :show_partial
before_filter :login_required, only: [:create, :comment, :merge_button, :sync, :dismiss_protip]
before_filter :login_required_redirect_for_public_repo, only: [:new]
before_filter :non_migrating_repository_required,
except: [:new, :show, :show_partial, :merge_button, :diff, :patch, :merge_button_matrix]
before_filter :check_for_empty_repository, only: [:create, :new]
before_filter :ensure_pull_head_pushable, only: [:cleanup, :undo_cleanup, :sync]
before_filter :content_authorization_required, only: [:create, :merge]
skip_before_filter :cap_pagination, unless: :robot?
layout :repository_layout
param_encoding :create, :base, "ASCII-8BIT"
param_encoding :create, :head, "ASCII-8BIT"
def new
redirect_to compare_path(current_repository, params[:range], true)
end
def create
repo = current_repository
return render_404 unless repo
return if reject_bully_for?(repo)
params[:pull_request] ||= {}
options = params.slice(:base, :head)
options[:user] = current_user
params[:issue] ||= {}
params[:issue][:title] = params[:pull_request][:title]
params[:issue][:body] = params[:pull_request][:body]
options[:issue] = build_issue
options[:collab_privs] = !!params[:collab_privs]
options[:reviewer_ids] = params[:reviewer_ids]
begin
@pull_request = PullRequest.create_for!(repo, options)
@comparison = @pull_request.comparison
@issue = @pull_request.issue
GitHub.instrument "pull_request.create", user: current_user
instrument_issue_creation_via_ui(@pull_request)
instrument_saved_reply_use(params[:saved_reply_id], "pull_request")
# Keep track of if a pull request targets the repo's
# default branch, or one in progress.
if options[:base].strip == "#{repo.owner}:#{repo.default_branch}"
GitHub.stats.increment("pullrequest.target.default")
else
GitHub.stats.increment("pullrequest.target.other")
end
if params[:quick_pull].present?
type = @pull_request.cross_repo? ? "cross" : "same"
GitHub.stats.increment("pullrequest.quick.total.create")
GitHub.stats.increment("pullrequest.quick.#{type}.create")
end
redirect_to pull_request_path(@pull_request, repo)
rescue ActiveRecord::RecordInvalid => e
flash[:error] = "Pull request creation failed. #{human_failure_message(e)}"
range = [options[:base], options[:head]].compact.join("...")
redirect_to compare_path(repo, range, true)
end
end
def show
@mobile_view_available = true
redirect_or_error = ensure_valid_pull_request
if performed?
return redirect_or_error
end
if params[:range]
env["pull_request.timeout_reason"] = "compute_diff"
if oids = parse_show_range_oid_components(@pull, params[:range])
oid1, oid2 = oids
else
return render template: "pull_requests/bad_range", status: :not_found
end
allowed_commits = @pull.changed_commit_oids
if !(oid1.nil? || allowed_commits.include?(oid1)) || !allowed_commits.include?(oid2)
return render template: "pull_requests/bad_range", status: :not_found
end
if oid1 && (oid1 == oid2 || !@pull.repository.rpc.descendant_of([[oid2, oid1]]).values.first)
return render template: "pull_requests/bad_range", status: :not_found
end
expected_canonical_range = oid1 ? "#{oid1}..#{oid2}" : "#{oid2}"
if params[:range] != expected_canonical_range
return redirect_to(range: expected_canonical_range)
end
if params[:tab] == "commits"
@specified_tab = "files"
oid1 = current_repository.commits.find(oid2).parent_oids.first
end
@comparison = @pull.historical_comparison
merge_base_oid = @comparison.compare_repository.rpc.best_merge_base(@pull.base_sha, oid2)
if merge_base_oid.nil?
return render template: "pull_requests/orphan_commit", status: :not_found, locals: { oid2: oid2 }
end
oid1 ||= @pull.compare_repository.rpc.best_merge_base(oid2, merge_base_oid) if merge_base_oid
unless @pull_comparison = PullRequest::Comparison.find(pull: @pull, start_commit_oid: oid1, end_commit_oid: oid2, base_commit_oid: merge_base_oid)
return render template: "pull_requests/bad_range", status: :not_found
end
load_diff
env["pull_request.timeout_reason"] = nil
else
@comparison = @pull.comparison
if specified_tab == "files"
env["pull_request.timeout_reason"] = "compute_diff"
start_oid, end_oid = @pull.merge_base, @pull.head_sha
if start_oid && end_oid
begin
start_commit, end_commit = @pull.compare_repository.commits.find([start_oid, end_oid])
@pull_comparison = PullRequest::Comparison.new(pull: @pull, start_commit: start_commit, end_commit: end_commit, base_commit: start_commit)
load_diff
rescue GitRPC::ObjectMissing
end
end
env["pull_request.timeout_reason"] = nil
end
end
prefetch_deferred do
if specified_tab == "files" && @pull_comparison && logged_in?
@pull_comparison.mark_as_seen(user: current_user)
mark_thread_as_read @pull.issue
elsif specified_tab == "discussion"
mark_thread_as_read @pull.issue
end
end
if prefetch_viewed?
return head(:no_content)
end
@labels = current_repository.sorted_labels
prepare_for_rendering(timeline: specified_tab == "discussion", pull_comparison: @pull_comparison)
unless "discussion" == specified_tab
override_analytics_location "/<user-name>/<repo-name>/pull_requests/show/#{specified_tab}"
end
if show_mobile_view? && (request_category != "raw")
return render_template_view("mobile/pull_requests/show", Mobile::PullRequests::ShowPageView, {
pull: @pull,
diffs: @pull_comparison.try(:diffs),
tab: specified_tab,
visible_timeline_items: visible_timeline_items,
showing_full_timeline: showing_full_timeline?
}, layout: "mobile/application")
end
respond_to do |format|
format.html do
if params[:shdds]
@syntax_highlighted_diffs_forced = true
render_to_string
head :no_content
else
if logged_in?
@current_review = @pull.latest_pending_review_for(current_user)
end
if specified_tab == "files"
if @pull_comparison.nil?
render "pull_requests/files_unavailable"
else
render "pull_requests/files"
end
elsif specified_tab == "commits"
render "pull_requests/commits"
else
render "pull_requests/conversation"
end
end
end
end
end
# Build enough of a pull request object to render the requested reviewer
# sidebar template on the pull request create page.
#
# Params: reviewer_ids - An array of User IDs.
#
# Returns an unsaved PullRequest.
def build_pull
pull =
if params[:range].present? && suggested_reviewers_enabled?
comparison = GitHub::Comparison.from_range(current_repository, params[:range])
if comparison.valid? && comparison.viewable_by?(current_user)
comparison.build_pull_request(user: current_user)
end
else
current_repository.pull_requests.build(user: current_user)
end
pull.issue = current_repository.issues.build
if current_repository.pushable_by?(current_user)
if reviewer_ids = params[:reviewer_ids]
reviewer_ids.select(&:present?).each do |id|
pull.review_requests.build(reviewer_id: id)
end
end
end
pull
end
TIMELINE_PARTIALS = %w[
pull_requests/timeline
pull_requests/timeline_marker
].freeze
VALID_REASONS = %w[
view-more-button
expose-fragment
].freeze
def show_partial
partial = params[:partial]
return head :not_found unless valid_partial?(partial)
GitHub.stats.time("pullrequest.show_partial_find") do
@pull =
if params[:id]
PullRequest.find_by_number_and_repo(params[:id].to_i, current_repository)
else
build_pull
end
end
return head :not_found unless @pull
if partial == "pull_requests/timeline"
if focused_timeline? && visible_timeline_items.empty?
return head :bad_request
end
reason = VALID_REASONS.include?(params[:reason]) ? params[:reason] : "other"
GitHub.stats.increment("pullrequest.show_partial.timeline.#{reason}.count")
end
if partial == "issues/sidebar/new/reviewers"
if suggested_reviewers_enabled?
suggested_reviewers = @pull.suggested_reviewers
end
else
prepare_for_rendering(timeline: TIMELINE_PARTIALS.include?(partial), pull_comparison: nil)
end
GitHub.stats.time("pullrequest.show_partial_render") do
respond_to do |format|
format.html do
render partial: partial, object: @pull, layout: false, locals: {
pull: @pull,
merge_type: params[:merge_type],
suggested_reviewers: suggested_reviewers
}
end
end
end
end
def short_method
"this just returns this string"
end
def show_partial_commit
partial = params[:partial]
return head :not_found unless valid_partial?(partial)
unless pull = PullRequest.find_by_number_and_repo(params[:id].to_i, current_repository)
return head :not_found
end
unless commit = current_commit
return head :not_found
end
Commit.prefill_combined_statuses([current_commit], current_repository)
GitHub.stats.time("pullrequest.show_partial_render") do
respond_to do |format|
format.html do
render partial: partial, object: commit, layout: false, locals: { pull: pull, commit: commit }
end
end
end
end
def show_partial_comparison
partial = params[:partial]
return head :not_found unless valid_partial?(partial)
unless pull = PullRequest.find_by_number_and_repo(params[:id].to_i, current_repository)
return head :not_found
end
unless pull_comparison = PullRequest::Comparison.find(pull: pull, start_commit_oid: params[:start_commit_oid], end_commit_oid: params[:end_commit_oid], base_commit_oid: params[:base_commit_oid])
return head :not_found
end
GitHub.stats.time("pullrequest.show_partial_render") do
respond_to do |format|
format.html do
render partial: partial, locals: { pull: pull, pull_comparison: pull_comparison }
end
end
end
end
def show_toc
pull = PullRequest.find_by_number_and_repo(params[:id], current_repository)
return head :not_found unless pull
return head :not_found unless valid_sha_param?(:sha1) &&
valid_sha_param?(:sha2) && params[:sha2].present? &&
valid_sha_param?(:base_sha)
diff_options = { base_sha: params[:base_sha] }
diff = GitHub::Diff.new(pull.compare_repository, params[:sha1], params[:sha2], diff_options)
respond_to do |format|
format.html do
render partial: "pull_requests/diffbar/toc_menu_items",
layout: false,
locals: {
summary_delta_views: diff.summary.deltas.map { |d| Diff::SummaryDeltaView.new(d) },
diff: diff
}
end
end
end
def cleanup
stats_key = ["pullrequest",
"delete_button",
@pull.cross_repo? ? "cross_repo" : "same_repo"].join(".")
# The branch was deleted by someone else before this user
# clicked the button. Send us to the bottom.
if !@pull.head_ref_exist?
raise Git::Ref::NotFound
end
result = @pull.cleanup_head_ref(current_user,
request_reflog_data("pull request branch delete button"))
GitHub.stats.increment("#{stats_key}.#{result ? "success" : "error"}")
if request.xhr?
render_head_ref_update
else
if result
flash[:notice] = "Branch deleted successfully."
else
flash[:error] = "Oops, something went wrong."
end
redirect_to pull_request_path(@pull)
end
# We can get here both from above where the branch has already
# been deleted before the button is clicked, or a race condition
# where the application code thinks the branch exists but by
# the time we execute the git command, someone else has already deleted it.
rescue Git::Ref::NotFound
GitHub.stats.increment("#{stats_key}.already_deleted")
render_head_ref_update
end
def undo_cleanup
stats_key = ["pullrequest",
"delete_button_undo",
@pull.cross_repo? ? "cross_repo" : "same_repo"].join(".")
if @pull.restore_head_ref(current_user,
request_reflog_data("pull request branch undo button"))
GitHub.stats.increment("#{stats_key}.success")
else
GitHub.stats.increment("#{stats_key}.error")
end
render_head_ref_update
end
def merge
@pull = current_repository.issues.find_by_number(params[:id].to_i).try(:pull_request)
return render_404 unless @pull
if params[:squash_commits] == "1"
merge_method = "squash"
elsif params[:do].present?
merge_method = params[:do]
else
if current_repository.merge_commit_allowed?
merge_method = "merge"
else
merge_method = "squash"
end
end
merge_method_allowed = case merge_method
when "merge"
current_repository.merge_commit_allowed?
when "squash"
current_repository.squash_merge_allowed?
when "rebase"
current_repository.rebase_merge_allowed?
else
false
end
if !merge_method_allowed
result, message = nil, "The selected merge method (#{merge_method}) is not allowed."
elsif @pull.git_merges_cleanly? and @pull.base_repository.pushable_by?(current_user)
GitHub.stats.increment("pullrequest.merge_button.click")
begin
result, message = @pull.merge(current_user,
message_title: params[:commit_title],
message: params[:commit_message],
reflog_data: request_reflog_data("pull request merge button"),
expected_head: params[:head_sha],
method: merge_method.to_sym)
rescue Git::Ref::HookFailed => e
@hook_out = e.message
result, message = nil, "Pre-receive hooks failed. See below for details."
end
if merge_method == "squash"
@pull.base_repository.set_sticky_merge_method(current_user, "squash")
elsif merge_method == "merge"
@pull.base_repository.set_sticky_merge_method(current_user, "merge_commit")
end
else
result, message = nil, "We couldnt merge this pull request. Reload the page before trying again."
end
if request.xhr?
if result
GitHub.dogstats.histogram("pull_request.merged.requested_reviewers.count", @pull.requested_reviewers.size)
respond_to do |format|
format.json do
prepare_for_rendering(timeline: true, pull_comparison: nil)
render_immediate_partials @pull, :timeline_marker, :sidebar, :merging, :form_actions
end
end
else
GitHub.stats.increment("pullrequest.merge_button.error")
respond_to do |format|
format.html do
render status: :unprocessable_entity, partial: "pull_requests/merging_error", locals: {
title: "Merge attempt failed",
message: (message || "We couldnt merge this pull request."),
hook_output: @hook_out
}
end
end
# render :plain => message, :status => :unprocessable_entity
end
else
if result
GitHub.dogstats.histogram("pull_request.merged.requested_reviewers.count", @pull.requested_reviewers.size)
redirect_to pull_request_path(@pull) + "#merged-event"
else
flash[:error] = message
GitHub.stats.increment("pullrequest.merge_button.error")
redirect_to pull_request_path(@pull)
end
end
end
def change_base
@pull = find_pull_request
return render_404 if !@pull || @pull.merged? ||
!@pull.issue.can_modify?(current_user) ||
!params[:new_base].present?
new_base = URI.decode(params[:new_base])
begin
@pull.change_base_branch(current_user, new_base)
flash[:notice] = "Updated base branch to #{new_base}."
rescue PullRequest::BadComparison, PullRequest::AlreadyExists => e
flash[:error] = e.ui_message
end
redirect_to pull_request_path(@pull)
end
def update_branch
@pull = find_pull_request
return render_404 unless @pull
update_method = params[:update_method] == "rebase" ? :rebase : :merge
begin
@pull.merge_base_into_head(
user: current_user,
method: update_method,
expected_head_oid: params[:expected_head_oid]
)
current_repository.set_sticky_update_method(current_user, update_method)
if request.xhr?
respond_to do |format|
format.json do
prepare_for_rendering(timeline: true, pull_comparison: nil)
render_immediate_partials(@pull, :timeline_marker, :merging)
end
end
else
redirect_to pull_request_path(@pull) + "#partial-pull-merging"
end
rescue GitHub::UIError => e
if request.xhr?
respond_to do |format|
format.html do
render status: :unprocessable_entity, partial: "pull_requests/merging_error", locals: {
title: "Update branch attempt failed",
message: e.ui_message
}
end
end
else
flash[:error] = e.ui_message
redirect_to pull_request_path(@pull) + "#partial-pull-merging"
end
end
end
def revert
@pull = find_pull_request
return render_404 unless @pull && @pull.revertable_by?(current_user)
stats_key = ["pullrequest",
"revert_button",
@pull.cross_repo? ? "cross_repo" : "same_repo"].join(".")
begin
revert_branch, error = @pull.revert(current_user, request_reflog_data("pull request revert button"))
if revert_branch
GitHub.stats.increment("#{stats_key}.success")
base_label, head_label =
if revert_branch.repository == @pull.base_repository
[@pull.base_ref_name, revert_branch.name]
else
["#{@pull.base_label(username_qualified: true)}", "#{revert_branch.repository.owner.login}:#{revert_branch.name}"]
end
flash[:pull_request] = {
title: revert_branch.target.message,
body: "Reverts #{@pull.base_repository.name_with_owner}##{@pull.number}"
}
redirect_to(compare_path(@pull.base_repository, "#{base_label}...#{head_label}", true))
else
if error == :merge_conflict
GitHub.stats.increment("#{stats_key}.merge_conflict")
else
GitHub.stats.increment("#{stats_key}.error")
end
flash[:error] = "Sorry, this pull request couldnt be reverted automatically. It may have \
already been reverted, or the content may have changed since it was merged."
redirect_to pull_request_path(@pull)
end
rescue Git::Ref::HookFailed => e
flash[:hook_out] = e.message
flash[:hook_message] = "Pull request could not be reverted."
redirect_to pull_request_path(@pull)
end
end
def merge_button
pull = current_repository.issues.find_by_number(params[:id].to_i).try(:pull_request)
return render_404 if pull.nil?
merge_state = pull.cached_merge_state(viewer: current_user)
respond_to do |format|
format.html do
if merge_state.unknown?
head :accepted
elsif alt_merge_box_ui_enabled?
render partial: "pull_requests/alt_ui/merge_button", locals: { pull: pull }
else
render partial: "pull_requests/merge_button", locals: { pull: pull }
end
end
format.json do
render json: {mergeable_state: merge_state.status}.to_json
end
end
end
def diff
# This might be a request for a redirect to the PR for a branch name ending in .diff,
# or might be a request for a numbered PR in .diff format.
diff_ref = "#{params[:id]}.diff"
if current_repository.heads.include?(diff_ref)
return redirect_or_404(diff_ref)
end
redirect_or_error = ensure_valid_pull_request
if performed?
return redirect_or_error
else
redirect_to build_pull_request_diff_url
end
end
def patch
# This might be a request for a redirect to the PR for a branch name ending in .patch,
# or might be a request for a numbered PR in .patch format.
patch_ref = "#{params[:id]}.patch"
if current_repository.heads.include?(patch_ref)
return redirect_or_404(patch_ref)
end
redirect_or_error = ensure_valid_pull_request
if performed?
return redirect_or_error
else
redirect_to build_pull_request_patch_url
end
end
def comment
return if reject_bully?
@pull = current_repository.issues.find_by_number(params[:id].to_i).try(:pull_request)
return render_404 unless @pull
issue = @pull.issue
valid = true
comment_body = params[:comment][:body]
if !comment_body.nil? && !can_skip_creating_comment?
comment = issue.comments.build(body: comment_body)
comment.user = current_user
comment.repository = current_repository
valid &= comment.save
elsif params[:comment_and_close] == "1"
comment = issue.comment_and_close(current_user, comment_body)
valid &= comment if comment_body.present?
GitHub.dogstats.histogram("pull_request.closed.requested_reviewers.count", @pull.requested_reviewers.size)
elsif params[:comment_and_open] == "1"
comment = issue.comment_and_open(current_user, comment_body)
valid &= comment if comment_body.present?
end
mark_thread_as_read issue
if valid && comment_body.present?
GitHub.instrument "comment.create", user: current_user
instrument_saved_reply_use(params[:saved_reply_id], "pull_request_comment")
end
respond_to do |format|
format.json do
if valid
prepare_for_rendering(timeline: true, pull_comparison: nil)
render_immediate_partials @pull, :timeline_marker, :sidebar, :merging, :form_actions, :title
else
errors = comment.errors.map { |attr, msg| msg }
render json: { errors: errors }, status: :unprocessable_entity
end
end
format.html do
if valid
anchor = comment ? "#issuecomment-#{comment.id}" : ""
redirect_to pull_request_path(@pull) + anchor
else
flash[:error] = comment.errors.full_messages.to_sentence
redirect_to :back
end
end
end
end
def dismiss_protip
current_user.dismiss_notice("continuous_integration_tip")
head :ok
end
if Rails.env.development?
def merge_button_matrix
if mobile?
return render(template: "pull_requests/merge_button_matrix_mobile.html", layout: "mobile/application")
end
end
end
def set_collab
pull = current_repository.issues.find_by_number(params[:id].to_i).try(:pull_request)
return render_404 if !pull || !pull.head_repository.repository.pushable_by?(current_user)
if !!params[:collab_privs]
pull.fork_collab = :allowed
else
pull.fork_collab = :denied
end
pull.save!
redirect_to pull_request_path(pull)
end
def resolve_conflicts
redirect_or_error = ensure_valid_pull_request
unless logged_in? && @pull.head_repository && @pull.head_repository.pushable_by?(current_user, ref: @pull.head_ref_name)
return render_404
end
return redirect_to pull_request_path(@pull) unless @pull.conflict_resolvable?
if performed?
return redirect_or_error
end
render "pull_requests/resolve_conflicts", locals: { pull: @pull }
end
def valid?
end
protected
helper_method :tab_specified?
def tab_specified?(tab_name)
specified_tab.to_s == tab_name.to_s
end
def id?
end
helper_method :pull_request_subscribe_enabled?
def pull_request_subscribe_enabled?
Rails.development? || Rails.test? || preview_features?
end
def specified_tab
@specified_tab || params[:tab].presence || "discussion"
end
helper_method :specified_tab
def valid_tab?
params[:tab].blank? || %w{commits files tasks}.include?(params[:tab])
end
def val
end
def reject_bully_for?(repo)
if blocked_by_owner?(repo.owner_id)
flash[:error] = "You can't perform that action at this time."
redirect_to repo.permalink
true
end
end
def reject_bully?
reject_bully_for? current_repository
end
def working_predicate?
end
def tree_name
if @pull && @pull.open?
@pull.head_ref_name
elsif @pull
@pull.head_sha
else
super
end
end
private
def find_pull_request
PullRequest.find_by_number_and_repo(params[:id].to_i, current_repository,
include: [{issue: { comments: :user }}])
end
# Private: For a given ref name, redirect to the appropriate pull request
# path if one exists, or 404 otherwise.
#
# Returns the redirect or render_404 result.
def redirect_or_404(ref)
# redirect to number version if ref is a branch name,
# redirect to new if ref is a branch with no pull request
# 404 otherwise
if pull = current_repository.pull_requests.for_branch(ref).last
redirect_to pull_request_path(pull)
elsif ref =~ /[:.]/ || current_repository.heads.include?(ref)
redirect_to new_pull_request_path(range: ref)
else
render_404
end
end
# Private: Validate the requested pull request ID. If necessary redirect to
# a more appropriate URL or return a 404 if the PR isn't/shouldn't be
# available.
def ensure_valid_pull_request
if params[:id] =~ /\D/
return redirect_or_404(params[:id])
end
@pull = find_pull_request
return redirect_to(issue_path(id: params[:id])) unless @pull
return redirect_to(pull_request_path @pull) unless valid_tab?
return render_404 if @pull.hide_from_user?(current_user)
@prose_url_hints = { tab: "files" }
@pull.set_diff_options(
use_summary: true,
ignore_whitespace: ignore_whitespace?
)
end
# Validates the provided parameter is a valid sha, or nil
def valid_sha_param?(param_name)
param = params[param_name]
param.nil? || param =~ /[0-9a-f]{40}/
end
def load_diff
@pull_comparison.ignore_whitespace = ignore_whitespace?
@pull_comparison.diff_options[:use_summary] = true
# load diff data
if !show_mobile_view?
@pull_comparison.diff_options[:top_only] = true
GitHub.dogstats.time("diff.load.initial", tags: dogstats_request_tags) do
@pull_comparison.diffs.apply_auto_load_single_entry_limits!
@pull_comparison.diffs.load_diff(timeout: request_time_left / 2)
end
else
@pull_comparison.diffs.load_diff(timeout: request_time_left / 2)
end
end
# Preload data needed for rendering the PR.
#
# timeline - Boolean specifying whether the PR's timeline will be rendered.
# pull_comparison - PullRequest::Comparison specifying whether the PR's diff will be rendered.
#
# Returns nothing.
def prepare_for_rendering(timeline:, pull_comparison:)
prepare_for_rendering_timeline_items(timeline: timeline, pull_comparison: pull_comparison)
prepare_for_rendering_diffs(timeline: timeline, pull_comparison: pull_comparison)
end
# Preload data needed for rendering timeline items.
#
# timeline - Boolean specifying whether the PR's timeline will be rendered.
# pull_comparison - PullRequest::Comparison specifying whether the PR's diff
# (which contains timeline items corresponding to live review threads) will be rendered.
#
# Returns nothing.
def prepare_for_rendering_timeline_items(timeline:, pull_comparison:)
timeline_items = visible_timeline_items
if pull_comparison
# Add in the review threads that are shown on the Files changed tab.
# Note that some of these might be also be shown on the Discussion tab,
# but the AR objects will actually be distinct so it's still worth
# prefilling/warming both objects.
timeline_items = timeline_items.dup
threads = pull_comparison.review_threads(viewer: current_user)
timeline_items.concat(threads.to_a)
end
@pull.prefill_timeline_associations(timeline_items, preload_diff_entries: timeline)
@pull.warm_timeline_caches(timeline_items)
preload_discussion_group_data(timeline_items)
end
# Preload data needed for rendering diffs.
#
# timeline - Boolean specifying whether the PR's timeline (which contains
# diffs for review threads) will be rendered.
# pull_comparison - PullRequest::Comparison specifying whether the PR's diff will be rendered.
#
# Returns nothing.
def prepare_for_rendering_diffs(timeline:, pull_comparison:)
return unless syntax_highlighted_diffs_enabled?
diffs_to_highlight = []
if timeline
visible_timeline_items.each do |item|
next unless item.is_a?(PullRequestReviewThread)
next unless item.diff_entry
diffs_to_highlight << item.diff_entry
end
end
if pull_comparison
diffs_to_highlight.concat(pull_comparison.diffs.to_a)
end
SyntaxHighlightedDiff.new(@pull.comparison.compare_repository, current_user).highlight!(diffs_to_highlight)
end
def can_skip_creating_comment?
params[:comment_and_close].present? ||
params[:comment_and_open].present?
end
# If it's empty, you can't issue a pull request. There will be no
# base SHA to merge against.
def check_for_empty_repository
if current_repository.empty?
redirect_to current_repository
end
end
def ensure_pull_head_pushable
@pull = current_repository.issues.find_by_number(params[:id].to_i).try(:pull_request)
if @pull.nil? || !@pull.head_repository.pushable_by?(current_user)
render_404
end
end
# Reload the pull so the deletable/restorable status is current,
# then render updates for the event list and the merge/delete buttons.
def render_head_ref_update
@pull.reload
respond_to do |format|
format.json do
prepare_for_rendering(timeline: true, pull_comparison: nil)
render_immediate_partials(@pull, :timeline_marker, :merging, :form_actions)
end
end
end
# Internal: Provide a better failure message than simply taking the validation errors
#
# e - the ActiveRecord::RecordInvalid exception from the creation failure
#
# Returns a String
def human_failure_message(e)
pr = e.record
# e.record can be an Issue, raised in PullRequest.create_for.
return e.message unless pr.is_a?(PullRequest)
if bad_branches = pr.missing_refs
if bad_branches.length == 1
"The #{bad_branches.first} branch doesnt exist."
else
"The #{bad_branches.join(' and ')} branches dont exist."
end
elsif [:base_ref, :head_ref].any? { |attr| pr.errors[attr].include?(GitHub::Validations::Unicode3Validator::ERROR_MESSAGE) }
"Branch names cannot contain unicode characters above 0xffff."
else
e.message
end
end
# If there are this many timeline items or fewer, we'll render the timeline
# inline when the user is viewing the Files tab. If there are more than this
# many items, we'll leave the timeline out and load it as needed to try to
# avoid timing out.
MAXIMUM_TIMELINE_SIZE_FOR_INLINE_RENDER = 149
def render_discussion_page?
return @render_discussion_page if defined?(@render_discussion_page)
@render_discussion_page =
tab_specified?("discussion") || (!show_mobile_view? && @pull.timeline_children_for(current_user).count <= MAXIMUM_TIMELINE_SIZE_FOR_INLINE_RENDER)
end
helper_method :render_discussion_page?
def render_files_page?
return @render_files_page if defined?(@render_files_page)
@render_files_page = tab_specified?("files") || (!show_mobile_view? && !@pull.corrupt? && !@pull.large_diff?)
end
helper_method :render_files_page?
def pull_request_authorization_token
current_user.signed_auth_token expires: 60.seconds.from_now,
scope: pull_request_authorization_token_scope_key
end
def build_pull_request_diff_url
route_options ||= {}
if GitHub.prs_content_domain?
route_options[:host] = GitHub.prs_content_host_name
end
if current_repository.private?
route_options[:token] = pull_request_authorization_token
end
pull_request_raw_diff_url(route_options)
end
def build_pull_request_patch_url
route_options ||= {}
if GitHub.prs_content_domain?
route_options[:host] = GitHub.prs_content_host_name
end
if current_repository.private?
route_options[:token] = pull_request_authorization_token
end
pull_request_raw_patch_url(route_options)
end
def request_reflog_data(via)
super(via).merge({ pr_author_login: @pull.safe_user.login })
end
def content_authorization_required
authorize_content(:pull_request, repo: current_repository)
end
# Internal: Extract OID components from PR range.
#
# /github/github/pull/123/files/abc123..def456
# /github/github/pull/123/files/def456
#
# pull - Current PullRequest
# range - String range parameter
#
# If a complete range is given, a pair of resolved String OIDs will be
# returned. If only one end sha is given, nil and a resolved String OID
# will be returned. Otherwise nil is returned if no range was matched.
def parse_show_range_oid_components(pull, range)
if m = range.to_s.match(/\A(?<sha1>[a-fA-F0-9]{7,40})\.\.(?<sha2>[a-fA-F0-9]{7,40}|HEAD)\z/)
sha2 = m[:sha2] == "HEAD" ? pull.head_sha : m[:sha2]
result = pull.repository.rpc.expand_shas([m[:sha1], sha2], "commit")
sha1, sha2 = result[m[:sha1]], result[sha2]
[sha1, sha2] if sha1 && sha2
elsif m = range.to_s.match(/^(?<sha2>[a-fA-F0-9]{7,40})$/)
result = pull.repository.rpc.expand_shas([m[:sha2]], "commit")
sha2 = result[m[:sha2]]
return [nil, sha2] if sha2
end
end
def visible_timeline_items
return [] unless render_discussion_page?
super
end
def showing_full_timeline?
return false unless render_discussion_page?
super
end
def timeline_owner
@pull
end
MobileCommitsQuery = parse_query <<-'GRAPHQL'
query($ids: [ID!]!) {
nodes(ids: $ids) {
... on Commit {
id
committer { date }
...Views::Mobile::Commits::GroupedCommit::Commit
}
}
}
GRAPHQL
# XXX: Adhoc GraphQL loader for PullRequest.changedChanges connection.
#
# Load required GraphQL data for each Commit in PullRequest#changed_commits and
# wrap it in a fake connection to be compatible with mobile/commits/list template.
def load_mobile_pull_request_commits
data = platform_execute(MobileCommitsQuery, variables: { "ids" => @pull.changed_commits.map(&:global_relay_id) })
history = { "edges" => data.nodes.compact.map { |commit| { "node" => commit.to_h } }, "pageInfo" => {} }
end
helper_method :load_mobile_pull_request_commits
end