- 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>tags/v1.22.0-rc1
@@ -0,0 +1,10 @@ | |||
// Copyright 2024 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package structs | |||
// Compare represents a comparison between two commits. | |||
type Compare struct { | |||
TotalCommits int `json:"total_commits"` // Total number of commits in the comparison. | |||
Commits []*Commit `json:"commits"` // List of commits in the comparison. | |||
} |
@@ -1066,6 +1066,8 @@ func Routes() *web.Route { | |||
m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate) | |||
m.Group("/{username}/{reponame}", func() { | |||
m.Get("/compare/*", reqRepoReader(unit.TypeCode), repo.CompareDiff) | |||
m.Combo("").Get(reqAnyRepoReader(), repo.Get). | |||
Delete(reqToken(), reqOwner(), repo.Delete). | |||
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit) |
@@ -0,0 +1,99 @@ | |||
// Copyright 2024 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package repo | |||
import ( | |||
"net/http" | |||
"strings" | |||
user_model "code.gitea.io/gitea/models/user" | |||
"code.gitea.io/gitea/modules/gitrepo" | |||
api "code.gitea.io/gitea/modules/structs" | |||
"code.gitea.io/gitea/services/context" | |||
"code.gitea.io/gitea/services/convert" | |||
) | |||
// CompareDiff compare two branches or commits | |||
func CompareDiff(ctx *context.APIContext) { | |||
// swagger:operation GET /repos/{owner}/{repo}/compare/{basehead} Get commit comparison information | |||
// --- | |||
// summary: Get commit comparison information | |||
// 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: basehead | |||
// in: path | |||
// description: compare two branches or commits | |||
// type: string | |||
// required: true | |||
// responses: | |||
// "200": | |||
// "$ref": "#/responses/Compare" | |||
// "404": | |||
// "$ref": "#/responses/notFound" | |||
if ctx.Repo.GitRepo == nil { | |||
gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository) | |||
if err != nil { | |||
ctx.Error(http.StatusInternalServerError, "OpenRepository", err) | |||
return | |||
} | |||
ctx.Repo.GitRepo = gitRepo | |||
defer gitRepo.Close() | |||
} | |||
infoPath := ctx.Params("*") | |||
infos := []string{ctx.Repo.Repository.DefaultBranch, ctx.Repo.Repository.DefaultBranch} | |||
if infoPath != "" { | |||
infos = strings.SplitN(infoPath, "...", 2) | |||
if len(infos) != 2 { | |||
if infos = strings.SplitN(infoPath, "..", 2); len(infos) != 2 { | |||
infos = []string{ctx.Repo.Repository.DefaultBranch, infoPath} | |||
} | |||
} | |||
} | |||
_, _, headGitRepo, ci, _, _ := parseCompareInfo(ctx, api.CreatePullRequestOption{ | |||
Base: infos[0], | |||
Head: infos[1], | |||
}) | |||
if ctx.Written() { | |||
return | |||
} | |||
defer headGitRepo.Close() | |||
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification") | |||
files := ctx.FormString("files") == "" || ctx.FormBool("files") | |||
apiCommits := make([]*api.Commit, 0, len(ci.Commits)) | |||
userCache := make(map[string]*user_model.User) | |||
for i := 0; i < len(ci.Commits); i++ { | |||
apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ci.Commits[i], userCache, | |||
convert.ToCommitOptions{ | |||
Stat: true, | |||
Verification: verification, | |||
Files: files, | |||
}) | |||
if err != nil { | |||
ctx.ServerError("toCommit", err) | |||
return | |||
} | |||
apiCommits = append(apiCommits, apiCommit) | |||
} | |||
ctx.JSON(http.StatusOK, &api.Compare{ | |||
TotalCommits: len(ci.Commits), | |||
Commits: apiCommits, | |||
}) | |||
} |
@@ -414,3 +414,9 @@ type swaggerRepoNewIssuePinsAllowed struct { | |||
// in:body | |||
Body api.NewIssuePinsAllowed `json:"body"` | |||
} | |||
// swagger:response Compare | |||
type swaggerCompare struct { | |||
// in:body | |||
Body api.Compare `json:"body"` | |||
} |
@@ -0,0 +1,21 @@ | |||
// Copyright 2024 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package common | |||
import ( | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
user_model "code.gitea.io/gitea/models/user" | |||
"code.gitea.io/gitea/modules/git" | |||
) | |||
// CompareInfo represents the collected results from ParseCompareInfo | |||
type CompareInfo struct { | |||
HeadUser *user_model.User | |||
HeadRepo *repo_model.Repository | |||
HeadGitRepo *git.Repository | |||
CompareInfo *git.CompareInfo | |||
BaseBranch string | |||
HeadBranch string | |||
DirectComparison bool | |||
} |
@@ -35,6 +35,7 @@ import ( | |||
api "code.gitea.io/gitea/modules/structs" | |||
"code.gitea.io/gitea/modules/typesniffer" | |||
"code.gitea.io/gitea/modules/util" | |||
"code.gitea.io/gitea/routers/common" | |||
"code.gitea.io/gitea/services/context" | |||
"code.gitea.io/gitea/services/context/upload" | |||
"code.gitea.io/gitea/services/gitdiff" | |||
@@ -185,21 +186,10 @@ func setCsvCompareContext(ctx *context.Context) { | |||
} | |||
} | |||
// CompareInfo represents the collected results from ParseCompareInfo | |||
type CompareInfo struct { | |||
HeadUser *user_model.User | |||
HeadRepo *repo_model.Repository | |||
HeadGitRepo *git.Repository | |||
CompareInfo *git.CompareInfo | |||
BaseBranch string | |||
HeadBranch string | |||
DirectComparison bool | |||
} | |||
// ParseCompareInfo parse compare info between two commit for preparing comparing references | |||
func ParseCompareInfo(ctx *context.Context) *CompareInfo { | |||
func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { | |||
baseRepo := ctx.Repo.Repository | |||
ci := &CompareInfo{} | |||
ci := &common.CompareInfo{} | |||
fileOnly := ctx.FormBool("file-only") | |||
@@ -576,7 +566,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { | |||
// PrepareCompareDiff renders compare diff page | |||
func PrepareCompareDiff( | |||
ctx *context.Context, | |||
ci *CompareInfo, | |||
ci *common.CompareInfo, | |||
whitespaceBehavior git.TrustedCmdArgs, | |||
) bool { | |||
var ( |
@@ -5340,6 +5340,51 @@ | |||
} | |||
} | |||
}, | |||
"/repos/{owner}/{repo}/compare/{basehead}": { | |||
"get": { | |||
"produces": [ | |||
"application/json" | |||
], | |||
"tags": [ | |||
"Get", | |||
"commit", | |||
"comparison" | |||
], | |||
"summary": "Get commit comparison information", | |||
"operationId": "information", | |||
"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": "string", | |||
"description": "compare two branches or commits", | |||
"name": "basehead", | |||
"in": "path", | |||
"required": true | |||
} | |||
], | |||
"responses": { | |||
"200": { | |||
"$ref": "#/responses/Compare" | |||
}, | |||
"404": { | |||
"$ref": "#/responses/notFound" | |||
} | |||
} | |||
} | |||
}, | |||
"/repos/{owner}/{repo}/contents": { | |||
"get": { | |||
"produces": [ | |||
@@ -18717,6 +18762,25 @@ | |||
}, | |||
"x-go-package": "code.gitea.io/gitea/modules/structs" | |||
}, | |||
"Compare": { | |||
"type": "object", | |||
"title": "Compare represents a comparison between two commits.", | |||
"properties": { | |||
"commits": { | |||
"type": "array", | |||
"items": { | |||
"$ref": "#/definitions/Commit" | |||
}, | |||
"x-go-name": "Commits" | |||
}, | |||
"total_commits": { | |||
"type": "integer", | |||
"format": "int64", | |||
"x-go-name": "TotalCommits" | |||
} | |||
}, | |||
"x-go-package": "code.gitea.io/gitea/modules/structs" | |||
}, | |||
"ContentsResponse": { | |||
"description": "ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content", | |||
"type": "object", | |||
@@ -24678,6 +24742,12 @@ | |||
} | |||
} | |||
}, | |||
"Compare": { | |||
"description": "", | |||
"schema": { | |||
"$ref": "#/definitions/Compare" | |||
} | |||
}, | |||
"ContentsListResponse": { | |||
"description": "ContentsListResponse", | |||
"schema": { |
@@ -0,0 +1,38 @@ | |||
// Copyright 2024 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package integration | |||
import ( | |||
"net/http" | |||
"testing" | |||
auth_model "code.gitea.io/gitea/models/auth" | |||
"code.gitea.io/gitea/models/unittest" | |||
user_model "code.gitea.io/gitea/models/user" | |||
api "code.gitea.io/gitea/modules/structs" | |||
"code.gitea.io/gitea/tests" | |||
"github.com/stretchr/testify/assert" | |||
) | |||
func TestAPICompareBranches(t *testing.T) { | |||
defer tests.PrepareTestEnv(t)() | |||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) | |||
// Login as User2. | |||
session := loginUser(t, user.Name) | |||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) | |||
repoName := "repo20" | |||
req := NewRequestf(t, "GET", "/api/v1/repos/user2/%s/compare/add-csv...remove-files-b", repoName). | |||
AddTokenAuth(token) | |||
resp := MakeRequest(t, req, http.StatusOK) | |||
var apiResp *api.Compare | |||
DecodeJSON(t, resp, &apiResp) | |||
assert.Equal(t, 2, apiResp.TotalCommits) | |||
assert.Len(t, apiResp.Commits, 2) | |||
} |