fix: resolve actions commit status permission per repository (#38977)

Various pages did not display the correct action run list tooltips. Fix
those tooltips like here on the `/pulls` page:

`ctx.Repo.Permission` is the zero value outside a repository route, so
on `/pulls`, `/issues`, `/notifications/subscriptions` and the dashboard
repo list the commit status "Details" link was always stripped. The live
job status is looked up from that target URL, so running checks also
rendered as a static pending dot instead of a spinner.

Resolve the Actions unit permission per repository instead.

Also drops the releases page's gate on *loading* statuses, which hid
external CI results from anyone without Actions read; it now loads them
and hides only the URL, like every other page.

Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
silverwind
2026-08-19 20:09:00 +02:00
committed by GitHub
parent e355c39e91
commit 4e813a262f
19 changed files with 98 additions and 97 deletions
+5
View File
@@ -24,6 +24,7 @@ import (
user_model "gitea.dev/models/user"
"gitea.dev/modules/base"
"gitea.dev/modules/cache"
"gitea.dev/modules/cachegroup"
"gitea.dev/modules/git"
"gitea.dev/modules/httplib"
code_indexer "gitea.dev/modules/indexer/code"
@@ -427,6 +428,10 @@ func repoAssignmentLegacy(ctx *Context, data *repoAssignmentPrepareDataStruct) {
return
}
}
// publish it so code resolving the same permission later in this request reuses it
if c := cache.GetContextCache(ctx); c != nil {
c.Put(cachegroup.RepoUserPermission, access_model.RepoUserPermissionCacheKey(repo.ID, ctx.Doer), ctx.Repo.Permission)
}
if !ctx.Repo.Permission.HasAnyUnitAccessOrPublicAccess() && !canWriteAsMaintainer(ctx) {
if ctx.FormString("go-get") == "1" {
+1 -7
View File
@@ -5,7 +5,6 @@ package convert
import (
"context"
"fmt"
git_model "gitea.dev/models/git"
issues_model "gitea.dev/models/issues"
@@ -55,12 +54,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
return nil
}
var doerID int64
if doer != nil {
doerID = doer.ID
}
repoUserPerm, err := cache.GetWithContextCache(ctx, cachegroup.RepoUserPermission, fmt.Sprintf("%d-%d", pr.BaseRepoID, doerID),
repoUserPerm, err := cache.GetWithContextCache(ctx, cachegroup.RepoUserPermission, access_model.RepoUserPermissionCacheKey(pr.BaseRepoID, doer),
func(ctx context.Context, _ string) (access_model.Permission, error) {
return access_model.GetDoerRepoPermission(ctx, pr.BaseRepo, doer)
},
+10 -4
View File
@@ -931,8 +931,9 @@ func formatSquashMergeCommitMessages(commits []*git.Commit) string {
return util.UnsafeBytesToString(buf)
}
// GetIssuesAllCommitStatus returns a map of issue ID to a list of all statuses for the most recent commit as well as a map of issue ID to only the commit's latest status
func GetIssuesAllCommitStatus(ctx context.Context, issues issues_model.IssueList) (map[int64][]*git_model.CommitStatus, map[int64]*git_model.CommitStatus, error) {
// GetIssuesAllCommitStatus returns a map of issue ID to a list of all statuses for the most recent commit as well as a map of issue ID to only the commit's latest status.
// The returned statuses are redacted for doer.
func GetIssuesAllCommitStatus(ctx context.Context, doer *user_model.User, issues issues_model.IssueList) (map[int64][]*git_model.CommitStatus, map[int64]*git_model.CommitStatus, error) {
if err := issues.LoadPullRequests(ctx); err != nil {
return nil, nil, err
}
@@ -966,7 +967,7 @@ func GetIssuesAllCommitStatus(ctx context.Context, issues issues_model.IssueList
gitRepos[issue.RepoID] = gitRepo
}
statuses, lastStatus, err := getAllCommitStatus(ctx, gitRepo, issue.PullRequest)
statuses, lastStatus, err := getAllCommitStatus(ctx, doer, gitRepo, issue.PullRequest)
if err != nil {
log.Error("getAllCommitStatus: cant get commit statuses of pull [%d]: %v", issue.PullRequest.ID, err)
continue
@@ -978,13 +979,18 @@ func GetIssuesAllCommitStatus(ctx context.Context, issues issues_model.IssueList
}
// getAllCommitStatus get pr's commit statuses.
func getAllCommitStatus(ctx context.Context, gitRepo *git.Repository, pr *issues_model.PullRequest) (statuses []*git_model.CommitStatus, lastStatus *git_model.CommitStatus, err error) {
func getAllCommitStatus(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, pr *issues_model.PullRequest) (statuses []*git_model.CommitStatus, lastStatus *git_model.CommitStatus, err error) {
sha, shaErr := gitRepo.GetRefCommitID(ctx, pr.GetGitHeadRefName())
if shaErr != nil {
return nil, nil, shaErr
}
statuses, err = git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptionsAll)
for _, status := range statuses {
status.Repo = pr.BaseRepo // the repo is already loaded, spare the permission lookup a query
}
// CalcCommitStatus copies a TargetURL out of the statuses, so hide before combining
git_model.CommitStatusesApplyDoerPermission(ctx, doer, statuses)
lastStatus = git_model.CalcCommitStatus(statuses)
return statuses, lastStatus, err
}
@@ -124,6 +124,8 @@ func FindReposLatestCommitStatuses(ctx context.Context, repos []*repo_model.Repo
for i, repo := range repos {
if cv := getCommitStatusCache(repo.ID, repo.DefaultBranch); cv != nil {
results[i] = &git_model.CommitStatus{
RepoID: repo.ID,
Repo: repo,
State: commitstatus.CommitStatusState(cv.State),
TargetURL: cv.TargetURL,
}
@@ -163,6 +165,7 @@ func FindReposLatestCommitStatuses(ctx context.Context, repos []*repo_model.Repo
for _, summary := range summaryResults {
for i, repo := range repos {
if repo.ID == summary.RepoID {
summary.Repo = repo
results[i] = summary
repoSHAs = slices.DeleteFunc(repoSHAs, func(repoSHA git_model.RepoSHA) bool {
return repoSHA.RepoID == repo.ID
@@ -190,6 +193,7 @@ func FindReposLatestCommitStatuses(ctx context.Context, repos []*repo_model.Repo
if results[i] == nil {
results[i] = git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID])
if results[i] != nil {
results[i].Repo = repo
if err := updateCommitStatusCache(repo.ID, repo.DefaultBranch, results[i].State, results[i].TargetURL); err != nil {
log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
}