diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index ecc8e66702b6f..52fe930b08b63 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -14,6 +14,7 @@ import ( "net/http" "net/url" "path/filepath" + "regexp" "strings" "code.gitea.io/gitea/models/db" @@ -178,13 +179,74 @@ 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 + HeadUser *user_model.User + HeadRepo *repo_model.Repository + HeadGitRepo *git.Repository + CompareInfo *git.CompareInfo + compareRouter +} + +var regCompare = regexp.MustCompile(`(?P[^\^\.]+)(?P\^*)(?P\.*)(?P.*)`) + +type compareRouter struct { + BaseBranch string + HeadBranch string + HeadOwner string + HeadRepoName string + CaretTimes int + DotTimes int +} + +func (cr *compareRouter) IsSameRepo() bool { + return cr.HeadOwner == "" && cr.HeadRepoName == "" +} + +func (cr *compareRouter) IsSameBranch() bool { + return cr.IsSameRepo() && cr.BaseBranch == cr.HeadBranch +} + +func (cr *compareRouter) DirectComparison() bool { + return cr.DotTimes == 2 +} + +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] +} + +func parseCompareRouters(router string) compareRouter { + matches := regCompare.FindStringSubmatch(router) + result := make(map[string]string) + for i, name := range regCompare.SubexpNames() { + if i != 0 && name != "" { + result[name] = matches[i] + } + } + + base := result["base"] + head := result["head"] + if result["dot"] == "" { + head = base + base = "" + } + + headOwner, headRepo, headBranch := parseHead(head) + + return compareRouter{ + BaseBranch: base, + HeadBranch: headBranch, + HeadOwner: headOwner, + HeadRepoName: headRepo, + CaretTimes: len(result["caret"]), + DotTimes: len(result["dot"]), + } } // ParseCompareInfo parse compare info between two commit for preparing comparing references @@ -197,9 +259,9 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { // Get compared branches information // 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} + // 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} @@ -221,57 +283,35 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { // same repo: master...feature var ( - isSameRepo bool - infoPath string - err error + infoPath string + err error ) infoPath = ctx.Params("*") - var infos []string + if infoPath == "" { - infos = []string{baseRepo.DefaultBranch, baseRepo.DefaultBranch} + ci.BaseBranch = baseRepo.DefaultBranch + ci.HeadBranch = baseRepo.DefaultBranch } else { - infos = strings.SplitN(infoPath, "...", 2) - if len(infos) != 2 { - if infos = strings.SplitN(infoPath, "..", 2); len(infos) == 2 { - ci.DirectComparison = true - ctx.Data["PageIsComparePull"] = false - } else { - infos = []string{baseRepo.DefaultBranch, infoPath} - } + ci.compareRouter = parseCompareRouters(infoPath) + if ci.BaseBranch == "" { + ci.BaseBranch = baseRepo.DefaultBranch } } - ctx.Data["BaseName"] = baseRepo.OwnerName - ci.BaseBranch = infos[0] - ctx.Data["BaseBranch"] = ci.BaseBranch - - // If there is no head repository, it means compare between same repository. - headInfos := strings.Split(infos[1], ":") - if len(headInfos) == 1 { - isSameRepo = true - ci.HeadUser = ctx.Repo.Owner - ci.HeadBranch = headInfos[0] - - } else if len(headInfos) == 2 { - headInfosSplit := strings.Split(headInfos[0], "/") - if len(headInfosSplit) == 1 { - ci.HeadUser, err = user_model.GetUserByName(ctx, headInfos[0]) - if err != nil { - if user_model.IsErrUserNotExist(err) { - ctx.NotFound("GetUserByName", nil) - } else { - ctx.ServerError("GetUserByName", err) - } - return nil - } - ci.HeadBranch = headInfos[1] - isSameRepo = ci.HeadUser.ID == ctx.Repo.Owner.ID - if isSameRepo { - ci.HeadRepo = baseRepo + if ci.HeadOwner != "" { + ci.HeadUser, err = user_model.GetUserByName(ctx, ci.HeadOwner) + if err != nil { + if user_model.IsErrUserNotExist(err) { + ctx.NotFound("GetUserByName", nil) + } else { + ctx.ServerError("GetUserByName", err) } - } else { - ci.HeadRepo, err = repo_model.GetRepositoryByOwnerAndName(ctx, headInfosSplit[0], headInfosSplit[1]) + return nil + } + + if ci.HeadRepoName != "" { + ci.HeadRepo, err = repo_model.GetRepositoryByOwnerAndName(ctx, ci.HeadOwner, ci.HeadRepoName) if err != nil { if repo_model.IsErrRepoNotExist(err) { ctx.NotFound("GetRepositoryByOwnerAndName", nil) @@ -280,25 +320,22 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { } return nil } - if err := ci.HeadRepo.LoadOwner(ctx); err != nil { - if user_model.IsErrUserNotExist(err) { - ctx.NotFound("GetUserByName", nil) - } else { - ctx.ServerError("GetUserByName", err) - } - return nil - } - ci.HeadBranch = headInfos[1] - ci.HeadUser = ci.HeadRepo.Owner - isSameRepo = ci.HeadRepo.ID == ctx.Repo.Repository.ID + ci.HeadRepo.Owner = ci.HeadUser } } else { - ctx.NotFound("CompareAndPullRequest", nil) - return nil + ci.HeadUser = baseRepo.Owner + ci.HeadRepo = baseRepo } + + if ci.DirectComparison() { + ctx.Data["PageIsComparePull"] = false + } + + ctx.Data["BaseName"] = baseRepo.OwnerName + ctx.Data["BaseBranch"] = ci.BaseBranch ctx.Data["HeadUser"] = ci.HeadUser ctx.Data["HeadBranch"] = ci.HeadBranch - ctx.Repo.PullRequest.SameRepo = isSameRepo + ctx.Repo.PullRequest.SameRepo = ci.IsSameRepo() // Check if base branch is valid. baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(ci.BaseBranch) @@ -311,7 +348,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { ctx.Data["BaseBranch"] = ci.BaseBranch baseIsCommit = true } else if ci.BaseBranch == git.EmptySHA { - if isSameRepo { + if ci.IsSameRepo() { ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ci.HeadBranch)) } else { ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ci.HeadRepo.FullName()) + ":" + util.PathEscapeSegments(ci.HeadBranch)) @@ -392,12 +429,12 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { } // 7. Otherwise if we're not the same repo and haven't found a repo give up - if !isSameRepo && !has { + if !ci.IsSameRepo() && !has { ctx.Data["PageIsComparePull"] = false } // 8. Finally open the git repo - if isSameRepo { + if ci.IsSameRepo() { ci.HeadRepo = ctx.Repo.Repository ci.HeadGitRepo = ctx.Repo.GitRepo } else if has { @@ -432,7 +469,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { } // If we're not merging from the same repo: - if !isSameRepo { + if !ci.IsSameRepo() { // Assert ctx.Doer has permission to read headRepo's codes permHead, err := access_model.GetUserRepoPermission(ctx, ci.HeadRepo, ctx.Doer) if err != nil { @@ -547,12 +584,12 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { headBranchRef = git.TagPrefix + ci.HeadBranch } - ci.CompareInfo, err = ci.HeadGitRepo.GetCompareInfo(baseRepo.RepoPath(), baseBranchRef, headBranchRef, ci.DirectComparison, fileOnly) + ci.CompareInfo, err = ci.HeadGitRepo.GetCompareInfo(baseRepo.RepoPath(), baseBranchRef, headBranchRef, ci.DirectComparison(), fileOnly) if err != nil { ctx.ServerError("GetCompareInfo", err) return nil } - if ci.DirectComparison { + if ci.DirectComparison() { ctx.Data["BeforeCommitID"] = ci.CompareInfo.BaseCommitID } else { ctx.Data["BeforeCommitID"] = ci.CompareInfo.MergeBase @@ -580,7 +617,7 @@ func PrepareCompareDiff( ctx.Data["AfterCommitID"] = headCommitID - if (headCommitID == ci.CompareInfo.MergeBase && !ci.DirectComparison) || + if (headCommitID == ci.CompareInfo.MergeBase && !ci.DirectComparison()) || headCommitID == ci.CompareInfo.BaseCommitID { ctx.Data["IsNothingToCompare"] = true if unit, err := repo.GetUnit(ctx, unit.TypePullRequests); err == nil { @@ -599,7 +636,7 @@ func PrepareCompareDiff( } beforeCommitID := ci.CompareInfo.MergeBase - if ci.DirectComparison { + if ci.DirectComparison() { beforeCommitID = ci.CompareInfo.BaseCommitID } @@ -618,7 +655,7 @@ func PrepareCompareDiff( MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters, MaxFiles: maxFiles, WhitespaceBehavior: whitespaceBehavior, - DirectComparison: ci.DirectComparison, + DirectComparison: ci.DirectComparison(), }, ctx.FormStrings("files")...) if err != nil { ctx.ServerError("GetDiffRangeWithWhitespaceBehavior", err) @@ -714,10 +751,10 @@ func CompareDiff(ctx *context.Context) { } ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes - ctx.Data["DirectComparison"] = ci.DirectComparison + ctx.Data["DirectComparison"] = ci.DirectComparison() ctx.Data["OtherCompareSeparator"] = ".." ctx.Data["CompareSeparator"] = "..." - if ci.DirectComparison { + if ci.DirectComparison() { ctx.Data["CompareSeparator"] = ".." ctx.Data["OtherCompareSeparator"] = "..." } @@ -797,7 +834,7 @@ func CompareDiff(ctx *context.Context) { afterCommitID := ctx.Data["AfterCommitID"].(string) separator := "..." - if ci.DirectComparison { + if ci.DirectComparison() { separator = ".." } ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + separator + base.ShortSha(afterCommitID) diff --git a/routers/web/repo/compare_test.go b/routers/web/repo/compare_test.go new file mode 100644 index 0000000000000..5ba3d78773397 --- /dev/null +++ b/routers/web/repo/compare_test.go @@ -0,0 +1,72 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCompareRouters(t *testing.T) { + kases := []struct { + router string + compareRouter compareRouter + }{ + { + router: "main...develop", + compareRouter: compareRouter{ + BaseBranch: "main", + HeadBranch: "develop", + DotTimes: 3, + }, + }, + { + router: "main^...develop", + compareRouter: compareRouter{ + BaseBranch: "main", + HeadBranch: "develop", + CaretTimes: 1, + DotTimes: 3, + }, + }, + { + router: "main^^^^^...develop", + compareRouter: compareRouter{ + BaseBranch: "main", + HeadBranch: "develop", + CaretTimes: 5, + DotTimes: 3, + }, + }, + { + router: "develop", + compareRouter: compareRouter{ + HeadBranch: "develop", + }, + }, + { + router: "lunny/forked_repo:develop", + compareRouter: compareRouter{ + HeadOwner: "lunny", + HeadRepoName: "forked_repo", + HeadBranch: "develop", + }, + }, + { + router: "main...lunny/forked_repo:develop", + compareRouter: compareRouter{ + BaseBranch: "main", + HeadOwner: "lunny", + HeadRepoName: "forked_repo", + HeadBranch: "develop", + DotTimes: 3, + }, + }, + } + for _, kase := range kases { + r := parseCompareRouters(kase.router) + assert.EqualValues(t, kase.compareRouter, r) + } +} diff --git a/services/repository/push.go b/services/repository/push.go index 97da45f52b4bf..bf552b862c330 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -227,30 +227,14 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { } oldCommitID := opts.OldCommitID - if oldCommitID == git.EmptySHA && len(commits.Commits) > 0 { - oldCommit, err := gitRepo.GetCommit(commits.Commits[len(commits.Commits)-1].Sha1) - if err != nil && !git.IsErrNotExist(err) { - log.Error("unable to GetCommit %s from %-v: %v", oldCommitID, repo, err) - } - if oldCommit != nil { - for i := 0; i < oldCommit.ParentCount(); i++ { - commitID, _ := oldCommit.ParentID(i) - if !commitID.IsZero() { - oldCommitID = commitID.String() - break - } - } - } - } - - if oldCommitID == git.EmptySHA && repo.DefaultBranch != branch { - oldCommitID = repo.DefaultBranch - } if oldCommitID != git.EmptySHA { commits.CompareURL = repo.ComposeCompareURL(oldCommitID, opts.NewCommitID) - } else { - commits.CompareURL = "" + } else if len(commits.Commits) == 1 { + commits.CompareURL = fmt.Sprintf("%s/commit/%s", repo.FullName(), opts.NewCommitID) + } else if len(commits.Commits) > 1 { + oldCommitID = commits.Commits[len(commits.Commits)-1].Sha1 + commits.CompareURL = fmt.Sprintf("%s/compare/%s^...%s", repo.FullName(), oldCommitID, opts.NewCommitID) } if len(commits.Commits) > setting.UI.FeedMaxCommitNum {