| 1 | class PullRequestsController < ApplicationController |
| 2 | helper_method :render_markdown |
| 3 | |
| 4 | before_action :load_owner |
| 5 | before_action :load_repository |
| 6 | before_action :ensure_can_read! |
| 7 | before_action :require_sign_in!, only: %i[new create close reopen merge add_comment] |
| 8 | before_action :load_pull_request, only: %i[show close reopen merge add_comment] |
| 9 | |
| 10 | def index |
| 11 | @state = %w[open closed merged all].include?(params[:state]) ? params[:state] : "open" |
| 12 | scope = @repository.pull_requests |
| 13 | scope = scope.where(state: @state) unless @state == "all" |
| 14 | @pull_requests = scope.order(number: :desc).includes(:user) |
| 15 | |
| 16 | @open_count = @repository.pull_requests.open.count |
| 17 | @closed_count = @repository.pull_requests.closed.count |
| 18 | @merged_count = @repository.pull_requests.merged.count |
| 19 | end |
| 20 | |
| 21 | def new |
| 22 | @branches = GitRepositoryService.branches(@repository.disk_path) |
| 23 | @pull_request = @repository.pull_requests.new( |
| 24 | head_ref: params[:head], base_ref: params[:base].presence || @repository.default_branch |
| 25 | ) |
| 26 | end |
| 27 | |
| 28 | def create |
| 29 | pr_params = params.require(:pull_request).permit(:title, :body, :head_ref, :base_ref) |
| 30 | @pull_request = PullRequestService.create( |
| 31 | repository: @repository, author: current_user, |
| 32 | title: pr_params[:title], head: pr_params[:head_ref], base: pr_params[:base_ref], body: pr_params[:body] |
| 33 | ) |
| 34 | redirect_to repository_pull_request_path(@owner.username, @repository.name, @pull_request.number), |
| 35 | notice: "Pull request ##{@pull_request.number} opened." |
| 36 | rescue PullRequestService::NotAuthorized => e |
| 37 | redirect_to repository_path(@owner.username, @repository.name), alert: e.message |
| 38 | rescue PullRequestService::Error, ActiveRecord::RecordInvalid => e |
| 39 | @branches = GitRepositoryService.branches(@repository.disk_path) |
| 40 | @pull_request = @repository.pull_requests.new(pr_params) |
| 41 | @pull_request.errors.add(:base, |
| 42 | e.is_a?(ActiveRecord::RecordInvalid) ? e.record.errors.full_messages.to_sentence : e.message) |
| 43 | render :new, status: :unprocessable_entity |
| 44 | end |
| 45 | |
| 46 | def show |
| 47 | @comments = @pull_request.comments.includes(:user).order(:created_at) |
| 48 | @commits = GitRepositoryService.commits_between(@repository.disk_path, @pull_request.base_ref, @pull_request.head_ref) |
| 49 | @diff = @repository.diff_between(@pull_request.base_ref, @pull_request.head_ref) |
| 50 | @mergeable = @pull_request.open? && |
| 51 | GitRepositoryService.mergeable?(@repository.disk_path, @pull_request.base_ref, @pull_request.head_ref) |
| 52 | end |
| 53 | |
| 54 | def add_comment |
| 55 | CommentService.create( |
| 56 | repository: @repository, author: current_user, number: @pull_request.number, body: params[:body] |
| 57 | ) |
| 58 | redirect_to repository_pull_request_path(@owner.username, @repository.name, @pull_request.number) |
| 59 | rescue ActiveRecord::RecordInvalid => e |
| 60 | redirect_to repository_pull_request_path(@owner.username, @repository.name, @pull_request.number), |
| 61 | alert: e.record.errors.full_messages.to_sentence |
| 62 | end |
| 63 | |
| 64 | def close |
| 65 | PullRequestService.close(pull_request: @pull_request, actor: current_user) |
| 66 | redirect_to repository_pull_request_path(@owner.username, @repository.name, @pull_request.number), |
| 67 | notice: "Pull request closed." |
| 68 | rescue PullRequestService::Error => e |
| 69 | redirect_to repository_pull_request_path(@owner.username, @repository.name, @pull_request.number), alert: e.message |
| 70 | end |
| 71 | |
| 72 | def reopen |
| 73 | PullRequestService.reopen(pull_request: @pull_request, actor: current_user) |
| 74 | redirect_to repository_pull_request_path(@owner.username, @repository.name, @pull_request.number), |
| 75 | notice: "Pull request reopened." |
| 76 | rescue PullRequestService::Error => e |
| 77 | redirect_to repository_pull_request_path(@owner.username, @repository.name, @pull_request.number), alert: e.message |
| 78 | end |
| 79 | |
| 80 | def merge |
| 81 | PullRequestService.merge(pull_request: @pull_request, merger: current_user) |
| 82 | redirect_to repository_pull_request_path(@owner.username, @repository.name, @pull_request.number), |
| 83 | notice: "Pull request merged." |
| 84 | rescue PullRequestService::Error => e |
| 85 | redirect_to repository_pull_request_path(@owner.username, @repository.name, @pull_request.number), alert: e.message |
| 86 | end |
| 87 | |
| 88 | private |
| 89 | |
| 90 | # PR/comment bodies are free-standing prose, not tied to a file path, so |
| 91 | # they render without the repo/ref/dir context RepositoriesController's |
| 92 | # README rendering resolves relative links against. |
| 93 | def render_markdown(text) |
| 94 | MarkdownRenderer.render(text.to_s).html_safe |
| 95 | end |
| 96 | |
| 97 | def load_pull_request |
| 98 | @pull_request = @repository.pull_requests.find_by!(number: params[:number]) |
| 99 | rescue ActiveRecord::RecordNotFound |
| 100 | render file: Rails.public_path.join("404.html"), status: :not_found, layout: false |
| 101 | end |
| 102 | |
| 103 | def load_owner |
| 104 | @owner = User.find_by!(username: params[:username]) |
| 105 | rescue ActiveRecord::RecordNotFound |
| 106 | render file: Rails.public_path.join("404.html"), status: :not_found, layout: false |
| 107 | end |
| 108 | |
| 109 | def load_repository |
| 110 | @repository = @owner.repositories.find_by!(name: params[:repository]) |
| 111 | rescue ActiveRecord::RecordNotFound |
| 112 | render file: Rails.public_path.join("404.html"), status: :not_found, layout: false |
| 113 | end |
| 114 | |
| 115 | def ensure_can_read! |
| 116 | unless !@repository.is_private || (signed_in? && current_user == @owner) |
| 117 | render file: Rails.public_path.join("404.html"), status: :not_found, layout: false |
| 118 | end |
| 119 | end |
| 120 | end |