From 717da8233f9ebe48ee3d8a7a330fdef553ade00e Mon Sep 17 00:00:00 2001 From: yystopf Date: Wed, 7 Dec 2022 16:24:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E5=89=8Dcompare=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/hat/hat.go | 3 + routers/hat/repo/repo.go | 480 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 483 insertions(+) create mode 100644 routers/hat/repo/repo.go diff --git a/routers/hat/hat.go b/routers/hat/hat.go index 6f68da9..9ac5231 100644 --- a/routers/hat/hat.go +++ b/routers/hat/hat.go @@ -55,6 +55,7 @@ func Routers() *web.Route { SignInRequired: setting.Service.RequireSignInView, })) + reqRepoCodeReader := context.RequireRepoReader(unit_model.TypeCode) m.Group("", func() { m.Get("/version", misc.Version) m.Group("/repos", func() { @@ -81,6 +82,8 @@ func Routers() *web.Route { m.Get("/*", repo.GetReadmeContentsByPath) }) m.Get("/commits_slice", repo.GetAllCommitsSliceByTime) + m.Get("/compare/*", repo.MustBeNotEmpty, reqRepoCodeReader, + repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.CompareDiff) m.Group("/pulls", func() { m.Group("/{index}", func() { m.Combo("").Get(repo.GetPullRequest) diff --git a/routers/hat/repo/repo.go b/routers/hat/repo/repo.go new file mode 100644 index 0000000..176ea57 --- /dev/null +++ b/routers/hat/repo/repo.go @@ -0,0 +1,480 @@ +package repo + +import ( + "fmt" + "net/http" + "strings" + + admin_model "code.gitea.io/gitea/models/admin" + access_model "code.gitea.io/gitea/models/perm/access" + repo_model "code.gitea.io/gitea/models/repo" + unit_model "code.gitea.io/gitea/models/unit" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" + gitea_git "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/gitdiff" +) + +func PrepareComapreDiff( + ctx *context.APIContext, + headUser *user_model.User, + headRepo *repo_model.Repository, + headGitRepo *gitea_git.Repository, + compareInfo *gitea_git.CompareInfo, + baseBranch, headBranch string) bool { + var ( + err error + ) + + ctx.Data["CommitRepoLink"] = headRepo.Link() + + headCommitID := headBranch + if ctx.Data["HeadIsCommit"] == false { + if ctx.Data["HeadIsTag"] == true { + headCommitID, err = headGitRepo.GetTagCommitID(headBranch) + } else { + headCommitID, err = headGitRepo.GetBranchCommitID(headBranch) + } + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetRefCommitID", err) + return false + } + } + + ctx.Data["AfterCommitID"] = headCommitID + + if headCommitID == compareInfo.MergeBase { + ctx.Data["IsNothingToCompare"] = true + return true + } + + repoPath := repo_model.RepoPath(headUser.Name, headRepo.Name) + + gitRepo, _ := gitea_git.OpenRepository(ctx, repoPath) + defer gitRepo.Close() + + diff, err := gitdiff.GetDiff(gitRepo, &gitdiff.DiffOptions{ + BeforeCommitID: compareInfo.MergeBase, + AfterCommitID: headCommitID, + MaxLines: setting.Git.MaxGitDiffLines, + MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters, + MaxFiles: setting.Git.MaxGitDiffFiles, + }) + + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetDiff", err) + return false + } + + if diff.NumFiles == 0 { + ctx.Data["Diff"] = nil + ctx.Data["DiffNotAvailable"] = true + } else { + ctx.Data["Diff"] = diff + ctx.Data["DiffNotAvailable"] = false + } + + return false +} + +type CompareCommit struct { + *git.Commit + Sha string + ParentShas []string +} + +func CompareDiff(ctx *context.APIContext) { + headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := ParseCompareInfo(ctx) + if ctx.Written() { + return + } + defer headGitRepo.Close() + + _ = PrepareComapreDiff(ctx, headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch) + + result := make([]CompareCommit, 0) + for _, commit := range compareInfo.Commits { + compareCommit := CompareCommit{ + Commit: commit, + Sha: commit.ID.String(), + } + for _, p := range commit.Parents { + compareCommit.ParentShas = append(compareCommit.ParentShas, p.String()) + } + result = append(result, compareCommit) + } + + different := struct { + Commits []CompareCommit + Diff interface{} + CommitsCount int + Latestsha string + }{ + Commits: result, + Diff: ctx.Context.Data["Diff"], + } + + different.CommitsCount = len(compareInfo.Commits) + different.Latestsha = compareInfo.HeadCommitID + + ctx.JSON(http.StatusOK, different) +} + +func ParseCompareInfo(ctx *context.APIContext) (*user_model.User, *repo_model.Repository, *gitea_git.Repository, *gitea_git.CompareInfo, string, string) { + baseRepo := ctx.Repo.Repository + + var ( + headUser *user_model.User + headRepo *repo_model.Repository + headBranch string + isSameRepo bool + infoPath string + err error + ) + + infoPath = ctx.Params("*") + infos := strings.SplitN(infoPath, "...", 2) + if len(infos) != 2 { + log.Trace("ParseCompareInfo[%d]: not enough compared branches information %s", baseRepo.ID, infos) + ctx.NotFound("CompareAndPullRequest", nil) + return nil, nil, nil, nil, "", "" + } + + ctx.Data["BaseName"] = baseRepo.OwnerName + baseBranch := infos[0] + ctx.Data["BaseBranch"] = baseBranch + + headInfos := strings.Split(infos[1], ":") + if len(headInfos) == 1 { + isSameRepo = true + headUser = ctx.Repo.Owner + headBranch = headInfos[0] + } else if len(headInfos) == 2 { + headInfosSplit := strings.Split(headInfos[0], "/") + if len(headInfosSplit) == 1 { + headUser, err = user_model.GetUserByName(ctx, headInfos[0]) + if err != nil { + if user_model.IsErrUserNotExist(err) { + ctx.NotFound("GetUserByName", nil) + } else { + ctx.Error(http.StatusInternalServerError, "GetUserByName", err) + } + return nil, nil, nil, nil, "", "" + } + + headBranch = headInfos[1] + isSameRepo = headUser.ID == ctx.Repo.Owner.ID + + if isSameRepo { + headRepo = baseRepo + } + } else { + headRepo, err = repo_model.GetRepositoryByOwnerAndName(headInfosSplit[0], headInfosSplit[1]) + if err != nil { + if repo_model.IsErrRepoNotExist(err) { + ctx.NotFound("GetRepositoryByOwnerAndName", nil) + } else { + ctx.Error(http.StatusInternalServerError, "GetRepositoryByOwnerAndName", err) + } + return nil, nil, nil, nil, "", "" + } + if err := headRepo.GetOwner(ctx); err != nil { + if user_model.IsErrUserNotExist(err) { + ctx.NotFound("GetOwner", nil) + } else { + ctx.Error(http.StatusInternalServerError, "GetOwner", err) + } + return nil, nil, nil, nil, "", "" + } + headBranch = headInfos[1] + headUser = headRepo.Owner + isSameRepo = headRepo.ID == ctx.Repo.Repository.ID + } + } else { + ctx.NotFound("CompareAndPullRequest", nil) + return nil, nil, nil, nil, "", "" + } + ctx.Data["HeadUser"] = headUser + ctx.Data["HeadBranch"] = headBranch + ctx.Repo.PullRequest.SameRepo = isSameRepo + if ctx.Repo.GitRepo == nil && !baseRepo.IsEmpty { + var err error + ctx.Repo.GitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.RepoPath()) + if err != nil { + ctx.Error(http.StatusInternalServerError, "OpenRepository", err) + return nil, nil, nil, nil, "", "" + } + defer ctx.Repo.GitRepo.Close() + } + + baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(baseBranch) + baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(baseBranch) + baseIsTag := ctx.Repo.GitRepo.IsTagExist(baseBranch) + if !baseIsCommit && !baseIsBranch && !baseIsTag { + if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(baseBranch); baseCommit == nil { + ctx.NotFound("IsRefExist", nil) + return nil, nil, nil, nil, "", "" + } else { + baseBranch = baseCommit.ID.String() + ctx.Data["BaseBranch"] = baseBranch + baseIsCommit = true + } + } + ctx.Data["BaseIsCommit"] = baseIsCommit + ctx.Data["BaseIsBranch"] = baseIsBranch + ctx.Data["BaseIsTag"] = baseIsTag + + var rootRepo *repo_model.Repository + if baseRepo.IsFork { + err = baseRepo.GetBaseRepo() + if err != nil { + if !repo_model.IsErrRepoNotExist(err) { + ctx.NotFound("GetBaseRepo", nil) + } else { + ctx.Error(http.StatusInternalServerError, "GetBaseRepo", err) + } + return nil, nil, nil, nil, "", "" + } else { + rootRepo = baseRepo.BaseRepo + } + } + + var ownForkRpo *repo_model.Repository + if ctx.Doer != nil && baseRepo.OwnerID != ctx.Doer.ID { + has := repo_model.HasForkedRepo(ctx.Doer.ID, baseRepo.ID) + if has { + ownForkRpo = repo_model.GetForkedRepo(ctx.Doer.ID, baseRepo.ID) + ctx.Data["OwnForkRepo"] = ownForkRpo + } + } + + has := headRepo != nil + if !has && rootRepo != nil && rootRepo.OwnerID == headUser.ID { + headRepo = rootRepo + has = true + } + + if !has && ownForkRpo != nil && ownForkRpo.OwnerID == headUser.ID { + headRepo = ownForkRpo + has = true + } + + if !has { + has = repo_model.HasForkedRepo(headUser.ID, baseRepo.ID) + headRepo = repo_model.GetForkedRepo(headUser.ID, baseRepo.ID) + } + + if !has && baseRepo.IsFork { + has = repo_model.HasForkedRepo(headUser.ID, baseRepo.ForkID) + headRepo = repo_model.GetForkedRepo(headUser.ID, baseRepo.ForkID) + } + + if !isSameRepo && !has { + ctx.Data["PageIsComparePull"] = false + } + + var headGitRepo *gitea_git.Repository + if isSameRepo { + headRepo = ctx.Repo.Repository + headGitRepo = ctx.Repo.GitRepo + } else if has { + headGitRepo, err = git.OpenRepository(ctx, headRepo.RepoPath()) + if err != nil { + ctx.Error(http.StatusInternalServerError, "OpenRepository", err) + return nil, nil, nil, nil, "", "" + } + defer headGitRepo.Close() + } + ctx.Data["HeadRepo"] = headRepo + + permBase, err := access_model.GetUserRepoPermission(ctx, baseRepo, ctx.Doer) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) + return nil, nil, nil, nil, "", "" + } + + if !permBase.CanRead(unit_model.TypeCode) { + if log.IsTrace() { + log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in baseRepo has Permissions: %-+v", + ctx.Doer, + baseRepo, + permBase) + } + ctx.NotFound("permBase.CanRead", nil) + return nil, nil, nil, nil, "", "" + } + + if !isSameRepo { + permHead, err := access_model.GetUserRepoPermission(ctx, headRepo, ctx.Doer) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) + return nil, nil, nil, nil, "", "" + } + if !permHead.CanRead(unit_model.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) + } + ctx.NotFound("perHead.CanRead", nil) + return nil, nil, nil, nil, "", "" + } + } + + if rootRepo != nil && + rootRepo.ID != headRepo.ID && + rootRepo.ID != baseRepo.ID { + perm, branches, err := getBranchesForRepo(ctx, ctx.Doer, rootRepo) + if err != nil { + ctx.Error(http.StatusInternalServerError, "getBranchesForRepo", err) + return nil, nil, nil, nil, "", "" + } + if perm { + ctx.Data["RootRepo"] = rootRepo + ctx.Data["RootRepoBranches"] = branches + } + } + + if ownForkRpo != nil && + ownForkRpo.ID != headRepo.ID && + ownForkRpo.ID != baseRepo.ID && + (rootRepo == nil || ownForkRpo.ID != rootRepo.ID) { + perm, branches, err := getBranchesForRepo(ctx, ctx.Doer, ownForkRpo) + if err != nil { + ctx.Error(http.StatusInternalServerError, "getBranchesForRepo", err) + return nil, nil, nil, nil, "", "" + } + if perm { + ctx.Data["OwnForkRepo"] = ownForkRpo + ctx.Data["OwnForkRepoBranches"] = branches + } + } + + headIsCommit := headGitRepo.IsCommitExist(headBranch) + headIsBranch := headGitRepo.IsBranchExist(headBranch) + headIsTag := headGitRepo.IsTagExist(headBranch) + if !headIsCommit && !headIsBranch && !headIsTag { + if headCommit, _ := headGitRepo.GetCommit(headBranch); headCommit == nil { + ctx.NotFound("isRefExist", nil) + return nil, nil, nil, nil, "", "" + } else { + headBranch = headCommit.ID.String() + ctx.Data["HeadBranch"] = headBranch + headIsCommit = true + } + } + + ctx.Data["HeadIsCommit"] = headIsCommit + ctx.Data["HeadIsBranch"] = headIsBranch + ctx.Data["HeadIsTag"] = headIsTag + + if ctx.Data["PageIsComparePull"] == nil { + ctx.Data["PageIsComparePull"] = headIsBranch && baseIsBranch + } + + if ctx.Data["PageIsComparePull"] == true && !permBase.CanReadIssuesOrPulls(true) { + if log.IsTrace() { + log.Trace("Permission Denied: User: %-v cannot create/read pull requests in Repo: %-v\nUser in baseRepo has Permissions: %-+v", + ctx.Doer, + baseRepo, + permBase) + } + ctx.NotFound("can not read issues or pulls", nil) + return nil, nil, nil, nil, "", "" + } + + baseBranchRef := baseBranch + if baseIsBranch { + baseBranchRef = git.BranchPrefix + baseBranch + } else if baseIsTag { + baseBranchRef = git.TagPrefix + baseBranch + } + headBranchRef := headBranch + if headIsBranch { + headBranchRef = git.BranchPrefix + headBranch + } else if headIsTag { + headBranchRef = git.TagPrefix + headBranch + } + + compareInfo, err := headGitRepo.GetCompareInfo(baseRepo.RepoPath(), baseBranchRef, headBranchRef, false, false) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err) + return nil, nil, nil, nil, "", "" + } + ctx.Data["BeforeCommitID"] = compareInfo.MergeBase + return headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch +} + +func getBranchesForRepo(ctx *context.APIContext, user *user_model.User, repo *repo_model.Repository) (bool, []string, error) { + perm, err := access_model.GetUserRepoPermission(ctx, repo, user) + if err != nil { + return false, nil, err + } + if !perm.CanRead(unit_model.TypeCode) { + return false, nil, nil + } + gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) + if err != nil { + return false, nil, err + } + defer gitRepo.Close() + branches, _, err := gitRepo.GetBranchNames(0, 0) + if err != nil { + return false, nil, err + } + return true, branches, nil +} + +func MustBeNotEmpty(ctx *context.Context) { + if ctx.Repo.Repository.IsEmpty { + ctx.NotFound("MustBeNotEmpty", nil) + } +} + +func SetEditorconfigIfExists(ctx *context.Context) { + if ctx.Repo.Repository.IsEmpty { + ctx.Data["Edidorconfig"] = nil + return + } + ec, err := ctx.Repo.GetEditorconfig() + + if err != nil && !git.IsErrNotExist(err) { + description := fmt.Sprintf("Error while getting .Editconfig file: %v", err) + if err := admin_model.CreateRepositoryNotice(description); err != nil { + ctx.ServerError("CreateRepositoryNotice", err) + } + return + } + ctx.Data["Editorconfig"] = ec +} + +// SetdiffViewStyle set diff style as render variable +func SetDiffViewStyle(ctx *context.Context) { + + queryStyle := ctx.Params("style") + if !ctx.IsSigned { + ctx.Data["IsSplitStyle"] = queryStyle == "split" + return + } + + var ( + userStyle = ctx.Doer.DiffViewStyle + style string + ) + + if queryStyle == "unified" || queryStyle == "split" { + style = queryStyle + } else if userStyle == "unified" || userStyle == "split" { + style = userStyle + } else { + style = "unified" + } + ctx.Data["IsSplitStyle"] = style == "split" + if err := user_model.UpdateUserDiffViewStyle(ctx.Doer, style); err != nil { + ctx.ServerError("ErrUpdateDiffViewStyle", err) + } +}