From 92328a3394be51e6200f69c91c402aa15ff6e06e Mon Sep 17 00:00:00 2001 From: sebastian-sauer Date: Fri, 2 Jul 2021 14:19:57 +0200 Subject: [PATCH] Add API to get commits of PR (#16300) * Add API to get commits of PR fixes #10918 Co-authored-by: Andrew Bezold Co-authored-by: 6543 <6543@obermui.de> --- integrations/api_pull_commits_test.go | 37 ++++++++ routers/api/v1/api.go | 1 + routers/api/v1/repo/pull.go | 121 ++++++++++++++++++++++++++ templates/swagger/v1_json.tmpl | 56 ++++++++++++ 4 files changed, 215 insertions(+) create mode 100644 integrations/api_pull_commits_test.go diff --git a/integrations/api_pull_commits_test.go b/integrations/api_pull_commits_test.go new file mode 100644 index 0000000000..30682d9c14 --- /dev/null +++ b/integrations/api_pull_commits_test.go @@ -0,0 +1,37 @@ +// Copyright 2021 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 integrations + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models" + api "code.gitea.io/gitea/modules/structs" + "github.com/stretchr/testify/assert" +) + +func TestAPIPullCommits(t *testing.T) { + defer prepareTestEnv(t)() + pullIssue := models.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2}).(*models.PullRequest) + assert.NoError(t, pullIssue.LoadIssue()) + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue.HeadRepoID}).(*models.Repository) + + session := loginUser(t, "user2") + req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/commits", repo.OwnerName, repo.Name, pullIssue.Index) + resp := session.MakeRequest(t, req, http.StatusOK) + + var commits []*api.Commit + DecodeJSON(t, resp, &commits) + + if !assert.Len(t, commits, 2) { + return + } + + assert.Equal(t, "5f22f7d0d95d614d25a5b68592adb345a4b5c7fd", commits[0].SHA) + assert.Equal(t, "4a357436d925b5c974181ff12a994538ddc5a269", commits[1].SHA) +} + +// TODO add tests for already merged PR and closed PR diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index c6b4ff04de..b6913ea1bc 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -905,6 +905,7 @@ func Routes() *web.Route { m.Get(".diff", repo.DownloadPullDiff) m.Get(".patch", repo.DownloadPullPatch) m.Post("/update", reqToken(), repo.UpdatePullRequest) + m.Get("/commits", repo.GetPullRequestCommits) m.Combo("/merge").Get(repo.IsPullRequestMerged). Post(reqToken(), mustNotBeArchived, bind(forms.MergePullRequestForm{}), repo.MergePullRequest) m.Group("/reviews", func() { diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index eff998ee99..0c09a9a86b 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -6,7 +6,9 @@ package repo import ( "fmt" + "math" "net/http" + "strconv" "strings" "time" @@ -1101,3 +1103,122 @@ func UpdatePullRequest(ctx *context.APIContext) { ctx.Status(http.StatusOK) } + +// GetPullRequestCommits gets all commits associated with a given PR +func GetPullRequestCommits(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/pulls/{index}/commits repository repoGetPullRequestCommits + // --- + // summary: Get commits for 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 + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/CommitList" + // "404": + // "$ref": "#/responses/notFound" + + 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 err := pr.LoadBaseRepo(); err != nil { + ctx.InternalServerError(err) + return + } + + var prInfo *git.CompareInfo + baseGitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath()) + if err != nil { + ctx.ServerError("OpenRepository", err) + return + } + defer baseGitRepo.Close() + if pr.HasMerged { + prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.MergeBase, pr.GetGitRefName()) + } else { + prInfo, err = baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName()) + } + if err != nil { + ctx.ServerError("GetCompareInfo", err) + return + } + commits := prInfo.Commits + + listOptions := utils.GetListOptions(ctx) + + totalNumberOfCommits := commits.Len() + totalNumberOfPages := int(math.Ceil(float64(totalNumberOfCommits) / float64(listOptions.PageSize))) + + userCache := make(map[string]*models.User) + + start, end := listOptions.GetStartEnd() + + if end > totalNumberOfCommits { + end = totalNumberOfCommits + } + + apiCommits := make([]*api.Commit, end-start) + + i := 0 + addedCommitsCount := 0 + for commitPointer := commits.Front(); commitPointer != nil; commitPointer = commitPointer.Next() { + if i < start { + i++ + continue + } + if i >= end { + break + } + + commit := commitPointer.Value.(*git.Commit) + + // Create json struct + apiCommits[addedCommitsCount], err = convert.ToCommit(ctx.Repo.Repository, commit, userCache) + addedCommitsCount++ + if err != nil { + ctx.ServerError("toCommit", err) + return + } + i++ + } + + ctx.SetLinkHeader(int(totalNumberOfCommits), listOptions.PageSize) + + ctx.Header().Set("X-Page", strconv.Itoa(listOptions.Page)) + ctx.Header().Set("X-PerPage", strconv.Itoa(listOptions.PageSize)) + ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", totalNumberOfCommits)) + ctx.Header().Set("X-PageCount", strconv.Itoa(totalNumberOfPages)) + ctx.Header().Set("X-HasMore", strconv.FormatBool(listOptions.Page < totalNumberOfPages)) + ctx.JSON(http.StatusOK, &apiCommits) +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index dfd08bcc68..a2e449228e 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -7333,6 +7333,62 @@ } } }, + "/repos/{owner}/{repo}/pulls/{index}/commits": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Get commits for a pull request", + "operationId": "repoGetPullRequestCommits", + "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 + }, + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/CommitList" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/repos/{owner}/{repo}/pulls/{index}/merge": { "get": { "produces": [