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