1
1
mirror of https://github.com/go-gitea/gitea synced 2024-12-26 02:24:27 +00:00
gitea/routers/api/v1/repo/pull.go

791 lines
22 KiB
Go
Raw Normal View History

2016-12-02 11:10:39 +00:00
// Copyright 2016 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
import (
"fmt"
"net/http"
2016-12-02 11:10:39 +00:00
"strings"
"time"
2016-12-02 11:10:39 +00:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
2016-12-02 11:10:39 +00:00
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
2016-12-02 11:10:39 +00:00
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
2016-12-02 11:10:39 +00:00
)
// ListPullRequests returns a list of all PRs
func ListPullRequests(ctx *context.APIContext, form api.ListPullRequestsOptions) {
// swagger:operation GET /repos/{owner}/{repo}/pulls repository repoListPullRequests
// ---
// summary: List a repo's pull requests
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: page
// in: query
// description: Page number
// type: integer
// - name: state
// in: query
// description: "State of pull request: open or closed (optional)"
// type: string
// enum: [closed, open, all]
// - name: sort
// in: query
// description: "Type of sort"
// type: string
// enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority]
// - name: milestone
// in: query
// description: "ID of the milestone"
// type: integer
// format: int64
// - name: labels
// in: query
// description: "Label IDs"
// type: array
// collectionFormat: multi
// items:
// type: integer
// format: int64
// responses:
// "200":
// "$ref": "#/responses/PullRequestList"
2016-12-02 11:10:39 +00:00
prs, maxResults, err := models.PullRequests(ctx.Repo.Repository.ID, &models.PullRequestsOptions{
Page: ctx.QueryInt("page"),
State: ctx.QueryTrim("state"),
SortType: ctx.QueryTrim("sort"),
Labels: ctx.QueryStrings("labels"),
MilestoneID: ctx.QueryInt64("milestone"),
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "PullRequests", err)
2016-12-02 11:10:39 +00:00
return
}
apiPrs := make([]*api.PullRequest, len(prs))
for i := range prs {
if err = prs[i].LoadIssue(); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
return
}
if err = prs[i].LoadAttributes(); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
return
}
if err = prs[i].GetBaseRepo(); err != nil {
ctx.Error(http.StatusInternalServerError, "GetBaseRepo", err)
return
}
if err = prs[i].GetHeadRepo(); err != nil {
ctx.Error(http.StatusInternalServerError, "GetHeadRepo", err)
return
}
2016-12-02 11:10:39 +00:00
apiPrs[i] = prs[i].APIFormat()
}
ctx.SetLinkHeader(int(maxResults), models.ItemsPerPage)
ctx.JSON(http.StatusOK, &apiPrs)
2016-12-02 11:10:39 +00:00
}
// GetPullRequest returns a single PR based on index
func GetPullRequest(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index} repository repoGetPullRequest
// ---
// summary: Get a pull request
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: index
// in: path
// description: index of the pull request to get
// type: integer
// format: int64
// required: true
// responses:
// "200":
// "$ref": "#/responses/PullRequest"
2016-12-02 11:10:39 +00:00
pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrPullRequestNotExist(err) {
2019-03-19 02:29:43 +00:00
ctx.NotFound()
2016-12-02 11:10:39 +00:00
} else {
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
2016-12-02 11:10:39 +00:00
}
return
}
if err = pr.GetBaseRepo(); err != nil {
ctx.Error(http.StatusInternalServerError, "GetBaseRepo", err)
return
}
if err = pr.GetHeadRepo(); err != nil {
ctx.Error(http.StatusInternalServerError, "GetHeadRepo", err)
return
}
ctx.JSON(http.StatusOK, pr.APIFormat())
2016-12-02 11:10:39 +00:00
}
// CreatePullRequest does what it says
func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption) {
// swagger:operation POST /repos/{owner}/{repo}/pulls repository repoCreatePullRequest
// ---
// summary: Create a pull request
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/CreatePullRequestOption"
// responses:
// "201":
// "$ref": "#/responses/PullRequest"
// "409":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
2016-12-02 11:10:39 +00:00
var (
repo = ctx.Repo.Repository
labelIDs []int64
assigneeID int64
milestoneID int64
)
// Get repo/branch information
_, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
2016-12-02 11:10:39 +00:00
if ctx.Written() {
return
}
defer headGitRepo.Close()
2016-12-02 11:10:39 +00:00
// Check if another PR exists with the same targets
existingPr, err := models.GetUnmergedPullRequest(headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch)
if err != nil {
if !models.IsErrPullRequestNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetUnmergedPullRequest", err)
2016-12-02 11:10:39 +00:00
return
}
} else {
err = models.ErrPullRequestAlreadyExists{
ID: existingPr.ID,
IssueID: existingPr.Index,
HeadRepoID: existingPr.HeadRepoID,
BaseRepoID: existingPr.BaseRepoID,
HeadBranch: existingPr.HeadBranch,
BaseBranch: existingPr.BaseBranch,
}
ctx.Error(http.StatusConflict, "GetUnmergedPullRequest", err)
2016-12-02 11:10:39 +00:00
return
}
if len(form.Labels) > 0 {
labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetLabelsInRepoByIDs", err)
2016-12-02 11:10:39 +00:00
return
}
labelIDs = make([]int64, len(labels))
for i := range labels {
labelIDs[i] = labels[i].ID
}
}
if form.Milestone > 0 {
milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
if err != nil {
if models.IsErrMilestoneNotExist(err) {
2019-03-19 02:29:43 +00:00
ctx.NotFound()
2016-12-02 11:10:39 +00:00
} else {
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
2016-12-02 11:10:39 +00:00
}
return
}
milestoneID = milestone.ID
}
var deadlineUnix timeutil.TimeStamp
Issue due date (#3794) * Started adding deadline to ui * Implemented basic issue due date managing * Improved UI for due date managing * Added at least write access to the repo in order to modify issue due dates * Ui improvements * Added issue comments creation when adding/modifying/removing a due date * Show due date in issue list * Added api support for issue due dates * Fixed lint suggestions * Added deadline to sdk * Updated css * Added support for adding/modifiying deadlines for pull requests via api * Fixed comments not created when updating or removing a deadline * update sdk (will do properly once go-gitea/go-sdk#103 is merged) * enhanced updateIssueDeadline * Removed unnessecary Issue.DeadlineString * UI improvements * Small improvments to comment creation + ui & validation improvements * Check if an issue is overdue is now a seperate function * Updated go-sdk with govendor as it was merged * Simplified isOverdue method * removed unessecary deadline to 0 set * Update swagger definitions * Added missing return * Added an explanary comment * Improved updateIssueDeadline method so it'll only update `deadline_unix` * Small changes and improvements * no need to explicitly load the issue when updating a deadline, just use whats already there * small optimisations * Added check if a deadline was modified before updating it * Moved comment creating logic into its own function * Code cleanup for creating deadline comment * locale improvement * When modifying a deadline, the old deadline is saved with the comment * small improvments to xorm session handling when updating an issue deadline + style nitpicks * style nitpicks * Moved checking for if the user has write acces to middleware
2018-05-01 19:05:28 +00:00
if form.Deadline != nil {
deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
Issue due date (#3794) * Started adding deadline to ui * Implemented basic issue due date managing * Improved UI for due date managing * Added at least write access to the repo in order to modify issue due dates * Ui improvements * Added issue comments creation when adding/modifying/removing a due date * Show due date in issue list * Added api support for issue due dates * Fixed lint suggestions * Added deadline to sdk * Updated css * Added support for adding/modifiying deadlines for pull requests via api * Fixed comments not created when updating or removing a deadline * update sdk (will do properly once go-gitea/go-sdk#103 is merged) * enhanced updateIssueDeadline * Removed unnessecary Issue.DeadlineString * UI improvements * Small improvments to comment creation + ui & validation improvements * Check if an issue is overdue is now a seperate function * Updated go-sdk with govendor as it was merged * Simplified isOverdue method * removed unessecary deadline to 0 set * Update swagger definitions * Added missing return * Added an explanary comment * Improved updateIssueDeadline method so it'll only update `deadline_unix` * Small changes and improvements * no need to explicitly load the issue when updating a deadline, just use whats already there * small optimisations * Added check if a deadline was modified before updating it * Moved comment creating logic into its own function * Code cleanup for creating deadline comment * locale improvement * When modifying a deadline, the old deadline is saved with the comment * small improvments to xorm session handling when updating an issue deadline + style nitpicks * style nitpicks * Moved checking for if the user has write acces to middleware
2018-05-01 19:05:28 +00:00
}
2016-12-02 11:10:39 +00:00
prIssue := &models.Issue{
Issue due date (#3794) * Started adding deadline to ui * Implemented basic issue due date managing * Improved UI for due date managing * Added at least write access to the repo in order to modify issue due dates * Ui improvements * Added issue comments creation when adding/modifying/removing a due date * Show due date in issue list * Added api support for issue due dates * Fixed lint suggestions * Added deadline to sdk * Updated css * Added support for adding/modifiying deadlines for pull requests via api * Fixed comments not created when updating or removing a deadline * update sdk (will do properly once go-gitea/go-sdk#103 is merged) * enhanced updateIssueDeadline * Removed unnessecary Issue.DeadlineString * UI improvements * Small improvments to comment creation + ui & validation improvements * Check if an issue is overdue is now a seperate function * Updated go-sdk with govendor as it was merged * Simplified isOverdue method * removed unessecary deadline to 0 set * Update swagger definitions * Added missing return * Added an explanary comment * Improved updateIssueDeadline method so it'll only update `deadline_unix` * Small changes and improvements * no need to explicitly load the issue when updating a deadline, just use whats already there * small optimisations * Added check if a deadline was modified before updating it * Moved comment creating logic into its own function * Code cleanup for creating deadline comment * locale improvement * When modifying a deadline, the old deadline is saved with the comment * small improvments to xorm session handling when updating an issue deadline + style nitpicks * style nitpicks * Moved checking for if the user has write acces to middleware
2018-05-01 19:05:28 +00:00
RepoID: repo.ID,
Title: form.Title,
PosterID: ctx.User.ID,
Poster: ctx.User,
MilestoneID: milestoneID,
AssigneeID: assigneeID,
IsPull: true,
Content: form.Body,
DeadlineUnix: deadlineUnix,
2016-12-02 11:10:39 +00:00
}
pr := &models.PullRequest{
HeadRepoID: headRepo.ID,
BaseRepoID: repo.ID,
HeadBranch: headBranch,
BaseBranch: baseBranch,
HeadRepo: headRepo,
BaseRepo: repo,
MergeBase: compareInfo.MergeBase,
Type: models.PullRequestGitea,
2016-12-02 11:10:39 +00:00
}
2018-05-09 16:29:04 +00:00
// Get all assignee IDs
assigneeIDs, err := models.MakeIDsFromAPIAssigneesToAdd(form.Assignee, form.Assignees)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
2018-05-09 16:29:04 +00:00
} else {
ctx.Error(http.StatusInternalServerError, "AddAssigneeByName", err)
2018-05-09 16:29:04 +00:00
}
return
}
// Check if the passed assignees is assignable
for _, aID := range assigneeIDs {
assignee, err := models.GetUserByID(aID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetUserByID", err)
return
}
2018-05-09 16:29:04 +00:00
valid, err := models.CanBeAssigned(assignee, repo, true)
if err != nil {
ctx.Error(http.StatusInternalServerError, "canBeAssigned", err)
return
}
if !valid {
ctx.Error(http.StatusUnprocessableEntity, "canBeAssigned", models.ErrUserDoesNotHaveAccessToRepo{UserID: aID, RepoName: repo.Name})
return
}
}
if err := pull_service.NewPullRequest(repo, prIssue, labelIDs, []string{}, pr, assigneeIDs); err != nil {
2018-05-09 16:29:04 +00:00
if models.IsErrUserDoesNotHaveAccessToRepo(err) {
ctx.Error(http.StatusBadRequest, "UserDoesNotHaveAccessToRepo", err)
2018-05-09 16:29:04 +00:00
return
}
ctx.Error(http.StatusInternalServerError, "NewPullRequest", err)
2016-12-02 11:10:39 +00:00
return
}
log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
ctx.JSON(http.StatusCreated, pr.APIFormat())
2016-12-02 11:10:39 +00:00
}
// EditPullRequest does what it says
func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
// swagger:operation PATCH /repos/{owner}/{repo}/pulls/{index} repository repoEditPullRequest
// ---
// summary: Update a pull request. If using deadline only the date will be taken into account, and time of day ignored.
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: index
// in: path
// description: index of the pull request to edit
// type: integer
// format: int64
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/EditPullRequestOption"
// responses:
// "201":
// "$ref": "#/responses/PullRequest"
// "403":
// "$ref": "#/responses/forbidden"
// "412":
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
2016-12-02 11:10:39 +00:00
pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrPullRequestNotExist(err) {
2019-03-19 02:29:43 +00:00
ctx.NotFound()
2016-12-02 11:10:39 +00:00
} else {
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
2016-12-02 11:10:39 +00:00
}
return
}
2019-06-12 19:41:28 +00:00
err = pr.LoadIssue()
if err != nil {
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
return
}
2016-12-02 11:10:39 +00:00
issue := pr.Issue
issue.Repo = ctx.Repo.Repository
2016-12-02 11:10:39 +00:00
if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypePullRequests) {
ctx.Status(http.StatusForbidden)
2016-12-02 11:10:39 +00:00
return
}
if len(form.Title) > 0 {
issue.Title = form.Title
}
if len(form.Body) > 0 {
issue.Content = form.Body
}
// Update or remove deadline if set
if form.Deadline != nil || form.RemoveDeadline != nil {
var deadlineUnix timeutil.TimeStamp
if (form.RemoveDeadline == nil || !*form.RemoveDeadline) && !form.Deadline.IsZero() {
deadline := time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
23, 59, 59, 0, form.Deadline.Location())
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
}
if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err)
return
}
issue.DeadlineUnix = deadlineUnix
Issue due date (#3794) * Started adding deadline to ui * Implemented basic issue due date managing * Improved UI for due date managing * Added at least write access to the repo in order to modify issue due dates * Ui improvements * Added issue comments creation when adding/modifying/removing a due date * Show due date in issue list * Added api support for issue due dates * Fixed lint suggestions * Added deadline to sdk * Updated css * Added support for adding/modifiying deadlines for pull requests via api * Fixed comments not created when updating or removing a deadline * update sdk (will do properly once go-gitea/go-sdk#103 is merged) * enhanced updateIssueDeadline * Removed unnessecary Issue.DeadlineString * UI improvements * Small improvments to comment creation + ui & validation improvements * Check if an issue is overdue is now a seperate function * Updated go-sdk with govendor as it was merged * Simplified isOverdue method * removed unessecary deadline to 0 set * Update swagger definitions * Added missing return * Added an explanary comment * Improved updateIssueDeadline method so it'll only update `deadline_unix` * Small changes and improvements * no need to explicitly load the issue when updating a deadline, just use whats already there * small optimisations * Added check if a deadline was modified before updating it * Moved comment creating logic into its own function * Code cleanup for creating deadline comment * locale improvement * When modifying a deadline, the old deadline is saved with the comment * small improvments to xorm session handling when updating an issue deadline + style nitpicks * style nitpicks * Moved checking for if the user has write acces to middleware
2018-05-01 19:05:28 +00:00
}
2018-05-09 16:29:04 +00:00
// Add/delete assignees
2016-12-02 11:10:39 +00:00
2019-03-09 21:15:45 +00:00
// Deleting is done the GitHub way (quote from their api documentation):
2018-05-09 16:29:04 +00:00
// https://developer.github.com/v3/issues/#edit-an-issue
// "assignees" (array): Logins for Users to assign to this issue.
// Pass one or more user logins to replace the set of assignees on this Issue.
// Send an empty array ([]) to clear all assignees from the Issue.
if ctx.Repo.CanWrite(models.UnitTypePullRequests) && (form.Assignees != nil || len(form.Assignee) > 0) {
err = issue_service.UpdateAssignees(issue, form.Assignee, form.Assignees, ctx.User)
2018-05-09 16:29:04 +00:00
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("Assignee does not exist: [name: %s]", err))
2018-05-09 16:29:04 +00:00
} else {
ctx.Error(http.StatusInternalServerError, "UpdateAssignees", err)
2018-05-09 16:29:04 +00:00
}
2016-12-02 11:10:39 +00:00
return
}
}
2018-05-09 16:29:04 +00:00
if ctx.Repo.CanWrite(models.UnitTypePullRequests) && form.Milestone != 0 &&
2016-12-02 11:10:39 +00:00
issue.MilestoneID != form.Milestone {
oldMilestoneID := issue.MilestoneID
issue.MilestoneID = form.Milestone
if err = issue_service.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
ctx.Error(http.StatusInternalServerError, "ChangeMilestoneAssign", err)
2016-12-02 11:10:39 +00:00
return
}
}
if ctx.Repo.CanWrite(models.UnitTypePullRequests) && form.Labels != nil {
labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetLabelsInRepoByIDsError", err)
return
}
if err = issue.ReplaceLabels(labels, ctx.User); err != nil {
ctx.Error(http.StatusInternalServerError, "ReplaceLabelsError", err)
return
}
}
if err = models.UpdateIssueByAPI(issue); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err)
2016-12-02 11:10:39 +00:00
return
}
if form.State != nil {
if err = issue_service.ChangeStatus(issue, ctx.User, api.StateClosed == api.StateType(*form.State)); err != nil {
if models.IsErrDependenciesLeft(err) {
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
return
}
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
2016-12-02 11:10:39 +00:00
return
}
}
// Refetch from database
pr, err = models.GetPullRequestByIndex(ctx.Repo.Repository.ID, pr.Index)
if err != nil {
if models.IsErrPullRequestNotExist(err) {
2019-03-19 02:29:43 +00:00
ctx.NotFound()
2016-12-02 11:10:39 +00:00
} else {
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
2016-12-02 11:10:39 +00:00
}
return
}
// TODO this should be 200, not 201
ctx.JSON(http.StatusCreated, pr.APIFormat())
2016-12-02 11:10:39 +00:00
}
// IsPullRequestMerged checks if a PR exists given an index
func IsPullRequestMerged(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/merge repository repoPullRequestIsMerged
// ---
// summary: Check if a pull request has been merged
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: index
// in: path
// description: index of the pull request
// type: integer
// format: int64
// required: true
// responses:
// "204":
// description: pull request has been merged
// "404":
// description: pull request has not been merged
2016-12-02 11:10:39 +00:00
pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrPullRequestNotExist(err) {
2019-03-19 02:29:43 +00:00
ctx.NotFound()
2016-12-02 11:10:39 +00:00
} else {
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
2016-12-02 11:10:39 +00:00
}
return
}
if pr.HasMerged {
ctx.Status(http.StatusNoContent)
2016-12-02 11:10:39 +00:00
}
2019-03-19 02:29:43 +00:00
ctx.NotFound()
2016-12-02 11:10:39 +00:00
}
// MergePullRequest merges a PR given an index
func MergePullRequest(ctx *context.APIContext, form auth.MergePullRequestForm) {
// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/merge repository repoMergePullRequest
// ---
// summary: Merge a pull request
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: index
// in: path
// description: index of the pull request to merge
// type: integer
// format: int64
// required: true
// - name: body
// in: body
// schema:
// $ref: "#/definitions/MergePullRequestOption"
// responses:
// "200":
// "$ref": "#/responses/empty"
// "405":
// "$ref": "#/responses/empty"
// "409":
// "$ref": "#/responses/error"
2016-12-02 11:10:39 +00:00
pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrPullRequestNotExist(err) {
ctx.NotFound("GetPullRequestByIndex", err)
2016-12-02 11:10:39 +00:00
} else {
ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err)
2016-12-02 11:10:39 +00:00
}
return
}
if err = pr.GetHeadRepo(); err != nil {
ctx.ServerError("GetHeadRepo", err)
2016-12-02 11:10:39 +00:00
return
}
2019-06-12 19:41:28 +00:00
err = pr.LoadIssue()
if err != nil {
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
return
}
2016-12-02 11:10:39 +00:00
pr.Issue.Repo = ctx.Repo.Repository
if ctx.IsSigned {
// Update issue-user.
if err = pr.Issue.ReadBy(ctx.User.ID); err != nil {
ctx.Error(http.StatusInternalServerError, "ReadBy", err)
2016-12-02 11:10:39 +00:00
return
}
}
if pr.Issue.IsClosed {
2019-03-19 02:29:43 +00:00
ctx.NotFound()
2016-12-02 11:10:39 +00:00
return
}
if !pr.CanAutoMerge() || pr.HasMerged || pr.IsWorkInProgress() {
ctx.Status(http.StatusMethodNotAllowed)
2016-12-02 11:10:39 +00:00
return
}
isPass, err := pull_service.IsPullCommitStatusPass(pr)
if err != nil {
ctx.Error(http.StatusInternalServerError, "IsPullCommitStatusPass", err)
return
}
if !isPass && !ctx.IsUserRepoAdmin() {
ctx.Status(http.StatusMethodNotAllowed)
return
}
if len(form.Do) == 0 {
form.Do = string(models.MergeStyleMerge)
}
message := strings.TrimSpace(form.MergeTitleField)
if len(message) == 0 {
if models.MergeStyle(form.Do) == models.MergeStyleMerge {
message = pr.GetDefaultMergeMessage()
}
if models.MergeStyle(form.Do) == models.MergeStyleSquash {
message = pr.GetDefaultSquashMessage()
}
}
form.MergeMessageField = strings.TrimSpace(form.MergeMessageField)
if len(form.MergeMessageField) > 0 {
message += "\n\n" + form.MergeMessageField
}
if err := pull_service.Merge(pr, ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil {
if models.IsErrInvalidMergeStyle(err) {
ctx.Status(http.StatusMethodNotAllowed)
return
} else if models.IsErrMergeConflicts(err) {
conflictError := err.(models.ErrMergeConflicts)
ctx.JSON(http.StatusConflict, conflictError)
} else if models.IsErrRebaseConflicts(err) {
conflictError := err.(models.ErrRebaseConflicts)
ctx.JSON(http.StatusConflict, conflictError)
} else if models.IsErrMergeUnrelatedHistories(err) {
conflictError := err.(models.ErrMergeUnrelatedHistories)
ctx.JSON(http.StatusConflict, conflictError)
} else if models.IsErrMergePushOutOfDate(err) {
ctx.Error(http.StatusConflict, "Merge", "merge push out of date")
return
} else if models.IsErrPushRejected(err) {
errPushRej := err.(models.ErrPushRejected)
if len(errPushRej.Message) == 0 {
ctx.Error(http.StatusConflict, "Merge", "PushRejected without remote error message")
return
}
ctx.Error(http.StatusConflict, "Merge", "PushRejected with remote message: "+errPushRej.Message)
return
}
ctx.Error(http.StatusInternalServerError, "Merge", err)
2016-12-02 11:10:39 +00:00
return
}
log.Trace("Pull request merged: %d", pr.ID)
ctx.Status(http.StatusOK)
2016-12-02 11:10:39 +00:00
}
Compare branches, commits and tags with each other (#6991) * Supports tags when comparing commits or branches Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Hide headline when only comparing and don't load unused data Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Merges compare logics to allow comparing branches, commits and tags with eachother Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Display branch or tag instead of commit when used for comparing Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Show pull request form after click on button Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Transfers relevant pull.go changes from master to compare.go Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Fixes error when comparing forks against a commit or tag Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes console.log from JavaScript file Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Show icon next to commit reference when comparing branch or tag Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Updates css file Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Fixes import order * Renames template variable * Update routers/repo/compare.go Co-Authored-By: zeripath <art27@cantab.net> * Update from master Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Allow short-shas in compare * Renames prInfo to compareInfo Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Check PR permissions only if compare is pull request Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adjusts comment Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use compareInfo instead of prInfo
2019-06-07 20:29:29 +00:00
func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*models.User, *models.Repository, *git.Repository, *git.CompareInfo, string, string) {
2016-12-02 11:10:39 +00:00
baseRepo := ctx.Repo.Repository
// Get compared branches information
// format: <base branch>...[<head repo>:]<head branch>
// base<-head: master...head:feature
// same repo: master...feature
// TODO: Validate form first?
baseBranch := form.Base
var (
headUser *models.User
headBranch string
isSameRepo bool
err error
)
// If there is no head repository, it means pull request between same repository.
headInfos := strings.Split(form.Head, ":")
if len(headInfos) == 1 {
isSameRepo = true
headUser = ctx.Repo.Owner
headBranch = headInfos[0]
} else if len(headInfos) == 2 {
headUser, err = models.GetUserByName(headInfos[0])
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.NotFound("GetUserByName")
2016-12-02 11:10:39 +00:00
} else {
ctx.ServerError("GetUserByName", err)
2016-12-02 11:10:39 +00:00
}
return nil, nil, nil, nil, "", ""
}
headBranch = headInfos[1]
} else {
2019-03-19 02:29:43 +00:00
ctx.NotFound()
2016-12-02 11:10:39 +00:00
return nil, nil, nil, nil, "", ""
}
ctx.Repo.PullRequest.SameRepo = isSameRepo
log.Info("Base branch: %s", baseBranch)
log.Info("Repo path: %s", ctx.Repo.GitRepo.Path)
// Check if base branch is valid.
if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) {
ctx.NotFound("IsBranchExist")
2016-12-02 11:10:39 +00:00
return nil, nil, nil, nil, "", ""
}
// Check if current user has fork of repository or in the same repository.
headRepo, has := models.HasForkedRepo(headUser.ID, baseRepo.ID)
if !has && !isSameRepo {
log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
ctx.NotFound("HasForkedRepo")
2016-12-02 11:10:39 +00:00
return nil, nil, nil, nil, "", ""
}
var headGitRepo *git.Repository
if isSameRepo {
headRepo = ctx.Repo.Repository
headGitRepo = ctx.Repo.GitRepo
} else {
headGitRepo, err = git.OpenRepository(models.RepoPath(headUser.Name, headRepo.Name))
if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
2016-12-02 11:10:39 +00:00
return nil, nil, nil, nil, "", ""
}
}
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User)
if err != nil {
headGitRepo.Close()
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(models.UnitTypeCode) {
if log.IsTrace() {
log.Trace("Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v",
ctx.User,
baseRepo,
permBase)
}
headGitRepo.Close()
ctx.NotFound("Can't read pulls or can't read UnitTypeCode")
return nil, nil, nil, nil, "", ""
}
// user should have permission to read headrepo's codes
permHead, err := models.GetUserRepoPermission(headRepo, ctx.User)
if err != nil {
headGitRepo.Close()
ctx.ServerError("GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
if !permHead.CanRead(models.UnitTypeCode) {
if log.IsTrace() {
log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v",
ctx.User,
headRepo,
permHead)
}
headGitRepo.Close()
ctx.NotFound("Can't read headRepo UnitTypeCode")
2016-12-02 11:10:39 +00:00
return nil, nil, nil, nil, "", ""
}
// Check if head branch is valid.
if !headGitRepo.IsBranchExist(headBranch) {
headGitRepo.Close()
2019-03-19 02:29:43 +00:00
ctx.NotFound()
2016-12-02 11:10:39 +00:00
return nil, nil, nil, nil, "", ""
}
Compare branches, commits and tags with each other (#6991) * Supports tags when comparing commits or branches Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Hide headline when only comparing and don't load unused data Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Merges compare logics to allow comparing branches, commits and tags with eachother Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Display branch or tag instead of commit when used for comparing Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Show pull request form after click on button Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Transfers relevant pull.go changes from master to compare.go Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Fixes error when comparing forks against a commit or tag Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes console.log from JavaScript file Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Show icon next to commit reference when comparing branch or tag Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Updates css file Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Fixes import order * Renames template variable * Update routers/repo/compare.go Co-Authored-By: zeripath <art27@cantab.net> * Update from master Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Allow short-shas in compare * Renames prInfo to compareInfo Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Check PR permissions only if compare is pull request Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adjusts comment Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use compareInfo instead of prInfo
2019-06-07 20:29:29 +00:00
compareInfo, err := headGitRepo.GetCompareInfo(models.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
2016-12-02 11:10:39 +00:00
if err != nil {
headGitRepo.Close()
ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err)
2016-12-02 11:10:39 +00:00
return nil, nil, nil, nil, "", ""
}
Compare branches, commits and tags with each other (#6991) * Supports tags when comparing commits or branches Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Hide headline when only comparing and don't load unused data Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Merges compare logics to allow comparing branches, commits and tags with eachother Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Display branch or tag instead of commit when used for comparing Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Show pull request form after click on button Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Transfers relevant pull.go changes from master to compare.go Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Fixes error when comparing forks against a commit or tag Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Removes console.log from JavaScript file Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Show icon next to commit reference when comparing branch or tag Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Updates css file Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Fixes import order * Renames template variable * Update routers/repo/compare.go Co-Authored-By: zeripath <art27@cantab.net> * Update from master Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Allow short-shas in compare * Renames prInfo to compareInfo Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Check PR permissions only if compare is pull request Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adjusts comment Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Use compareInfo instead of prInfo
2019-06-07 20:29:29 +00:00
return headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch
2016-12-02 11:10:39 +00:00
}