Skip to content

Commit c70e442

Browse files
appleboylunny
andauthored
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 <[email protected]> Co-authored-by: Lunny Xiao <[email protected]>
1 parent 3b045ee commit c70e442

File tree

8 files changed

+250
-14
lines changed

8 files changed

+250
-14
lines changed

modules/structs/repo_compare.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
}

routers/api/v1/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,8 @@ func Routes() *web.Route {
10661066
m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate)
10671067

10681068
m.Group("/{username}/{reponame}", func() {
1069+
m.Get("/compare/*", reqRepoReader(unit.TypeCode), repo.CompareDiff)
1070+
10691071
m.Combo("").Get(reqAnyRepoReader(), repo.Get).
10701072
Delete(reqToken(), reqOwner(), repo.Delete).
10711073
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)

routers/api/v1/repo/compare.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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+
}

routers/api/v1/swagger/repo.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,3 +414,9 @@ type swaggerRepoNewIssuePinsAllowed struct {
414414
// in:body
415415
Body api.NewIssuePinsAllowed `json:"body"`
416416
}
417+
418+
// swagger:response Compare
419+
type swaggerCompare struct {
420+
// in:body
421+
Body api.Compare `json:"body"`
422+
}

routers/common/compare.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
}

routers/web/repo/compare.go

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
api "code.gitea.io/gitea/modules/structs"
3636
"code.gitea.io/gitea/modules/typesniffer"
3737
"code.gitea.io/gitea/modules/util"
38+
"code.gitea.io/gitea/routers/common"
3839
"code.gitea.io/gitea/services/context"
3940
"code.gitea.io/gitea/services/context/upload"
4041
"code.gitea.io/gitea/services/gitdiff"
@@ -185,21 +186,10 @@ func setCsvCompareContext(ctx *context.Context) {
185186
}
186187
}
187188

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-
199189
// 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 {
201191
baseRepo := ctx.Repo.Repository
202-
ci := &CompareInfo{}
192+
ci := &common.CompareInfo{}
203193

204194
fileOnly := ctx.FormBool("file-only")
205195

@@ -576,7 +566,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo {
576566
// PrepareCompareDiff renders compare diff page
577567
func PrepareCompareDiff(
578568
ctx *context.Context,
579-
ci *CompareInfo,
569+
ci *common.CompareInfo,
580570
whitespaceBehavior git.TrustedCmdArgs,
581571
) bool {
582572
var (

templates/swagger/v1_json.tmpl

Lines changed: 70 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
}

0 commit comments

Comments
 (0)