Merge branch 'main' of github.com:go-gitea/gitea into api-repo-actions

This commit is contained in:
chesterip
2024-04-18 22:20:40 -04:00
365 changed files with 6445 additions and 9680 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ func TestCreateAuthorizationToken(t *testing.T) {
assert.Nil(t, err)
assert.NotEqual(t, "", token)
claims := jwt.MapClaims{}
_, err = jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) {
_, err = jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
return setting.GetGeneralTokenSigningSecret(), nil
})
assert.Nil(t, err)
+8 -12
View File
@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus"
"github.com/nektos/act/pkg/jobparser"
)
@@ -122,18 +123,13 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
if err != nil {
return fmt.Errorf("HashTypeInterfaceFromHashString: %w", err)
}
if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{
Repo: repo,
SHA: commitID,
Creator: creator,
CommitStatus: &git_model.CommitStatus{
SHA: sha,
TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), index),
Description: description,
Context: ctxname,
CreatorID: creator.ID,
State: state,
},
if err := commitstatus_service.CreateCommitStatus(ctx, repo, creator, commitID.String(), &git_model.CommitStatus{
SHA: sha,
TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), index),
Description: description,
Context: ctxname,
CreatorID: creator.ID,
State: state,
}); err != nil {
return fmt.Errorf("NewCommitStatus: %w", err)
}
+5 -5
View File
@@ -49,7 +49,7 @@ func (n *actionsNotifier) NewIssue(ctx context.Context, issue *issues_model.Issu
newNotifyInputFromIssue(issue, webhook_module.HookEventIssues).WithPayload(&api.IssuePayload{
Action: api.HookIssueOpened,
Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue),
Issue: convert.ToAPIIssue(ctx, issue.Poster, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, issue.Poster, nil),
}).Notify(withMethod(ctx, "NewIssue"))
@@ -89,7 +89,7 @@ func (n *actionsNotifier) IssueChangeContent(ctx context.Context, doer *user_mod
WithPayload(&api.IssuePayload{
Action: api.HookIssueEdited,
Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue),
Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
}).
@@ -127,7 +127,7 @@ func (n *actionsNotifier) IssueChangeStatus(ctx context.Context, doer *user_mode
}
apiIssue := &api.IssuePayload{
Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue),
Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
}
@@ -229,7 +229,7 @@ func notifyIssueChange(ctx context.Context, doer *user_model.User, issue *issues
WithPayload(&api.IssuePayload{
Action: action,
Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue),
Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
}).
@@ -293,7 +293,7 @@ func notifyIssueCommentChange(ctx context.Context, doer *user_model.User, commen
payload := &api.IssueCommentPayload{
Action: action,
Issue: convert.ToAPIIssue(ctx, comment.Issue),
Issue: convert.ToAPIIssue(ctx, doer, comment.Issue),
Comment: convert.ToAPIComment(ctx, comment.Issue.Repo, comment),
Repository: convert.ToRepo(ctx, comment.Issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
+3 -6
View File
@@ -525,12 +525,9 @@ func DetectAndHandleSchedules(ctx context.Context, repo *repo_model.Repository)
}
// We need a notifyInput to call handleSchedules
// Here we use the commit author as the Doer of the notifyInput
commitUser, err := user_model.GetUserByEmail(ctx, commit.Author.Email)
if err != nil {
return fmt.Errorf("get user by email: %w", err)
}
notifyInput := newNotifyInput(repo, commitUser, webhook_module.HookEventSchedule)
// if repo is a mirror, commit author maybe an external user,
// so we use action user as the Doer of the notifyInput
notifyInput := newNotifyInput(repo, user_model.NewActionsUser(), webhook_module.HookEventSchedule)
return handleSchedules(ctx, scheduleWorkflows, commit, notifyInput, repo.DefaultBranch)
}
+17 -9
View File
@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
notify_service "code.gitea.io/gitea/services/notify"
pull_service "code.gitea.io/gitea/services/pull"
)
@@ -145,10 +146,14 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
results = append(results, private.HookProcReceiveRefResult{
Ref: pr.GetGitRefName(),
OriginalRef: opts.RefFullNames[i],
OldOID: objectFormat.EmptyObjectID().String(),
NewOID: opts.NewCommitIDs[i],
Ref: pr.GetGitRefName(),
OriginalRef: opts.RefFullNames[i],
OldOID: objectFormat.EmptyObjectID().String(),
NewOID: opts.NewCommitIDs[i],
IsCreatePR: true,
URL: fmt.Sprintf("%s/pulls/%d", repo.HTMLURL(), pr.Index),
ShouldShowMessage: setting.Git.PullRequestPushMessage && repo.AllowsPulls(ctx),
HeadBranch: headBranch,
})
continue
}
@@ -208,11 +213,14 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
isForcePush := comment != nil && comment.IsForcePush
results = append(results, private.HookProcReceiveRefResult{
OldOID: oldCommitID,
NewOID: opts.NewCommitIDs[i],
Ref: pr.GetGitRefName(),
OriginalRef: opts.RefFullNames[i],
IsForcePush: isForcePush,
OldOID: oldCommitID,
NewOID: opts.NewCommitIDs[i],
Ref: pr.GetGitRefName(),
OriginalRef: opts.RefFullNames[i],
IsForcePush: isForcePush,
IsCreatePR: false,
URL: fmt.Sprintf("%s/pulls/%d", repo.HTMLURL(), pr.Index),
ShouldShowMessage: setting.Git.PullRequestPushMessage && repo.AllowsPulls(ctx),
})
}
+2 -1
View File
@@ -9,6 +9,7 @@ import (
"net/http"
"code.gitea.io/gitea/modules/log"
session_module "code.gitea.io/gitea/modules/session"
chiSession "gitea.com/go-chi/session"
"github.com/gorilla/sessions"
@@ -65,7 +66,7 @@ func (st *SessionsStore) Save(r *http.Request, w http.ResponseWriter, session *s
chiStore := chiSession.GetSession(r)
if session.IsNew {
_, _ = chiSession.RegenerateSession(w, r)
_, _ = session_module.RegenerateSession(w, r)
session.IsNew = false
}
+3 -5
View File
@@ -13,7 +13,7 @@ import (
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
mc "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httpcache"
@@ -21,15 +21,13 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
web_types "code.gitea.io/gitea/modules/web/types"
"gitea.com/go-chi/cache"
)
// APIContext is a specific context for API service
type APIContext struct {
*Base
Cache cache.Cache
Cache cache.StringCache
Doer *user_model.User // current signed-in user
IsSigned bool
@@ -217,7 +215,7 @@ func APIContexter() func(http.Handler) http.Handler {
base, baseCleanUp := NewBaseContext(w, req)
ctx := &APIContext{
Base: base,
Cache: mc.GetCache(),
Cache: cache.GetCache(),
Repo: &Repository{PullRequest: &PullRequest{}},
Org: &APIOrganization{},
}
+3 -3
View File
@@ -30,7 +30,7 @@ func GetImageCaptcha() *captcha.Captcha {
cpt = captcha.NewCaptcha(captcha.Options{
SubURL: setting.AppSubURL,
})
cpt.Store = cache.GetCache()
cpt.Store = cache.GetCache().ChiCache()
})
return cpt
}
@@ -79,11 +79,11 @@ func VerifyCaptcha(ctx *Context, tpl base.TplName, form any) {
case setting.CfTurnstile:
valid, err = turnstile.Verify(ctx, ctx.Req.Form.Get(cfTurnstileResponseField))
default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("unknown Captcha Type: %s", setting.Service.CaptchaType))
return
}
if err != nil {
log.Debug("%v", err)
log.Debug("Captcha Verify failed: %v", err)
}
if !valid {
+16 -4
View File
@@ -17,7 +17,7 @@ import (
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
mc "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/setting"
@@ -27,7 +27,6 @@ import (
"code.gitea.io/gitea/modules/web/middleware"
web_types "code.gitea.io/gitea/modules/web/types"
"gitea.com/go-chi/cache"
"gitea.com/go-chi/session"
)
@@ -46,7 +45,7 @@ type Context struct {
Render Render
PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`
Cache cache.Cache
Cache cache.StringCache
Csrf CSRFProtector
Flash *middleware.Flash
Session session.Store
@@ -102,6 +101,19 @@ func NewTemplateContextForWeb(ctx *Context) TemplateContext {
tmplCtx := NewTemplateContext(ctx)
tmplCtx["Locale"] = ctx.Base.Locale
tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx)
tmplCtx["RootData"] = ctx.Data
tmplCtx["Consts"] = map[string]any{
"RepoUnitTypeCode": unit.TypeCode,
"RepoUnitTypeIssues": unit.TypeIssues,
"RepoUnitTypePullRequests": unit.TypePullRequests,
"RepoUnitTypeReleases": unit.TypeReleases,
"RepoUnitTypeWiki": unit.TypeWiki,
"RepoUnitTypeExternalWiki": unit.TypeExternalWiki,
"RepoUnitTypeExternalTracker": unit.TypeExternalTracker,
"RepoUnitTypeProjects": unit.TypeProjects,
"RepoUnitTypePackages": unit.TypePackages,
"RepoUnitTypeActions": unit.TypeActions,
}
return tmplCtx
}
@@ -111,7 +123,7 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context {
Render: render,
Session: session,
Cache: mc.GetCache(),
Cache: cache.GetCache(),
Link: setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"),
Repo: &Repository{PullRequest: &PullRequest{}},
Org: &Organization{},
-17
View File
@@ -383,7 +383,6 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
ctx.NotFound("no access right", nil)
return
}
ctx.Data["HasAccess"] = true
ctx.Data["Permission"] = &ctx.Repo.Permission
if repo.IsMirror {
@@ -1052,19 +1051,3 @@ func GitHookService() func(ctx *Context) {
}
}
}
// UnitTypes returns a middleware to set unit types to context variables.
func UnitTypes() func(ctx *Context) {
return func(ctx *Context) {
ctx.Data["UnitTypeCode"] = unit_model.TypeCode
ctx.Data["UnitTypeIssues"] = unit_model.TypeIssues
ctx.Data["UnitTypePullRequests"] = unit_model.TypePullRequests
ctx.Data["UnitTypeReleases"] = unit_model.TypeReleases
ctx.Data["UnitTypeWiki"] = unit_model.TypeWiki
ctx.Data["UnitTypeExternalWiki"] = unit_model.TypeExternalWiki
ctx.Data["UnitTypeExternalTracker"] = unit_model.TypeExternalTracker
ctx.Data["UnitTypeProjects"] = unit_model.TypeProjects
ctx.Data["UnitTypePackages"] = unit_model.TypePackages
ctx.Data["UnitTypeActions"] = unit_model.TypeActions
}
}
+36 -22
View File
@@ -22,6 +22,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
@@ -106,33 +107,46 @@ func ToBranch(ctx context.Context, repo *repo_model.Repository, branchName strin
return branch, nil
}
// getWhitelistEntities returns the names of the entities that are in the whitelist
func getWhitelistEntities[T *user_model.User | *organization.Team](entities []T, whitelistIDs []int64) []string {
whitelistUserIDsSet := container.SetOf(whitelistIDs...)
whitelistNames := make([]string, 0)
for _, entity := range entities {
switch v := any(entity).(type) {
case *user_model.User:
if whitelistUserIDsSet.Contains(v.ID) {
whitelistNames = append(whitelistNames, v.Name)
}
case *organization.Team:
if whitelistUserIDsSet.Contains(v.ID) {
whitelistNames = append(whitelistNames, v.Name)
}
}
}
return whitelistNames
}
// ToBranchProtection convert a ProtectedBranch to api.BranchProtection
func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch) *api.BranchProtection {
pushWhitelistUsernames, err := user_model.GetUserNamesByIDs(ctx, bp.WhitelistUserIDs)
func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo *repo_model.Repository) *api.BranchProtection {
readers, err := access_model.GetRepoReaders(ctx, repo)
if err != nil {
log.Error("GetUserNamesByIDs (WhitelistUserIDs): %v", err)
log.Error("GetRepoReaders: %v", err)
}
mergeWhitelistUsernames, err := user_model.GetUserNamesByIDs(ctx, bp.MergeWhitelistUserIDs)
pushWhitelistUsernames := getWhitelistEntities(readers, bp.WhitelistUserIDs)
mergeWhitelistUsernames := getWhitelistEntities(readers, bp.MergeWhitelistUserIDs)
approvalsWhitelistUsernames := getWhitelistEntities(readers, bp.ApprovalsWhitelistUserIDs)
teamReaders, err := organization.OrgFromUser(repo.Owner).TeamsWithAccessToRepo(ctx, repo.ID, perm.AccessModeRead)
if err != nil {
log.Error("GetUserNamesByIDs (MergeWhitelistUserIDs): %v", err)
}
approvalsWhitelistUsernames, err := user_model.GetUserNamesByIDs(ctx, bp.ApprovalsWhitelistUserIDs)
if err != nil {
log.Error("GetUserNamesByIDs (ApprovalsWhitelistUserIDs): %v", err)
}
pushWhitelistTeams, err := organization.GetTeamNamesByID(ctx, bp.WhitelistTeamIDs)
if err != nil {
log.Error("GetTeamNamesByID (WhitelistTeamIDs): %v", err)
}
mergeWhitelistTeams, err := organization.GetTeamNamesByID(ctx, bp.MergeWhitelistTeamIDs)
if err != nil {
log.Error("GetTeamNamesByID (MergeWhitelistTeamIDs): %v", err)
}
approvalsWhitelistTeams, err := organization.GetTeamNamesByID(ctx, bp.ApprovalsWhitelistTeamIDs)
if err != nil {
log.Error("GetTeamNamesByID (ApprovalsWhitelistTeamIDs): %v", err)
log.Error("Repo.Owner.TeamsWithAccessToRepo: %v", err)
}
pushWhitelistTeams := getWhitelistEntities(teamReaders, bp.WhitelistTeamIDs)
mergeWhitelistTeams := getWhitelistEntities(teamReaders, bp.MergeWhitelistTeamIDs)
approvalsWhitelistTeams := getWhitelistEntities(teamReaders, bp.ApprovalsWhitelistTeamIDs)
branchName := ""
if !git_model.IsRuleNameSpecial(bp.RuleName) {
branchName = bp.RuleName
@@ -346,7 +360,7 @@ func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([]
Description: t.Description,
IncludesAllRepositories: t.IncludesAllRepositories,
CanCreateOrgRepo: t.CanCreateOrgRepo,
Permission: t.AccessMode.String(),
Permission: t.AccessMode.ToString(),
Units: t.GetUnitNames(),
UnitsMap: t.GetUnitsMap(),
}
+14 -14
View File
@@ -18,19 +18,19 @@ import (
api "code.gitea.io/gitea/modules/structs"
)
func ToIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue {
return toIssue(ctx, issue, WebAssetDownloadURL)
func ToIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) *api.Issue {
return toIssue(ctx, doer, issue, WebAssetDownloadURL)
}
// ToAPIIssue converts an Issue to API format
// it assumes some fields assigned with values:
// Required - Poster, Labels,
// Optional - Milestone, Assignee, PullRequest
func ToAPIIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue {
return toIssue(ctx, issue, APIAssetDownloadURL)
func ToAPIIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) *api.Issue {
return toIssue(ctx, doer, issue, APIAssetDownloadURL)
}
func toIssue(ctx context.Context, issue *issues_model.Issue, getDownloadURL func(repo *repo_model.Repository, attach *repo_model.Attachment) string) *api.Issue {
func toIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, getDownloadURL func(repo *repo_model.Repository, attach *repo_model.Attachment) string) *api.Issue {
if err := issue.LoadLabels(ctx); err != nil {
return &api.Issue{}
}
@@ -44,7 +44,7 @@ func toIssue(ctx context.Context, issue *issues_model.Issue, getDownloadURL func
apiIssue := &api.Issue{
ID: issue.ID,
Index: issue.Index,
Poster: ToUser(ctx, issue.Poster, nil),
Poster: ToUser(ctx, issue.Poster, doer),
Title: issue.Title,
Body: issue.Content,
Attachments: toAttachments(issue.Repo, issue.Attachments, getDownloadURL),
@@ -114,25 +114,25 @@ func toIssue(ctx context.Context, issue *issues_model.Issue, getDownloadURL func
}
// ToIssueList converts an IssueList to API format
func ToIssueList(ctx context.Context, il issues_model.IssueList) []*api.Issue {
func ToIssueList(ctx context.Context, doer *user_model.User, il issues_model.IssueList) []*api.Issue {
result := make([]*api.Issue, len(il))
for i := range il {
result[i] = ToIssue(ctx, il[i])
result[i] = ToIssue(ctx, doer, il[i])
}
return result
}
// ToAPIIssueList converts an IssueList to API format
func ToAPIIssueList(ctx context.Context, il issues_model.IssueList) []*api.Issue {
func ToAPIIssueList(ctx context.Context, doer *user_model.User, il issues_model.IssueList) []*api.Issue {
result := make([]*api.Issue, len(il))
for i := range il {
result[i] = ToAPIIssue(ctx, il[i])
result[i] = ToAPIIssue(ctx, doer, il[i])
}
return result
}
// ToTrackedTime converts TrackedTime to API format
func ToTrackedTime(ctx context.Context, t *issues_model.TrackedTime) (apiT *api.TrackedTime) {
func ToTrackedTime(ctx context.Context, doer *user_model.User, t *issues_model.TrackedTime) (apiT *api.TrackedTime) {
apiT = &api.TrackedTime{
ID: t.ID,
IssueID: t.IssueID,
@@ -141,7 +141,7 @@ func ToTrackedTime(ctx context.Context, t *issues_model.TrackedTime) (apiT *api.
Created: t.Created,
}
if t.Issue != nil {
apiT.Issue = ToAPIIssue(ctx, t.Issue)
apiT.Issue = ToAPIIssue(ctx, doer, t.Issue)
}
if t.User != nil {
apiT.UserName = t.User.Name
@@ -192,10 +192,10 @@ func ToStopWatches(ctx context.Context, sws []*issues_model.Stopwatch) (api.Stop
}
// ToTrackedTimeList converts TrackedTimeList to API format
func ToTrackedTimeList(ctx context.Context, tl issues_model.TrackedTimeList) api.TrackedTimeList {
func ToTrackedTimeList(ctx context.Context, doer *user_model.User, tl issues_model.TrackedTimeList) api.TrackedTimeList {
result := make([]*api.TrackedTime, 0, len(tl))
for _, t := range tl {
result = append(result, ToTrackedTime(ctx, t))
result = append(result, ToTrackedTime(ctx, doer, t))
}
return result
}
+3 -3
View File
@@ -120,7 +120,7 @@ func ToTimelineComment(ctx context.Context, repo *repo_model.Repository, c *issu
return nil
}
comment.TrackedTime = ToTrackedTime(ctx, c.Time)
comment.TrackedTime = ToTrackedTime(ctx, doer, c.Time)
}
if c.RefIssueID != 0 {
@@ -129,7 +129,7 @@ func ToTimelineComment(ctx context.Context, repo *repo_model.Repository, c *issu
log.Error("GetIssueByID(%d): %v", c.RefIssueID, err)
return nil
}
comment.RefIssue = ToAPIIssue(ctx, issue)
comment.RefIssue = ToAPIIssue(ctx, doer, issue)
}
if c.RefCommentID != 0 {
@@ -180,7 +180,7 @@ func ToTimelineComment(ctx context.Context, repo *repo_model.Repository, c *issu
}
if c.DependentIssue != nil {
comment.DependentIssue = ToAPIIssue(ctx, c.DependentIssue)
comment.DependentIssue = ToAPIIssue(ctx, doer, c.DependentIssue)
}
return comment
+1 -1
View File
@@ -33,7 +33,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u
return nil
}
apiIssue := ToAPIIssue(ctx, pr.Issue)
apiIssue := ToAPIIssue(ctx, doer, pr.Issue)
if err := pr.LoadBaseRepo(ctx); err != nil {
log.Error("GetRepositoryById[%d]: %v", pr.ID, err)
return nil
+6 -5
View File
@@ -25,12 +25,13 @@ func ToRepo(ctx context.Context, repo *repo_model.Repository, permissionInRepo a
func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInRepo access_model.Permission, isParent bool) *api.Repository {
var parent *api.Repository
if permissionInRepo.Units == nil && permissionInRepo.UnitsMode == nil {
// If Units and UnitsMode are both nil, it means that it's a hard coded permission,
// like access_model.Permission{AccessMode: perm.AccessModeAdmin}.
// So we need to load units for the repo, or UnitAccessMode will always return perm.AccessModeNone.
if !permissionInRepo.HasUnits() && permissionInRepo.AccessMode > perm.AccessModeNone {
// If units is empty, it means that it's a hard-coded permission, like access_model.Permission{AccessMode: perm.AccessModeAdmin}
// So we need to load units for the repo, otherwise UnitAccessMode will just return perm.AccessModeNone.
// TODO: this logic is still not right (because unit modes are not correctly prepared)
// the caller should prepare a proper "permission" before calling this function.
_ = repo.LoadUnits(ctx) // the error is not important, so ignore it
permissionInRepo.Units = repo.Units
permissionInRepo.SetUnitsWithDefaultAccessMode(repo.Units, permissionInRepo.AccessMode)
}
cloneLink := repo.CloneLink()
+3 -2
View File
@@ -75,6 +75,7 @@ func toUser(ctx context.Context, user *user_model.User, signed, authed bool) *ap
if authed {
result.IsAdmin = user.IsAdmin
result.LoginName = user.LoginName
result.SourceID = user.LoginSource
result.LastLogin = user.LastLoginUnix.AsTime()
result.Language = user.Language
result.IsActive = user.IsActive
@@ -102,7 +103,7 @@ func User2UserSettings(user *user_model.User) api.UserSettings {
func ToUserAndPermission(ctx context.Context, user, doer *user_model.User, accessMode perm.AccessMode) api.RepoCollaboratorPermission {
return api.RepoCollaboratorPermission{
User: ToUser(ctx, user, doer),
Permission: accessMode.String(),
RoleName: accessMode.String(),
Permission: accessMode.ToString(),
RoleName: accessMode.ToString(),
}
}
+14 -11
View File
@@ -61,26 +61,20 @@ func asFixer(fn func(ctx context.Context) error) func(ctx context.Context) (int6
}
}
func genericOrphanCheck(name, subject, refobject, joincond string) consistencyCheck {
func genericOrphanCheck(name, subject, refObject, joinCond string) consistencyCheck {
return consistencyCheck{
Name: name,
Counter: func(ctx context.Context) (int64, error) {
return db.CountOrphanedObjects(ctx, subject, refobject, joincond)
return db.CountOrphanedObjects(ctx, subject, refObject, joinCond)
},
Fixer: func(ctx context.Context) (int64, error) {
err := db.DeleteOrphanedObjects(ctx, subject, refobject, joincond)
err := db.DeleteOrphanedObjects(ctx, subject, refObject, joinCond)
return -1, err
},
}
}
func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) error {
// make sure DB version is uptodate
if err := db.InitEngineWithMigration(ctx, migrations.EnsureUpToDate); err != nil {
logger.Critical("Model version on the database does not match the current Gitea version. Model consistency will not be checked until the database is upgraded")
return err
}
func prepareDBConsistencyChecks() []consistencyCheck {
consistencyChecks := []consistencyCheck{
{
// find labels without existing repo or org
@@ -210,7 +204,7 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
"oauth2_grant", "user", "oauth2_grant.user_id=`user`.id"),
// find OAuth2Application without existing user
genericOrphanCheck("Orphaned OAuth2Application without existing User",
"oauth2_application", "user", "oauth2_application.uid=`user`.id"),
"oauth2_application", "user", "oauth2_application.uid=0 OR oauth2_application.uid=`user`.id"),
// find OAuth2AuthorizationCode without existing OAuth2Grant
genericOrphanCheck("Orphaned OAuth2AuthorizationCode without existing OAuth2Grant",
"oauth2_authorization_code", "oauth2_grant", "oauth2_authorization_code.grant_id=oauth2_grant.id"),
@@ -224,7 +218,16 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er
genericOrphanCheck("Orphaned Redirects without existing redirect user",
"user_redirect", "user", "user_redirect.redirect_user_id=`user`.id"),
)
return consistencyChecks
}
func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) error {
// make sure DB version is uptodate
if err := db.InitEngineWithMigration(ctx, migrations.EnsureUpToDate); err != nil {
logger.Critical("Model version on the database does not match the current Gitea version. Model consistency will not be checked until the database is upgraded")
return err
}
consistencyChecks := prepareDBConsistencyChecks()
for _, c := range consistencyChecks {
if err := c.Run(ctx, logger, autofix); err != nil {
return err
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package doctor
import (
"slices"
"testing"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"github.com/stretchr/testify/assert"
)
func TestConsistencyCheck(t *testing.T) {
checks := prepareDBConsistencyChecks()
idx := slices.IndexFunc(checks, func(check consistencyCheck) bool {
return check.Name == "Orphaned OAuth2Application without existing User"
})
if !assert.NotEqual(t, -1, idx) {
return
}
_ = db.TruncateBeans(db.DefaultContext, &auth.OAuth2Application{}, &user.User{})
_ = db.TruncateBeans(db.DefaultContext, &auth.OAuth2Application{}, &auth.OAuth2Application{})
err := db.Insert(db.DefaultContext, &user.User{ID: 1})
assert.NoError(t, err)
err = db.Insert(db.DefaultContext, &auth.OAuth2Application{Name: "test-oauth2-app-1", ClientID: "client-id-1"})
assert.NoError(t, err)
err = db.Insert(db.DefaultContext, &auth.OAuth2Application{Name: "test-oauth2-app-2", ClientID: "client-id-2", UID: 1})
assert.NoError(t, err)
err = db.Insert(db.DefaultContext, &auth.OAuth2Application{Name: "test-oauth2-app-3", ClientID: "client-id-3", UID: 99999999})
assert.NoError(t, err)
unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ClientID: "client-id-1"})
unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ClientID: "client-id-2"})
unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ClientID: "client-id-3"})
oauth2AppCheck := checks[idx]
err = oauth2AppCheck.Run(db.DefaultContext, log.GetManager().GetLogger(log.DEFAULT), true)
assert.NoError(t, err)
unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ClientID: "client-id-1"})
unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ClientID: "client-id-2"})
unittest.AssertNotExistsBean(t, &auth.OAuth2Application{ClientID: "client-id-3"})
}
+11
View File
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
)
// Check represents a Doctor check
@@ -25,6 +26,7 @@ type Check struct {
AbortIfFailed bool
SkipDatabaseInitialization bool
Priority int
InitStorage bool
}
func initDBSkipLogger(ctx context.Context) error {
@@ -84,6 +86,7 @@ func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) err
logger := log.BaseLoggerToGeneralLogger(&doctorCheckLogger{colorize: colorize})
loggerStep := log.BaseLoggerToGeneralLogger(&doctorCheckStepLogger{colorize: colorize})
dbIsInit := false
storageIsInit := false
for i, check := range checks {
if !dbIsInit && !check.SkipDatabaseInitialization {
// Only open database after the most basic configuration check
@@ -94,6 +97,14 @@ func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) err
}
dbIsInit = true
}
if !storageIsInit && check.InitStorage {
if err := storage.Init(); err != nil {
logger.Error("Error whilst initializing the storage: %v", err)
logger.Error("Check if you are using the right config file. You can use a --config directive to specify one.")
return nil
}
storageIsInit = true
}
logger.Info("\n[%d] %s", i+1, check.Title)
if err := check.Run(ctx, loggerStep, autofix); err != nil {
if check.AbortIfFailed {
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package doctor
import (
"testing"
"code.gitea.io/gitea/models/unittest"
)
func TestMain(m *testing.M) {
unittest.MainTest(m)
}
+1
View File
@@ -134,6 +134,7 @@ type RepoSettingForm struct {
EnableWiki bool
EnableExternalWiki bool
DefaultWikiBranch string
DefaultWikiEveryoneAccess string
ExternalWikiURL string
EnableIssues bool
EnableExternalTracker bool
+4 -4
View File
@@ -51,14 +51,14 @@ func PullRequestCodeOwnersReview(ctx context.Context, issue *issues_model.Issue,
return nil, err
}
if pr.HeadRepo.IsFork {
return nil, nil
}
if err := pr.LoadBaseRepo(ctx); err != nil {
return nil, err
}
if pr.BaseRepo.IsFork {
return nil, nil
}
repo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo)
if err != nil {
return nil, err
@@ -36,10 +36,10 @@ func TestProcessorHelperCodePreview(t *testing.T) {
<table class="file-view">
<tbody><tr>
<td class="lines-num"><span data-line-number="1"></span></td>
<td class="lines-code chroma"><code class="code-inner"><span class="gh"># repo1</code></td>
<td class="lines-code chroma"><div class="code-inner"><span class="gh"># repo1</div></td>
</tr><tr>
<td class="lines-num"><span data-line-number="2"></span></td>
<td class="lines-code chroma"><code class="code-inner"></span><span class="gh"></span></code></td>
<td class="lines-code chroma"><div class="code-inner"></span><span class="gh"></span></div></td>
</tr></tbody>
</table>
</div>
@@ -63,7 +63,7 @@ func TestProcessorHelperCodePreview(t *testing.T) {
<table class="file-view">
<tbody><tr>
<td class="lines-num"><span data-line-number="1"></span></td>
<td class="lines-code chroma"><code class="code-inner"><span class="gh"># repo1</code></td>
<td class="lines-code chroma"><div class="code-inner"><span class="gh"># repo1</div></td>
</tr></tbody>
</table>
</div>
+5
View File
@@ -72,6 +72,11 @@ func (g *GitBucketDownloader) LogString() string {
// NewGitBucketDownloader creates a GitBucket downloader
func NewGitBucketDownloader(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GitBucketDownloader {
githubDownloader := NewGithubDownloaderV3(ctx, baseURL, userName, password, token, repoOwner, repoName)
// Gitbucket 4.40 uses different internal hard-coded perPage values.
// Issues, PRs, and other major parts use 25. Release page uses 10.
// Some API doesn't support paging yet. Sounds difficult, but using
// minimum number among them worked out very well.
githubDownloader.maxPerPage = 10
githubDownloader.SkipReactions = true
githubDownloader.SkipReviews = true
return &GitBucketDownloader{
+33 -19
View File
@@ -13,6 +13,7 @@ import (
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
giturl "code.gitea.io/gitea/modules/git/url"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
@@ -30,10 +31,15 @@ const gitShortEmptySha = "0000000"
// UpdateAddress writes new address to Git repository and database
func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error {
u, err := giturl.Parse(addr)
if err != nil {
return fmt.Errorf("invalid addr: %v", err)
}
remoteName := m.GetRemoteName()
repoPath := m.GetRepository(ctx).RepoPath()
// Remove old remote
_, _, err := git.NewCommand(ctx, "remote", "rm").AddDynamicArguments(remoteName).RunStdString(&git.RunOpts{Dir: repoPath})
_, _, err = git.NewCommand(ctx, "remote", "rm").AddDynamicArguments(remoteName).RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") {
return err
}
@@ -70,7 +76,9 @@ func UpdateAddress(ctx context.Context, m *repo_model.Mirror, addr string) error
}
}
m.Repo.OriginalURL = addr
// erase authentication before storing in database
u.User = nil
m.Repo.OriginalURL = u.String()
return repo_model.UpdateRepositoryCols(ctx, m.Repo, "original_url")
}
@@ -449,19 +457,17 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
return false
}
var gitRepo *git.Repository
if len(results) == 0 {
log.Trace("SyncMirrors [repo: %-v]: no branches updated", m.Repo)
} else {
log.Trace("SyncMirrors [repo: %-v]: %d branches updated", m.Repo, len(results))
gitRepo, err = gitrepo.OpenRepository(ctx, m.Repo)
if err != nil {
log.Error("SyncMirrors [repo: %-v]: unable to OpenRepository: %v", m.Repo, err)
return false
}
defer gitRepo.Close()
gitRepo, err := gitrepo.OpenRepository(ctx, m.Repo)
if err != nil {
log.Error("SyncMirrors [repo: %-v]: unable to OpenRepository: %v", m.Repo, err)
return false
}
defer gitRepo.Close()
log.Trace("SyncMirrors [repo: %-v]: %d branches updated", m.Repo, len(results))
if len(results) > 0 {
if ok := checkAndUpdateEmptyRepository(ctx, m, gitRepo, results); !ok {
log.Error("SyncMirrors [repo: %-v]: checkAndUpdateEmptyRepository: %v", m.Repo, err)
return false
}
}
@@ -534,16 +540,24 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
}
log.Trace("SyncMirrors [repo: %-v]: done notifying updated branches/tags - now updating last commit time", m.Repo)
// Get latest commit date and update to current repository updated time
commitDate, err := git.GetLatestCommitTime(ctx, m.Repo.RepoPath())
isEmpty, err := gitRepo.IsEmpty()
if err != nil {
log.Error("SyncMirrors [repo: %-v]: unable to GetLatestCommitDate: %v", m.Repo, err)
log.Error("SyncMirrors [repo: %-v]: unable to check empty git repo: %v", m.Repo, err)
return false
}
if !isEmpty {
// Get latest commit date and update to current repository updated time
commitDate, err := git.GetLatestCommitTime(ctx, m.Repo.RepoPath())
if err != nil {
log.Error("SyncMirrors [repo: %-v]: unable to GetLatestCommitDate: %v", m.Repo, err)
return false
}
if err = repo_model.UpdateRepositoryUpdatedTime(ctx, m.RepoID, commitDate); err != nil {
log.Error("SyncMirrors [repo: %-v]: unable to update repository 'updated_unix': %v", m.Repo, err)
return false
}
if err = repo_model.UpdateRepositoryUpdatedTime(ctx, m.RepoID, commitDate); err != nil {
log.Error("SyncMirrors [repo: %-v]: unable to update repository 'updated_unix': %v", m.Repo, err)
return false
}
log.Trace("SyncMirrors [repo: %-v]: Successfully updated", m.Repo)
+2 -2
View File
@@ -91,7 +91,7 @@ func AutoMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues
// NewPullRequest notifies new pull request to notifiers
func NewPullRequest(ctx context.Context, pr *issues_model.PullRequest, mentions []*user_model.User) {
if err := pr.LoadIssue(ctx); err != nil {
log.Error("%v", err)
log.Error("LoadIssue failed: %v", err)
return
}
if err := pr.Issue.LoadPoster(ctx); err != nil {
@@ -112,7 +112,7 @@ func PullRequestSynchronized(ctx context.Context, doer *user_model.User, pr *iss
// PullRequestReview notifies new pull request review
func PullRequestReview(ctx context.Context, pr *issues_model.PullRequest, review *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) {
if err := review.LoadReviewer(ctx); err != nil {
log.Error("%v", err)
log.Error("LoadReviewer failed: %v", err)
return
}
for _, notifier := range notifiers {
+4 -4
View File
@@ -989,12 +989,12 @@ func GetPullCommits(ctx *gitea_context.Context, issue *issues_model.Issue) ([]Co
for _, commit := range prInfo.Commits {
var committerOrAuthorName string
var commitTime time.Time
if commit.Committer != nil {
committerOrAuthorName = commit.Committer.Name
commitTime = commit.Committer.When
} else {
if commit.Author != nil {
committerOrAuthorName = commit.Author.Name
commitTime = commit.Author.When
} else {
committerOrAuthorName = commit.Committer.Name
commitTime = commit.Committer.When
}
commits = append(commits, CommitInfo{
+5 -6
View File
@@ -26,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/queue"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
notify_service "code.gitea.io/gitea/services/notify"
files_service "code.gitea.io/gitea/services/repository/files"
@@ -119,17 +120,15 @@ func getDivergenceCacheKey(repoID int64, branchName string) string {
// getDivergenceFromCache gets the divergence from cache
func getDivergenceFromCache(repoID int64, branchName string) (*git.DivergeObject, bool) {
data := cache.GetCache().Get(getDivergenceCacheKey(repoID, branchName))
data, ok := cache.GetCache().Get(getDivergenceCacheKey(repoID, branchName))
res := git.DivergeObject{
Ahead: -1,
Behind: -1,
}
s, ok := data.([]byte)
if !ok || len(s) == 0 {
if !ok || data == "" {
return &res, false
}
if err := json.Unmarshal(s, &res); err != nil {
if err := json.Unmarshal(util.UnsafeStringToBytes(data), &res); err != nil {
log.Error("json.UnMarshal failed: %v", err)
return &res, false
}
@@ -141,7 +140,7 @@ func putDivergenceFromCache(repoID int64, branchName string, divergence *git.Div
if err != nil {
return err
}
return cache.GetCache().Put(getDivergenceCacheKey(repoID, branchName), bs, 30*24*60*60)
return cache.GetCache().Put(getDivergenceCacheKey(repoID, branchName), util.UnsafeBytesToString(bs), 30*24*60*60)
}
func DelDivergenceFromCache(repoID int64, branchName string) error {
@@ -7,6 +7,7 @@ import (
"context"
"crypto/sha256"
"fmt"
"slices"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
@@ -15,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/automerge"
@@ -25,12 +27,41 @@ func getCacheKey(repoID int64, brancheName string) string {
return fmt.Sprintf("commit_status:%x", hashBytes)
}
func updateCommitStatusCache(ctx context.Context, repoID int64, branchName string, status api.CommitStatusState) error {
c := cache.GetCache()
return c.Put(getCacheKey(repoID, branchName), string(status), 3*24*60)
type commitStatusCacheValue struct {
State string `json:"state"`
TargetURL string `json:"target_url"`
}
func deleteCommitStatusCache(ctx context.Context, repoID int64, branchName string) error {
func getCommitStatusCache(repoID int64, branchName string) *commitStatusCacheValue {
c := cache.GetCache()
statusStr, ok := c.Get(getCacheKey(repoID, branchName))
if ok && statusStr != "" {
var cv commitStatusCacheValue
err := json.Unmarshal([]byte(statusStr), &cv)
if err == nil && cv.State != "" {
return &cv
}
if err != nil {
log.Warn("getCommitStatusCache: json.Unmarshal failed: %v", err)
}
}
return nil
}
func updateCommitStatusCache(repoID int64, branchName string, state api.CommitStatusState, targetURL string) error {
c := cache.GetCache()
bs, err := json.Marshal(commitStatusCacheValue{
State: state.String(),
TargetURL: targetURL,
})
if err != nil {
log.Warn("updateCommitStatusCache: json.Marshal failed: %v", err)
return nil
}
return c.Put(getCacheKey(repoID, branchName), string(bs), 3*24*60)
}
func deleteCommitStatusCache(repoID int64, branchName string) error {
c := cache.GetCache()
return c.Delete(getCacheKey(repoID, branchName))
}
@@ -59,13 +90,19 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
sha = commit.ID.String()
}
if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{
Repo: repo,
Creator: creator,
SHA: commit.ID,
CommitStatus: status,
if err := db.WithTx(ctx, func(ctx context.Context) error {
if err := git_model.NewCommitStatus(ctx, git_model.NewCommitStatusOptions{
Repo: repo,
Creator: creator,
SHA: commit.ID,
CommitStatus: status,
}); err != nil {
return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
}
return git_model.UpdateCommitStatusSummary(ctx, repo.ID, commit.ID.String())
}); err != nil {
return fmt.Errorf("NewCommitStatus[repo_id: %d, user_id: %d, sha: %s]: %w", repo.ID, creator.ID, sha, err)
return err
}
defaultBranchCommit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
@@ -74,7 +111,7 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
}
if commit.ID.String() == defaultBranchCommit.ID.String() { // since one commit status updated, the combined commit status should be invalid
if err := deleteCommitStatusCache(ctx, repo.ID, repo.DefaultBranch); err != nil {
if err := deleteCommitStatusCache(repo.ID, repo.DefaultBranch); err != nil {
log.Error("deleteCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err)
}
}
@@ -91,12 +128,12 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
// FindReposLastestCommitStatuses loading repository default branch latest combinded commit status with cache
func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) {
results := make([]*git_model.CommitStatus, len(repos))
c := cache.GetCache()
for i, repo := range repos {
status, ok := c.Get(getCacheKey(repo.ID, repo.DefaultBranch)).(string)
if ok && status != "" {
results[i] = &git_model.CommitStatus{State: api.CommitStatusState(status)}
if cv := getCommitStatusCache(repo.ID, repo.DefaultBranch); cv != nil {
results[i] = &git_model.CommitStatus{
State: api.CommitStatusState(cv.State),
TargetURL: cv.TargetURL,
}
}
}
@@ -114,8 +151,35 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep
return nil, fmt.Errorf("FindBranchesByRepoAndBranchName: %v", err)
}
var repoSHAs []git_model.RepoSHA
for id, sha := range repoIDsToLatestCommitSHAs {
repoSHAs = append(repoSHAs, git_model.RepoSHA{RepoID: id, SHA: sha})
}
summaryResults, err := git_model.GetLatestCommitStatusForRepoAndSHAs(ctx, repoSHAs)
if err != nil {
return nil, fmt.Errorf("GetLatestCommitStatusForRepoAndSHAs: %v", err)
}
for _, summary := range summaryResults {
for i, repo := range repos {
if repo.ID == summary.RepoID {
results[i] = summary
_ = slices.DeleteFunc(repoSHAs, func(repoSHA git_model.RepoSHA) bool {
return repoSHA.RepoID == repo.ID
})
if results[i].State != "" {
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)
}
}
break
}
}
}
// call the database O(1) times to get the commit statuses for all repos
repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoIDsToLatestCommitSHAs, db.ListOptionsAll)
repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoSHAs)
if err != nil {
return nil, fmt.Errorf("GetLatestCommitStatusForPairs: %v", err)
}
@@ -124,7 +188,7 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep
if results[i] == nil {
results[i] = git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID])
if results[i].State != "" {
if err := updateCommitStatusCache(ctx, repo.ID, repo.DefaultBranch, results[i].State); err != nil {
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)
}
}
+12 -20
View File
@@ -17,13 +17,12 @@ import (
"code.gitea.io/gitea/models/avatars"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"gitea.com/go-chi/cache"
)
const (
@@ -79,13 +78,13 @@ func findLastSundayBeforeDate(dateStr string) (string, error) {
}
// GetContributorStats returns contributors stats for git commits for given revision or default branch
func GetContributorStats(ctx context.Context, cache cache.Cache, repo *repo_model.Repository, revision string) (map[string]*ContributorData, error) {
func GetContributorStats(ctx context.Context, cache cache.StringCache, repo *repo_model.Repository, revision string) (map[string]*ContributorData, error) {
// as GetContributorStats is resource intensive we cache the result
cacheKey := fmt.Sprintf(contributorStatsCacheKey, repo.FullName(), revision)
if !cache.IsExist(cacheKey) {
genReady := make(chan struct{})
// dont start multible async generations
// dont start multiple async generations
_, run := generateLock.Load(cacheKey)
if run {
return nil, ErrAwaitGeneration
@@ -104,15 +103,11 @@ func GetContributorStats(ctx context.Context, cache cache.Cache, repo *repo_mode
}
}
// TODO: renew timeout of cache cache.UpdateTimeout(cacheKey, contributorStatsCacheTimeout)
switch v := cache.Get(cacheKey).(type) {
case error:
return nil, v
case map[string]*ContributorData:
return v, nil
default:
return nil, fmt.Errorf("unexpected type in cache detected")
var res map[string]*ContributorData
if _, cacheErr := cache.GetJSON(cacheKey, &res); cacheErr != nil {
return nil, fmt.Errorf("cached error: %w", cacheErr.ToError())
}
return res, nil
}
// getExtendedCommitStats return the list of *ExtendedCommitStats for the given revision
@@ -205,13 +200,12 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int
return extendedCommitStats, nil
}
func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey string, repo *repo_model.Repository, revision string) {
func generateContributorStats(genDone chan struct{}, cache cache.StringCache, cacheKey string, repo *repo_model.Repository, revision string) {
ctx := graceful.GetManager().HammerContext()
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
if err != nil {
err := fmt.Errorf("OpenRepository: %w", err)
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout)
_ = cache.PutJSON(cacheKey, fmt.Errorf("OpenRepository: %w", err), contributorStatsCacheTimeout)
return
}
defer closer.Close()
@@ -221,13 +215,11 @@ func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey
}
extendedCommitStats, err := getExtendedCommitStats(gitRepo, revision)
if err != nil {
err := fmt.Errorf("ExtendedCommitStats: %w", err)
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout)
_ = cache.PutJSON(cacheKey, fmt.Errorf("ExtendedCommitStats: %w", err), contributorStatsCacheTimeout)
return
}
if len(extendedCommitStats) == 0 {
err := fmt.Errorf("no commit stats returned for revision '%s'", revision)
_ = cache.Put(cacheKey, err, contributorStatsCacheTimeout)
_ = cache.PutJSON(cacheKey, fmt.Errorf("no commit stats returned for revision '%s'", revision), contributorStatsCacheTimeout)
return
}
@@ -309,7 +301,7 @@ func generateContributorStats(genDone chan struct{}, cache cache.Cache, cacheKey
total.TotalCommits++
}
_ = cache.Put(cacheKey, contributorsCommitStats, contributorStatsCacheTimeout)
_ = cache.PutJSON(cacheKey, contributorsCommitStats, contributorStatsCacheTimeout)
generateLock.Delete(cacheKey)
if genDone != nil {
genDone <- struct{}{}
+9 -11
View File
@@ -10,9 +10,9 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/setting"
"gitea.com/go-chi/cache"
"github.com/stretchr/testify/assert"
)
@@ -20,20 +20,18 @@ func TestRepository_ContributorsGraph(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
assert.NoError(t, repo.LoadOwner(db.DefaultContext))
mockCache, err := cache.NewCacher(cache.Options{
Adapter: "memory",
Interval: 24 * 60,
})
mockCache, err := cache.NewStringCache(setting.Cache{})
assert.NoError(t, err)
generateContributorStats(nil, mockCache, "key", repo, "404ref")
err, isErr := mockCache.Get("key").(error)
assert.True(t, isErr)
assert.ErrorAs(t, err, &git.ErrNotExist{})
var data map[string]*ContributorData
_, getErr := mockCache.GetJSON("key", &data)
assert.NotNil(t, getErr)
assert.ErrorContains(t, getErr.ToError(), "object does not exist")
generateContributorStats(nil, mockCache, "key2", repo, "master")
data, isData := mockCache.Get("key2").(map[string]*ContributorData)
assert.True(t, isData)
exist, _ := mockCache.GetJSON("key2", &data)
assert.True(t, exist)
var keys []string
for k := range data {
keys = append(keys, k)
+1
View File
@@ -163,6 +163,7 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID
&actions_model.ActionScheduleSpec{RepoID: repoID},
&actions_model.ActionSchedule{RepoID: repoID},
&actions_model.ActionArtifact{RepoID: repoID},
&actions_model.ActionRunnerToken{RepoID: repoID},
); err != nil {
return fmt.Errorf("deleteBeans: %w", err)
}
+1 -1
View File
@@ -28,7 +28,7 @@ func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_mod
t, err := NewTemporaryUploadRepository(ctx, repo)
if err != nil {
log.Error("%v", err)
log.Error("NewTemporaryUploadRepository failed: %v", err)
}
defer t.Close()
if err := t.Clone(opts.OldBranch, false); err != nil {
+1 -1
View File
@@ -111,7 +111,7 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user
t, err := NewTemporaryUploadRepository(ctx, repo)
if err != nil {
log.Error("%v", err)
log.Error("NewTemporaryUploadRepository failed: %v", err)
}
defer t.Close()
if err := t.Clone(opts.OldBranch, true); err != nil {
+1 -1
View File
@@ -143,7 +143,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
t, err := NewTemporaryUploadRepository(ctx, repo)
if err != nil {
log.Error("%v", err)
log.Error("NewTemporaryUploadRepository failed: %v", err)
}
defer t.Close()
hasOldBranch := true
+1
View File
@@ -94,6 +94,7 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error)
&actions_model.ActionRunner{OwnerID: u.ID},
&user_model.Blocking{BlockerID: u.ID},
&user_model.Blocking{BlockeeID: u.ID},
&actions_model.ActionRunnerToken{OwnerID: u.ID},
); err != nil {
return fmt.Errorf("deleteBeans: %w", err)
}
+11 -11
View File
@@ -67,7 +67,7 @@ func (m *webhookNotifier) IssueClearLabels(ctx context.Context, doer *user_model
err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssueLabel, &api.IssuePayload{
Action: api.HookIssueLabelCleared,
Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue),
Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
})
@@ -168,7 +168,7 @@ func (m *webhookNotifier) IssueChangeAssignee(ctx context.Context, doer *user_mo
permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
apiIssue := &api.IssuePayload{
Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue),
Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
}
@@ -214,7 +214,7 @@ func (m *webhookNotifier) IssueChangeTitle(ctx context.Context, doer *user_model
From: oldTitle,
},
},
Issue: convert.ToAPIIssue(ctx, issue),
Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
})
@@ -250,7 +250,7 @@ func (m *webhookNotifier) IssueChangeStatus(ctx context.Context, doer *user_mode
} else {
apiIssue := &api.IssuePayload{
Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue),
Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
CommitID: commitID,
@@ -281,7 +281,7 @@ func (m *webhookNotifier) NewIssue(ctx context.Context, issue *issues_model.Issu
if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssues, &api.IssuePayload{
Action: api.HookIssueOpened,
Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue),
Issue: convert.ToAPIIssue(ctx, issue.Poster, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, issue.Poster, nil),
}); err != nil {
@@ -349,7 +349,7 @@ func (m *webhookNotifier) IssueChangeContent(ctx context.Context, doer *user_mod
From: oldContent,
},
},
Issue: convert.ToAPIIssue(ctx, issue),
Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
})
@@ -384,7 +384,7 @@ func (m *webhookNotifier) UpdateComment(ctx context.Context, doer *user_model.Us
permission, _ := access_model.GetUserRepoPermission(ctx, c.Issue.Repo, doer)
if err := PrepareWebhooks(ctx, EventSource{Repository: c.Issue.Repo}, eventType, &api.IssueCommentPayload{
Action: api.HookIssueCommentEdited,
Issue: convert.ToAPIIssue(ctx, c.Issue),
Issue: convert.ToAPIIssue(ctx, doer, c.Issue),
Comment: convert.ToAPIComment(ctx, c.Issue.Repo, c),
Changes: &api.ChangesPayload{
Body: &api.ChangesFromPayload{
@@ -412,7 +412,7 @@ func (m *webhookNotifier) CreateIssueComment(ctx context.Context, doer *user_mod
permission, _ := access_model.GetUserRepoPermission(ctx, repo, doer)
if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, eventType, &api.IssueCommentPayload{
Action: api.HookIssueCommentCreated,
Issue: convert.ToAPIIssue(ctx, issue),
Issue: convert.ToAPIIssue(ctx, doer, issue),
Comment: convert.ToAPIComment(ctx, repo, comment),
Repository: convert.ToRepo(ctx, repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
@@ -449,7 +449,7 @@ func (m *webhookNotifier) DeleteComment(ctx context.Context, doer *user_model.Us
permission, _ := access_model.GetUserRepoPermission(ctx, comment.Issue.Repo, doer)
if err := PrepareWebhooks(ctx, EventSource{Repository: comment.Issue.Repo}, eventType, &api.IssueCommentPayload{
Action: api.HookIssueCommentDeleted,
Issue: convert.ToAPIIssue(ctx, comment.Issue),
Issue: convert.ToAPIIssue(ctx, doer, comment.Issue),
Comment: convert.ToAPIComment(ctx, comment.Issue.Repo, comment),
Repository: convert.ToRepo(ctx, comment.Issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
@@ -533,7 +533,7 @@ func (m *webhookNotifier) IssueChangeLabels(ctx context.Context, doer *user_mode
err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssueLabel, &api.IssuePayload{
Action: api.HookIssueLabelUpdated,
Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue),
Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
})
@@ -575,7 +575,7 @@ func (m *webhookNotifier) IssueChangeMilestone(ctx context.Context, doer *user_m
err = PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssueMilestone, &api.IssuePayload{
Action: hookAction,
Index: issue.Index,
Issue: convert.ToAPIIssue(ctx, issue),
Issue: convert.ToAPIIssue(ctx, doer, issue),
Repository: convert.ToRepo(ctx, issue.Repo, permission),
Sender: convert.ToUser(ctx, doer, nil),
})
+11 -7
View File
@@ -161,7 +161,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
if isOldWikiExist {
err := gitRepo.RemoveFilesFromIndex(oldWikiPath)
if err != nil {
log.Error("%v", err)
log.Error("RemoveFilesFromIndex failed: %v", err)
return err
}
}
@@ -171,18 +171,18 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
objectHash, err := gitRepo.HashObject(strings.NewReader(content))
if err != nil {
log.Error("%v", err)
log.Error("HashObject failed: %v", err)
return err
}
if err := gitRepo.AddObjectToIndex("100644", objectHash, newWikiPath); err != nil {
log.Error("%v", err)
log.Error("AddObjectToIndex failed: %v", err)
return err
}
tree, err := gitRepo.WriteTree()
if err != nil {
log.Error("%v", err)
log.Error("WriteTree failed: %v", err)
return err
}
@@ -207,7 +207,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), committer, tree, commitTreeOpts)
if err != nil {
log.Error("%v", err)
log.Error("CommitTree failed: %v", err)
return err
}
@@ -222,11 +222,11 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
0,
),
}); err != nil {
log.Error("%v", err)
log.Error("Push failed: %v", err)
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
return err
}
return fmt.Errorf("Push: %w", err)
return fmt.Errorf("failed to push: %w", err)
}
return nil
@@ -370,6 +370,10 @@ func ChangeDefaultWikiBranch(ctx context.Context, repo *repo_model.Repository, n
return fmt.Errorf("unable to update database: %w", err)
}
if !repo.HasWiki() {
return nil
}
oldDefBranch, err := gitrepo.GetWikiDefaultBranch(ctx, repo)
if err != nil {
return fmt.Errorf("unable to get default branch: %w", err)