// 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<-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 }