loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(api): implement branch/commit comparison API (#30349)

- Add new `Compare` struct to represent comparison between two commits
- Introduce new API endpoint `/compare/*` to get commit comparison
information
- Create new file `repo_compare.go` with the `Compare` struct definition
- Add new file `compare.go` in `routers/api/v1/repo` to handle
comparison logic
- Add new file `compare.go` in `routers/common` to define `CompareInfo`
struct
- Refactor `ParseCompareInfo` function to use `common.CompareInfo`
struct
- Update Swagger documentation to include the new API endpoint for
commit comparison
- Remove duplicate `CompareInfo` struct from
`routers/web/repo/compare.go`
- Adjust base path in Swagger template to be relative (`/api/v1`)

GitHub API
https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#compare-two-commits

---------

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
(cherry picked from commit c70e442ce4b99e2a1f1bf216afcfa1ad78d1925a)

Conflicts:
- routers/api/v1/swagger/repo.go
Conflict resolved by manually adding the lines from the Gitea
PR.

authored by

Bo-Yi Wu
Lunny Xiao
and committed by
Gergely Nagy
e025ec01 ac0a2036

+250 -14
+10
modules/structs/repo_compare.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package structs 5 + 6 + // Compare represents a comparison between two commits. 7 + type Compare struct { 8 + TotalCommits int `json:"total_commits"` // Total number of commits in the comparison. 9 + Commits []*Commit `json:"commits"` // List of commits in the comparison. 10 + }
+2
routers/api/v1/api.go
··· 983 983 m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate) 984 984 985 985 m.Group("/{username}/{reponame}", func() { 986 + m.Get("/compare/*", reqRepoReader(unit.TypeCode), repo.CompareDiff) 987 + 986 988 m.Combo("").Get(reqAnyRepoReader(), repo.Get). 987 989 Delete(reqToken(), reqOwner(), repo.Delete). 988 990 Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)
+99
routers/api/v1/repo/compare.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package repo 5 + 6 + import ( 7 + "net/http" 8 + "strings" 9 + 10 + user_model "code.gitea.io/gitea/models/user" 11 + "code.gitea.io/gitea/modules/gitrepo" 12 + api "code.gitea.io/gitea/modules/structs" 13 + "code.gitea.io/gitea/services/context" 14 + "code.gitea.io/gitea/services/convert" 15 + ) 16 + 17 + // CompareDiff compare two branches or commits 18 + func CompareDiff(ctx *context.APIContext) { 19 + // swagger:operation GET /repos/{owner}/{repo}/compare/{basehead} Get commit comparison information 20 + // --- 21 + // summary: Get commit comparison information 22 + // produces: 23 + // - application/json 24 + // parameters: 25 + // - name: owner 26 + // in: path 27 + // description: owner of the repo 28 + // type: string 29 + // required: true 30 + // - name: repo 31 + // in: path 32 + // description: name of the repo 33 + // type: string 34 + // required: true 35 + // - name: basehead 36 + // in: path 37 + // description: compare two branches or commits 38 + // type: string 39 + // required: true 40 + // responses: 41 + // "200": 42 + // "$ref": "#/responses/Compare" 43 + // "404": 44 + // "$ref": "#/responses/notFound" 45 + 46 + if ctx.Repo.GitRepo == nil { 47 + gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository) 48 + if err != nil { 49 + ctx.Error(http.StatusInternalServerError, "OpenRepository", err) 50 + return 51 + } 52 + ctx.Repo.GitRepo = gitRepo 53 + defer gitRepo.Close() 54 + } 55 + 56 + infoPath := ctx.Params("*") 57 + infos := []string{ctx.Repo.Repository.DefaultBranch, ctx.Repo.Repository.DefaultBranch} 58 + if infoPath != "" { 59 + infos = strings.SplitN(infoPath, "...", 2) 60 + if len(infos) != 2 { 61 + if infos = strings.SplitN(infoPath, "..", 2); len(infos) != 2 { 62 + infos = []string{ctx.Repo.Repository.DefaultBranch, infoPath} 63 + } 64 + } 65 + } 66 + 67 + _, _, headGitRepo, ci, _, _ := parseCompareInfo(ctx, api.CreatePullRequestOption{ 68 + Base: infos[0], 69 + Head: infos[1], 70 + }) 71 + if ctx.Written() { 72 + return 73 + } 74 + defer headGitRepo.Close() 75 + 76 + verification := ctx.FormString("verification") == "" || ctx.FormBool("verification") 77 + files := ctx.FormString("files") == "" || ctx.FormBool("files") 78 + 79 + apiCommits := make([]*api.Commit, 0, len(ci.Commits)) 80 + userCache := make(map[string]*user_model.User) 81 + for i := 0; i < len(ci.Commits); i++ { 82 + apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ci.Commits[i], userCache, 83 + convert.ToCommitOptions{ 84 + Stat: true, 85 + Verification: verification, 86 + Files: files, 87 + }) 88 + if err != nil { 89 + ctx.ServerError("toCommit", err) 90 + return 91 + } 92 + apiCommits = append(apiCommits, apiCommit) 93 + } 94 + 95 + ctx.JSON(http.StatusOK, &api.Compare{ 96 + TotalCommits: len(ci.Commits), 97 + Commits: apiCommits, 98 + }) 99 + }
+6
routers/api/v1/swagger/repo.go
··· 421 421 // in:body 422 422 Body []api.BlockedUser `json:"body"` 423 423 } 424 + 425 + // swagger:response Compare 426 + type swaggerCompare struct { 427 + // in:body 428 + Body api.Compare `json:"body"` 429 + }
+21
routers/common/compare.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package common 5 + 6 + import ( 7 + repo_model "code.gitea.io/gitea/models/repo" 8 + user_model "code.gitea.io/gitea/models/user" 9 + "code.gitea.io/gitea/modules/git" 10 + ) 11 + 12 + // CompareInfo represents the collected results from ParseCompareInfo 13 + type CompareInfo struct { 14 + HeadUser *user_model.User 15 + HeadRepo *repo_model.Repository 16 + HeadGitRepo *git.Repository 17 + CompareInfo *git.CompareInfo 18 + BaseBranch string 19 + HeadBranch string 20 + DirectComparison bool 21 + }
+4 -14
routers/web/repo/compare.go
··· 35 35 api "code.gitea.io/gitea/modules/structs" 36 36 "code.gitea.io/gitea/modules/typesniffer" 37 37 "code.gitea.io/gitea/modules/util" 38 + "code.gitea.io/gitea/routers/common" 38 39 "code.gitea.io/gitea/services/context" 39 40 "code.gitea.io/gitea/services/context/upload" 40 41 "code.gitea.io/gitea/services/gitdiff" ··· 185 186 } 186 187 } 187 188 188 - // CompareInfo represents the collected results from ParseCompareInfo 189 - type CompareInfo struct { 190 - HeadUser *user_model.User 191 - HeadRepo *repo_model.Repository 192 - HeadGitRepo *git.Repository 193 - CompareInfo *git.CompareInfo 194 - BaseBranch string 195 - HeadBranch string 196 - DirectComparison bool 197 - } 198 - 199 189 // ParseCompareInfo parse compare info between two commit for preparing comparing references 200 - func ParseCompareInfo(ctx *context.Context) *CompareInfo { 190 + func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { 201 191 baseRepo := ctx.Repo.Repository 202 - ci := &CompareInfo{} 192 + ci := &common.CompareInfo{} 203 193 204 194 fileOnly := ctx.FormBool("file-only") 205 195 ··· 576 566 // PrepareCompareDiff renders compare diff page 577 567 func PrepareCompareDiff( 578 568 ctx *context.Context, 579 - ci *CompareInfo, 569 + ci *common.CompareInfo, 580 570 whitespaceBehavior git.TrustedCmdArgs, 581 571 ) bool { 582 572 var (
+70
templates/swagger/v1_json.tmpl
··· 5210 5210 } 5211 5211 } 5212 5212 }, 5213 + "/repos/{owner}/{repo}/compare/{basehead}": { 5214 + "get": { 5215 + "produces": [ 5216 + "application/json" 5217 + ], 5218 + "tags": [ 5219 + "Get", 5220 + "commit", 5221 + "comparison" 5222 + ], 5223 + "summary": "Get commit comparison information", 5224 + "operationId": "information", 5225 + "parameters": [ 5226 + { 5227 + "type": "string", 5228 + "description": "owner of the repo", 5229 + "name": "owner", 5230 + "in": "path", 5231 + "required": true 5232 + }, 5233 + { 5234 + "type": "string", 5235 + "description": "name of the repo", 5236 + "name": "repo", 5237 + "in": "path", 5238 + "required": true 5239 + }, 5240 + { 5241 + "type": "string", 5242 + "description": "compare two branches or commits", 5243 + "name": "basehead", 5244 + "in": "path", 5245 + "required": true 5246 + } 5247 + ], 5248 + "responses": { 5249 + "200": { 5250 + "$ref": "#/responses/Compare" 5251 + }, 5252 + "404": { 5253 + "$ref": "#/responses/notFound" 5254 + } 5255 + } 5256 + } 5257 + }, 5213 5258 "/repos/{owner}/{repo}/contents": { 5214 5259 "get": { 5215 5260 "produces": [ ··· 19017 19062 }, 19018 19063 "x-go-package": "code.gitea.io/gitea/modules/structs" 19019 19064 }, 19065 + "Compare": { 19066 + "type": "object", 19067 + "title": "Compare represents a comparison between two commits.", 19068 + "properties": { 19069 + "commits": { 19070 + "type": "array", 19071 + "items": { 19072 + "$ref": "#/definitions/Commit" 19073 + }, 19074 + "x-go-name": "Commits" 19075 + }, 19076 + "total_commits": { 19077 + "type": "integer", 19078 + "format": "int64", 19079 + "x-go-name": "TotalCommits" 19080 + } 19081 + }, 19082 + "x-go-package": "code.gitea.io/gitea/modules/structs" 19083 + }, 19020 19084 "ContentsResponse": { 19021 19085 "description": "ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content", 19022 19086 "type": "object", ··· 25086 25150 "items": { 25087 25151 "$ref": "#/definitions/CommitStatus" 25088 25152 } 25153 + } 25154 + }, 25155 + "Compare": { 25156 + "description": "", 25157 + "schema": { 25158 + "$ref": "#/definitions/Compare" 25089 25159 } 25090 25160 }, 25091 25161 "ContentsListResponse": {
+38
tests/integration/api_repo_compare_test.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package integration 5 + 6 + import ( 7 + "net/http" 8 + "testing" 9 + 10 + auth_model "code.gitea.io/gitea/models/auth" 11 + "code.gitea.io/gitea/models/unittest" 12 + user_model "code.gitea.io/gitea/models/user" 13 + api "code.gitea.io/gitea/modules/structs" 14 + "code.gitea.io/gitea/tests" 15 + 16 + "github.com/stretchr/testify/assert" 17 + ) 18 + 19 + func TestAPICompareBranches(t *testing.T) { 20 + defer tests.PrepareTestEnv(t)() 21 + 22 + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 23 + // Login as User2. 24 + session := loginUser(t, user.Name) 25 + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 26 + 27 + repoName := "repo20" 28 + 29 + req := NewRequestf(t, "GET", "/api/v1/repos/user2/%s/compare/add-csv...remove-files-b", repoName). 30 + AddTokenAuth(token) 31 + resp := MakeRequest(t, req, http.StatusOK) 32 + 33 + var apiResp *api.Compare 34 + DecodeJSON(t, resp, &apiResp) 35 + 36 + assert.Equal(t, 2, apiResp.TotalCommits) 37 + assert.Len(t, apiResp.Commits, 2) 38 + }