From f0ec508fb9978b001d09c72ed77dd31600a48826 Mon Sep 17 00:00:00 2001 From: a1012112796 <1012112796@qq.com> Date: Sat, 10 Jun 2023 11:25:25 +0000 Subject: [PATCH 1/3] enhancement of compare feature 1. add three compare modes instead of single mode, (that's like gitub) they are: a: `Compare in same repository`: will only show branch/tags dropdown, wich used to compare or create pull request in same repo. b: `Compare across repositorys` which will not only show branch/tags dropdown, but also show repository dropdown, by default will single show fork repository of this repo in this list, but will load more repositorys if you entry a word in filter box. c: `Compare with repository in other service`, a new mode which github not suport also :), in this mode, the head repo select box will be replaced with a repo url entry box, you can entry a git repo link to other website, then you can comare with it. TODO: will suport creating pull request from repository in other service in future, now only support compare To prevent to much time cost, has add `--depth=100` as a limit. 2. show a single error log instead of a `404 page` when the head repo or user, the head or base ref is not exist. then user still can choose other option easyly. --- models/perm/access/repo_permission.go | 5 + modules/git/repo_branch_gogit.go | 36 +++ modules/git/repo_branch_nogogit.go | 9 + options/locale/locale_en-US.ini | 8 + routers/web/repo/compare.go | 433 ++++++++++++++++++++------ routers/web/repo/pull.go | 4 + templates/repo/diff/compare.tmpl | 189 +++++------ web_src/css/repo.css | 5 + web_src/js/features/repo-common.js | 69 ++++ web_src/js/features/repo-legacy.js | 6 +- 10 files changed, 571 insertions(+), 193 deletions(-) diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index 64df5355bba6a..be0aec37c3f6c 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -128,6 +128,11 @@ func (p *Permission) LogString() string { // GetUserRepoPermission returns the user permissions to the repository func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) { + if repo.ID == -1 { + perm.AccessMode = perm_model.AccessModeNone + return + } + if log.IsTrace() { defer func() { if user == nil { diff --git a/modules/git/repo_branch_gogit.go b/modules/git/repo_branch_gogit.go index f9896a7a09047..047aa92ce0b4a 100644 --- a/modules/git/repo_branch_gogit.go +++ b/modules/git/repo_branch_gogit.go @@ -8,6 +8,7 @@ package git import ( "context" + "fmt" "strings" "github.com/go-git/go-git/v5/plumbing" @@ -80,6 +81,41 @@ func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { return branchNames, count, nil } +// GetRemotetBranchNames returns branches from the repository remote, skipping "skip" initial branches and +// returning at most "limit" branches, or all branches if "limit" is 0. +func (repo *Repository) GetRemotetBranchNames(remote string, skip, limit int) ([]string, int, error) { + var branchNames []string + + refs, err := repo.gogitRepo.References() + if err != nil { + return nil, 0, nil + } + + i := 0 + count := 0 + refPrefix := fmt.Sprintf("refs/remotes/%s/", remote) + + _ = refs.ForEach(func(ref *plumbing.Reference) error { + refName := ref.Name().String() + if !strings.HasPrefix(refName, refPrefix) { + return nil + } + + count++ + if i < skip { + i++ + return nil + } else if limit != 0 && count > skip+limit { + return nil + } + + branchNames = append(branchNames, strings.TrimPrefix(refName, refPrefix)) + return nil + }) + + return branchNames, count, nil +} + // WalkReferences walks all the references from the repository // refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty. func WalkReferences(ctx context.Context, repoPath string, walkfn func(sha1, refname string) error) (int, error) { diff --git a/modules/git/repo_branch_nogogit.go b/modules/git/repo_branch_nogogit.go index b1e7c8b73e640..a4e221500921e 100644 --- a/modules/git/repo_branch_nogogit.go +++ b/modules/git/repo_branch_nogogit.go @@ -10,6 +10,7 @@ import ( "bufio" "bytes" "context" + "fmt" "io" "strings" @@ -65,6 +66,14 @@ func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { return callShowRef(repo.Ctx, repo.Path, BranchPrefix, TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}, skip, limit) } +// GetRemotetBranchNames returns branches from the repository remote, skipping "skip" initial branches and +// returning at most "limit" branches, or all branches if "limit" is 0. +func (repo *Repository) GetRemotetBranchNames(remote string, skip, limit int) ([]string, int, error) { + refPrefix := fmt.Sprintf("refs/remotes/%s/", remote) + + return callShowRef(repo.Ctx, repo.Path, refPrefix, ToTrustedCmdArgs([]string{refPrefix, "--sort=-committerdate"}), skip, limit) +} + // WalkReferences walks all the references from the repository func WalkReferences(ctx context.Context, repoPath string, walkfn func(sha1, refname string) error) (int, error) { return walkShowRef(ctx, repoPath, nil, 0, 0, walkfn) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 25456d0493426..18d5379425a4d 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1112,6 +1112,7 @@ branch = Branch tree = Tree clear_ref = `Clear current reference` filter_branch_and_tag = Filter branch or tag +filter_repo = Filter repository find_tag = Find tag branches = Branches tags = Tags @@ -1627,6 +1628,13 @@ issues.reference_link = Reference: %s compare.compare_base = base compare.compare_head = compare +compare.mode.in_same_repo = Compare in same repository +compare.mode.across_repos = Compare across repositorys +compare.mode.across_service = Compare with repository in other service +compare.titile = Comparing %s +compare.button_title = Compare +compare.refs_not_exist = Head or base ref is not exist. +compare.head_info_not_exist = Head user or repository is not exist. pulls.desc = Enable pull requests and code reviews. pulls.new = New Pull Request diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 0ca1f90547efc..a63ae361a4c57 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -16,6 +16,7 @@ import ( "path/filepath" "strings" + "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" access_model "code.gitea.io/gitea/models/perm/access" @@ -29,6 +30,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" + repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/upload" @@ -175,6 +177,8 @@ func setCsvCompareContext(ctx *context.Context) { } } +type CompareMode string + // CompareInfo represents the collected results from ParseCompareInfo type CompareInfo struct { HeadUser *user_model.User @@ -184,8 +188,129 @@ type CompareInfo struct { BaseBranch string HeadBranch string DirectComparison bool + CompareMode CompareMode + RefsNotExist bool + HeadInfoNotExist bool + + HeadRef string + BaseRef string + ExternalRepoURL string + tmpReop *tmpGitContext +} + +const ( + // compareModeInSameRepo compare in same repository + compareModeInSameRepo CompareMode = "in_same_repo" + // compareModeAcrossRepos compare across repositorys + compareModeAcrossRepos CompareMode = "across_repos" + // compareModeAcrossService Compare with repository in other service + compareModeAcrossService CompareMode = "across_service" +) + +func (c CompareMode) IsInSameRepo() bool { + return c == compareModeInSameRepo +} + +func (c CompareMode) IsAcrossRepos() bool { + return c == compareModeAcrossRepos +} + +func (c CompareMode) IsAcrossService() bool { + return c == compareModeAcrossService +} + +func (c CompareMode) ToLocal() string { + return "repo.compare.mode." + string(c) +} + +type tmpGitContext struct { + gocontext.Context + tmpRepoPath string + outbuf *strings.Builder // we keep these around to help reduce needless buffer recreation, + errbuf *strings.Builder // any use should be preceded by a Reset and preferably after use +} + +func (ctx *tmpGitContext) RunOpts() *git.RunOpts { + ctx.outbuf.Reset() + ctx.errbuf.Reset() + return &git.RunOpts{ + Dir: ctx.tmpRepoPath, + Stdout: ctx.outbuf, + Stderr: ctx.errbuf, + } +} + +func (ctx *tmpGitContext) FetchRemote(url string) error { + if err := git.NewCommand(ctx, "remote", "add").AddDynamicArguments("origin", url). + Run(ctx.RunOpts()); err != nil { + return ctx.Error("remote add", err) + } + + fetchArgs := git.TrustedCmdArgs{"--tags", "--depth=100"} + if git.CheckGitVersionAtLeast("2.25.0") == nil { + // Writing the commit graph can be slow and is not needed here + fetchArgs = append(fetchArgs, "--no-write-commit-graph") + } + + if err := git.NewCommand(ctx, "fetch", "origin").AddArguments(fetchArgs...).Run(ctx.RunOpts()); err != nil { + return ctx.Error("fetch origin", err) + } + + return nil +} + +func (ctx *tmpGitContext) FetchRemoteRef(ref string) error { + fetchArgs := git.TrustedCmdArgs{"--no-tags"} + if git.CheckGitVersionAtLeast("2.25.0") == nil { + // Writing the commit graph can be slow and is not needed here + fetchArgs = append(fetchArgs, "--no-write-commit-graph") + } + + if err := git.NewCommand(ctx, "fetch", "origin", "--depth=100").AddArguments(fetchArgs...).AddDashesAndList(ref + ":" + ref). + Run(ctx.RunOpts()); err != nil { + return ctx.Error("fetch origin", err) + } + + return nil +} + +func (ctx *tmpGitContext) Close() { + if err := repo_module.RemoveTemporaryPath(ctx.tmpRepoPath); err != nil { + log.Error("Error whilst removing removing temporary repo: %v", err) + } +} + +func (ctx *tmpGitContext) OpenRepository() (*git.Repository, error) { + return git.OpenRepository(ctx, ctx.tmpRepoPath) +} + +func (ctx *tmpGitContext) Error(name string, err error) error { + return fmt.Errorf("git error %v: %v\n%s\n%s", name, err, ctx.outbuf.String(), ctx.errbuf.String()) +} + +func openTempGitRepo(ctx gocontext.Context) (*tmpGitContext, error) { + tmpRepoPath, err := repo_module.CreateTemporaryPath("compare") + if err != nil { + return nil, err + } + + tmpCtx := &tmpGitContext{ + Context: ctx, + tmpRepoPath: tmpRepoPath, + outbuf: &strings.Builder{}, + errbuf: &strings.Builder{}, + } + + if err := git.InitRepository(ctx, tmpRepoPath, true); err != nil { + tmpCtx.Close() + return nil, err + } + + return tmpCtx, nil } +const exampleExternalRepoURL = "https://example.git.com/unknow.git" + // ParseCompareInfo parse compare info between two commit for preparing comparing references func ParseCompareInfo(ctx *context.Context) *CompareInfo { baseRepo := ctx.Repo.Repository @@ -202,6 +327,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { // 4. /{:baseOwner}/{:baseRepoName}/compare/{:headBranch} // 5. /{:baseOwner}/{:baseRepoName}/compare/{:headOwner}:{:headBranch} // 6. /{:baseOwner}/{:baseRepoName}/compare/{:headOwner}/{:headRepoName}:{:headBranch} + // 7. /{:baseOwner}/{:baseRepoName}/compare/{:headBranch}:{:headBranch}?head_repo_url={:head_repo_url} // // Here we obtain the infoPath "{:baseBranch}...[{:headOwner}/{:headRepoName}:]{:headBranch}" as ctx.Params("*") // with the :baseRepo in ctx.Repo. @@ -240,8 +366,12 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { } } } + ci.CompareMode = compareModeInSameRepo + ci.HeadRef = infos[1] + ci.BaseRef = infos[0] ctx.Data["BaseName"] = baseRepo.OwnerName + ctx.Data["BaseRepo"] = baseRepo ci.BaseBranch = infos[0] ctx.Data["BaseBranch"] = ci.BaseBranch @@ -250,46 +380,71 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { if len(headInfos) == 1 { isSameRepo = true ci.HeadUser = ctx.Repo.Owner + ctx.Data["HeadUserName"] = ctx.Repo.Owner.Name ci.HeadBranch = headInfos[0] + if headRepoURL := ctx.FormString("head_repo_url"); len(headRepoURL) != 0 { + isSameRepo = false + ci.CompareMode = compareModeAcrossService + ctx.Data["HeadRepoName"] = ctx.Repo.Repository.Name + ci.ExternalRepoURL, _ = url.QueryUnescape(headRepoURL) + ci.HeadRepo = &repo_model.Repository{ + ID: -1, + Owner: user_model.NewGhostUser(), + OwnerID: -1, + } + } } else if len(headInfos) == 2 { + ci.CompareMode = compareModeAcrossRepos + headInfosSplit := strings.Split(headInfos[0], "/") if len(headInfosSplit) == 1 { + ctx.Data["HeadUserName"] = headInfos[0] + ctx.Data["HeadRepoName"] = "" + ci.HeadBranch = headInfos[1] + ci.HeadUser, err = user_model.GetUserByName(ctx, headInfos[0]) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.NotFound("GetUserByName", nil) + ci.HeadInfoNotExist = true + ci.RefsNotExist = true } else { ctx.ServerError("GetUserByName", err) + return nil + } + } else { + isSameRepo = ci.HeadUser.ID == ctx.Repo.Owner.ID + if isSameRepo { + ci.HeadRepo = baseRepo } - return nil - } - ci.HeadBranch = headInfos[1] - isSameRepo = ci.HeadUser.ID == ctx.Repo.Owner.ID - if isSameRepo { - ci.HeadRepo = baseRepo } } else { + ctx.Data["HeadUserName"] = headInfosSplit[0] + ctx.Data["HeadRepoName"] = headInfosSplit[1] + ci.HeadRepo, err = repo_model.GetRepositoryByOwnerAndName(ctx, headInfosSplit[0], headInfosSplit[1]) if err != nil { if repo_model.IsErrRepoNotExist(err) { - ctx.NotFound("GetRepositoryByOwnerAndName", nil) + ci.HeadInfoNotExist = true + ci.RefsNotExist = true } else { ctx.ServerError("GetRepositoryByOwnerAndName", err) + return nil } - return nil - } - if err := ci.HeadRepo.LoadOwner(ctx); err != nil { + } else if err := ci.HeadRepo.LoadOwner(ctx); err != nil { if user_model.IsErrUserNotExist(err) { - ctx.NotFound("GetUserByName", nil) + ci.HeadInfoNotExist = true + ci.RefsNotExist = true } else { ctx.ServerError("GetUserByName", err) + return nil } - return nil } ci.HeadBranch = headInfos[1] - ci.HeadUser = ci.HeadRepo.Owner - isSameRepo = ci.HeadRepo.ID == ctx.Repo.Repository.ID + if ci.HeadRepo != nil { + ci.HeadUser = ci.HeadRepo.Owner + isSameRepo = ci.HeadRepo.ID == ctx.Repo.Repository.ID + } } } else { ctx.NotFound("CompareAndPullRequest", nil) @@ -298,6 +453,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { ctx.Data["HeadUser"] = ci.HeadUser ctx.Data["HeadBranch"] = ci.HeadBranch ctx.Repo.PullRequest.SameRepo = isSameRepo + ctx.Data["CompareMode"] = ci.CompareMode // Check if base branch is valid. baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(ci.BaseBranch) @@ -317,8 +473,9 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { } return nil } else { - ctx.NotFound("IsRefExist", nil) - return nil + ctx.Data["CompareRefsNotFound"] = true + ci.RefsNotExist = true + // not return on time because should load head repo data } } ctx.Data["BaseIsCommit"] = baseIsCommit @@ -363,6 +520,74 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { } } + loadForkReps := func() *CompareInfo { + // list all fork repos in acrossmode + if !ci.CompareMode.IsInSameRepo() { + var ( + forks []*repo_model.Repository + err error + ) + + if rootRepo == nil { + forks, err = repo_model.GetForks(baseRepo, db.ListOptions{ + Page: 0, + PageSize: 20, + }) + } else { + forks, err = repo_model.GetForks(rootRepo, db.ListOptions{ + Page: 0, + PageSize: 20, + }) + } + + if err != nil { + ctx.ServerError("GetForks", err) + return nil + } + + forkmap := make(map[int64]*repo_model.Repository) + for _, fork := range forks { + forkmap[fork.ID] = fork + } + + if _, ok := forkmap[baseRepo.ID]; !ok { + forkmap[baseRepo.ID] = baseRepo + } + + if rootRepo != nil { + if _, ok := forkmap[rootRepo.ID]; !ok { + forkmap[rootRepo.ID] = rootRepo + } + } + + if ownForkRepo != nil { + if _, ok := forkmap[ownForkRepo.ID]; !ok { + forkmap[ownForkRepo.ID] = ownForkRepo + } + } + + forks = make([]*repo_model.Repository, 0, len(forkmap)) + for _, fork := range forkmap { + forks = append(forks, fork) + } + + ctx.Data["CompareRepos"] = forks + } + + if ci.CompareMode == compareModeAcrossService { + ctx.Data["ExternalRepoURL"] = ci.ExternalRepoURL + } else { + ctx.Data["ExternalRepoURL"] = exampleExternalRepoURL + } + + return ci + } + + if ci.HeadInfoNotExist { + ctx.Data["HeadInfoNotExist"] = true + return loadForkReps() + } + has := ci.HeadRepo != nil // 3. If the base is a forked from "RootRepo" and the owner of // the "RootRepo" is the :headUser - set headRepo to that @@ -399,6 +624,43 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { if isSameRepo { ci.HeadRepo = ctx.Repo.Repository ci.HeadGitRepo = ctx.Repo.GitRepo + } else if ci.CompareMode == compareModeAcrossService { + if ci.ExternalRepoURL == exampleExternalRepoURL { + ci.HeadInfoNotExist = true + ci.RefsNotExist = true + ctx.Data["HeadInfoNotExist"] = true + return loadForkReps() + } + + tmpCtx, err := openTempGitRepo(ctx) + if err != nil { + ci.HeadInfoNotExist = true + ci.RefsNotExist = true + ctx.Data["HeadInfoNotExist"] = true + return loadForkReps() + } + + ci.HeadGitRepo, err = tmpCtx.OpenRepository() + if err != nil { + ctx.ServerError("OpenRepository", err) + return nil + } + ci.tmpReop = tmpCtx + + err = tmpCtx.FetchRemote(ci.ExternalRepoURL) + if err != nil { + ci.HeadInfoNotExist = true + ci.RefsNotExist = true + ctx.Data["HeadInfoNotExist"] = true + return loadForkReps() + } + + err = tmpCtx.FetchRemoteRef(ci.HeadBranch) + if err != nil { + ci.RefsNotExist = true + ctx.Data["CompareRefsNotFound"] = true + } + } else if has { ci.HeadGitRepo, err = git.OpenRepository(ctx, ci.HeadRepo.RepoPath()) if err != nil { @@ -410,6 +672,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { ctx.Data["HeadRepo"] = ci.HeadRepo ctx.Data["BaseCompareRepo"] = ctx.Repo.Repository + ctx.Data["HeadRepoName"] = ci.HeadRepo.Name // Now we need to assert that the ctx.Doer has permission to read // the baseRepo's code and pulls @@ -438,7 +701,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { ctx.ServerError("GetUserRepoPermission", err) return nil } - if !permHead.CanRead(unit.TypeCode) { + if !permHead.CanRead(unit.TypeCode) && ci.CompareMode != compareModeAcrossService { if log.IsTrace() { log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v", ctx.Doer, @@ -451,51 +714,12 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { ctx.Data["CanWriteToHeadRepo"] = permHead.CanWrite(unit.TypeCode) } - // If we have a rootRepo and it's different from: - // 1. the computed base - // 2. the computed head - // then get the branches of it - if rootRepo != nil && - rootRepo.ID != ci.HeadRepo.ID && - rootRepo.ID != baseRepo.ID { - canRead := access_model.CheckRepoUnitUser(ctx, rootRepo, ctx.Doer, unit.TypeCode) - if canRead && rootRepo.AllowsPulls() { - ctx.Data["RootRepo"] = rootRepo - if !fileOnly { - branches, tags, err := getBranchesAndTagsForRepo(ctx, rootRepo) - if err != nil { - ctx.ServerError("GetBranchesForRepo", err) - return nil - } - - ctx.Data["RootRepoBranches"] = branches - ctx.Data["RootRepoTags"] = tags - } - } + if loadForkReps() == nil { + return nil } - // If we have a ownForkRepo and it's different from: - // 1. The computed base - // 2. The computed head - // 3. The rootRepo (if we have one) - // then get the branches from it. - if ownForkRepo != nil && - ownForkRepo.ID != ci.HeadRepo.ID && - ownForkRepo.ID != baseRepo.ID && - (rootRepo == nil || ownForkRepo.ID != rootRepo.ID) { - canRead := access_model.CheckRepoUnitUser(ctx, ownForkRepo, ctx.Doer, unit.TypeCode) - if canRead { - ctx.Data["OwnForkRepo"] = ownForkRepo - if !fileOnly { - branches, tags, err := getBranchesAndTagsForRepo(ctx, ownForkRepo) - if err != nil { - ctx.ServerError("GetBranchesForRepo", err) - return nil - } - ctx.Data["OwnForkRepoBranches"] = branches - ctx.Data["OwnForkRepoTags"] = tags - } - } + if ci.RefsNotExist { + return ci } // Check if head branch is valid. @@ -509,8 +733,9 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { ctx.Data["HeadBranch"] = ci.HeadBranch headIsCommit = true } else { - ctx.NotFound("IsRefExist", nil) - return nil + ctx.Data["CompareRefsNotFound"] = true + ci.RefsNotExist = true + return ci } } ctx.Data["HeadIsCommit"] = headIsCommit @@ -676,24 +901,6 @@ func PrepareCompareDiff( return false } -func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repository) (branches, tags []string, err error) { - gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) - if err != nil { - return nil, nil, err - } - defer gitRepo.Close() - - branches, _, err = gitRepo.GetBranchNames(0, 0) - if err != nil { - return nil, nil, err - } - tags, err = gitRepo.GetTags(0, 0) - if err != nil { - return nil, nil, err - } - return branches, tags, nil -} - // CompareDiff show different from one commit to another commit func CompareDiff(ctx *context.Context) { ci := ParseCompareInfo(ctx) @@ -701,6 +908,10 @@ func CompareDiff(ctx *context.Context) { if ci != nil && ci.HeadGitRepo != nil { ci.HeadGitRepo.Close() } + + if ci.tmpReop != nil { + ci.tmpReop.Close() + } }() if ctx.Written() { return @@ -715,10 +926,15 @@ func CompareDiff(ctx *context.Context) { ctx.Data["OtherCompareSeparator"] = "..." } - nothingToCompare := PrepareCompareDiff(ctx, ci, - gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string))) - if ctx.Written() { - return + var nothingToCompare bool + if ci.RefsNotExist { + nothingToCompare = true + } else { + nothingToCompare = PrepareCompareDiff(ctx, ci, + gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string))) + if ctx.Written() { + return + } } baseTags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID) @@ -734,21 +950,40 @@ func CompareDiff(ctx *context.Context) { return } - headBranches, _, err := ci.HeadGitRepo.GetBranchNames(0, 0) - if err != nil { - ctx.ServerError("GetBranches", err) - return - } - ctx.Data["HeadBranches"] = headBranches + if !ci.HeadInfoNotExist { + if ci.CompareMode == compareModeAcrossService { + headBranches, _, err := ci.HeadGitRepo.GetRemotetBranchNames("origin", 0, 0) + if err != nil { + ctx.ServerError("GetBranches", err) + return + } + ctx.Data["HeadBranches"] = headBranches - headTags, err := repo_model.GetTagNamesByRepoID(ctx, ci.HeadRepo.ID) - if err != nil { - ctx.ServerError("GetTagNamesByRepoID", err) - return + headTags, err := ci.HeadGitRepo.GetTags(0, 0) + if err != nil { + ctx.ServerError("GetBranches", err) + return + } + + ctx.Data["HeadTags"] = headTags + } else { + headBranches, _, err := ci.HeadGitRepo.GetBranchNames(0, 0) + if err != nil { + ctx.ServerError("GetBranches", err) + return + } + ctx.Data["HeadBranches"] = headBranches + + headTags, err := repo_model.GetTagNamesByRepoID(ctx, ci.HeadRepo.ID) + if err != nil { + ctx.ServerError("GetTagNamesByRepoID", err) + return + } + ctx.Data["HeadTags"] = headTags + } } - ctx.Data["HeadTags"] = headTags - if ctx.Data["PageIsComparePull"] == true { + if !ci.HeadInfoNotExist && ctx.Data["PageIsComparePull"] == true { pr, err := issues_model.GetUnmergedPullRequest(ctx, ci.HeadRepo.ID, ctx.Repo.Repository.ID, ci.HeadBranch, ci.BaseBranch, issues_model.PullRequestFlowGithub) if err != nil { if !issues_model.IsErrPullRequestNotExist(err) { @@ -774,14 +1009,12 @@ func CompareDiff(ctx *context.Context) { } } } - beforeCommitID := ctx.Data["BeforeCommitID"].(string) - afterCommitID := ctx.Data["AfterCommitID"].(string) separator := "..." if ci.DirectComparison { separator = ".." } - ctx.Data["Title"] = "Comparing " + base.ShortSha(beforeCommitID) + separator + base.ShortSha(afterCommitID) + ctx.Data["Title"] = ctx.Tr("repo.compare.titile", ci.BaseRef+separator+ci.HeadRef) ctx.Data["IsRepoToolbarCommits"] = true ctx.Data["IsDiffCompare"] = true diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 09dbc23eac2cc..24be3185c0312 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1195,6 +1195,10 @@ func CompareAndPullRequestPost(ctx *context.Context) { if ctx.Written() { return } + if ci.RefsNotExist { + ctx.NotFound("RefsNotExist", nil) + return + } labelIDs, assigneeIDs, milestoneID, _ := ValidateRepoMetas(ctx, *form, true) if ctx.Written() { diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl index 4efe81682ce19..93d1c0db02ba9 100644 --- a/templates/repo/diff/compare.tmpl +++ b/templates/repo/diff/compare.tmpl @@ -19,27 +19,47 @@ */}} {{template "base/alert" .}} {{end}} - {{$BaseCompareName := $.BaseName -}} - {{- $HeadCompareName := $.HeadRepo.OwnerName -}} - {{- if and (eq $.BaseName $.HeadRepo.OwnerName) (ne $.Repository.Name $.HeadRepo.Name) -}} - {{- $HeadCompareName = printf "%s/%s" $.HeadRepo.OwnerName $.HeadRepo.Name -}} - {{- end -}} - {{- $OwnForkCompareName := "" -}} - {{- if .OwnForkRepo -}} - {{- $OwnForkCompareName = .OwnForkRepo.OwnerName -}} - {{- end -}} - {{- $RootRepoCompareName := "" -}} - {{- if .RootRepo -}} - {{- $RootRepoCompareName = .RootRepo.OwnerName -}} - {{- if eq $.HeadRepo.OwnerName .RootRepo.OwnerName -}} - {{- $HeadCompareName = printf "%s/%s" $.HeadRepo.OwnerName $.HeadRepo.Name -}} - {{- end -}} - {{- end -}}
- {{svg "octicon-git-compare"}} - - {{.CompareSeparator}} - + + {{if $.CompareMode.IsAcrossService}} +
+ +
+ {{end}}
- {{if .IsNothingToCompare}} - {{if and $.IsSigned $.AllowEmptyPr (not .Repository.IsArchived)}} + {{if .HeadInfoNotExist}} +
{{.locale.Tr "repo.compare.head_info_not_exist"}}
+ {{else if .CompareRefsNotFound}} +
{{.locale.Tr "repo.compare.refs_not_exist"}}
+ {{else if .IsNothingToCompare}} + {{if and $.IsSigned $.AllowEmptyPr (not .Repository.IsArchived) (not $.CompareMode.IsAcrossService)}}
{{.locale.Tr "repo.pulls.nothing_to_compare_and_allow_empty_pr"}}
@@ -203,13 +210,13 @@ {{svg "octicon-git-merge" 16}} {{.locale.Tr "repo.pulls.view"}} {{else if .Issue.IsClosed}} {{svg "octicon-issue-closed" 16}} {{.locale.Tr "repo.pulls.view"}} - {{else}} + {{else if not $.CompareMode.IsAcrossService}} {{svg "octicon-git-pull-request" 16}} {{.locale.Tr "repo.pulls.view"}} {{end}}
{{else}} - {{if and $.IsSigned (not .Repository.IsArchived)}} + {{if and $.IsSigned (not .Repository.IsArchived) (not $.CompareMode.IsAcrossService)}}
@@ -222,7 +229,7 @@ {{end}} {{end}} - {{if $.IsSigned}} + {{if and $.IsSigned (not $.CompareMode.IsAcrossService)}}
{{template "repo/issue/new_form" .}}
diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 546fff32f429f..2165d0aa33cf4 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -3401,3 +3401,8 @@ tbody.commit-list { font-size: 18px; margin-left: 4px; } + +.repository .compare-switch-btn { + display: inline-flex; + flex-direction: column; +} diff --git a/web_src/js/features/repo-common.js b/web_src/js/features/repo-common.js index d99a1a8da08c0..224a1723b5361 100644 --- a/web_src/js/features/repo-common.js +++ b/web_src/js/features/repo-common.js @@ -98,6 +98,75 @@ export function initRepoCommonFilterSearchDropdown(selector) { }); } +const {appSubUrl} = window.config; + +export function initRepoCommonForksRepoSearchDropdown(selector) { + const $dropdown = $(selector); + $dropdown.find('input').on('input', function() { + const $root = $(this).closest(selector).find('.reference-list-menu'); + const $query = $(this).val().trim(); + if ($query.length === 0) { + return; + } + + $.get(`${appSubUrl}/repo/search?q=${$query}`).done((data) => { + if (data.ok !== true) { + return; + } + + const $linkTmpl = $root.data('url-tmpl'); + + for (let i = 0; i < data.data.length; i++) { + const {id, full_name, link} = data.data[i].repository; + + const found = $root.find('.item').filter(function() { + return $(this).data('id') === id; + }); + + if (found.length !== 0) { + continue; + } + + const compareLink = $linkTmpl.replace('{REPO_LINK}', link).replace('{REOP_FULL_NAME}', full_name); + $root.append($(`
${full_name}
`)); + } + }).always(() => { + $root.find('.item').each((_, e) => { + if (!$(e).html().includes($query)) { + $(e).addClass('filtered'); + } + }); + }); + + return false; + }); + + $dropdown.dropdown({ + fullTextSearch: 'exact', + selectOnKeydown: false, + onChange(_text, _value, $choice) { + if ($choice.attr('data-url')) { + window.location.href = $choice.attr('data-url'); + } + }, + message: {noResults: $dropdown.attr('data-no-results')}, + }); + + const $acrossServiceCompareBtn = $('.choose.branch .compare-across-server-btn'); + const $acrossServiceCompareInput = $('.choose.branch .compare-across-server-input'); + + if ($acrossServiceCompareBtn.length === 0 || $acrossServiceCompareInput.length === 0) { + return; + } + + $acrossServiceCompareBtn.on('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + + window.location.href = $(this).data('compare-url') + encodeURIComponent($acrossServiceCompareInput.val()); + }); +} + export function initRepoCommonLanguageStats() { // Language stats if ($('.language-stats').length > 0) { diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index f23ff45470c10..7c53492a291a1 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -11,6 +11,7 @@ import {htmlEscape} from 'escape-goat'; import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue'; import { initRepoCloneLink, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown, + initRepoCommonForksRepoSearchDropdown, initRepoCommonLanguageStats, } from './repo-common.js'; import {initCitationFileCopyContent} from './citation.js'; @@ -518,8 +519,9 @@ export function initRepository() { // Compare or pull request const $repoDiff = $('.repository.diff'); if ($repoDiff.length) { - initRepoCommonBranchOrTagDropdown('.choose.branch .dropdown'); - initRepoCommonFilterSearchDropdown('.choose.branch .dropdown'); + initRepoCommonBranchOrTagDropdown('.choose.branch .branch-search-box'); + initRepoCommonFilterSearchDropdown('.choose.branch .branch-search-box'); + initRepoCommonForksRepoSearchDropdown('.choose.branch .repo-search-box'); } initRepoCloneLink(); From 9a918dc5e6f024d617476db38a3930a7e5bd0e48 Mon Sep 17 00:00:00 2001 From: a1012112796 <1012112796@qq.com> Date: Mon, 19 Jun 2023 10:47:29 +0000 Subject: [PATCH 2/3] fix Signed-off-by: a1012112796 <1012112796@qq.com> --- models/perm/access/repo_permission.go | 5 ----- options/locale/locale_en-US.ini | 4 ++-- routers/web/repo/compare.go | 24 ++++++++++++++++++------ templates/repo/diff/compare.tmpl | 2 +- tests/integration/compare_test.go | 4 ++-- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index be0aec37c3f6c..64df5355bba6a 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -128,11 +128,6 @@ func (p *Permission) LogString() string { // GetUserRepoPermission returns the user permissions to the repository func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) { - if repo.ID == -1 { - perm.AccessMode = perm_model.AccessModeNone - return - } - if log.IsTrace() { defer func() { if user == nil { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index e74b79bd09988..3c1e23ab65162 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1114,7 +1114,7 @@ branch = Branch tree = Tree clear_ref = `Clear current reference` filter_branch_and_tag = Filter branch or tag -filter_repo = Filter repository +filter_repo = Search repository find_tag = Find tag branches = Branches tags = Tags @@ -1632,7 +1632,7 @@ compare.compare_base = base compare.compare_head = compare compare.mode.in_same_repo = Compare in same repository compare.mode.across_repos = Compare across repositorys -compare.mode.across_service = Compare with repository in other service +compare.mode.across_service = Compare with external repository compare.titile = Comparing %s compare.button_title = Compare compare.refs_not_exist = Head or base ref is not exist. diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index a63ae361a4c57..31a3ebc8af1f1 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -203,7 +203,7 @@ const ( compareModeInSameRepo CompareMode = "in_same_repo" // compareModeAcrossRepos compare across repositorys compareModeAcrossRepos CompareMode = "across_repos" - // compareModeAcrossService Compare with repository in other service + // compareModeAcrossService compare with external repository compareModeAcrossService CompareMode = "across_service" ) @@ -321,12 +321,15 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { // Get compared branches information // A full compare url is of the form: // + // Compare in same repository: // 1. /{:baseOwner}/{:baseRepoName}/compare/{:baseBranch}...{:headBranch} + // Compare across repositorys: // 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} + // Compare with external repository: // 7. /{:baseOwner}/{:baseRepoName}/compare/{:headBranch}:{:headBranch}?head_repo_url={:head_repo_url} // // Here we obtain the infoPath "{:baseBranch}...[{:headOwner}/{:headRepoName}:]{:headBranch}" as ctx.Params("*") @@ -695,12 +698,21 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { // If we're not merging from the same repo: if !isSameRepo { - // Assert ctx.Doer has permission to read headRepo's codes - permHead, err := access_model.GetUserRepoPermission(ctx, ci.HeadRepo, ctx.Doer) - if err != nil { - ctx.ServerError("GetUserRepoPermission", err) - return nil + var ( + permHead access_model.Permission + err error + ) + + // permission is meaningless of external repo + if ci.CompareMode != compareModeAcrossService { + // Assert ctx.Doer has permission to read headRepo's codes + permHead, err = access_model.GetUserRepoPermission(ctx, ci.HeadRepo, ctx.Doer) + if err != nil { + ctx.ServerError("GetUserRepoPermission", err) + return nil + } } + if !permHead.CanRead(unit.TypeCode) && ci.CompareMode != compareModeAcrossService { if log.IsTrace() { log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v", diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl index 93d1c0db02ba9..e17f4a7bac211 100644 --- a/templates/repo/diff/compare.tmpl +++ b/templates/repo/diff/compare.tmpl @@ -172,7 +172,7 @@ {{if $.CompareMode.IsAcrossService}}
-
diff --git a/tests/integration/compare_test.go b/tests/integration/compare_test.go index 509524ca5627c..7d03ae012661c 100644 --- a/tests/integration/compare_test.go +++ b/tests/integration/compare_test.go @@ -26,8 +26,8 @@ func TestCompareTag(t *testing.T) { assert.Lenf(t, selection.Nodes, 2, "The template has changed") req = NewRequest(t, "GET", "/user2/repo1/compare/invalid") - resp = session.MakeRequest(t, req, http.StatusNotFound) - assert.False(t, strings.Contains(resp.Body.String(), "/assets/img/500.png"), "expect 404 page not 500") + resp = session.MakeRequest(t, req, http.StatusOK) + assert.True(t, strings.Contains(resp.Body.String(), "Head or base ref is not exist.")) } // Compare with inferred default branch (master) From 68dbca9f73d1dfc960af1150c5d44a23a1bddded Mon Sep 17 00:00:00 2001 From: a1012112796 <1012112796@qq.com> Date: Fri, 8 Dec 2023 07:51:03 +0000 Subject: [PATCH 3/3] fix build Signed-off-by: a1012112796 <1012112796@qq.com> --- routers/web/repo/compare.go | 11 +++++-- templates/repo/diff/compare.tmpl | 46 ++++++++++++++---------------- web_src/js/features/repo-legacy.js | 1 + 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index f7a425770541b..0c53909255417 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -539,12 +539,12 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { ) if rootRepo == nil { - forks, err = repo_model.GetForks(baseRepo, db.ListOptions{ + forks, err = repo_model.GetForks(ctx, baseRepo, db.ListOptions{ Page: 0, PageSize: 20, }) } else { - forks, err = repo_model.GetForks(rootRepo, db.ListOptions{ + forks, err = repo_model.GetForks(ctx, rootRepo, db.ListOptions{ Page: 0, PageSize: 20, }) @@ -966,6 +966,13 @@ func CompareDiff(ctx *context.Context) { } ctx.Data["Tags"] = baseTags + branches, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0) + if err != nil { + ctx.ServerError("GetBranches", err) + return + } + ctx.Data["Branches"] = branches + fileOnly := ctx.FormBool("file-only") if fileOnly { ctx.HTML(http.StatusOK, tplDiffBox) diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl index 65ac03f3a6764..65b35c27bfd74 100644 --- a/templates/repo/diff/compare.tmpl +++ b/templates/repo/diff/compare.tmpl @@ -21,31 +21,31 @@ {{end}}
{{if not $.CompareMode.IsAcrossService}} - {{svg "octicon-git-compare"}} + {{svg "octicon-git-compare"}} {{end}} {{if $.CompareMode.IsAcrossRepos}}