diff --git a/integrations/pull_update_test.go b/integrations/pull_update_test.go index 484390001c..2dc966316e 100644 --- a/integrations/pull_update_test.go +++ b/integrations/pull_update_test.go @@ -5,7 +5,7 @@ package integrations import ( - "fmt" + "net/http" "net/url" "testing" "time" @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestPullUpdate(t *testing.T) { +func TestAPIPullUpdate(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { //Create PR to test user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) @@ -31,17 +31,19 @@ func TestPullUpdate(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, diffCount.Behind) assert.EqualValues(t, 1, diffCount.Ahead) + assert.NoError(t, pr.LoadBaseRepo()) + assert.NoError(t, pr.LoadIssue()) - message := fmt.Sprintf("Merge branch '%s' into %s", pr.BaseBranch, pr.HeadBranch) - err = pull_service.Update(pr, user, message) - assert.NoError(t, err) + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session) + req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?token="+token, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index) + session.MakeRequest(t, req, http.StatusOK) //Test GetDiverging after update diffCount, err = pull_service.GetDiverging(pr) assert.NoError(t, err) assert.EqualValues(t, 0, diffCount.Behind) assert.EqualValues(t, 2, diffCount.Ahead) - }) } diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index f67eebacc4..506e6a3ec0 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -806,6 +806,7 @@ func RegisterRoutes(m *macaron.Macaron) { Patch(reqToken(), reqRepoWriter(models.UnitTypePullRequests), bind(api.EditPullRequestOption{}), repo.EditPullRequest) m.Get(".diff", repo.DownloadPullDiff) m.Get(".patch", repo.DownloadPullPatch) + m.Post("/update", reqToken(), repo.UpdatePullRequest) m.Combo("/merge").Get(repo.IsPullRequestMerged). Post(reqToken(), mustNotBeArchived, bind(auth.MergePullRequestForm{}), repo.MergePullRequest) m.Group("/reviews", func() { diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 5acbb9e297..5fc0cd8cfb 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -968,3 +968,99 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) return headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch } + +// UpdatePullRequest merge PR's baseBranch into headBranch +func UpdatePullRequest(ctx *context.APIContext) { + // swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/update repository repoUpdatePullRequest + // --- + // summary: Merge PR's baseBranch into headBranch + // 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/empty" + // "403": + // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" + // "409": + // "$ref": "#/responses/error" + // "422": + // "$ref": "#/responses/validationError" + + pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if models.IsErrPullRequestNotExist(err) { + ctx.NotFound() + } else { + ctx.Error(http.StatusInternalServerError, "GetPullRequestByIndex", err) + } + return + } + + if pr.HasMerged { + ctx.Error(http.StatusUnprocessableEntity, "UpdatePullRequest", err) + return + } + + if err = pr.LoadIssue(); err != nil { + ctx.Error(http.StatusInternalServerError, "LoadIssue", err) + return + } + + if pr.Issue.IsClosed { + ctx.Error(http.StatusUnprocessableEntity, "UpdatePullRequest", err) + return + } + + if err = pr.LoadBaseRepo(); err != nil { + ctx.Error(http.StatusInternalServerError, "LoadBaseRepo", err) + return + } + if err = pr.LoadHeadRepo(); err != nil { + ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err) + return + } + + allowedUpdate, err := pull_service.IsUserAllowedToUpdate(pr, ctx.User) + if err != nil { + ctx.Error(http.StatusInternalServerError, "IsUserAllowedToMerge", err) + return + } + + if !allowedUpdate { + ctx.Status(http.StatusForbidden) + return + } + + // default merge commit message + message := fmt.Sprintf("Merge branch '%s' into %s", pr.BaseBranch, pr.HeadBranch) + + if err = pull_service.Update(pr, ctx.User, message); err != nil { + if models.IsErrMergeConflicts(err) { + ctx.Error(http.StatusConflict, "Update", "merge failed because of conflict") + return + } + ctx.Error(http.StatusInternalServerError, "pull_service.Update", err) + return + } + + ctx.Status(http.StatusOK) +} diff --git a/routers/repo/pull.go b/routers/repo/pull.go index 60326db03a..cfe30a1a19 100644 --- a/routers/repo/pull.go +++ b/routers/repo/pull.go @@ -666,7 +666,7 @@ func ViewPullFiles(ctx *context.Context) { ctx.HTML(200, tplPullFiles) } -// UpdatePullRequest merge master into PR +// UpdatePullRequest merge PR's baseBranch into headBranch func UpdatePullRequest(ctx *context.Context) { issue := checkPullInfo(ctx) if ctx.Written() { diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index c601809a75..d5e5c86cd8 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -7285,6 +7285,59 @@ } } }, + "/repos/{owner}/{repo}/pulls/{index}/update": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Merge PR's baseBranch into headBranch", + "operationId": "repoUpdatePullRequest", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + }, + { + "type": "integer", + "format": "int64", + "description": "index of the pull request to get", + "name": "index", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/empty" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + }, + "409": { + "$ref": "#/responses/error" + }, + "422": { + "$ref": "#/responses/validationError" + } + } + } + }, "/repos/{owner}/{repo}/raw/{filepath}": { "get": { "produces": [