Make API "compare" accept commit IDs (#32801)

This commit is contained in:
wxiaoguang 2024-12-12 16:10:09 +08:00 committed by GitHub
parent 01b1896bf5
commit 22bf2ca6ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 154 additions and 124 deletions

View File

@ -474,3 +474,17 @@ func (c *Commit) GetRepositoryDefaultPublicGPGKey(forceUpdate bool) (*GPGSetting
} }
return c.repo.GetDefaultPublicGPGKey(forceUpdate) return c.repo.GetDefaultPublicGPGKey(forceUpdate)
} }
func IsStringLikelyCommitID(objFmt ObjectFormat, s string, minLength ...int) bool {
minLen := util.OptionalArg(minLength, objFmt.FullLength())
if len(s) < minLen || len(s) > objFmt.FullLength() {
return false
}
for _, c := range s {
isHex := (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
if !isHex {
return false
}
}
return true
}

View File

@ -142,7 +142,6 @@ func (ref RefName) RemoteName() string {
// ShortName returns the short name of the reference name // ShortName returns the short name of the reference name
func (ref RefName) ShortName() string { func (ref RefName) ShortName() string {
refName := string(ref)
if ref.IsBranch() { if ref.IsBranch() {
return ref.BranchName() return ref.BranchName()
} }
@ -158,8 +157,7 @@ func (ref RefName) ShortName() string {
if ref.IsFor() { if ref.IsFor() {
return ref.ForBranchName() return ref.ForBranchName()
} }
return string(ref) // usually it is a commit ID
return refName
} }
// RefGroup returns the group type of the reference // RefGroup returns the group type of the reference

View File

@ -61,3 +61,31 @@ func parseTags(refs []string) []string {
} }
return results return results
} }
// UnstableGuessRefByShortName does the best guess to see whether a "short name" provided by user is a branch, tag or commit.
// It could guess wrongly if the input is already ambiguous. For example:
// * "refs/heads/the-name" vs "refs/heads/refs/heads/the-name"
// * "refs/tags/1234567890" vs commit "1234567890"
// In most cases, it SHOULD AVOID using this function, unless there is an irresistible reason (eg: make API friendly to end users)
// If the function is used, the caller SHOULD CHECK the ref type carefully.
func (repo *Repository) UnstableGuessRefByShortName(shortName string) RefName {
if repo.IsBranchExist(shortName) {
return RefNameFromBranch(shortName)
}
if repo.IsTagExist(shortName) {
return RefNameFromTag(shortName)
}
if strings.HasPrefix(shortName, "refs/") {
if repo.IsReferenceExist(shortName) {
return RefName(shortName)
}
}
commit, err := repo.GetCommit(shortName)
if err == nil {
commitIDString := commit.ID.String()
if strings.HasPrefix(commitIDString, shortName) {
return RefName(commitIDString)
}
}
return ""
}

View File

@ -64,7 +64,7 @@ func TestLockAndDo(t *testing.T) {
} }
func testLockAndDo(t *testing.T) { func testLockAndDo(t *testing.T) {
const concurrency = 1000 const concurrency = 50
ctx := context.Background() ctx := context.Background()
count := 0 count := 0

View File

@ -64,22 +64,19 @@ func CompareDiff(ctx *context.APIContext) {
} }
} }
_, headGitRepo, ci, _, _ := parseCompareInfo(ctx, api.CreatePullRequestOption{ compareResult, closer := parseCompareInfo(ctx, api.CreatePullRequestOption{Base: infos[0], Head: infos[1]})
Base: infos[0],
Head: infos[1],
})
if ctx.Written() { if ctx.Written() {
return return
} }
defer headGitRepo.Close() defer closer()
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification") verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
files := ctx.FormString("files") == "" || ctx.FormBool("files") files := ctx.FormString("files") == "" || ctx.FormBool("files")
apiCommits := make([]*api.Commit, 0, len(ci.Commits)) apiCommits := make([]*api.Commit, 0, len(compareResult.compareInfo.Commits))
userCache := make(map[string]*user_model.User) userCache := make(map[string]*user_model.User)
for i := 0; i < len(ci.Commits); i++ { for i := 0; i < len(compareResult.compareInfo.Commits); i++ {
apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ci.Commits[i], userCache, apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, compareResult.compareInfo.Commits[i], userCache,
convert.ToCommitOptions{ convert.ToCommitOptions{
Stat: true, Stat: true,
Verification: verification, Verification: verification,
@ -93,7 +90,7 @@ func CompareDiff(ctx *context.APIContext) {
} }
ctx.JSON(http.StatusOK, &api.Compare{ ctx.JSON(http.StatusOK, &api.Compare{
TotalCommits: len(ci.Commits), TotalCommits: len(compareResult.compareInfo.Commits),
Commits: apiCommits, Commits: apiCommits,
}) })
} }

View File

@ -389,8 +389,7 @@ func CreatePullRequest(ctx *context.APIContext) {
form := *web.GetForm(ctx).(*api.CreatePullRequestOption) form := *web.GetForm(ctx).(*api.CreatePullRequestOption)
if form.Head == form.Base { if form.Head == form.Base {
ctx.Error(http.StatusUnprocessableEntity, "BaseHeadSame", ctx.Error(http.StatusUnprocessableEntity, "BaseHeadSame", "Invalid PullRequest: There are no changes between the head and the base")
"Invalid PullRequest: There are no changes between the head and the base")
return return
} }
@ -401,14 +400,22 @@ func CreatePullRequest(ctx *context.APIContext) {
) )
// Get repo/branch information // Get repo/branch information
headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form) compareResult, closer := parseCompareInfo(ctx, form)
if ctx.Written() { if ctx.Written() {
return return
} }
defer headGitRepo.Close() defer closer()
if !compareResult.baseRef.IsBranch() || !compareResult.headRef.IsBranch() {
ctx.Error(http.StatusUnprocessableEntity, "BaseHeadInvalidRefType", "Invalid PullRequest: base and head must be branches")
return
}
// Check if another PR exists with the same targets // Check if another PR exists with the same targets
existingPr, err := issues_model.GetUnmergedPullRequest(ctx, headRepo.ID, ctx.Repo.Repository.ID, headBranch, baseBranch, issues_model.PullRequestFlowGithub) existingPr, err := issues_model.GetUnmergedPullRequest(ctx, compareResult.headRepo.ID, ctx.Repo.Repository.ID,
compareResult.headRef.ShortName(), compareResult.baseRef.ShortName(),
issues_model.PullRequestFlowGithub,
)
if err != nil { if err != nil {
if !issues_model.IsErrPullRequestNotExist(err) { if !issues_model.IsErrPullRequestNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetUnmergedPullRequest", err) ctx.Error(http.StatusInternalServerError, "GetUnmergedPullRequest", err)
@ -484,13 +491,13 @@ func CreatePullRequest(ctx *context.APIContext) {
DeadlineUnix: deadlineUnix, DeadlineUnix: deadlineUnix,
} }
pr := &issues_model.PullRequest{ pr := &issues_model.PullRequest{
HeadRepoID: headRepo.ID, HeadRepoID: compareResult.headRepo.ID,
BaseRepoID: repo.ID, BaseRepoID: repo.ID,
HeadBranch: headBranch, HeadBranch: compareResult.headRef.ShortName(),
BaseBranch: baseBranch, BaseBranch: compareResult.baseRef.ShortName(),
HeadRepo: headRepo, HeadRepo: compareResult.headRepo,
BaseRepo: repo, BaseRepo: repo,
MergeBase: compareInfo.MergeBase, MergeBase: compareResult.compareInfo.MergeBase,
Type: issues_model.PullRequestGitea, Type: issues_model.PullRequestGitea,
} }
@ -1080,32 +1087,32 @@ func MergePullRequest(ctx *context.APIContext) {
ctx.Status(http.StatusOK) ctx.Status(http.StatusOK)
} }
func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*repo_model.Repository, *git.Repository, *git.CompareInfo, string, string) { type parseCompareInfoResult struct {
baseRepo := ctx.Repo.Repository headRepo *repo_model.Repository
headGitRepo *git.Repository
compareInfo *git.CompareInfo
baseRef git.RefName
headRef git.RefName
}
// parseCompareInfo returns non-nil if it succeeds, it always writes to the context and returns nil if it fails
func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (result *parseCompareInfoResult, closer func()) {
var err error
// Get compared branches information // Get compared branches information
// format: <base branch>...[<head repo>:]<head branch> // format: <base branch>...[<head repo>:]<head branch>
// base<-head: master...head:feature // base<-head: master...head:feature
// same repo: master...feature // same repo: master...feature
baseRepo := ctx.Repo.Repository
baseRefToGuess := form.Base
// TODO: Validate form first? headUser := ctx.Repo.Owner
headRefToGuess := form.Head
baseBranch := form.Base if headInfos := strings.Split(form.Head, ":"); len(headInfos) == 1 {
// If there is no head repository, it means pull request between same repository.
var ( // Do nothing here because the head variables have been assigned above.
headUser *user_model.User
headBranch string
isSameRepo bool
err error
)
// If there is no head repository, it means pull request between same repository.
headInfos := strings.Split(form.Head, ":")
if len(headInfos) == 1 {
isSameRepo = true
headUser = ctx.Repo.Owner
headBranch = headInfos[0]
} else if len(headInfos) == 2 { } else if len(headInfos) == 2 {
// There is a head repository (the head repository could also be the same base repo)
headRefToGuess = headInfos[1]
headUser, err = user_model.GetUserByName(ctx, headInfos[0]) headUser, err = user_model.GetUserByName(ctx, headInfos[0])
if err != nil { if err != nil {
if user_model.IsErrUserNotExist(err) { if user_model.IsErrUserNotExist(err) {
@ -1113,38 +1120,29 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
} else { } else {
ctx.Error(http.StatusInternalServerError, "GetUserByName", err) ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
} }
return nil, nil, nil, "", "" return nil, nil
} }
headBranch = headInfos[1]
// The head repository can also point to the same repo
isSameRepo = ctx.Repo.Owner.ID == headUser.ID
} else { } else {
ctx.NotFound() ctx.NotFound()
return nil, nil, nil, "", "" return nil, nil
} }
ctx.Repo.PullRequest.SameRepo = isSameRepo isSameRepo := ctx.Repo.Owner.ID == headUser.ID
log.Trace("Repo path: %q, base branch: %q, head branch: %q", ctx.Repo.GitRepo.Path, baseBranch, headBranch)
// Check if base branch is valid.
if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) && !ctx.Repo.GitRepo.IsTagExist(baseBranch) {
ctx.NotFound("BaseNotExist")
return nil, nil, nil, "", ""
}
// Check if current user has fork of repository or in the same repository. // Check if current user has fork of repository or in the same repository.
headRepo := repo_model.GetForkedRepo(ctx, headUser.ID, baseRepo.ID) headRepo := repo_model.GetForkedRepo(ctx, headUser.ID, baseRepo.ID)
if headRepo == nil && !isSameRepo { if headRepo == nil && !isSameRepo {
err := baseRepo.GetBaseRepo(ctx) err = baseRepo.GetBaseRepo(ctx)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBaseRepo", err) ctx.Error(http.StatusInternalServerError, "GetBaseRepo", err)
return nil, nil, nil, "", "" return nil, nil
} }
// Check if baseRepo's base repository is the same as headUser's repository. // Check if baseRepo's base repository is the same as headUser's repository.
if baseRepo.BaseRepo == nil || baseRepo.BaseRepo.OwnerID != headUser.ID { if baseRepo.BaseRepo == nil || baseRepo.BaseRepo.OwnerID != headUser.ID {
log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID) log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
ctx.NotFound("GetBaseRepo") ctx.NotFound("GetBaseRepo")
return nil, nil, nil, "", "" return nil, nil
} }
// Assign headRepo so it can be used below. // Assign headRepo so it can be used below.
headRepo = baseRepo.BaseRepo headRepo = baseRepo.BaseRepo
@ -1154,67 +1152,68 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
if isSameRepo { if isSameRepo {
headRepo = ctx.Repo.Repository headRepo = ctx.Repo.Repository
headGitRepo = ctx.Repo.GitRepo headGitRepo = ctx.Repo.GitRepo
closer = func() {} // no need to close the head repo because it shares the base repo
} else { } else {
headGitRepo, err = gitrepo.OpenRepository(ctx, headRepo) headGitRepo, err = gitrepo.OpenRepository(ctx, headRepo)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err) ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return nil, nil, nil, "", "" return nil, nil
} }
closer = func() { _ = headGitRepo.Close() }
} }
defer func() {
if result == nil && !isSameRepo {
_ = headGitRepo.Close()
}
}()
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's // user should have permission to read baseRepo's codes and pulls, NOT headRepo's
permBase, err := access_model.GetUserRepoPermission(ctx, baseRepo, ctx.Doer) permBase, err := access_model.GetUserRepoPermission(ctx, baseRepo, ctx.Doer)
if err != nil { if err != nil {
headGitRepo.Close()
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
return nil, nil, nil, "", "" return nil, nil
}
if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(unit.TypeCode) {
if log.IsTrace() {
log.Trace("Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v",
ctx.Doer,
baseRepo,
permBase)
}
headGitRepo.Close()
ctx.NotFound("Can't read pulls or can't read UnitTypeCode")
return nil, nil, nil, "", ""
} }
// user should have permission to read headrepo's codes if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(unit.TypeCode) {
log.Trace("Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v", ctx.Doer, baseRepo, permBase)
ctx.NotFound("Can't read pulls or can't read UnitTypeCode")
return nil, nil
}
// user should have permission to read headRepo's codes
// TODO: could the logic be simplified if the headRepo is the same as the baseRepo? Need to think more about it.
permHead, err := access_model.GetUserRepoPermission(ctx, headRepo, ctx.Doer) permHead, err := access_model.GetUserRepoPermission(ctx, headRepo, ctx.Doer)
if err != nil { if err != nil {
headGitRepo.Close()
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
return nil, nil, nil, "", "" return nil, nil
} }
if !permHead.CanRead(unit.TypeCode) { if !permHead.CanRead(unit.TypeCode) {
if log.IsTrace() { log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v", ctx.Doer, headRepo, permHead)
log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v",
ctx.Doer,
headRepo,
permHead)
}
headGitRepo.Close()
ctx.NotFound("Can't read headRepo UnitTypeCode") ctx.NotFound("Can't read headRepo UnitTypeCode")
return nil, nil, nil, "", "" return nil, nil
} }
// Check if head branch is valid. baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(baseRefToGuess)
if !headGitRepo.IsBranchExist(headBranch) && !headGitRepo.IsTagExist(headBranch) { headRef := headGitRepo.UnstableGuessRefByShortName(headRefToGuess)
headGitRepo.Close()
log.Trace("Repo path: %q, base ref: %q->%q, head ref: %q->%q", ctx.Repo.GitRepo.Path, baseRefToGuess, baseRef, headRefToGuess, headRef)
baseRefValid := baseRef.IsBranch() || baseRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(ctx.Repo.Repository.ObjectFormatName), baseRef.ShortName())
headRefValid := headRef.IsBranch() || headRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(headRepo.ObjectFormatName), headRef.ShortName())
// Check if base&head ref are valid.
if !baseRefValid || !headRefValid {
ctx.NotFound() ctx.NotFound()
return nil, nil, nil, "", "" return nil, nil
} }
compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch, false, false) compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseRef.ShortName(), headRef.ShortName(), false, false)
if err != nil { if err != nil {
headGitRepo.Close()
ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err) ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err)
return nil, nil, nil, "", "" return nil, nil
} }
return headRepo, headGitRepo, compareInfo, baseBranch, headBranch result = &parseCompareInfoResult{headRepo: headRepo, headGitRepo: headGitRepo, compareInfo: compareInfo, baseRef: baseRef, headRef: headRef}
return result, closer
} }
// UpdatePullRequest merge PR's baseBranch into headBranch // UpdatePullRequest merge PR's baseBranch into headBranch

View File

@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"strings" "strings"
@ -114,7 +113,6 @@ func MustAllowPulls(ctx *context.Context) {
// User can send pull request if owns a forked repository. // User can send pull request if owns a forked repository.
if ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) { if ctx.IsSigned && repo_model.HasForkedRepo(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) {
ctx.Repo.PullRequest.Allowed = true ctx.Repo.PullRequest.Allowed = true
ctx.Repo.PullRequest.HeadInfoSubURL = url.PathEscape(ctx.Doer.Name) + ":" + util.PathEscapeSegments(ctx.Repo.BranchName)
} }
} }

View File

@ -39,10 +39,9 @@ import (
// PullRequest contains information to make a pull request // PullRequest contains information to make a pull request
type PullRequest struct { type PullRequest struct {
BaseRepo *repo_model.Repository BaseRepo *repo_model.Repository
Allowed bool Allowed bool // it only used by the web tmpl: "PullRequestCtx.Allowed"
SameRepo bool SameRepo bool // it only used by the web tmpl: "PullRequestCtx.SameRepo"
HeadInfoSubURL string // [<user>:]<branch> url segment
} }
// Repository contains information to operate a repository // Repository contains information to operate a repository
@ -401,6 +400,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
// RepoAssignment returns a middleware to handle repository assignment // RepoAssignment returns a middleware to handle repository assignment
func RepoAssignment(ctx *Context) context.CancelFunc { func RepoAssignment(ctx *Context) context.CancelFunc {
if _, repoAssignmentOnce := ctx.Data["repoAssignmentExecuted"]; repoAssignmentOnce { if _, repoAssignmentOnce := ctx.Data["repoAssignmentExecuted"]; repoAssignmentOnce {
// FIXME: it should panic in dev/test modes to have a clear behavior
log.Trace("RepoAssignment was exec already, skipping second call ...") log.Trace("RepoAssignment was exec already, skipping second call ...")
return nil return nil
} }
@ -697,7 +697,6 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
ctx.Data["BaseRepo"] = repo.BaseRepo ctx.Data["BaseRepo"] = repo.BaseRepo
ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
ctx.Repo.PullRequest.Allowed = canPush ctx.Repo.PullRequest.Allowed = canPush
ctx.Repo.PullRequest.HeadInfoSubURL = url.PathEscape(ctx.Repo.Owner.Name) + ":" + util.PathEscapeSegments(ctx.Repo.BranchName)
} else if repo.AllowsPulls(ctx) { } else if repo.AllowsPulls(ctx) {
// Or, this is repository accepts pull requests between branches. // Or, this is repository accepts pull requests between branches.
canCompare = true canCompare = true
@ -705,7 +704,6 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
ctx.Repo.PullRequest.BaseRepo = repo ctx.Repo.PullRequest.BaseRepo = repo
ctx.Repo.PullRequest.Allowed = canPush ctx.Repo.PullRequest.Allowed = canPush
ctx.Repo.PullRequest.SameRepo = true ctx.Repo.PullRequest.SameRepo = true
ctx.Repo.PullRequest.HeadInfoSubURL = util.PathEscapeSegments(ctx.Repo.BranchName)
} }
ctx.Data["CanCompareOrPull"] = canCompare ctx.Data["CanCompareOrPull"] = canCompare
ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
@ -771,20 +769,6 @@ func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool
return "" return ""
} }
func isStringLikelyCommitID(objFmt git.ObjectFormat, s string, minLength ...int) bool {
minLen := util.OptionalArg(minLength, objFmt.FullLength())
if len(s) < minLen || len(s) > objFmt.FullLength() {
return false
}
for _, c := range s {
isHex := (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
if !isHex {
return false
}
}
return true
}
func getRefNameLegacy(ctx *Base, repo *Repository, optionalExtraRef ...string) (string, RepoRefType) { func getRefNameLegacy(ctx *Base, repo *Repository, optionalExtraRef ...string) (string, RepoRefType) {
extraRef := util.OptionalArg(optionalExtraRef) extraRef := util.OptionalArg(optionalExtraRef)
reqPath := ctx.PathParam("*") reqPath := ctx.PathParam("*")
@ -799,7 +783,7 @@ func getRefNameLegacy(ctx *Base, repo *Repository, optionalExtraRef ...string) (
// For legacy support only full commit sha // For legacy support only full commit sha
parts := strings.Split(reqPath, "/") parts := strings.Split(reqPath, "/")
if isStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), parts[0]) { if git.IsStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), parts[0]) {
// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists // FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
repo.TreePath = strings.Join(parts[1:], "/") repo.TreePath = strings.Join(parts[1:], "/")
return parts[0], RepoRefCommit return parts[0], RepoRefCommit
@ -849,7 +833,7 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
return getRefNameFromPath(repo, path, repo.GitRepo.IsTagExist) return getRefNameFromPath(repo, path, repo.GitRepo.IsTagExist)
case RepoRefCommit: case RepoRefCommit:
parts := strings.Split(path, "/") parts := strings.Split(path, "/")
if isStringLikelyCommitID(repo.GetObjectFormat(), parts[0], 7) { if git.IsStringLikelyCommitID(repo.GetObjectFormat(), parts[0], 7) {
// FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists // FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
repo.TreePath = strings.Join(parts[1:], "/") repo.TreePath = strings.Join(parts[1:], "/")
return parts[0] return parts[0]
@ -985,7 +969,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
return cancel return cancel
} }
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
} else if isStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refName, 7) { } else if git.IsStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refName, 7) {
ctx.Repo.IsViewCommit = true ctx.Repo.IsViewCommit = true
ctx.Repo.CommitID = refName ctx.Repo.CommitID = refName

View File

@ -24,15 +24,27 @@ func TestAPICompareBranches(t *testing.T) {
session := loginUser(t, user.Name) session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
repoName := "repo20" t.Run("CompareBranches", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv...remove-files-b").AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
req := NewRequestf(t, "GET", "/api/v1/repos/user2/%s/compare/add-csv...remove-files-b", repoName). var apiResp *api.Compare
AddTokenAuth(token) DecodeJSON(t, resp, &apiResp)
resp := MakeRequest(t, req, http.StatusOK)
var apiResp *api.Compare assert.Equal(t, 2, apiResp.TotalCommits)
DecodeJSON(t, resp, &apiResp) assert.Len(t, apiResp.Commits, 2)
})
assert.Equal(t, 2, apiResp.TotalCommits) t.Run("CompareCommits", func(t *testing.T) {
assert.Len(t, apiResp.Commits, 2) defer tests.PrintCurrentTest(t)()
req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/808038d2f71b0ab02099...c8e31bc7688741a5287f").AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var apiResp *api.Compare
DecodeJSON(t, resp, &apiResp)
assert.Equal(t, 1, apiResp.TotalCommits)
assert.Len(t, apiResp.Commits, 1)
})
} }

View File

@ -440,7 +440,7 @@ func DecodeJSON(t testing.TB, resp *httptest.ResponseRecorder, v any) {
t.Helper() t.Helper()
decoder := json.NewDecoder(resp.Body) decoder := json.NewDecoder(resp.Body)
assert.NoError(t, decoder.Decode(v)) require.NoError(t, decoder.Decode(v))
} }
func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile string) { func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile string) {