code.gitea.io/gitea@v1.21.7/routers/api/v1/repo/pull.go (about)

     1  // Copyright 2016 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package repo
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"math"
    10  	"net/http"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"code.gitea.io/gitea/models"
    16  	activities_model "code.gitea.io/gitea/models/activities"
    17  	git_model "code.gitea.io/gitea/models/git"
    18  	issues_model "code.gitea.io/gitea/models/issues"
    19  	access_model "code.gitea.io/gitea/models/perm/access"
    20  	pull_model "code.gitea.io/gitea/models/pull"
    21  	repo_model "code.gitea.io/gitea/models/repo"
    22  	"code.gitea.io/gitea/models/unit"
    23  	user_model "code.gitea.io/gitea/models/user"
    24  	"code.gitea.io/gitea/modules/context"
    25  	"code.gitea.io/gitea/modules/git"
    26  	"code.gitea.io/gitea/modules/log"
    27  	"code.gitea.io/gitea/modules/setting"
    28  	api "code.gitea.io/gitea/modules/structs"
    29  	"code.gitea.io/gitea/modules/timeutil"
    30  	"code.gitea.io/gitea/modules/web"
    31  	"code.gitea.io/gitea/routers/api/v1/utils"
    32  	asymkey_service "code.gitea.io/gitea/services/asymkey"
    33  	"code.gitea.io/gitea/services/automerge"
    34  	"code.gitea.io/gitea/services/convert"
    35  	"code.gitea.io/gitea/services/forms"
    36  	"code.gitea.io/gitea/services/gitdiff"
    37  	issue_service "code.gitea.io/gitea/services/issue"
    38  	notify_service "code.gitea.io/gitea/services/notify"
    39  	pull_service "code.gitea.io/gitea/services/pull"
    40  	repo_service "code.gitea.io/gitea/services/repository"
    41  )
    42  
    43  // ListPullRequests returns a list of all PRs
    44  func ListPullRequests(ctx *context.APIContext) {
    45  	// swagger:operation GET /repos/{owner}/{repo}/pulls repository repoListPullRequests
    46  	// ---
    47  	// summary: List a repo's pull requests
    48  	// produces:
    49  	// - application/json
    50  	// parameters:
    51  	// - name: owner
    52  	//   in: path
    53  	//   description: owner of the repo
    54  	//   type: string
    55  	//   required: true
    56  	// - name: repo
    57  	//   in: path
    58  	//   description: name of the repo
    59  	//   type: string
    60  	//   required: true
    61  	// - name: state
    62  	//   in: query
    63  	//   description: "State of pull request: open or closed (optional)"
    64  	//   type: string
    65  	//   enum: [closed, open, all]
    66  	// - name: sort
    67  	//   in: query
    68  	//   description: "Type of sort"
    69  	//   type: string
    70  	//   enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority]
    71  	// - name: milestone
    72  	//   in: query
    73  	//   description: "ID of the milestone"
    74  	//   type: integer
    75  	//   format: int64
    76  	// - name: labels
    77  	//   in: query
    78  	//   description: "Label IDs"
    79  	//   type: array
    80  	//   collectionFormat: multi
    81  	//   items:
    82  	//     type: integer
    83  	//     format: int64
    84  	// - name: page
    85  	//   in: query
    86  	//   description: page number of results to return (1-based)
    87  	//   type: integer
    88  	// - name: limit
    89  	//   in: query
    90  	//   description: page size of results
    91  	//   type: integer
    92  	// responses:
    93  	//   "200":
    94  	//     "$ref": "#/responses/PullRequestList"
    95  	//   "404":
    96  	//     "$ref": "#/responses/notFound"
    97  
    98  	listOptions := utils.GetListOptions(ctx)
    99  
   100  	prs, maxResults, err := issues_model.PullRequests(ctx, ctx.Repo.Repository.ID, &issues_model.PullRequestsOptions{
   101  		ListOptions: listOptions,
   102  		State:       ctx.FormTrim("state"),
   103  		SortType:    ctx.FormTrim("sort"),
   104  		Labels:      ctx.FormStrings("labels"),
   105  		MilestoneID: ctx.FormInt64("milestone"),
   106  	})
   107  	if err != nil {
   108  		ctx.Error(http.StatusInternalServerError, "PullRequests", err)
   109  		return
   110  	}
   111  
   112  	apiPrs := make([]*api.PullRequest, len(prs))
   113  	for i := range prs {
   114  		if err = prs[i].LoadIssue(ctx); err != nil {
   115  			ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
   116  			return
   117  		}
   118  		if err = prs[i].LoadAttributes(ctx); err != nil {
   119  			ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
   120  			return
   121  		}
   122  		if err = prs[i].LoadBaseRepo(ctx); err != nil {
   123  			ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
   124  			return
   125  		}
   126  		if err = prs[i].LoadHeadRepo(ctx); err != nil {
   127  			ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
   128  			return
   129  		}
   130  		apiPrs[i] = convert.ToAPIPullRequest(ctx, prs[i], ctx.Doer)
   131  	}
   132  
   133  	ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
   134  	ctx.SetTotalCountHeader(maxResults)
   135  	ctx.JSON(http.StatusOK, &apiPrs)
   136  }
   137  
   138  // GetPullRequest returns a single PR based on index
   139  func GetPullRequest(ctx *context.APIContext) {
   140  	// swagger:operation GET /repos/{owner}/{repo}/pulls/{index} repository repoGetPullRequest
   141  	// ---
   142  	// summary: Get a pull request
   143  	// produces:
   144  	// - application/json
   145  	// parameters:
   146  	// - name: owner
   147  	//   in: path
   148  	//   description: owner of the repo
   149  	//   type: string
   150  	//   required: true
   151  	// - name: repo
   152  	//   in: path
   153  	//   description: name of the repo
   154  	//   type: string
   155  	//   required: true
   156  	// - name: index
   157  	//   in: path
   158  	//   description: index of the pull request to get
   159  	//   type: integer
   160  	//   format: int64
   161  	//   required: true
   162  	// responses:
   163  	//   "200":
   164  	//     "$ref": "#/responses/PullRequest"
   165  	//   "404":
   166  	//     "$ref": "#/responses/notFound"
   167  
   168  	pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
   169  	if err != nil {
   170  		if issues_model.IsErrPullRequestNotExist(err) {
   171  			ctx.NotFound()
   172  		} else {
   173  			ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
   174  		}
   175  		return
   176  	}
   177  
   178  	if err = pr.LoadBaseRepo(ctx); err != nil {
   179  		ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
   180  		return
   181  	}
   182  	if err = pr.LoadHeadRepo(ctx); err != nil {
   183  		ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
   184  		return
   185  	}
   186  	ctx.JSON(http.StatusOK, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
   187  }
   188  
   189  // DownloadPullDiffOrPatch render a pull's raw diff or patch
   190  func DownloadPullDiffOrPatch(ctx *context.APIContext) {
   191  	// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}.{diffType} repository repoDownloadPullDiffOrPatch
   192  	// ---
   193  	// summary: Get a pull request diff or patch
   194  	// produces:
   195  	// - text/plain
   196  	// parameters:
   197  	// - name: owner
   198  	//   in: path
   199  	//   description: owner of the repo
   200  	//   type: string
   201  	//   required: true
   202  	// - name: repo
   203  	//   in: path
   204  	//   description: name of the repo
   205  	//   type: string
   206  	//   required: true
   207  	// - name: index
   208  	//   in: path
   209  	//   description: index of the pull request to get
   210  	//   type: integer
   211  	//   format: int64
   212  	//   required: true
   213  	// - name: diffType
   214  	//   in: path
   215  	//   description: whether the output is diff or patch
   216  	//   type: string
   217  	//   enum: [diff, patch]
   218  	//   required: true
   219  	// - name: binary
   220  	//   in: query
   221  	//   description: whether to include binary file changes. if true, the diff is applicable with `git apply`
   222  	//   type: boolean
   223  	// responses:
   224  	//   "200":
   225  	//     "$ref": "#/responses/string"
   226  	//   "404":
   227  	//     "$ref": "#/responses/notFound"
   228  	pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
   229  	if err != nil {
   230  		if issues_model.IsErrPullRequestNotExist(err) {
   231  			ctx.NotFound()
   232  		} else {
   233  			ctx.InternalServerError(err)
   234  		}
   235  		return
   236  	}
   237  	var patch bool
   238  	if ctx.Params(":diffType") == "diff" {
   239  		patch = false
   240  	} else {
   241  		patch = true
   242  	}
   243  
   244  	binary := ctx.FormBool("binary")
   245  
   246  	if err := pull_service.DownloadDiffOrPatch(ctx, pr, ctx, patch, binary); err != nil {
   247  		ctx.InternalServerError(err)
   248  		return
   249  	}
   250  }
   251  
   252  // CreatePullRequest does what it says
   253  func CreatePullRequest(ctx *context.APIContext) {
   254  	// swagger:operation POST /repos/{owner}/{repo}/pulls repository repoCreatePullRequest
   255  	// ---
   256  	// summary: Create a pull request
   257  	// consumes:
   258  	// - application/json
   259  	// produces:
   260  	// - application/json
   261  	// parameters:
   262  	// - name: owner
   263  	//   in: path
   264  	//   description: owner of the repo
   265  	//   type: string
   266  	//   required: true
   267  	// - name: repo
   268  	//   in: path
   269  	//   description: name of the repo
   270  	//   type: string
   271  	//   required: true
   272  	// - name: body
   273  	//   in: body
   274  	//   schema:
   275  	//     "$ref": "#/definitions/CreatePullRequestOption"
   276  	// responses:
   277  	//   "201":
   278  	//     "$ref": "#/responses/PullRequest"
   279  	//   "404":
   280  	//     "$ref": "#/responses/notFound"
   281  	//   "409":
   282  	//     "$ref": "#/responses/error"
   283  	//   "422":
   284  	//     "$ref": "#/responses/validationError"
   285  
   286  	form := *web.GetForm(ctx).(*api.CreatePullRequestOption)
   287  	if form.Head == form.Base {
   288  		ctx.Error(http.StatusUnprocessableEntity, "BaseHeadSame",
   289  			"Invalid PullRequest: There are no changes between the head and the base")
   290  		return
   291  	}
   292  
   293  	var (
   294  		repo        = ctx.Repo.Repository
   295  		labelIDs    []int64
   296  		milestoneID int64
   297  	)
   298  
   299  	// Get repo/branch information
   300  	_, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
   301  	if ctx.Written() {
   302  		return
   303  	}
   304  	defer headGitRepo.Close()
   305  
   306  	// Check if another PR exists with the same targets
   307  	existingPr, err := issues_model.GetUnmergedPullRequest(ctx, headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch, issues_model.PullRequestFlowGithub)
   308  	if err != nil {
   309  		if !issues_model.IsErrPullRequestNotExist(err) {
   310  			ctx.Error(http.StatusInternalServerError, "GetUnmergedPullRequest", err)
   311  			return
   312  		}
   313  	} else {
   314  		err = issues_model.ErrPullRequestAlreadyExists{
   315  			ID:         existingPr.ID,
   316  			IssueID:    existingPr.Index,
   317  			HeadRepoID: existingPr.HeadRepoID,
   318  			BaseRepoID: existingPr.BaseRepoID,
   319  			HeadBranch: existingPr.HeadBranch,
   320  			BaseBranch: existingPr.BaseBranch,
   321  		}
   322  		ctx.Error(http.StatusConflict, "GetUnmergedPullRequest", err)
   323  		return
   324  	}
   325  
   326  	if len(form.Labels) > 0 {
   327  		labels, err := issues_model.GetLabelsInRepoByIDs(ctx, ctx.Repo.Repository.ID, form.Labels)
   328  		if err != nil {
   329  			ctx.Error(http.StatusInternalServerError, "GetLabelsInRepoByIDs", err)
   330  			return
   331  		}
   332  
   333  		labelIDs = make([]int64, 0, len(labels))
   334  		for _, label := range labels {
   335  			labelIDs = append(labelIDs, label.ID)
   336  		}
   337  
   338  		if ctx.Repo.Owner.IsOrganization() {
   339  			orgLabels, err := issues_model.GetLabelsInOrgByIDs(ctx, ctx.Repo.Owner.ID, form.Labels)
   340  			if err != nil {
   341  				ctx.Error(http.StatusInternalServerError, "GetLabelsInOrgByIDs", err)
   342  				return
   343  			}
   344  
   345  			orgLabelIDs := make([]int64, 0, len(orgLabels))
   346  			for _, orgLabel := range orgLabels {
   347  				orgLabelIDs = append(orgLabelIDs, orgLabel.ID)
   348  			}
   349  			labelIDs = append(labelIDs, orgLabelIDs...)
   350  		}
   351  	}
   352  
   353  	if form.Milestone > 0 {
   354  		milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, form.Milestone)
   355  		if err != nil {
   356  			if issues_model.IsErrMilestoneNotExist(err) {
   357  				ctx.NotFound()
   358  			} else {
   359  				ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
   360  			}
   361  			return
   362  		}
   363  
   364  		milestoneID = milestone.ID
   365  	}
   366  
   367  	var deadlineUnix timeutil.TimeStamp
   368  	if form.Deadline != nil {
   369  		deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
   370  	}
   371  
   372  	prIssue := &issues_model.Issue{
   373  		RepoID:       repo.ID,
   374  		Title:        form.Title,
   375  		PosterID:     ctx.Doer.ID,
   376  		Poster:       ctx.Doer,
   377  		MilestoneID:  milestoneID,
   378  		IsPull:       true,
   379  		Content:      form.Body,
   380  		DeadlineUnix: deadlineUnix,
   381  	}
   382  	pr := &issues_model.PullRequest{
   383  		HeadRepoID: headRepo.ID,
   384  		BaseRepoID: repo.ID,
   385  		HeadBranch: headBranch,
   386  		BaseBranch: baseBranch,
   387  		HeadRepo:   headRepo,
   388  		BaseRepo:   repo,
   389  		MergeBase:  compareInfo.MergeBase,
   390  		Type:       issues_model.PullRequestGitea,
   391  	}
   392  
   393  	// Get all assignee IDs
   394  	assigneeIDs, err := issues_model.MakeIDsFromAPIAssigneesToAdd(ctx, form.Assignee, form.Assignees)
   395  	if err != nil {
   396  		if user_model.IsErrUserNotExist(err) {
   397  			ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
   398  		} else {
   399  			ctx.Error(http.StatusInternalServerError, "AddAssigneeByName", err)
   400  		}
   401  		return
   402  	}
   403  	// Check if the passed assignees is assignable
   404  	for _, aID := range assigneeIDs {
   405  		assignee, err := user_model.GetUserByID(ctx, aID)
   406  		if err != nil {
   407  			ctx.Error(http.StatusInternalServerError, "GetUserByID", err)
   408  			return
   409  		}
   410  
   411  		valid, err := access_model.CanBeAssigned(ctx, assignee, repo, true)
   412  		if err != nil {
   413  			ctx.Error(http.StatusInternalServerError, "canBeAssigned", err)
   414  			return
   415  		}
   416  		if !valid {
   417  			ctx.Error(http.StatusUnprocessableEntity, "canBeAssigned", repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name})
   418  			return
   419  		}
   420  	}
   421  
   422  	if err := pull_service.NewPullRequest(ctx, repo, prIssue, labelIDs, []string{}, pr, assigneeIDs); err != nil {
   423  		if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) {
   424  			ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err)
   425  			return
   426  		}
   427  		ctx.Error(http.StatusInternalServerError, "NewPullRequest", err)
   428  		return
   429  	}
   430  
   431  	log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
   432  	ctx.JSON(http.StatusCreated, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
   433  }
   434  
   435  // EditPullRequest does what it says
   436  func EditPullRequest(ctx *context.APIContext) {
   437  	// swagger:operation PATCH /repos/{owner}/{repo}/pulls/{index} repository repoEditPullRequest
   438  	// ---
   439  	// summary: Update a pull request. If using deadline only the date will be taken into account, and time of day ignored.
   440  	// consumes:
   441  	// - application/json
   442  	// produces:
   443  	// - application/json
   444  	// parameters:
   445  	// - name: owner
   446  	//   in: path
   447  	//   description: owner of the repo
   448  	//   type: string
   449  	//   required: true
   450  	// - name: repo
   451  	//   in: path
   452  	//   description: name of the repo
   453  	//   type: string
   454  	//   required: true
   455  	// - name: index
   456  	//   in: path
   457  	//   description: index of the pull request to edit
   458  	//   type: integer
   459  	//   format: int64
   460  	//   required: true
   461  	// - name: body
   462  	//   in: body
   463  	//   schema:
   464  	//     "$ref": "#/definitions/EditPullRequestOption"
   465  	// responses:
   466  	//   "201":
   467  	//     "$ref": "#/responses/PullRequest"
   468  	//   "403":
   469  	//     "$ref": "#/responses/forbidden"
   470  	//   "404":
   471  	//     "$ref": "#/responses/notFound"
   472  	//   "409":
   473  	//     "$ref": "#/responses/error"
   474  	//   "412":
   475  	//     "$ref": "#/responses/error"
   476  	//   "422":
   477  	//     "$ref": "#/responses/validationError"
   478  
   479  	form := web.GetForm(ctx).(*api.EditPullRequestOption)
   480  	pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
   481  	if err != nil {
   482  		if issues_model.IsErrPullRequestNotExist(err) {
   483  			ctx.NotFound()
   484  		} else {
   485  			ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
   486  		}
   487  		return
   488  	}
   489  
   490  	err = pr.LoadIssue(ctx)
   491  	if err != nil {
   492  		ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
   493  		return
   494  	}
   495  	issue := pr.Issue
   496  	issue.Repo = ctx.Repo.Repository
   497  
   498  	if err := issue.LoadAttributes(ctx); err != nil {
   499  		ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
   500  		return
   501  	}
   502  
   503  	if !issue.IsPoster(ctx.Doer.ID) && !ctx.Repo.CanWrite(unit.TypePullRequests) {
   504  		ctx.Status(http.StatusForbidden)
   505  		return
   506  	}
   507  
   508  	oldTitle := issue.Title
   509  	if len(form.Title) > 0 {
   510  		issue.Title = form.Title
   511  	}
   512  	if len(form.Body) > 0 {
   513  		issue.Content = form.Body
   514  	}
   515  
   516  	// Update or remove deadline if set
   517  	if form.Deadline != nil || form.RemoveDeadline != nil {
   518  		var deadlineUnix timeutil.TimeStamp
   519  		if (form.RemoveDeadline == nil || !*form.RemoveDeadline) && !form.Deadline.IsZero() {
   520  			deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
   521  				23, 59, 59, 0, form.Deadline.Location())
   522  			deadlineUnix = timeutil.TimeStamp(deadline.Unix())
   523  		}
   524  
   525  		if err := issues_model.UpdateIssueDeadline(ctx, issue, deadlineUnix, ctx.Doer); err != nil {
   526  			ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err)
   527  			return
   528  		}
   529  		issue.DeadlineUnix = deadlineUnix
   530  	}
   531  
   532  	// Add/delete assignees
   533  
   534  	// Deleting is done the GitHub way (quote from their api documentation):
   535  	// https://developer.github.com/v3/issues/#edit-an-issue
   536  	// "assignees" (array): Logins for Users to assign to this issue.
   537  	// Pass one or more user logins to replace the set of assignees on this Issue.
   538  	// Send an empty array ([]) to clear all assignees from the Issue.
   539  
   540  	if ctx.Repo.CanWrite(unit.TypePullRequests) && (form.Assignees != nil || len(form.Assignee) > 0) {
   541  		err = issue_service.UpdateAssignees(ctx, issue, form.Assignee, form.Assignees, ctx.Doer)
   542  		if err != nil {
   543  			if user_model.IsErrUserNotExist(err) {
   544  				ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
   545  			} else {
   546  				ctx.Error(http.StatusInternalServerError, "UpdateAssignees", err)
   547  			}
   548  			return
   549  		}
   550  	}
   551  
   552  	if ctx.Repo.CanWrite(unit.TypePullRequests) && form.Milestone != 0 &&
   553  		issue.MilestoneID != form.Milestone {
   554  		oldMilestoneID := issue.MilestoneID
   555  		issue.MilestoneID = form.Milestone
   556  		if err = issue_service.ChangeMilestoneAssign(issue, ctx.Doer, oldMilestoneID); err != nil {
   557  			ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err)
   558  			return
   559  		}
   560  	}
   561  
   562  	if ctx.Repo.CanWrite(unit.TypePullRequests) && form.Labels != nil {
   563  		labels, err := issues_model.GetLabelsInRepoByIDs(ctx, ctx.Repo.Repository.ID, form.Labels)
   564  		if err != nil {
   565  			ctx.Error(http.StatusInternalServerError, "GetLabelsInRepoByIDsError", err)
   566  			return
   567  		}
   568  
   569  		if ctx.Repo.Owner.IsOrganization() {
   570  			orgLabels, err := issues_model.GetLabelsInOrgByIDs(ctx, ctx.Repo.Owner.ID, form.Labels)
   571  			if err != nil {
   572  				ctx.Error(http.StatusInternalServerError, "GetLabelsInOrgByIDs", err)
   573  				return
   574  			}
   575  
   576  			labels = append(labels, orgLabels...)
   577  		}
   578  
   579  		if err = issues_model.ReplaceIssueLabels(issue, labels, ctx.Doer); err != nil {
   580  			ctx.Error(http.StatusInternalServerError, "ReplaceLabelsError", err)
   581  			return
   582  		}
   583  	}
   584  
   585  	if form.State != nil {
   586  		if pr.HasMerged {
   587  			ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged")
   588  			return
   589  		}
   590  		issue.IsClosed = api.StateClosed == api.StateType(*form.State)
   591  	}
   592  	statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer)
   593  	if err != nil {
   594  		if issues_model.IsErrDependenciesLeft(err) {
   595  			ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
   596  			return
   597  		}
   598  		ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err)
   599  		return
   600  	}
   601  
   602  	if titleChanged {
   603  		notify_service.IssueChangeTitle(ctx, ctx.Doer, issue, oldTitle)
   604  	}
   605  
   606  	if statusChangeComment != nil {
   607  		notify_service.IssueChangeStatus(ctx, ctx.Doer, "", issue, statusChangeComment, issue.IsClosed)
   608  	}
   609  
   610  	// change pull target branch
   611  	if !pr.HasMerged && len(form.Base) != 0 && form.Base != pr.BaseBranch {
   612  		if !ctx.Repo.GitRepo.IsBranchExist(form.Base) {
   613  			ctx.Error(http.StatusNotFound, "NewBaseBranchNotExist", fmt.Errorf("new base '%s' not exist", form.Base))
   614  			return
   615  		}
   616  		if err := pull_service.ChangeTargetBranch(ctx, pr, ctx.Doer, form.Base); err != nil {
   617  			if issues_model.IsErrPullRequestAlreadyExists(err) {
   618  				ctx.Error(http.StatusConflict, "IsErrPullRequestAlreadyExists", err)
   619  				return
   620  			} else if issues_model.IsErrIssueIsClosed(err) {
   621  				ctx.Error(http.StatusUnprocessableEntity, "IsErrIssueIsClosed", err)
   622  				return
   623  			} else if models.IsErrPullRequestHasMerged(err) {
   624  				ctx.Error(http.StatusConflict, "IsErrPullRequestHasMerged", err)
   625  				return
   626  			} else {
   627  				ctx.InternalServerError(err)
   628  			}
   629  			return
   630  		}
   631  		notify_service.PullRequestChangeTargetBranch(ctx, ctx.Doer, pr, form.Base)
   632  	}
   633  
   634  	// update allow edits
   635  	if form.AllowMaintainerEdit != nil {
   636  		if err := pull_service.SetAllowEdits(ctx, ctx.Doer, pr, *form.AllowMaintainerEdit); err != nil {
   637  			if errors.Is(pull_service.ErrUserHasNoPermissionForAction, err) {
   638  				ctx.Error(http.StatusForbidden, "SetAllowEdits", fmt.Sprintf("SetAllowEdits: %s", err))
   639  				return
   640  			}
   641  			ctx.ServerError("SetAllowEdits", err)
   642  			return
   643  		}
   644  	}
   645  
   646  	// Refetch from database
   647  	pr, err = issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, pr.Index)
   648  	if err != nil {
   649  		if issues_model.IsErrPullRequestNotExist(err) {
   650  			ctx.NotFound()
   651  		} else {
   652  			ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
   653  		}
   654  		return
   655  	}
   656  
   657  	// TODO this should be 200, not 201
   658  	ctx.JSON(http.StatusCreated, convert.ToAPIPullRequest(ctx, pr, ctx.Doer))
   659  }
   660  
   661  // IsPullRequestMerged checks if a PR exists given an index
   662  func IsPullRequestMerged(ctx *context.APIContext) {
   663  	// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/merge repository repoPullRequestIsMerged
   664  	// ---
   665  	// summary: Check if a pull request has been merged
   666  	// produces:
   667  	// - application/json
   668  	// parameters:
   669  	// - name: owner
   670  	//   in: path
   671  	//   description: owner of the repo
   672  	//   type: string
   673  	//   required: true
   674  	// - name: repo
   675  	//   in: path
   676  	//   description: name of the repo
   677  	//   type: string
   678  	//   required: true
   679  	// - name: index
   680  	//   in: path
   681  	//   description: index of the pull request
   682  	//   type: integer
   683  	//   format: int64
   684  	//   required: true
   685  	// responses:
   686  	//   "204":
   687  	//     description: pull request has been merged
   688  	//   "404":
   689  	//     description: pull request has not been merged
   690  
   691  	pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
   692  	if err != nil {
   693  		if issues_model.IsErrPullRequestNotExist(err) {
   694  			ctx.NotFound()
   695  		} else {
   696  			ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
   697  		}
   698  		return
   699  	}
   700  
   701  	if pr.HasMerged {
   702  		ctx.Status(http.StatusNoContent)
   703  	}
   704  	ctx.NotFound()
   705  }
   706  
   707  // MergePullRequest merges a PR given an index
   708  func MergePullRequest(ctx *context.APIContext) {
   709  	// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/merge repository repoMergePullRequest
   710  	// ---
   711  	// summary: Merge a pull request
   712  	// produces:
   713  	// - application/json
   714  	// parameters:
   715  	// - name: owner
   716  	//   in: path
   717  	//   description: owner of the repo
   718  	//   type: string
   719  	//   required: true
   720  	// - name: repo
   721  	//   in: path
   722  	//   description: name of the repo
   723  	//   type: string
   724  	//   required: true
   725  	// - name: index
   726  	//   in: path
   727  	//   description: index of the pull request to merge
   728  	//   type: integer
   729  	//   format: int64
   730  	//   required: true
   731  	// - name: body
   732  	//   in: body
   733  	//   schema:
   734  	//     $ref: "#/definitions/MergePullRequestOption"
   735  	// responses:
   736  	//   "200":
   737  	//     "$ref": "#/responses/empty"
   738  	//   "404":
   739  	//     "$ref": "#/responses/notFound"
   740  	//   "405":
   741  	//     "$ref": "#/responses/empty"
   742  	//   "409":
   743  	//     "$ref": "#/responses/error"
   744  
   745  	form := web.GetForm(ctx).(*forms.MergePullRequestForm)
   746  
   747  	pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
   748  	if err != nil {
   749  		if issues_model.IsErrPullRequestNotExist(err) {
   750  			ctx.NotFound("GetPullRequestByIndex", err)
   751  		} else {
   752  			ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
   753  		}
   754  		return
   755  	}
   756  
   757  	if err := pr.LoadHeadRepo(ctx); err != nil {
   758  		ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
   759  		return
   760  	}
   761  
   762  	if err := pr.LoadIssue(ctx); err != nil {
   763  		ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
   764  		return
   765  	}
   766  	pr.Issue.Repo = ctx.Repo.Repository
   767  
   768  	if ctx.IsSigned {
   769  		// Update issue-user.
   770  		if err = activities_model.SetIssueReadBy(ctx, pr.Issue.ID, ctx.Doer.ID); err != nil {
   771  			ctx.Error(http.StatusInternalServerError, "ReadBy", err)
   772  			return
   773  		}
   774  	}
   775  
   776  	manuallyMerged := repo_model.MergeStyle(form.Do) == repo_model.MergeStyleManuallyMerged
   777  
   778  	mergeCheckType := pull_service.MergeCheckTypeGeneral
   779  	if form.MergeWhenChecksSucceed {
   780  		mergeCheckType = pull_service.MergeCheckTypeAuto
   781  	}
   782  	if manuallyMerged {
   783  		mergeCheckType = pull_service.MergeCheckTypeManually
   784  	}
   785  
   786  	// start with merging by checking
   787  	if err := pull_service.CheckPullMergable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, form.ForceMerge); err != nil {
   788  		if errors.Is(err, pull_service.ErrIsClosed) {
   789  			ctx.NotFound()
   790  		} else if errors.Is(err, pull_service.ErrUserNotAllowedToMerge) {
   791  			ctx.Error(http.StatusMethodNotAllowed, "Merge", "User not allowed to merge PR")
   792  		} else if errors.Is(err, pull_service.ErrHasMerged) {
   793  			ctx.Error(http.StatusMethodNotAllowed, "PR already merged", "")
   794  		} else if errors.Is(err, pull_service.ErrIsWorkInProgress) {
   795  			ctx.Error(http.StatusMethodNotAllowed, "PR is a work in progress", "Work in progress PRs cannot be merged")
   796  		} else if errors.Is(err, pull_service.ErrNotMergableState) {
   797  			ctx.Error(http.StatusMethodNotAllowed, "PR not in mergeable state", "Please try again later")
   798  		} else if models.IsErrDisallowedToMerge(err) {
   799  			ctx.Error(http.StatusMethodNotAllowed, "PR is not ready to be merged", err)
   800  		} else if asymkey_service.IsErrWontSign(err) {
   801  			ctx.Error(http.StatusMethodNotAllowed, fmt.Sprintf("Protected branch %s requires signed commits but this merge would not be signed", pr.BaseBranch), err)
   802  		} else {
   803  			ctx.InternalServerError(err)
   804  		}
   805  		return
   806  	}
   807  
   808  	// handle manually-merged mark
   809  	if manuallyMerged {
   810  		if err := pull_service.MergedManually(pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
   811  			if models.IsErrInvalidMergeStyle(err) {
   812  				ctx.Error(http.StatusMethodNotAllowed, "Invalid merge style", fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do)))
   813  				return
   814  			}
   815  			if strings.Contains(err.Error(), "Wrong commit ID") {
   816  				ctx.JSON(http.StatusConflict, err)
   817  				return
   818  			}
   819  			ctx.Error(http.StatusInternalServerError, "Manually-Merged", err)
   820  			return
   821  		}
   822  		ctx.Status(http.StatusOK)
   823  		return
   824  	}
   825  
   826  	if len(form.Do) == 0 {
   827  		form.Do = string(repo_model.MergeStyleMerge)
   828  	}
   829  
   830  	message := strings.TrimSpace(form.MergeTitleField)
   831  	if len(message) == 0 {
   832  		message, _, err = pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pr, repo_model.MergeStyle(form.Do))
   833  		if err != nil {
   834  			ctx.Error(http.StatusInternalServerError, "GetDefaultMergeMessage", err)
   835  			return
   836  		}
   837  	}
   838  
   839  	form.MergeMessageField = strings.TrimSpace(form.MergeMessageField)
   840  	if len(form.MergeMessageField) > 0 {
   841  		message += "\n\n" + form.MergeMessageField
   842  	}
   843  
   844  	if form.MergeWhenChecksSucceed {
   845  		scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message)
   846  		if err != nil {
   847  			if pull_model.IsErrAlreadyScheduledToAutoMerge(err) {
   848  				ctx.Error(http.StatusConflict, "ScheduleAutoMerge", err)
   849  				return
   850  			}
   851  			ctx.Error(http.StatusInternalServerError, "ScheduleAutoMerge", err)
   852  			return
   853  		} else if scheduled {
   854  			// nothing more to do ...
   855  			ctx.Status(http.StatusCreated)
   856  			return
   857  		}
   858  	}
   859  
   860  	if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil {
   861  		if models.IsErrInvalidMergeStyle(err) {
   862  			ctx.Error(http.StatusMethodNotAllowed, "Invalid merge style", fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do)))
   863  		} else if models.IsErrMergeConflicts(err) {
   864  			conflictError := err.(models.ErrMergeConflicts)
   865  			ctx.JSON(http.StatusConflict, conflictError)
   866  		} else if models.IsErrRebaseConflicts(err) {
   867  			conflictError := err.(models.ErrRebaseConflicts)
   868  			ctx.JSON(http.StatusConflict, conflictError)
   869  		} else if models.IsErrMergeUnrelatedHistories(err) {
   870  			conflictError := err.(models.ErrMergeUnrelatedHistories)
   871  			ctx.JSON(http.StatusConflict, conflictError)
   872  		} else if git.IsErrPushOutOfDate(err) {
   873  			ctx.Error(http.StatusConflict, "Merge", "merge push out of date")
   874  		} else if models.IsErrSHADoesNotMatch(err) {
   875  			ctx.Error(http.StatusConflict, "Merge", "head out of date")
   876  		} else if git.IsErrPushRejected(err) {
   877  			errPushRej := err.(*git.ErrPushRejected)
   878  			if len(errPushRej.Message) == 0 {
   879  				ctx.Error(http.StatusConflict, "Merge", "PushRejected without remote error message")
   880  			} else {
   881  				ctx.Error(http.StatusConflict, "Merge", "PushRejected with remote message: "+errPushRej.Message)
   882  			}
   883  		} else {
   884  			ctx.Error(http.StatusInternalServerError, "Merge", err)
   885  		}
   886  		return
   887  	}
   888  	log.Trace("Pull request merged: %d", pr.ID)
   889  
   890  	if form.DeleteBranchAfterMerge {
   891  		// Don't cleanup when there are other PR's that use this branch as head branch.
   892  		exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch)
   893  		if err != nil {
   894  			ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err)
   895  			return
   896  		}
   897  		if exist {
   898  			ctx.Status(http.StatusOK)
   899  			return
   900  		}
   901  
   902  		var headRepo *git.Repository
   903  		if ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.HeadRepoID && ctx.Repo.GitRepo != nil {
   904  			headRepo = ctx.Repo.GitRepo
   905  		} else {
   906  			headRepo, err = git.OpenRepository(ctx, pr.HeadRepo.RepoPath())
   907  			if err != nil {
   908  				ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.RepoPath()), err)
   909  				return
   910  			}
   911  			defer headRepo.Close()
   912  		}
   913  		if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, headRepo, pr.HeadBranch); err != nil {
   914  			switch {
   915  			case git.IsErrBranchNotExist(err):
   916  				ctx.NotFound(err)
   917  			case errors.Is(err, repo_service.ErrBranchIsDefault):
   918  				ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
   919  			case errors.Is(err, git_model.ErrBranchIsProtected):
   920  				ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
   921  			default:
   922  				ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
   923  			}
   924  			return
   925  		}
   926  		if err := issues_model.AddDeletePRBranchComment(ctx, ctx.Doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
   927  			// Do not fail here as branch has already been deleted
   928  			log.Error("DeleteBranch: %v", err)
   929  		}
   930  	}
   931  
   932  	ctx.Status(http.StatusOK)
   933  }
   934  
   935  func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*user_model.User, *repo_model.Repository, *git.Repository, *git.CompareInfo, string, string) {
   936  	baseRepo := ctx.Repo.Repository
   937  
   938  	// Get compared branches information
   939  	// format: <base branch>...[<head repo>:]<head branch>
   940  	// base<-head: master...head:feature
   941  	// same repo: master...feature
   942  
   943  	// TODO: Validate form first?
   944  
   945  	baseBranch := form.Base
   946  
   947  	var (
   948  		headUser   *user_model.User
   949  		headBranch string
   950  		isSameRepo bool
   951  		err        error
   952  	)
   953  
   954  	// If there is no head repository, it means pull request between same repository.
   955  	headInfos := strings.Split(form.Head, ":")
   956  	if len(headInfos) == 1 {
   957  		isSameRepo = true
   958  		headUser = ctx.Repo.Owner
   959  		headBranch = headInfos[0]
   960  
   961  	} else if len(headInfos) == 2 {
   962  		headUser, err = user_model.GetUserByName(ctx, headInfos[0])
   963  		if err != nil {
   964  			if user_model.IsErrUserNotExist(err) {
   965  				ctx.NotFound("GetUserByName")
   966  			} else {
   967  				ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
   968  			}
   969  			return nil, nil, nil, nil, "", ""
   970  		}
   971  		headBranch = headInfos[1]
   972  
   973  	} else {
   974  		ctx.NotFound()
   975  		return nil, nil, nil, nil, "", ""
   976  	}
   977  
   978  	ctx.Repo.PullRequest.SameRepo = isSameRepo
   979  	log.Info("Base branch: %s", baseBranch)
   980  	log.Info("Repo path: %s", ctx.Repo.GitRepo.Path)
   981  	// Check if base branch is valid.
   982  	if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) {
   983  		ctx.NotFound("IsBranchExist")
   984  		return nil, nil, nil, nil, "", ""
   985  	}
   986  
   987  	// Check if current user has fork of repository or in the same repository.
   988  	headRepo := repo_model.GetForkedRepo(ctx, headUser.ID, baseRepo.ID)
   989  	if headRepo == nil && !isSameRepo {
   990  		log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
   991  		ctx.NotFound("GetForkedRepo")
   992  		return nil, nil, nil, nil, "", ""
   993  	}
   994  
   995  	var headGitRepo *git.Repository
   996  	if isSameRepo {
   997  		headRepo = ctx.Repo.Repository
   998  		headGitRepo = ctx.Repo.GitRepo
   999  	} else {
  1000  		headGitRepo, err = git.OpenRepository(ctx, repo_model.RepoPath(headUser.Name, headRepo.Name))
  1001  		if err != nil {
  1002  			ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
  1003  			return nil, nil, nil, nil, "", ""
  1004  		}
  1005  	}
  1006  
  1007  	// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
  1008  	permBase, err := access_model.GetUserRepoPermission(ctx, baseRepo, ctx.Doer)
  1009  	if err != nil {
  1010  		headGitRepo.Close()
  1011  		ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
  1012  		return nil, nil, nil, nil, "", ""
  1013  	}
  1014  	if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(unit.TypeCode) {
  1015  		if log.IsTrace() {
  1016  			log.Trace("Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v",
  1017  				ctx.Doer,
  1018  				baseRepo,
  1019  				permBase)
  1020  		}
  1021  		headGitRepo.Close()
  1022  		ctx.NotFound("Can't read pulls or can't read UnitTypeCode")
  1023  		return nil, nil, nil, nil, "", ""
  1024  	}
  1025  
  1026  	// user should have permission to read headrepo's codes
  1027  	permHead, err := access_model.GetUserRepoPermission(ctx, headRepo, ctx.Doer)
  1028  	if err != nil {
  1029  		headGitRepo.Close()
  1030  		ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
  1031  		return nil, nil, nil, nil, "", ""
  1032  	}
  1033  	if !permHead.CanRead(unit.TypeCode) {
  1034  		if log.IsTrace() {
  1035  			log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v",
  1036  				ctx.Doer,
  1037  				headRepo,
  1038  				permHead)
  1039  		}
  1040  		headGitRepo.Close()
  1041  		ctx.NotFound("Can't read headRepo UnitTypeCode")
  1042  		return nil, nil, nil, nil, "", ""
  1043  	}
  1044  
  1045  	// Check if head branch is valid.
  1046  	if !headGitRepo.IsBranchExist(headBranch) {
  1047  		headGitRepo.Close()
  1048  		ctx.NotFound()
  1049  		return nil, nil, nil, nil, "", ""
  1050  	}
  1051  
  1052  	compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch, false, false)
  1053  	if err != nil {
  1054  		headGitRepo.Close()
  1055  		ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err)
  1056  		return nil, nil, nil, nil, "", ""
  1057  	}
  1058  
  1059  	return headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch
  1060  }
  1061  
  1062  // UpdatePullRequest merge PR's baseBranch into headBranch
  1063  func UpdatePullRequest(ctx *context.APIContext) {
  1064  	// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/update repository repoUpdatePullRequest
  1065  	// ---
  1066  	// summary: Merge PR's baseBranch into headBranch
  1067  	// produces:
  1068  	// - application/json
  1069  	// parameters:
  1070  	// - name: owner
  1071  	//   in: path
  1072  	//   description: owner of the repo
  1073  	//   type: string
  1074  	//   required: true
  1075  	// - name: repo
  1076  	//   in: path
  1077  	//   description: name of the repo
  1078  	//   type: string
  1079  	//   required: true
  1080  	// - name: index
  1081  	//   in: path
  1082  	//   description: index of the pull request to get
  1083  	//   type: integer
  1084  	//   format: int64
  1085  	//   required: true
  1086  	// - name: style
  1087  	//   in: query
  1088  	//   description: how to update pull request
  1089  	//   type: string
  1090  	//   enum: [merge, rebase]
  1091  	// responses:
  1092  	//   "200":
  1093  	//     "$ref": "#/responses/empty"
  1094  	//   "403":
  1095  	//     "$ref": "#/responses/forbidden"
  1096  	//   "404":
  1097  	//     "$ref": "#/responses/notFound"
  1098  	//   "409":
  1099  	//     "$ref": "#/responses/error"
  1100  	//   "422":
  1101  	//     "$ref": "#/responses/validationError"
  1102  
  1103  	pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  1104  	if err != nil {
  1105  		if issues_model.IsErrPullRequestNotExist(err) {
  1106  			ctx.NotFound()
  1107  		} else {
  1108  			ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
  1109  		}
  1110  		return
  1111  	}
  1112  
  1113  	if pr.HasMerged {
  1114  		ctx.Error(http.StatusUnprocessableEntity, "UpdatePullRequest", err)
  1115  		return
  1116  	}
  1117  
  1118  	if err = pr.LoadIssue(ctx); err != nil {
  1119  		ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
  1120  		return
  1121  	}
  1122  
  1123  	if pr.Issue.IsClosed {
  1124  		ctx.Error(http.StatusUnprocessableEntity, "UpdatePullRequest", err)
  1125  		return
  1126  	}
  1127  
  1128  	if err = pr.LoadBaseRepo(ctx); err != nil {
  1129  		ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err)
  1130  		return
  1131  	}
  1132  	if err = pr.LoadHeadRepo(ctx); err != nil {
  1133  		ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
  1134  		return
  1135  	}
  1136  
  1137  	rebase := ctx.FormString("style") == "rebase"
  1138  
  1139  	allowedUpdateByMerge, allowedUpdateByRebase, err := pull_service.IsUserAllowedToUpdate(ctx, pr, ctx.Doer)
  1140  	if err != nil {
  1141  		ctx.Error(http.StatusInternalServerError, "IsUserAllowedToMerge", err)
  1142  		return
  1143  	}
  1144  
  1145  	if (!allowedUpdateByMerge && !rebase) || (rebase && !allowedUpdateByRebase) {
  1146  		ctx.Status(http.StatusForbidden)
  1147  		return
  1148  	}
  1149  
  1150  	// default merge commit message
  1151  	message := fmt.Sprintf("Merge branch '%s' into %s", pr.BaseBranch, pr.HeadBranch)
  1152  
  1153  	if err = pull_service.Update(ctx, pr, ctx.Doer, message, rebase); err != nil {
  1154  		if models.IsErrMergeConflicts(err) {
  1155  			ctx.Error(http.StatusConflict, "Update", "merge failed because of conflict")
  1156  			return
  1157  		} else if models.IsErrRebaseConflicts(err) {
  1158  			ctx.Error(http.StatusConflict, "Update", "rebase failed because of conflict")
  1159  			return
  1160  		}
  1161  		ctx.Error(http.StatusInternalServerError, "pull_service.Update", err)
  1162  		return
  1163  	}
  1164  
  1165  	ctx.Status(http.StatusOK)
  1166  }
  1167  
  1168  // MergePullRequest cancel an auto merge scheduled for a given PullRequest by index
  1169  func CancelScheduledAutoMerge(ctx *context.APIContext) {
  1170  	// swagger:operation DELETE /repos/{owner}/{repo}/pulls/{index}/merge repository repoCancelScheduledAutoMerge
  1171  	// ---
  1172  	// summary: Cancel the scheduled auto merge for the given pull request
  1173  	// produces:
  1174  	// - application/json
  1175  	// parameters:
  1176  	// - name: owner
  1177  	//   in: path
  1178  	//   description: owner of the repo
  1179  	//   type: string
  1180  	//   required: true
  1181  	// - name: repo
  1182  	//   in: path
  1183  	//   description: name of the repo
  1184  	//   type: string
  1185  	//   required: true
  1186  	// - name: index
  1187  	//   in: path
  1188  	//   description: index of the pull request to merge
  1189  	//   type: integer
  1190  	//   format: int64
  1191  	//   required: true
  1192  	// responses:
  1193  	//   "204":
  1194  	//     "$ref": "#/responses/empty"
  1195  	//   "403":
  1196  	//     "$ref": "#/responses/forbidden"
  1197  	//   "404":
  1198  	//     "$ref": "#/responses/notFound"
  1199  
  1200  	pullIndex := ctx.ParamsInt64(":index")
  1201  	pull, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, pullIndex)
  1202  	if err != nil {
  1203  		if issues_model.IsErrPullRequestNotExist(err) {
  1204  			ctx.NotFound()
  1205  			return
  1206  		}
  1207  		ctx.InternalServerError(err)
  1208  		return
  1209  	}
  1210  
  1211  	exist, autoMerge, err := pull_model.GetScheduledMergeByPullID(ctx, pull.ID)
  1212  	if err != nil {
  1213  		ctx.InternalServerError(err)
  1214  		return
  1215  	}
  1216  	if !exist {
  1217  		ctx.NotFound()
  1218  		return
  1219  	}
  1220  
  1221  	if ctx.Doer.ID != autoMerge.DoerID {
  1222  		allowed, err := access_model.IsUserRepoAdmin(ctx, ctx.Repo.Repository, ctx.Doer)
  1223  		if err != nil {
  1224  			ctx.InternalServerError(err)
  1225  			return
  1226  		}
  1227  		if !allowed {
  1228  			ctx.Error(http.StatusForbidden, "No permission to cancel", "user has no permission to cancel the scheduled auto merge")
  1229  			return
  1230  		}
  1231  	}
  1232  
  1233  	if err := automerge.RemoveScheduledAutoMerge(ctx, ctx.Doer, pull); err != nil {
  1234  		ctx.InternalServerError(err)
  1235  	} else {
  1236  		ctx.Status(http.StatusNoContent)
  1237  	}
  1238  }
  1239  
  1240  // GetPullRequestCommits gets all commits associated with a given PR
  1241  func GetPullRequestCommits(ctx *context.APIContext) {
  1242  	// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/commits repository repoGetPullRequestCommits
  1243  	// ---
  1244  	// summary: Get commits for a pull request
  1245  	// produces:
  1246  	// - application/json
  1247  	// parameters:
  1248  	// - name: owner
  1249  	//   in: path
  1250  	//   description: owner of the repo
  1251  	//   type: string
  1252  	//   required: true
  1253  	// - name: repo
  1254  	//   in: path
  1255  	//   description: name of the repo
  1256  	//   type: string
  1257  	//   required: true
  1258  	// - name: index
  1259  	//   in: path
  1260  	//   description: index of the pull request to get
  1261  	//   type: integer
  1262  	//   format: int64
  1263  	//   required: true
  1264  	// - name: page
  1265  	//   in: query
  1266  	//   description: page number of results to return (1-based)
  1267  	//   type: integer
  1268  	// - name: limit
  1269  	//   in: query
  1270  	//   description: page size of results
  1271  	//   type: integer
  1272  	// - name: verification
  1273  	//   in: query
  1274  	//   description: include verification for every commit (disable for speedup, default 'true')
  1275  	//   type: boolean
  1276  	// - name: files
  1277  	//   in: query
  1278  	//   description: include a list of affected files for every commit (disable for speedup, default 'true')
  1279  	//   type: boolean
  1280  	// responses:
  1281  	//   "200":
  1282  	//     "$ref": "#/responses/CommitList"
  1283  	//   "404":
  1284  	//     "$ref": "#/responses/notFound"
  1285  
  1286  	pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  1287  	if err != nil {
  1288  		if issues_model.IsErrPullRequestNotExist(err) {
  1289  			ctx.NotFound()
  1290  		} else {
  1291  			ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
  1292  		}
  1293  		return
  1294  	}
  1295  
  1296  	if err := pr.LoadBaseRepo(ctx); err != nil {
  1297  		ctx.InternalServerError(err)
  1298  		return
  1299  	}
  1300  
  1301  	var prInfo *git.CompareInfo
  1302  	baseGitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, pr.BaseRepo.RepoPath())
  1303  	if err != nil {
  1304  		ctx.ServerError("OpenRepository", err)
  1305  		return
  1306  	}
  1307  	defer closer.Close()
  1308  
  1309  	if pr.HasMerged {
  1310  		prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.MergeBase, pr.GetGitRefName(), false, false)
  1311  	} else {
  1312  		prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName(), false, false)
  1313  	}
  1314  	if err != nil {
  1315  		ctx.ServerError("GetCompareInfo", err)
  1316  		return
  1317  	}
  1318  	commits := prInfo.Commits
  1319  
  1320  	listOptions := utils.GetListOptions(ctx)
  1321  
  1322  	totalNumberOfCommits := len(commits)
  1323  	totalNumberOfPages := int(math.Ceil(float64(totalNumberOfCommits) / float64(listOptions.PageSize)))
  1324  
  1325  	userCache := make(map[string]*user_model.User)
  1326  
  1327  	start, end := listOptions.GetStartEnd()
  1328  
  1329  	if end > totalNumberOfCommits {
  1330  		end = totalNumberOfCommits
  1331  	}
  1332  
  1333  	verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
  1334  	files := ctx.FormString("files") == "" || ctx.FormBool("files")
  1335  
  1336  	apiCommits := make([]*api.Commit, 0, end-start)
  1337  	for i := start; i < end; i++ {
  1338  		apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, baseGitRepo, commits[i], userCache,
  1339  			convert.ToCommitOptions{
  1340  				Stat:         true,
  1341  				Verification: verification,
  1342  				Files:        files,
  1343  			})
  1344  		if err != nil {
  1345  			ctx.ServerError("toCommit", err)
  1346  			return
  1347  		}
  1348  		apiCommits = append(apiCommits, apiCommit)
  1349  	}
  1350  
  1351  	ctx.SetLinkHeader(totalNumberOfCommits, listOptions.PageSize)
  1352  	ctx.SetTotalCountHeader(int64(totalNumberOfCommits))
  1353  
  1354  	ctx.RespHeader().Set("X-Page", strconv.Itoa(listOptions.Page))
  1355  	ctx.RespHeader().Set("X-PerPage", strconv.Itoa(listOptions.PageSize))
  1356  	ctx.RespHeader().Set("X-PageCount", strconv.Itoa(totalNumberOfPages))
  1357  	ctx.RespHeader().Set("X-HasMore", strconv.FormatBool(listOptions.Page < totalNumberOfPages))
  1358  	ctx.AppendAccessControlExposeHeaders("X-Page", "X-PerPage", "X-PageCount", "X-HasMore")
  1359  
  1360  	ctx.JSON(http.StatusOK, &apiCommits)
  1361  }
  1362  
  1363  // GetPullRequestFiles gets all changed files associated with a given PR
  1364  func GetPullRequestFiles(ctx *context.APIContext) {
  1365  	// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/files repository repoGetPullRequestFiles
  1366  	// ---
  1367  	// summary: Get changed files for a pull request
  1368  	// produces:
  1369  	// - application/json
  1370  	// parameters:
  1371  	// - name: owner
  1372  	//   in: path
  1373  	//   description: owner of the repo
  1374  	//   type: string
  1375  	//   required: true
  1376  	// - name: repo
  1377  	//   in: path
  1378  	//   description: name of the repo
  1379  	//   type: string
  1380  	//   required: true
  1381  	// - name: index
  1382  	//   in: path
  1383  	//   description: index of the pull request to get
  1384  	//   type: integer
  1385  	//   format: int64
  1386  	//   required: true
  1387  	// - name: skip-to
  1388  	//   in: query
  1389  	//   description: skip to given file
  1390  	//   type: string
  1391  	// - name: whitespace
  1392  	//   in: query
  1393  	//   description: whitespace behavior
  1394  	//   type: string
  1395  	//   enum: [ignore-all, ignore-change, ignore-eol, show-all]
  1396  	// - name: page
  1397  	//   in: query
  1398  	//   description: page number of results to return (1-based)
  1399  	//   type: integer
  1400  	// - name: limit
  1401  	//   in: query
  1402  	//   description: page size of results
  1403  	//   type: integer
  1404  	// responses:
  1405  	//   "200":
  1406  	//     "$ref": "#/responses/ChangedFileList"
  1407  	//   "404":
  1408  	//     "$ref": "#/responses/notFound"
  1409  
  1410  	pr, err := issues_model.GetPullRequestByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
  1411  	if err != nil {
  1412  		if issues_model.IsErrPullRequestNotExist(err) {
  1413  			ctx.NotFound()
  1414  		} else {
  1415  			ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
  1416  		}
  1417  		return
  1418  	}
  1419  
  1420  	if err := pr.LoadBaseRepo(ctx); err != nil {
  1421  		ctx.InternalServerError(err)
  1422  		return
  1423  	}
  1424  
  1425  	if err := pr.LoadHeadRepo(ctx); err != nil {
  1426  		ctx.InternalServerError(err)
  1427  		return
  1428  	}
  1429  
  1430  	baseGitRepo := ctx.Repo.GitRepo
  1431  
  1432  	var prInfo *git.CompareInfo
  1433  	if pr.HasMerged {
  1434  		prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.MergeBase, pr.GetGitRefName(), true, false)
  1435  	} else {
  1436  		prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName(), true, false)
  1437  	}
  1438  	if err != nil {
  1439  		ctx.ServerError("GetCompareInfo", err)
  1440  		return
  1441  	}
  1442  
  1443  	headCommitID, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
  1444  	if err != nil {
  1445  		ctx.ServerError("GetRefCommitID", err)
  1446  		return
  1447  	}
  1448  
  1449  	startCommitID := prInfo.MergeBase
  1450  	endCommitID := headCommitID
  1451  
  1452  	maxLines := setting.Git.MaxGitDiffLines
  1453  
  1454  	// FIXME: If there are too many files in the repo, may cause some unpredictable issues.
  1455  	diff, err := gitdiff.GetDiff(baseGitRepo,
  1456  		&gitdiff.DiffOptions{
  1457  			BeforeCommitID:     startCommitID,
  1458  			AfterCommitID:      endCommitID,
  1459  			SkipTo:             ctx.FormString("skip-to"),
  1460  			MaxLines:           maxLines,
  1461  			MaxLineCharacters:  setting.Git.MaxGitDiffLineCharacters,
  1462  			MaxFiles:           -1, // GetDiff() will return all files
  1463  			WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.FormString("whitespace")),
  1464  		})
  1465  	if err != nil {
  1466  		ctx.ServerError("GetDiff", err)
  1467  		return
  1468  	}
  1469  
  1470  	listOptions := utils.GetListOptions(ctx)
  1471  
  1472  	totalNumberOfFiles := diff.NumFiles
  1473  	totalNumberOfPages := int(math.Ceil(float64(totalNumberOfFiles) / float64(listOptions.PageSize)))
  1474  
  1475  	start, end := listOptions.GetStartEnd()
  1476  
  1477  	if end > totalNumberOfFiles {
  1478  		end = totalNumberOfFiles
  1479  	}
  1480  
  1481  	lenFiles := end - start
  1482  	if lenFiles < 0 {
  1483  		lenFiles = 0
  1484  	}
  1485  
  1486  	apiFiles := make([]*api.ChangedFile, 0, lenFiles)
  1487  	for i := start; i < end; i++ {
  1488  		apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID))
  1489  	}
  1490  
  1491  	ctx.SetLinkHeader(totalNumberOfFiles, listOptions.PageSize)
  1492  	ctx.SetTotalCountHeader(int64(totalNumberOfFiles))
  1493  
  1494  	ctx.RespHeader().Set("X-Page", strconv.Itoa(listOptions.Page))
  1495  	ctx.RespHeader().Set("X-PerPage", strconv.Itoa(listOptions.PageSize))
  1496  	ctx.RespHeader().Set("X-PageCount", strconv.Itoa(totalNumberOfPages))
  1497  	ctx.RespHeader().Set("X-HasMore", strconv.FormatBool(listOptions.Page < totalNumberOfPages))
  1498  	ctx.AppendAccessControlExposeHeaders("X-Page", "X-PerPage", "X-PageCount", "X-HasMore")
  1499  
  1500  	ctx.JSON(http.StatusOK, &apiFiles)
  1501  }