Files

479 lines
14 KiB
Go
Raw Permalink Normal View History

2014-03-24 18:25:15 +08:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
2014-03-24 18:25:15 +08:00
package repo
2014-07-26 02:28:04 -04:00
import (
"errors"
"fmt"
2024-01-15 09:49:24 +01:00
"html/template"
"net/http"
2024-01-15 09:49:24 +01:00
"path"
"strings"
2014-07-26 02:28:04 -04:00
2021-12-10 16:14:24 +08:00
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
2022-06-12 23:51:54 +08:00
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
2024-11-24 16:18:57 +08:00
"code.gitea.io/gitea/models/renderhelper"
2022-06-12 23:51:54 +08:00
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/base"
2019-08-15 09:07:28 -03:00
"code.gitea.io/gitea/modules/charset"
2025-04-29 10:51:32 +08:00
"code.gitea.io/gitea/modules/fileicon"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/log"
2024-01-15 09:49:24 +01:00
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
2024-01-15 09:49:24 +01:00
"code.gitea.io/gitea/modules/util"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/context"
git_service "code.gitea.io/gitea/services/git"
"code.gitea.io/gitea/services/gitdiff"
2024-10-02 04:25:08 +09:00
repo_service "code.gitea.io/gitea/services/repository"
"code.gitea.io/gitea/services/repository/gitgraph"
2014-07-26 02:28:04 -04:00
)
const (
tplCommits templates.TplName = "repo/commits"
tplGraph templates.TplName = "repo/graph"
tplGraphDiv templates.TplName = "repo/graph/div"
tplCommitPage templates.TplName = "repo/commit_page"
2014-07-26 02:28:04 -04:00
)
2016-11-24 15:04:31 +08:00
// RefCommits render commits page
2016-03-11 11:56:52 -05:00
func RefCommits(ctx *context.Context) {
2014-11-06 22:06:41 -05:00
switch {
2016-08-24 21:35:03 -07:00
case len(ctx.Repo.TreePath) == 0:
2014-11-06 22:06:41 -05:00
Commits(ctx)
2016-08-24 21:35:03 -07:00
case ctx.Repo.TreePath == "search":
2014-11-06 22:06:41 -05:00
SearchCommits(ctx)
default:
FileHistory(ctx)
}
}
2016-11-24 15:04:31 +08:00
// Commits render branch's commits
2016-03-11 11:56:52 -05:00
func Commits(ctx *context.Context) {
2015-08-20 20:18:49 +08:00
ctx.Data["PageIsCommits"] = true
2017-07-31 03:23:10 +02:00
if ctx.Repo.Commit == nil {
2025-02-17 14:13:17 +08:00
ctx.NotFound(nil)
2017-07-31 03:23:10 +02:00
return
}
2017-10-26 02:49:16 +02:00
ctx.Data["PageIsViewCode"] = true
2014-07-26 02:28:04 -04:00
2025-01-16 21:52:21 +08:00
commitsCount := ctx.Repo.CommitsCount
2014-07-26 02:28:04 -04:00
2025-06-18 03:48:09 +02:00
page := max(ctx.FormInt("page"), 1)
2014-07-26 02:28:04 -04:00
pageSize := ctx.FormInt("limit")
2020-01-24 19:00:29 +00:00
if pageSize <= 0 {
pageSize = setting.Git.CommitsRangeSize
2020-01-24 19:00:29 +00:00
}
2014-07-26 02:28:04 -04:00
// Both `git log branchName` and `git log commitId` work.
commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "", "", "")
2014-07-26 02:28:04 -04:00
if err != nil {
2018-01-10 22:34:17 +01:00
ctx.ServerError("CommitsByRange", err)
2014-07-26 02:28:04 -04:00
return
}
ctx.Data["Commits"], err = processGitCommits(ctx, commits)
if err != nil {
ctx.ServerError("processGitCommits", err)
return
}
2024-08-20 01:04:06 +08:00
commitIDs := make([]string, 0, len(commits))
for _, c := range commits {
commitIDs = append(commitIDs, c.ID.String())
}
commitsTagsMap, err := repo_model.FindTagsByCommitIDs(ctx, ctx.Repo.Repository.ID, commitIDs...)
if err != nil {
log.Error("FindTagsByCommitIDs: %v", err)
ctx.Flash.Error(ctx.Tr("internal_error_skipped", "FindTagsByCommitIDs"))
2024-08-20 01:04:06 +08:00
} else {
ctx.Data["CommitsTagsMap"] = commitsTagsMap
}
2015-11-10 16:46:17 -05:00
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
2014-07-26 02:28:04 -04:00
ctx.Data["CommitCount"] = commitsCount
2026-03-08 15:35:50 +01:00
pager := context.NewPagination(commitsCount, pageSize, page, 5)
2024-12-30 09:57:38 +08:00
pager.AddParamFromRequest(ctx.Req)
ctx.Data["Page"] = pager
ctx.HTML(http.StatusOK, tplCommits)
2014-07-26 02:28:04 -04:00
}
2016-12-29 00:44:32 +01:00
// Graph render commit graph - show commits from all branches.
func Graph(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.commit_graph")
2016-12-29 00:44:32 +01:00
ctx.Data["PageIsCommits"] = true
2017-10-26 02:49:16 +02:00
ctx.Data["PageIsViewCode"] = true
mode := strings.ToLower(ctx.FormTrim("mode"))
2020-08-06 09:04:08 +01:00
if mode != "monochrome" {
mode = "color"
}
ctx.Data["Mode"] = mode
hidePRRefs := ctx.FormBool("hide-pr-refs")
ctx.Data["HidePRRefs"] = hidePRRefs
branches := ctx.FormStrings("branch")
realBranches := make([]string, len(branches))
copy(realBranches, branches)
for i, branch := range realBranches {
if strings.HasPrefix(branch, "--") {
2021-12-02 08:28:08 +01:00
realBranches[i] = git.BranchPrefix + branch
}
}
ctx.Data["SelectedBranches"] = realBranches
files := ctx.FormStrings("file")
2016-12-29 00:44:32 +01:00
graphCommitsCount, err := ctx.Repo.GetCommitGraphsCount(ctx, hidePRRefs, realBranches, files)
if err != nil {
log.Warn("GetCommitGraphsCount error for generate graph exclude prs: %t branches: %s in %-v, Will Ignore branches and try again. Underlying Error: %v", hidePRRefs, branches, ctx.Repo.Repository, err)
realBranches = []string{}
graphCommitsCount, err = ctx.Repo.GetCommitGraphsCount(ctx, hidePRRefs, realBranches, files)
if err != nil {
ctx.ServerError("GetCommitGraphsCount", err)
return
}
}
page := ctx.FormInt("page")
2019-10-15 00:38:35 +03:00
graph, err := gitgraph.GetCommitGraph(ctx.Repo.GitRepo, page, 0, hidePRRefs, realBranches, files)
2016-12-29 00:44:32 +01:00
if err != nil {
2018-01-10 22:34:17 +01:00
ctx.ServerError("GetCommitGraph", err)
2016-12-29 00:44:32 +01:00
return
}
if err := graph.LoadAndProcessCommits(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo); err != nil {
ctx.ServerError("LoadAndProcessCommits", err)
return
}
2016-12-29 00:44:32 +01:00
ctx.Data["Graph"] = graph
gitRefs, err := ctx.Repo.GitRepo.GetRefs()
if err != nil {
ctx.ServerError("GitRepo.GetRefs", err)
return
}
ctx.Data["AllRefs"] = gitRefs
2016-12-29 00:44:32 +01:00
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
2023-08-24 10:31:54 +09:00
2025-07-04 23:41:19 +08:00
divOnly := ctx.FormBool("div-only")
queryParams := ctx.Req.URL.Query()
queryParams.Del("div-only")
2026-03-08 15:35:50 +01:00
paginator := context.NewPagination(graphCommitsCount, setting.UI.GraphMaxCommitNum, page, 5)
2025-07-04 23:41:19 +08:00
paginator.AddParamFromQuery(queryParams)
2020-08-06 09:04:08 +01:00
ctx.Data["Page"] = paginator
2025-07-04 23:41:19 +08:00
if divOnly {
ctx.HTML(http.StatusOK, tplGraphDiv)
return
}
ctx.HTML(http.StatusOK, tplGraph)
2016-12-29 00:44:32 +01:00
}
2016-11-24 15:04:31 +08:00
// SearchCommits render commits filtered by keyword
2016-03-11 11:56:52 -05:00
func SearchCommits(ctx *context.Context) {
2015-08-20 20:18:49 +08:00
ctx.Data["PageIsCommits"] = true
2017-10-26 02:49:16 +02:00
ctx.Data["PageIsViewCode"] = true
2014-07-26 02:28:04 -04:00
query := ctx.FormTrim("q")
if len(query) == 0 {
2025-01-12 11:39:46 +08:00
ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.RefTypeNameSubURL())
2014-07-26 02:28:04 -04:00
return
}
all := ctx.FormBool("all")
opts := git.NewSearchCommitsOptions(query, all)
commits, err := ctx.Repo.Commit.SearchCommits(opts)
2014-07-26 02:28:04 -04:00
if err != nil {
2018-01-10 22:34:17 +01:00
ctx.ServerError("SearchCommits", err)
2014-07-26 02:28:04 -04:00
return
}
2021-08-09 20:08:51 +02:00
ctx.Data["CommitCount"] = len(commits)
ctx.Data["Commits"], err = processGitCommits(ctx, commits)
if err != nil {
ctx.ServerError("processGitCommits", err)
return
}
2014-07-26 02:28:04 -04:00
ctx.Data["Keyword"] = query
if all {
2024-03-15 00:24:59 +01:00
ctx.Data["All"] = true
}
2015-11-10 16:46:17 -05:00
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
ctx.HTML(http.StatusOK, tplCommits)
2014-07-26 02:28:04 -04:00
}
2016-11-24 15:04:31 +08:00
// FileHistory show a file's reversions
2016-03-11 11:56:52 -05:00
func FileHistory(ctx *context.Context) {
2025-04-14 03:27:31 +08:00
if ctx.Repo.TreePath == "" {
2014-11-06 22:06:41 -05:00
Commits(ctx)
return
}
commitsCount, err := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository, ctx.Repo.RefFullName.ShortName(), ctx.Repo.TreePath)
2014-11-06 22:06:41 -05:00
if err != nil {
2018-01-10 22:34:17 +01:00
ctx.ServerError("FileCommitsCount", err)
2014-11-06 22:06:41 -05:00
return
} else if commitsCount == 0 {
2025-02-17 14:13:17 +08:00
ctx.NotFound(nil)
2014-11-06 22:06:41 -05:00
return
}
2025-06-18 03:48:09 +02:00
page := max(ctx.FormInt("page"), 1)
2014-11-06 22:06:41 -05:00
2023-05-08 00:10:53 -07:00
commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
Revision: ctx.Repo.RefFullName.ShortName(), // FIXME: legacy code used ShortName
2025-04-14 03:27:31 +08:00
File: ctx.Repo.TreePath,
2023-05-08 00:10:53 -07:00
Page: page,
})
2014-11-06 22:06:41 -05:00
if err != nil {
2018-01-10 22:34:17 +01:00
ctx.ServerError("CommitsByFileAndRange", err)
2014-11-06 22:06:41 -05:00
return
}
ctx.Data["Commits"], err = processGitCommits(ctx, commits)
if err != nil {
ctx.ServerError("processGitCommits", err)
return
}
2015-11-10 16:46:17 -05:00
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
2025-04-14 03:27:31 +08:00
ctx.Data["FileTreePath"] = ctx.Repo.TreePath
2014-11-06 22:06:41 -05:00
ctx.Data["CommitCount"] = commitsCount
2026-03-08 15:35:50 +01:00
pager := context.NewPagination(commitsCount, setting.Git.CommitsRangeSize, page, 5)
2024-12-30 09:57:38 +08:00
pager.AddParamFromRequest(ctx.Req)
ctx.Data["Page"] = pager
ctx.HTML(http.StatusOK, tplCommits)
2014-11-06 22:06:41 -05:00
}
func LoadBranchesAndTags(ctx *context.Context) {
2024-10-02 04:25:08 +09:00
response, err := repo_service.LoadBranchesAndTags(ctx, ctx.Repo, ctx.PathParam("sha"))
if err == nil {
ctx.JSON(http.StatusOK, response)
return
}
2024-06-19 06:32:45 +08:00
ctx.NotFoundOrServerError(fmt.Sprintf("could not load branches and tags the commit %s belongs to", ctx.PathParam("sha")), git.IsErrNotExist, err)
}
2016-11-24 15:04:31 +08:00
// Diff show different from current commit to previous commit
2016-03-11 11:56:52 -05:00
func Diff(ctx *context.Context) {
2015-08-21 00:18:30 +08:00
ctx.Data["PageIsDiff"] = true
2014-07-26 02:28:04 -04:00
userName := ctx.Repo.Owner.Name
repoName := ctx.Repo.Repository.Name
2024-12-24 21:47:45 +08:00
commitID := ctx.PathParam("sha")
diffBlobExcerptData := &gitdiff.DiffBlobExcerptData{
BaseLink: ctx.Repo.RepoLink + "/blob_excerpt",
2026-01-09 12:37:16 +08:00
DiffStyle: GetDiffViewStyle(ctx),
AfterCommitID: commitID,
}
gitRepo := ctx.Repo.GitRepo
var gitRepoStore gitrepo.Repository = ctx.Repo.Repository
if ctx.Data["PageIsWiki"] != nil {
var err error
gitRepoStore = ctx.Repo.Repository.WikiStorageRepo()
gitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, gitRepoStore)
if err != nil {
ctx.ServerError("Repo.GitRepo.GetCommit", err)
return
}
diffBlobExcerptData.BaseLink = ctx.Repo.RepoLink + "/wiki/blob_excerpt"
}
2016-03-21 10:49:46 -04:00
commit, err := gitRepo.GetCommit(commitID)
2016-03-21 10:49:46 -04:00
if err != nil {
2016-08-15 15:27:19 -07:00
if git.IsErrNotExist(err) {
2025-02-17 14:13:17 +08:00
ctx.NotFound(err)
2016-08-15 15:27:19 -07:00
} else {
2018-01-10 22:34:17 +01:00
ctx.ServerError("Repo.GitRepo.GetCommit", err)
2016-08-15 15:27:19 -07:00
}
2016-03-21 10:49:46 -04:00
return
}
2023-12-13 21:02:00 +00:00
if len(commitID) != commit.ID.Type().FullLength() {
2016-11-06 22:15:44 +01:00
commitID = commit.ID.String()
}
fileOnly := ctx.FormBool("file-only")
maxLines, maxFiles := setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles
files := ctx.FormStrings("files")
if fileOnly && (len(files) == 2 || len(files) == 1) {
maxLines, maxFiles = -1, -1
}
diff, err := gitdiff.GetDiffForRender(ctx, ctx.Repo.RepoLink, gitRepo, &gitdiff.DiffOptions{
AfterCommitID: commitID,
SkipTo: ctx.FormString("skip-to"),
MaxLines: maxLines,
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
MaxFiles: maxFiles,
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)),
}, files...)
2014-07-26 02:28:04 -04:00
if err != nil {
2025-02-17 14:13:17 +08:00
ctx.NotFound(err)
2014-07-26 02:28:04 -04:00
return
}
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, gitRepoStore, gitRepo, "", commitID)
if err != nil {
ctx.ServerError("GetDiffShortStat", err)
return
}
ctx.Data["DiffShortStat"] = diffShortStat
2014-07-26 02:28:04 -04:00
parents := make([]string, commit.ParentCount())
for i := 0; i < commit.ParentCount(); i++ {
sha, err := commit.ParentID(i)
2014-07-26 02:28:04 -04:00
if err != nil {
2025-02-17 14:13:17 +08:00
ctx.NotFound(err)
2014-07-26 02:28:04 -04:00
return
}
2020-02-28 00:10:27 +01:00
parents[i] = sha.String()
2014-07-26 02:28:04 -04:00
}
2016-03-21 10:49:46 -04:00
ctx.Data["CommitID"] = commitID
ctx.Data["AfterCommitID"] = commitID
2014-07-26 02:28:04 -04:00
ctx.Data["Username"] = userName
ctx.Data["Reponame"] = repoName
var parentCommit *git.Commit
var parentCommitID string
2019-09-16 11:03:22 +02:00
if commit.ParentCount() > 0 {
parentCommit, err = gitRepo.GetCommit(parents[0])
2019-09-16 11:03:22 +02:00
if err != nil {
2025-02-17 14:13:17 +08:00
ctx.NotFound(err)
2019-09-16 11:03:22 +02:00
return
}
parentCommitID = parentCommit.ID.String()
2019-09-16 11:03:22 +02:00
}
2021-11-16 18:18:25 +00:00
setCompareContext(ctx, parentCommit, commit, userName, repoName)
2015-08-31 16:24:28 +09:00
ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID)
2014-07-26 02:28:04 -04:00
ctx.Data["Commit"] = commit
ctx.Data["Diff"] = diff
ctx.Data["DiffBlobExcerptData"] = diffBlobExcerptData
if !fileOnly {
diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, parentCommitID, commitID)
if err != nil {
ctx.ServerError("GetDiffTree", err)
return
}
2025-04-29 10:51:32 +08:00
renderedIconPool := fileicon.NewRenderedIconPool()
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(renderedIconPool, diffTree, nil)
ctx.PageData["FolderIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolder())
ctx.PageData["FolderOpenIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolderOpen())
ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
}
statuses, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll)
if err != nil {
log.Error("GetLatestCommitStatus: %v", err)
}
if !ctx.Repo.CanRead(unit_model.TypeActions) {
git_model.CommitStatusesHideActionsURL(ctx, statuses)
}
2022-06-12 23:51:54 +08:00
ctx.Data["CommitStatus"] = git_model.CalcCommitStatus(statuses)
ctx.Data["CommitStatuses"] = statuses
verification := asymkey_service.ParseCommitWithSignature(ctx, commit)
ctx.Data["Verification"] = verification
ctx.Data["Author"] = user_model.ValidateCommitWithEmail(ctx, commit)
2014-07-26 02:28:04 -04:00
ctx.Data["Parents"] = parents
ctx.Data["DiffNotAvailable"] = diffShortStat.NumFiles == 0
2019-05-24 10:52:05 +03:00
2021-12-10 16:14:24 +08:00
if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
2023-09-29 14:12:54 +02:00
return repo_model.IsOwnerMemberCollaborator(ctx, ctx.Repo.Repository, user.ID)
2021-12-10 16:14:24 +08:00
}, nil); err != nil {
ctx.ServerError("CalculateTrustStatus", err)
return
}
2019-05-24 10:52:05 +03:00
note := &git.Note{}
2021-06-07 00:44:58 +01:00
err = git.GetNote(ctx, ctx.Repo.GitRepo, commitID, note)
2019-05-24 10:52:05 +03:00
if err == nil {
ctx.Data["NoteCommit"] = note.Commit
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
2024-11-24 16:18:57 +08:00
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{CurrentRefPath: path.Join("commit", util.PathEscapeSegments(commitID))})
ctx.Data["NoteRendered"], err = markup.PostProcessCommitMessage(rctx, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
2024-01-15 09:49:24 +01:00
if err != nil {
ctx.ServerError("PostProcessCommitMessage", err)
2024-01-15 09:49:24 +01:00
return
}
} else if !git.IsErrNotExist(err) {
log.Error("GetNote: %v", err)
2019-05-24 10:52:05 +03:00
}
pr, _ := issues_model.GetPullRequestByMergedCommit(ctx, ctx.Repo.Repository.ID, commitID)
if pr != nil {
ctx.Data["MergedPRIssueNumber"] = pr.Index
}
ctx.HTML(http.StatusOK, tplCommitPage)
2014-07-26 02:28:04 -04:00
}
2016-11-24 15:04:31 +08:00
// RawDiff dumps diff results of repository in given commit ID to io.Writer
2016-03-21 10:49:46 -04:00
func RawDiff(ctx *context.Context) {
var gitRepo *git.Repository
if ctx.Data["PageIsWiki"] != nil {
wikiRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
if err != nil {
ctx.ServerError("OpenRepository", err)
return
}
defer wikiRepo.Close()
gitRepo = wikiRepo
} else {
gitRepo = ctx.Repo.GitRepo
if gitRepo == nil {
ctx.ServerError("GitRepo not open", fmt.Errorf("no open git repo for '%s'", ctx.Repo.Repository.FullName()))
return
}
}
if err := git.GetRawDiff(
gitRepo,
2024-12-24 21:47:45 +08:00
ctx.PathParam("sha"),
git.RawDiffType(ctx.PathParam("ext")),
ctx.Resp,
); err != nil {
if git.IsErrNotExist(err) {
2025-02-17 14:13:17 +08:00
ctx.NotFound(errors.New("commit " + ctx.PathParam("sha") + " does not exist."))
return
}
2018-01-10 22:34:17 +01:00
ctx.ServerError("GetRawDiff", err)
2016-07-30 11:02:22 -04:00
return
}
2016-03-21 10:49:46 -04:00
}
func processGitCommits(ctx *context.Context, gitCommits []*git.Commit) ([]*git_model.SignCommitWithStatuses, error) {
commits, err := git_service.ConvertFromGitCommit(ctx, gitCommits, ctx.Repo.Repository)
if err != nil {
return nil, err
}
if !ctx.Repo.CanRead(unit_model.TypeActions) {
for _, commit := range commits {
if commit.Status == nil {
continue
}
commit.Status.HideActionsURL(ctx)
git_model.CommitStatusesHideActionsURL(ctx, commit.Statuses)
}
}
return commits, nil
}