0
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-12-08 17:55:50 +01:00
gitea/routers/common/compare.go
2025-12-07 22:14:02 -08:00

136 lines
4.1 KiB
Go

// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package common
import (
"strings"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/util"
pull_service "code.gitea.io/gitea/services/pull"
)
// CompareInfo represents the collected results from ParseCompareInfo
type CompareInfo struct {
HeadUser *user_model.User
HeadRepo *repo_model.Repository
HeadGitRepo *git.Repository
CompareInfo *pull_service.CompareInfo
BaseBranch string
HeadBranch string
DirectComparison bool
}
type CompareRouterReq struct {
BaseOriRef string
HeadOwner string
HeadRepoName string
HeadOriRef string
CaretTimes int // ^ times after base ref
DotTimes int
}
func (cr *CompareRouterReq) DirectComparison() bool {
return cr.DotTimes == 2
}
func (cr *CompareRouterReq) CompareDots() string {
return strings.Repeat(".", cr.DotTimes)
}
func parseBase(base string) (string, int) {
parts := strings.SplitN(base, "^", 2)
if len(parts) == 1 {
return base, 0
}
return parts[0], len(parts[1]) + 1
}
func parseHead(head string) (string, string, string) {
paths := strings.SplitN(head, ":", 2)
if len(paths) == 1 {
return "", "", paths[0]
}
ownerRepo := strings.SplitN(paths[0], "/", 2)
if len(ownerRepo) == 1 {
return paths[0], "", paths[1]
}
return ownerRepo[0], ownerRepo[1], paths[1]
}
// ParseCompareRouterParam Get compare information from the router parameter.
// A full compare url is of the form:
//
// 1. /{:baseOwner}/{:baseRepoName}/compare/{:baseBranch}...{:headBranch}
// 2. /{:baseOwner}/{:baseRepoName}/compare/{:baseBranch}...{:headOwner}:{:headBranch}
// 3. /{:baseOwner}/{:baseRepoName}/compare/{:baseBranch}...{:headOwner}/{:headRepoName}:{:headBranch}
// 4. /{:baseOwner}/{:baseRepoName}/compare/{:headBranch}
// 5. /{:baseOwner}/{:baseRepoName}/compare/{:headOwner}:{:headBranch}
// 6. /{:baseOwner}/{:baseRepoName}/compare/{:headOwner}/{:headRepoName}:{:headBranch}
//
// Here we obtain the infoPath "{:baseBranch}...[{:headOwner}/{:headRepoName}:]{:headBranch}" as ctx.PathParam("*")
// with the :baseRepo in ctx.Repo.
//
// Note: Generally :headRepoName is not provided here - we are only passed :headOwner.
//
// How do we determine the :headRepo?
//
// 1. If :headOwner is not set then the :headRepo = :baseRepo
// 2. If :headOwner is set - then look for the fork of :baseRepo owned by :headOwner
// 3. But... :baseRepo could be a fork of :headOwner's repo - so check that
// 4. Now, :baseRepo and :headRepos could be forks of the same repo - so check that
//
// format: <base branch>...[<head repo>:]<head branch>
// base<-head: master...head:feature
// same repo: master...feature
func ParseCompareRouterParam(baseRepo *repo_model.Repository, routerParam string) (*CompareRouterReq, error) {
if routerParam == "" {
return &CompareRouterReq{
BaseOriRef: baseRepo.DefaultBranch,
HeadOwner: baseRepo.Owner.Name,
HeadRepoName: baseRepo.Name,
HeadOriRef: baseRepo.DefaultBranch,
DotTimes: 2,
}, nil
}
var basePart, headPart string
dotTimes := 3
parts := strings.Split(routerParam, "...")
if len(parts) > 2 {
return nil, util.NewInvalidArgumentErrorf("invalid compare router: %s", routerParam)
}
if len(parts) != 2 {
parts = strings.Split(routerParam, "..")
if len(parts) == 1 {
headOwnerName, headRepoName, headRef := parseHead(routerParam)
return &CompareRouterReq{
BaseOriRef: baseRepo.DefaultBranch,
HeadOriRef: headRef,
HeadOwner: headOwnerName,
HeadRepoName: headRepoName,
DotTimes: dotTimes,
}, nil
} else if len(parts) > 2 {
return nil, util.NewInvalidArgumentErrorf("invalid compare router: %s", routerParam)
}
dotTimes = 2
}
basePart, headPart = parts[0], parts[1]
baseRef, caretTimes := parseBase(basePart)
headOwnerName, headRepoName, headRef := parseHead(headPart)
return &CompareRouterReq{
BaseOriRef: baseRef,
HeadOriRef: headRef,
HeadOwner: headOwnerName,
HeadRepoName: headRepoName,
CaretTimes: caretTimes,
DotTimes: dotTimes,
}, nil
}