mirror of
https://github.com/github/semantic.git
synced 2024-12-22 22:31:36 +03:00
1117 lines
36 KiB
Ruby
1117 lines
36 KiB
Ruby
|
# 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 couldn’t 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 couldn’t 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 couldn’t 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 doesn’t exist."
|
|||
|
else
|
|||
|
"The #{bad_branches.join(' and ')} branches don’t 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
|