mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-23 11:01:49 +01:00
Merge pull request '[gitea] week 2024-47 cherry pick (gitea/main -> forgejo)' (#5997) from earl-warren/wcp/2024-47 into forgejo
Some checks are pending
/ release (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
Some checks are pending
/ release (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5997 Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
commit
1597dc078d
32 changed files with 454 additions and 293 deletions
|
@ -289,9 +289,6 @@ code.gitea.io/gitea/services/pull
|
||||||
code.gitea.io/gitea/services/repository
|
code.gitea.io/gitea/services/repository
|
||||||
IsErrForkAlreadyExist
|
IsErrForkAlreadyExist
|
||||||
|
|
||||||
code.gitea.io/gitea/services/repository/archiver
|
|
||||||
ArchiveRepository
|
|
||||||
|
|
||||||
code.gitea.io/gitea/services/repository/files
|
code.gitea.io/gitea/services/repository/files
|
||||||
ContentType.String
|
ContentType.String
|
||||||
GetFileResponseFromCommit
|
GetFileResponseFromCommit
|
||||||
|
|
|
@ -254,6 +254,7 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertRun inserts a run
|
// InsertRun inserts a run
|
||||||
|
// The title will be cut off at 255 characters if it's longer than 255 characters.
|
||||||
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
|
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
|
||||||
ctx, commiter, err := db.TxContext(ctx)
|
ctx, commiter, err := db.TxContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -266,6 +267,7 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
run.Index = index
|
run.Index = index
|
||||||
|
run.Title, _ = util.SplitStringAtByteN(run.Title, 255)
|
||||||
|
|
||||||
if err := db.Insert(ctx, run); err != nil {
|
if err := db.Insert(ctx, run); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -391,6 +393,7 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
|
||||||
if len(cols) > 0 {
|
if len(cols) > 0 {
|
||||||
sess.Cols(cols...)
|
sess.Cols(cols...)
|
||||||
}
|
}
|
||||||
|
run.Title, _ = util.SplitStringAtByteN(run.Title, 255)
|
||||||
affected, err := sess.Update(run)
|
affected, err := sess.Update(run)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -271,6 +271,7 @@ func GetRunnerByID(ctx context.Context, id int64) (*ActionRunner, error) {
|
||||||
// UpdateRunner updates runner's information.
|
// UpdateRunner updates runner's information.
|
||||||
func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error {
|
func UpdateRunner(ctx context.Context, r *ActionRunner, cols ...string) error {
|
||||||
e := db.GetEngine(ctx)
|
e := db.GetEngine(ctx)
|
||||||
|
r.Name, _ = util.SplitStringAtByteN(r.Name, 255)
|
||||||
var err error
|
var err error
|
||||||
if len(cols) == 0 {
|
if len(cols) == 0 {
|
||||||
_, err = e.ID(r.ID).AllCols().Update(r)
|
_, err = e.ID(r.ID).AllCols().Update(r)
|
||||||
|
@ -312,6 +313,7 @@ func CreateRunner(ctx context.Context, t *ActionRunner) error {
|
||||||
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
|
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
|
||||||
t.OwnerID = 0
|
t.OwnerID = 0
|
||||||
}
|
}
|
||||||
|
t.Name, _ = util.SplitStringAtByteN(t.Name, 255)
|
||||||
return db.Insert(ctx, t)
|
return db.Insert(ctx, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -67,6 +68,7 @@ func CreateScheduleTask(ctx context.Context, rows []*ActionSchedule) error {
|
||||||
|
|
||||||
// Loop through each schedule row
|
// Loop through each schedule row
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
|
row.Title, _ = util.SplitStringAtByteN(row.Title, 255)
|
||||||
// Create new schedule row
|
// Create new schedule row
|
||||||
if err = db.Insert(ctx, row); err != nil {
|
if err = db.Insert(ctx, row); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -341,7 +341,7 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error {
|
||||||
// UpdateTaskByState updates the task by the state.
|
// UpdateTaskByState updates the task by the state.
|
||||||
// It will always update the task if the state is not final, even there is no change.
|
// It will always update the task if the state is not final, even there is no change.
|
||||||
// So it will update ActionTask.Updated to avoid the task being judged as a zombie task.
|
// So it will update ActionTask.Updated to avoid the task being judged as a zombie task.
|
||||||
func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionTask, error) {
|
func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.TaskState) (*ActionTask, error) {
|
||||||
stepStates := map[int64]*runnerv1.StepState{}
|
stepStates := map[int64]*runnerv1.StepState{}
|
||||||
for _, v := range state.Steps {
|
for _, v := range state.Steps {
|
||||||
stepStates[v.Id] = v
|
stepStates[v.Id] = v
|
||||||
|
@ -360,6 +360,8 @@ func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionT
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
return nil, util.ErrNotExist
|
return nil, util.ErrNotExist
|
||||||
|
} else if runnerID != task.RunnerID {
|
||||||
|
return nil, fmt.Errorf("invalid runner for task")
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.Status.IsDone() {
|
if task.Status.IsDone() {
|
||||||
|
|
|
@ -250,6 +250,9 @@ func (a *Action) GetActDisplayNameTitle(ctx context.Context) string {
|
||||||
// GetRepoUserName returns the name of the action repository owner.
|
// GetRepoUserName returns the name of the action repository owner.
|
||||||
func (a *Action) GetRepoUserName(ctx context.Context) string {
|
func (a *Action) GetRepoUserName(ctx context.Context) string {
|
||||||
a.loadRepo(ctx)
|
a.loadRepo(ctx)
|
||||||
|
if a.Repo == nil {
|
||||||
|
return "(non-existing-repo)"
|
||||||
|
}
|
||||||
return a.Repo.OwnerName
|
return a.Repo.OwnerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,6 +265,9 @@ func (a *Action) ShortRepoUserName(ctx context.Context) string {
|
||||||
// GetRepoName returns the name of the action repository.
|
// GetRepoName returns the name of the action repository.
|
||||||
func (a *Action) GetRepoName(ctx context.Context) string {
|
func (a *Action) GetRepoName(ctx context.Context) string {
|
||||||
a.loadRepo(ctx)
|
a.loadRepo(ctx)
|
||||||
|
if a.Repo == nil {
|
||||||
|
return "(non-existing-repo)"
|
||||||
|
}
|
||||||
return a.Repo.Name
|
return a.Repo.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/references"
|
"code.gitea.io/gitea/modules/references"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
@ -154,6 +155,7 @@ func ChangeIssueTitle(ctx context.Context, issue *Issue, doer *user_model.User,
|
||||||
}
|
}
|
||||||
defer committer.Close()
|
defer committer.Close()
|
||||||
|
|
||||||
|
issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255)
|
||||||
if err = UpdateIssueCols(ctx, issue, "name"); err != nil {
|
if err = UpdateIssueCols(ctx, issue, "name"); err != nil {
|
||||||
return fmt.Errorf("updateIssueCols: %w", err)
|
return fmt.Errorf("updateIssueCols: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -409,6 +411,7 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIssue creates new issue with labels for repository.
|
// NewIssue creates new issue with labels for repository.
|
||||||
|
// The title will be cut off at 255 characters if it's longer than 255 characters.
|
||||||
func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
|
func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -422,6 +425,7 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, la
|
||||||
}
|
}
|
||||||
|
|
||||||
issue.Index = idx
|
issue.Index = idx
|
||||||
|
issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255)
|
||||||
|
|
||||||
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
|
|
|
@ -566,6 +566,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Iss
|
||||||
}
|
}
|
||||||
|
|
||||||
issue.Index = idx
|
issue.Index = idx
|
||||||
|
issue.Title, _ = util.SplitStringAtByteN(issue.Title, 255)
|
||||||
|
|
||||||
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
if err = NewIssueWithIndex(ctx, issue.Poster, NewIssueOptions{
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package organization
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
|
||||||
"code.gitea.io/gitea/models/unit"
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
|
||||||
|
|
||||||
"xorm.io/builder"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MinimalOrg represents a simple organization with only the needed columns
|
|
||||||
type MinimalOrg = Organization
|
|
||||||
|
|
||||||
// GetUserOrgsList returns all organizations the given user has access to
|
|
||||||
func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, error) {
|
|
||||||
schema, err := db.TableInfo(new(user_model.User))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
outputCols := []string{
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"full_name",
|
|
||||||
"visibility",
|
|
||||||
"avatar",
|
|
||||||
"avatar_email",
|
|
||||||
"use_custom_avatar",
|
|
||||||
}
|
|
||||||
|
|
||||||
groupByCols := &strings.Builder{}
|
|
||||||
for _, col := range outputCols {
|
|
||||||
fmt.Fprintf(groupByCols, "`%s`.%s,", schema.Name, col)
|
|
||||||
}
|
|
||||||
groupByStr := groupByCols.String()
|
|
||||||
groupByStr = groupByStr[0 : len(groupByStr)-1]
|
|
||||||
|
|
||||||
sess := db.GetEngine(ctx)
|
|
||||||
sess = sess.Select(groupByStr+", count(distinct repo_id) as org_count").
|
|
||||||
Table("user").
|
|
||||||
Join("INNER", "team", "`team`.org_id = `user`.id").
|
|
||||||
Join("INNER", "team_user", "`team`.id = `team_user`.team_id").
|
|
||||||
Join("LEFT", builder.
|
|
||||||
Select("id as repo_id, owner_id as repo_owner_id").
|
|
||||||
From("repository").
|
|
||||||
Where(repo_model.AccessibleRepositoryCondition(user, unit.TypeInvalid)), "`repository`.repo_owner_id = `team`.org_id").
|
|
||||||
Where("`team_user`.uid = ?", user.ID).
|
|
||||||
GroupBy(groupByStr)
|
|
||||||
|
|
||||||
type OrgCount struct {
|
|
||||||
Organization `xorm:"extends"`
|
|
||||||
OrgCount int
|
|
||||||
}
|
|
||||||
|
|
||||||
orgCounts := make([]*OrgCount, 0, 10)
|
|
||||||
|
|
||||||
if err := sess.
|
|
||||||
Asc("`user`.name").
|
|
||||||
Find(&orgCounts); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
orgs := make([]*MinimalOrg, len(orgCounts))
|
|
||||||
for i, orgCount := range orgCounts {
|
|
||||||
orgCount.Organization.NumRepos = orgCount.OrgCount
|
|
||||||
orgs[i] = &orgCount.Organization
|
|
||||||
}
|
|
||||||
|
|
||||||
return orgs, nil
|
|
||||||
}
|
|
|
@ -24,13 +24,6 @@ import (
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ________ .__ __ .__
|
|
||||||
// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____
|
|
||||||
// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \
|
|
||||||
// / | \ | \/ /_/ > __ \| | \ |/ / / __ \| | | ( <_> ) | \
|
|
||||||
// \_______ /__| \___ (____ /___| /__/_____ \(____ /__| |__|\____/|___| /
|
|
||||||
// \/ /_____/ \/ \/ \/ \/ \/
|
|
||||||
|
|
||||||
// ErrOrgNotExist represents a "OrgNotExist" kind of error.
|
// ErrOrgNotExist represents a "OrgNotExist" kind of error.
|
||||||
type ErrOrgNotExist struct {
|
type ErrOrgNotExist struct {
|
||||||
ID int64
|
ID int64
|
||||||
|
@ -141,8 +134,9 @@ func (org *Organization) LoadTeams(ctx context.Context) ([]*Team, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMembers returns all members of organization.
|
// GetMembers returns all members of organization.
|
||||||
func (org *Organization) GetMembers(ctx context.Context) (user_model.UserList, map[int64]bool, error) {
|
func (org *Organization) GetMembers(ctx context.Context, doer *user_model.User) (user_model.UserList, map[int64]bool, error) {
|
||||||
return FindOrgMembers(ctx, &FindOrgMembersOpts{
|
return FindOrgMembers(ctx, &FindOrgMembersOpts{
|
||||||
|
Doer: doer,
|
||||||
OrgID: org.ID,
|
OrgID: org.ID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -195,16 +189,22 @@ func (org *Organization) CanCreateRepo() bool {
|
||||||
// FindOrgMembersOpts represensts find org members conditions
|
// FindOrgMembersOpts represensts find org members conditions
|
||||||
type FindOrgMembersOpts struct {
|
type FindOrgMembersOpts struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
|
Doer *user_model.User
|
||||||
|
IsDoerMember bool
|
||||||
OrgID int64
|
OrgID int64
|
||||||
PublicOnly bool
|
}
|
||||||
|
|
||||||
|
func (opts FindOrgMembersOpts) PublicOnly() bool {
|
||||||
|
return opts.Doer == nil || !(opts.IsDoerMember || opts.Doer.IsAdmin)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountOrgMembers counts the organization's members
|
// CountOrgMembers counts the organization's members
|
||||||
func CountOrgMembers(ctx context.Context, opts *FindOrgMembersOpts) (int64, error) {
|
func CountOrgMembers(ctx context.Context, opts *FindOrgMembersOpts) (int64, error) {
|
||||||
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
|
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
|
||||||
if opts.PublicOnly {
|
if opts.PublicOnly() {
|
||||||
sess.And("is_public = ?", true)
|
sess.And("is_public = ?", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sess.Count(new(OrgUser))
|
return sess.Count(new(OrgUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,42 +439,6 @@ func GetUsersWhoCanCreateOrgRepo(ctx context.Context, orgID int64) (map[int64]*u
|
||||||
And("team_user.org_id = ?", orgID).Find(&users)
|
And("team_user.org_id = ?", orgID).Find(&users)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchOrganizationsOptions options to filter organizations
|
|
||||||
type SearchOrganizationsOptions struct {
|
|
||||||
db.ListOptions
|
|
||||||
All bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindOrgOptions finds orgs options
|
|
||||||
type FindOrgOptions struct {
|
|
||||||
db.ListOptions
|
|
||||||
UserID int64
|
|
||||||
IncludePrivate bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
|
|
||||||
cond := builder.Eq{"uid": userID}
|
|
||||||
if !includePrivate {
|
|
||||||
cond["is_public"] = true
|
|
||||||
}
|
|
||||||
return builder.Select("org_id").From("org_user").Where(cond)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts FindOrgOptions) ToConds() builder.Cond {
|
|
||||||
var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization}
|
|
||||||
if opts.UserID > 0 {
|
|
||||||
cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate)))
|
|
||||||
}
|
|
||||||
if !opts.IncludePrivate {
|
|
||||||
cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})
|
|
||||||
}
|
|
||||||
return cond
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opts FindOrgOptions) ToOrders() string {
|
|
||||||
return "`user`.name ASC"
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasOrgOrUserVisible tells if the given user can see the given org or user
|
// HasOrgOrUserVisible tells if the given user can see the given org or user
|
||||||
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
|
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
|
||||||
// If user is nil, it's an anonymous user/request.
|
// If user is nil, it's an anonymous user/request.
|
||||||
|
@ -507,26 +471,13 @@ func HasOrgsVisible(ctx context.Context, orgs []*Organization, user *user_model.
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID
|
|
||||||
// are allowed to create repos.
|
|
||||||
func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) {
|
|
||||||
orgs := make([]*Organization, 0, 10)
|
|
||||||
|
|
||||||
return orgs, db.GetEngine(ctx).Where(builder.In("id", builder.Select("`user`.id").From("`user`").
|
|
||||||
Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id").
|
|
||||||
Join("INNER", "`team`", "`team`.id = `team_user`.team_id").
|
|
||||||
Where(builder.Eq{"`team_user`.uid": userID}).
|
|
||||||
And(builder.Eq{"`team`.authorize": perm.AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})))).
|
|
||||||
Asc("`user`.name").
|
|
||||||
Find(&orgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOrgUsersByOrgID returns all organization-user relations by organization ID.
|
// GetOrgUsersByOrgID returns all organization-user relations by organization ID.
|
||||||
func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUser, error) {
|
func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUser, error) {
|
||||||
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
|
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
|
||||||
if opts.PublicOnly {
|
if opts.PublicOnly() {
|
||||||
sess.And("is_public = ?", true)
|
sess.And("is_public = ?", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.ListOptions.PageSize > 0 {
|
if opts.ListOptions.PageSize > 0 {
|
||||||
sess = db.SetSessionPagination(sess, opts)
|
sess = db.SetSessionPagination(sess, opts)
|
||||||
|
|
||||||
|
|
138
models/organization/org_list.go
Normal file
138
models/organization/org_list.go
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package organization
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SearchOrganizationsOptions options to filter organizations
|
||||||
|
type SearchOrganizationsOptions struct {
|
||||||
|
db.ListOptions
|
||||||
|
All bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindOrgOptions finds orgs options
|
||||||
|
type FindOrgOptions struct {
|
||||||
|
db.ListOptions
|
||||||
|
UserID int64
|
||||||
|
IncludePrivate bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
|
||||||
|
cond := builder.Eq{"uid": userID}
|
||||||
|
if !includePrivate {
|
||||||
|
cond["is_public"] = true
|
||||||
|
}
|
||||||
|
return builder.Select("org_id").From("org_user").Where(cond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts FindOrgOptions) ToConds() builder.Cond {
|
||||||
|
var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization}
|
||||||
|
if opts.UserID > 0 {
|
||||||
|
cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate)))
|
||||||
|
}
|
||||||
|
if !opts.IncludePrivate {
|
||||||
|
cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})
|
||||||
|
}
|
||||||
|
return cond
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts FindOrgOptions) ToOrders() string {
|
||||||
|
return "`user`.lower_name ASC"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID
|
||||||
|
// are allowed to create repos.
|
||||||
|
func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) {
|
||||||
|
orgs := make([]*Organization, 0, 10)
|
||||||
|
|
||||||
|
return orgs, db.GetEngine(ctx).Where(builder.In("id", builder.Select("`user`.id").From("`user`").
|
||||||
|
Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id").
|
||||||
|
Join("INNER", "`team`", "`team`.id = `team_user`.team_id").
|
||||||
|
Where(builder.Eq{"`team_user`.uid": userID}).
|
||||||
|
And(builder.Eq{"`team`.authorize": perm.AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})))).
|
||||||
|
Asc("`user`.name").
|
||||||
|
Find(&orgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinimalOrg represents a simple organization with only the needed columns
|
||||||
|
type MinimalOrg = Organization
|
||||||
|
|
||||||
|
// GetUserOrgsList returns all organizations the given user has access to
|
||||||
|
func GetUserOrgsList(ctx context.Context, user *user_model.User) ([]*MinimalOrg, error) {
|
||||||
|
schema, err := db.TableInfo(new(user_model.User))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outputCols := []string{
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"full_name",
|
||||||
|
"visibility",
|
||||||
|
"avatar",
|
||||||
|
"avatar_email",
|
||||||
|
"use_custom_avatar",
|
||||||
|
}
|
||||||
|
|
||||||
|
selectColumns := &strings.Builder{}
|
||||||
|
for i, col := range outputCols {
|
||||||
|
fmt.Fprintf(selectColumns, "`%s`.%s", schema.Name, col)
|
||||||
|
if i < len(outputCols)-1 {
|
||||||
|
selectColumns.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
columnsStr := selectColumns.String()
|
||||||
|
|
||||||
|
var orgs []*MinimalOrg
|
||||||
|
if err := db.GetEngine(ctx).Select(columnsStr).
|
||||||
|
Table("user").
|
||||||
|
Where(builder.In("`user`.`id`", queryUserOrgIDs(user.ID, true))).
|
||||||
|
Find(&orgs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type orgCount struct {
|
||||||
|
OrgID int64
|
||||||
|
RepoCount int
|
||||||
|
}
|
||||||
|
var orgCounts []orgCount
|
||||||
|
if err := db.GetEngine(ctx).
|
||||||
|
Select("owner_id AS org_id, COUNT(DISTINCT(repository.id)) as repo_count").
|
||||||
|
Table("repository").
|
||||||
|
Join("INNER", "org_user", "owner_id = org_user.org_id").
|
||||||
|
Where("org_user.uid = ?", user.ID).
|
||||||
|
And(builder.Or(
|
||||||
|
builder.Eq{"repository.is_private": false},
|
||||||
|
builder.In("repository.id", builder.Select("repo_id").From("team_repo").
|
||||||
|
InnerJoin("team_user", "team_user.team_id = team_repo.team_id").
|
||||||
|
Where(builder.Eq{"team_user.uid": user.ID})),
|
||||||
|
builder.In("repository.id", builder.Select("repo_id").From("collaboration").
|
||||||
|
Where(builder.Eq{"user_id": user.ID})),
|
||||||
|
)).
|
||||||
|
GroupBy("owner_id").Find(&orgCounts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
orgCountMap := make(map[int64]int, len(orgCounts))
|
||||||
|
for _, orgCount := range orgCounts {
|
||||||
|
orgCountMap[orgCount.OrgID] = orgCount.RepoCount
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, org := range orgs {
|
||||||
|
org.NumRepos = orgCountMap[org.ID]
|
||||||
|
}
|
||||||
|
|
||||||
|
return orgs, nil
|
||||||
|
}
|
63
models/organization/org_list_test.go
Normal file
63
models/organization/org_list_test.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package organization_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCountOrganizations(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludePrivate: true})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expected, cnt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindOrgs(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||||
|
UserID: 4,
|
||||||
|
IncludePrivate: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
if assert.Len(t, orgs, 1) {
|
||||||
|
assert.EqualValues(t, 3, orgs[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
orgs, err = db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||||
|
UserID: 4,
|
||||||
|
IncludePrivate: false,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Empty(t, orgs)
|
||||||
|
|
||||||
|
total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
||||||
|
UserID: 4,
|
||||||
|
IncludePrivate: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUserOrgsList(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4})
|
||||||
|
require.NoError(t, err)
|
||||||
|
if assert.Len(t, orgs, 1) {
|
||||||
|
assert.EqualValues(t, 3, orgs[0].ID)
|
||||||
|
// repo_id: 3 is in the team, 32 is public, 5 is private with no team
|
||||||
|
assert.EqualValues(t, 2, orgs[0].NumRepos)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@
|
||||||
package organization_test
|
package organization_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
@ -104,7 +105,7 @@ func TestUser_GetTeams(t *testing.T) {
|
||||||
func TestUser_GetMembers(t *testing.T) {
|
func TestUser_GetMembers(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||||
members, _, err := org.GetMembers(db.DefaultContext)
|
members, _, err := org.GetMembers(db.DefaultContext, &user_model.User{IsAdmin: true})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if assert.Len(t, members, 3) {
|
if assert.Len(t, members, 3) {
|
||||||
assert.Equal(t, int64(2), members[0].ID)
|
assert.Equal(t, int64(2), members[0].ID)
|
||||||
|
@ -128,15 +129,6 @@ func TestGetOrgByName(t *testing.T) {
|
||||||
assert.True(t, organization.IsErrOrgNotExist(err))
|
assert.True(t, organization.IsErrOrgNotExist(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCountOrganizations(t *testing.T) {
|
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludePrivate: true})
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, expected, cnt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsOrganizationOwner(t *testing.T) {
|
func TestIsOrganizationOwner(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
test := func(orgID, userID int64, expected bool) {
|
test := func(orgID, userID int64, expected bool) {
|
||||||
|
@ -181,67 +173,45 @@ func TestIsPublicMembership(t *testing.T) {
|
||||||
test(unittest.NonexistentID, unittest.NonexistentID, false)
|
test(unittest.NonexistentID, unittest.NonexistentID, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindOrgs(t *testing.T) {
|
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
|
|
||||||
orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
|
||||||
UserID: 4,
|
|
||||||
IncludePrivate: true,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
if assert.Len(t, orgs, 1) {
|
|
||||||
assert.EqualValues(t, 3, orgs[0].ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
orgs, err = db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
|
||||||
UserID: 4,
|
|
||||||
IncludePrivate: false,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Empty(t, orgs)
|
|
||||||
|
|
||||||
total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
|
|
||||||
UserID: 4,
|
|
||||||
IncludePrivate: true,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.EqualValues(t, 1, total)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetOrgUsersByOrgID(t *testing.T) {
|
func TestGetOrgUsersByOrgID(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
orgUsers, err := organization.GetOrgUsersByOrgID(db.DefaultContext, &organization.FindOrgMembersOpts{
|
opts := &organization.FindOrgMembersOpts{
|
||||||
ListOptions: db.ListOptions{},
|
Doer: &user_model.User{IsAdmin: true},
|
||||||
OrgID: 3,
|
OrgID: 3,
|
||||||
PublicOnly: false,
|
}
|
||||||
})
|
assert.False(t, opts.PublicOnly())
|
||||||
|
orgUsers, err := organization.GetOrgUsersByOrgID(db.DefaultContext, opts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if assert.Len(t, orgUsers, 3) {
|
sort.Slice(orgUsers, func(i, j int) bool {
|
||||||
assert.Equal(t, organization.OrgUser{
|
return orgUsers[i].ID < orgUsers[j].ID
|
||||||
ID: orgUsers[0].ID,
|
})
|
||||||
|
assert.EqualValues(t, []*organization.OrgUser{{
|
||||||
|
ID: 1,
|
||||||
OrgID: 3,
|
OrgID: 3,
|
||||||
UID: 2,
|
UID: 2,
|
||||||
IsPublic: true,
|
IsPublic: true,
|
||||||
}, *orgUsers[0])
|
}, {
|
||||||
assert.Equal(t, organization.OrgUser{
|
ID: 2,
|
||||||
ID: orgUsers[1].ID,
|
|
||||||
OrgID: 3,
|
OrgID: 3,
|
||||||
UID: 4,
|
UID: 4,
|
||||||
IsPublic: false,
|
IsPublic: false,
|
||||||
}, *orgUsers[1])
|
}, {
|
||||||
assert.Equal(t, organization.OrgUser{
|
ID: 9,
|
||||||
ID: orgUsers[2].ID,
|
|
||||||
OrgID: 3,
|
OrgID: 3,
|
||||||
UID: 28,
|
UID: 28,
|
||||||
IsPublic: true,
|
IsPublic: true,
|
||||||
}, *orgUsers[2])
|
}}, orgUsers)
|
||||||
}
|
|
||||||
|
opts = &organization.FindOrgMembersOpts{OrgID: 3}
|
||||||
|
assert.True(t, opts.PublicOnly())
|
||||||
|
orgUsers, err = organization.GetOrgUsersByOrgID(db.DefaultContext, opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, orgUsers, 2)
|
||||||
|
|
||||||
orgUsers, err = organization.GetOrgUsersByOrgID(db.DefaultContext, &organization.FindOrgMembersOpts{
|
orgUsers, err = organization.GetOrgUsersByOrgID(db.DefaultContext, &organization.FindOrgMembersOpts{
|
||||||
ListOptions: db.ListOptions{},
|
ListOptions: db.ListOptions{},
|
||||||
OrgID: unittest.NonexistentID,
|
OrgID: unittest.NonexistentID,
|
||||||
PublicOnly: false,
|
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Empty(t, orgUsers)
|
assert.Empty(t, orgUsers)
|
||||||
|
|
|
@ -95,7 +95,7 @@ func TestUserListIsPublicMember(t *testing.T) {
|
||||||
func testUserListIsPublicMember(t *testing.T, orgID int64, expected map[int64]bool) {
|
func testUserListIsPublicMember(t *testing.T, orgID int64, expected map[int64]bool) {
|
||||||
org, err := organization.GetOrgByID(db.DefaultContext, orgID)
|
org, err := organization.GetOrgByID(db.DefaultContext, orgID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, membersIsPublic, err := org.GetMembers(db.DefaultContext)
|
_, membersIsPublic, err := org.GetMembers(db.DefaultContext, &user_model.User{IsAdmin: true})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, expected, membersIsPublic)
|
assert.Equal(t, expected, membersIsPublic)
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ func TestUserListIsUserOrgOwner(t *testing.T) {
|
||||||
func testUserListIsUserOrgOwner(t *testing.T, orgID int64, expected map[int64]bool) {
|
func testUserListIsUserOrgOwner(t *testing.T, orgID int64, expected map[int64]bool) {
|
||||||
org, err := organization.GetOrgByID(db.DefaultContext, orgID)
|
org, err := organization.GetOrgByID(db.DefaultContext, orgID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
members, _, err := org.GetMembers(db.DefaultContext)
|
members, _, err := org.GetMembers(db.DefaultContext, &user_model.User{IsAdmin: true})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, expected, organization.IsUserOrgOwner(db.DefaultContext, members, orgID))
|
assert.Equal(t, expected, organization.IsUserOrgOwner(db.DefaultContext, members, orgID))
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,6 +242,7 @@ func GetSearchOrderByBySortType(sortType string) db.SearchOrderBy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProject creates a new Project
|
// NewProject creates a new Project
|
||||||
|
// The title will be cut off at 255 characters if it's longer than 255 characters.
|
||||||
func NewProject(ctx context.Context, p *Project) error {
|
func NewProject(ctx context.Context, p *Project) error {
|
||||||
if !IsTemplateTypeValid(p.TemplateType) {
|
if !IsTemplateTypeValid(p.TemplateType) {
|
||||||
p.TemplateType = TemplateTypeNone
|
p.TemplateType = TemplateTypeNone
|
||||||
|
@ -255,6 +256,8 @@ func NewProject(ctx context.Context, p *Project) error {
|
||||||
return util.NewInvalidArgumentErrorf("project type is not valid")
|
return util.NewInvalidArgumentErrorf("project type is not valid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.Title, _ = util.SplitStringAtByteN(p.Title, 255)
|
||||||
|
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
if err := db.Insert(ctx, p); err != nil {
|
if err := db.Insert(ctx, p); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -302,6 +305,7 @@ func UpdateProject(ctx context.Context, p *Project) error {
|
||||||
p.CardType = CardTypeTextOnly
|
p.CardType = CardTypeTextOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.Title, _ = util.SplitStringAtByteN(p.Title, 255)
|
||||||
_, err := db.GetEngine(ctx).ID(p.ID).Cols(
|
_, err := db.GetEngine(ctx).ID(p.ID).Cols(
|
||||||
"title",
|
"title",
|
||||||
"description",
|
"description",
|
||||||
|
|
|
@ -171,6 +171,7 @@ func IsReleaseExist(ctx context.Context, repoID int64, tagName string) (bool, er
|
||||||
|
|
||||||
// UpdateRelease updates all columns of a release
|
// UpdateRelease updates all columns of a release
|
||||||
func UpdateRelease(ctx context.Context, rel *Release) error {
|
func UpdateRelease(ctx context.Context, rel *Release) error {
|
||||||
|
rel.Title, _ = util.SplitStringAtByteN(rel.Title, 255)
|
||||||
_, err := db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel)
|
_, err := db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ const (
|
||||||
// SanitizerRules implements markup.Renderer
|
// SanitizerRules implements markup.Renderer
|
||||||
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||||
return []setting.MarkupSanitizerRule{
|
return []setting.MarkupSanitizerRule{
|
||||||
{Element: "div", AllowAttr: "class", Regexp: regexp.MustCompile(playerClassName)},
|
{Element: "div", AllowAttr: "class", Regexp: regexp.MustCompile("^" + playerClassName + "$")},
|
||||||
{Element: "div", AllowAttr: playerSrcAttr},
|
{Element: "div", AllowAttr: playerSrcAttr},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,9 @@ func (Renderer) Extensions() []string {
|
||||||
// SanitizerRules implements markup.Renderer
|
// SanitizerRules implements markup.Renderer
|
||||||
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||||
return []setting.MarkupSanitizerRule{
|
return []setting.MarkupSanitizerRule{
|
||||||
{Element: "table", AllowAttr: "class", Regexp: regexp.MustCompile(`data-table`)},
|
{Element: "table", AllowAttr: "class", Regexp: regexp.MustCompile(`^data-table$`)},
|
||||||
{Element: "th", AllowAttr: "class", Regexp: regexp.MustCompile(`line-num`)},
|
{Element: "th", AllowAttr: "class", Regexp: regexp.MustCompile(`^line-num$`)},
|
||||||
{Element: "td", AllowAttr: "class", Regexp: regexp.MustCompile(`line-num`)},
|
{Element: "td", AllowAttr: "class", Regexp: regexp.MustCompile(`^line-num$`)},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
8
release-notes/5997.md
Normal file
8
release-notes/5997.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
fix(security): [commit](https://codeberg.org/forgejo/forgejo/commit/45435a8789f8ff69603799a9031246d2d621d139) Fix and refactor markdown rendering
|
||||||
|
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/a8f2002a9b061ec1092df67c6f05e30aa7d2e2d2) Remove transaction for archive download
|
||||||
|
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/96ee0f56475204b2bbdc7f2aeb35b1c32eac469c) Fix oauth2 error handle not return immediately
|
||||||
|
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/c2e8790df37a14b4d2f72c7377db75309e0ebf1d) Trim title before insert/update to database to match the size requirements of database
|
||||||
|
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/03ab73d92eabaf774278effe3332623b1dc3580a) Fix nil panic if repo doesn't exist
|
||||||
|
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/56971f9ed90a01fd74a634b7496593e6f62ac260) Disable Oauth check if oauth disabled
|
||||||
|
fix: [commit](https://codeberg.org/forgejo/forgejo/commit/7f51210672031aee7a790455d51a17ce11a70559) Harden runner updateTask and updateLog api
|
||||||
|
feat: [commit](https://codeberg.org/forgejo/forgejo/commit/dd3c4d7096cff91854bcc6641f55d9d093e5c86e) Add a doctor check to disable the "Actions" unit for mirrors
|
|
@ -177,7 +177,9 @@ func (s *Service) UpdateTask(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *connect.Request[runnerv1.UpdateTaskRequest],
|
req *connect.Request[runnerv1.UpdateTaskRequest],
|
||||||
) (*connect.Response[runnerv1.UpdateTaskResponse], error) {
|
) (*connect.Response[runnerv1.UpdateTaskResponse], error) {
|
||||||
task, err := actions_model.UpdateTaskByState(ctx, req.Msg.State)
|
runner := GetRunner(ctx)
|
||||||
|
|
||||||
|
task, err := actions_model.UpdateTaskByState(ctx, runner.ID, req.Msg.State)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "update task: %v", err)
|
return nil, status.Errorf(codes.Internal, "update task: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -239,11 +241,15 @@ func (s *Service) UpdateLog(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *connect.Request[runnerv1.UpdateLogRequest],
|
req *connect.Request[runnerv1.UpdateLogRequest],
|
||||||
) (*connect.Response[runnerv1.UpdateLogResponse], error) {
|
) (*connect.Response[runnerv1.UpdateLogResponse], error) {
|
||||||
|
runner := GetRunner(ctx)
|
||||||
|
|
||||||
res := connect.NewResponse(&runnerv1.UpdateLogResponse{})
|
res := connect.NewResponse(&runnerv1.UpdateLogResponse{})
|
||||||
|
|
||||||
task, err := actions_model.GetTaskByID(ctx, req.Msg.TaskId)
|
task, err := actions_model.GetTaskByID(ctx, req.Msg.TaskId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Internal, "get task: %v", err)
|
return nil, status.Errorf(codes.Internal, "get task: %v", err)
|
||||||
|
} else if runner.ID != task.RunnerID {
|
||||||
|
return nil, status.Errorf(codes.Internal, "invalid runner for task")
|
||||||
}
|
}
|
||||||
ack := task.LogLength
|
ack := task.LogLength
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// listMembers list an organization's members
|
// listMembers list an organization's members
|
||||||
func listMembers(ctx *context.APIContext, publicOnly bool) {
|
func listMembers(ctx *context.APIContext, isMember bool) {
|
||||||
opts := &organization.FindOrgMembersOpts{
|
opts := &organization.FindOrgMembersOpts{
|
||||||
|
Doer: ctx.Doer,
|
||||||
|
IsDoerMember: isMember,
|
||||||
OrgID: ctx.Org.Organization.ID,
|
OrgID: ctx.Org.Organization.ID,
|
||||||
PublicOnly: publicOnly,
|
|
||||||
ListOptions: utils.GetListOptions(ctx),
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,16 +74,19 @@ func ListMembers(ctx *context.APIContext) {
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
publicOnly := true
|
var (
|
||||||
|
isMember bool
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
if ctx.Doer != nil {
|
if ctx.Doer != nil {
|
||||||
isMember, err := ctx.Org.Organization.IsOrgMember(ctx, ctx.Doer.ID)
|
isMember, err = ctx.Org.Organization.IsOrgMember(ctx, ctx.Doer.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
|
ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
publicOnly = !isMember && !ctx.Doer.IsAdmin
|
|
||||||
}
|
}
|
||||||
listMembers(ctx, publicOnly)
|
listMembers(ctx, isMember)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPublicMembers list an organization's public members
|
// ListPublicMembers list an organization's public members
|
||||||
|
@ -112,7 +116,7 @@ func ListPublicMembers(ctx *context.APIContext) {
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
listMembers(ctx, true)
|
listMembers(ctx, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsMember check if a user is a member of an organization
|
// IsMember check if a user is a member of an organization
|
||||||
|
|
|
@ -1013,6 +1013,8 @@ func SignInOAuthCallback(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
if err, ok := err.(*go_oauth2.RetrieveError); ok {
|
if err, ok := err.(*go_oauth2.RetrieveError); ok {
|
||||||
ctx.Flash.Error("OAuth2 RetrieveError: "+err.Error(), true)
|
ctx.Flash.Error("OAuth2 RetrieveError: "+err.Error(), true)
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/login")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
ctx.ServerError("UserSignIn", err)
|
ctx.ServerError("UserSignIn", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -110,10 +110,12 @@ func Home(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := &organization.FindOrgMembersOpts{
|
opts := &organization.FindOrgMembersOpts{
|
||||||
|
Doer: ctx.Doer,
|
||||||
OrgID: org.ID,
|
OrgID: org.ID,
|
||||||
PublicOnly: ctx.Org.PublicMemberOnly,
|
IsDoerMember: ctx.Org.IsMember,
|
||||||
ListOptions: db.ListOptions{Page: 1, PageSize: 25},
|
ListOptions: db.ListOptions{Page: 1, PageSize: 25},
|
||||||
}
|
}
|
||||||
|
|
||||||
members, _, err := organization.FindOrgMembers(ctx, opts)
|
members, _, err := organization.FindOrgMembers(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("FindOrgMembers", err)
|
ctx.ServerError("FindOrgMembers", err)
|
||||||
|
|
|
@ -33,8 +33,8 @@ func Members(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := &organization.FindOrgMembersOpts{
|
opts := &organization.FindOrgMembersOpts{
|
||||||
|
Doer: ctx.Doer,
|
||||||
OrgID: org.ID,
|
OrgID: org.ID,
|
||||||
PublicOnly: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Doer != nil {
|
if ctx.Doer != nil {
|
||||||
|
@ -43,9 +43,9 @@ func Members(ctx *context.Context) {
|
||||||
ctx.Error(http.StatusInternalServerError, "IsOrgMember")
|
ctx.Error(http.StatusInternalServerError, "IsOrgMember")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
opts.PublicOnly = !isMember && !ctx.Doer.IsAdmin
|
opts.IsDoerMember = isMember
|
||||||
}
|
}
|
||||||
ctx.Data["PublicOnly"] = opts.PublicOnly
|
ctx.Data["PublicOnly"] = opts.PublicOnly()
|
||||||
|
|
||||||
total, err := organization.CountOrgMembers(ctx, opts)
|
total, err := organization.CountOrgMembers(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -327,6 +327,13 @@ func registerRoutes(m *web.Route) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oauth2Enabled := func(ctx *context.Context) {
|
||||||
|
if !setting.OAuth2.Enabled {
|
||||||
|
ctx.Error(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
reqMilestonesDashboardPageEnabled := func(ctx *context.Context) {
|
reqMilestonesDashboardPageEnabled := func(ctx *context.Context) {
|
||||||
if !setting.Service.ShowMilestonesDashboardPage {
|
if !setting.Service.ShowMilestonesDashboardPage {
|
||||||
ctx.Error(http.StatusForbidden)
|
ctx.Error(http.StatusForbidden)
|
||||||
|
@ -516,16 +523,18 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Any("/user/events", routing.MarkLongPolling, events.Events)
|
m.Any("/user/events", routing.MarkLongPolling, events.Events)
|
||||||
|
|
||||||
m.Group("/login/oauth", func() {
|
m.Group("/login/oauth", func() {
|
||||||
|
m.Group("", func() {
|
||||||
m.Get("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth)
|
m.Get("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth)
|
||||||
m.Post("/grant", web.Bind(forms.GrantApplicationForm{}), auth.GrantApplicationOAuth)
|
m.Post("/grant", web.Bind(forms.GrantApplicationForm{}), auth.GrantApplicationOAuth)
|
||||||
// TODO manage redirection
|
// TODO manage redirection
|
||||||
m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth)
|
m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth)
|
||||||
}, ignSignInAndCsrf, reqSignIn)
|
}, ignSignInAndCsrf, reqSignIn)
|
||||||
|
|
||||||
m.Methods("GET, OPTIONS", "/login/oauth/userinfo", optionsCorsHandler(), ignSignInAndCsrf, auth.InfoOAuth)
|
m.Methods("GET, OPTIONS", "/userinfo", optionsCorsHandler(), ignSignInAndCsrf, auth.InfoOAuth)
|
||||||
m.Methods("POST, OPTIONS", "/login/oauth/access_token", optionsCorsHandler(), web.Bind(forms.AccessTokenForm{}), ignSignInAndCsrf, auth.AccessTokenOAuth)
|
m.Methods("POST, OPTIONS", "/access_token", optionsCorsHandler(), web.Bind(forms.AccessTokenForm{}), ignSignInAndCsrf, auth.AccessTokenOAuth)
|
||||||
m.Methods("GET, OPTIONS", "/login/oauth/keys", optionsCorsHandler(), ignSignInAndCsrf, auth.OIDCKeys)
|
m.Methods("GET, OPTIONS", "/keys", optionsCorsHandler(), ignSignInAndCsrf, auth.OIDCKeys)
|
||||||
m.Methods("POST, OPTIONS", "/login/oauth/introspect", optionsCorsHandler(), web.Bind(forms.IntrospectTokenForm{}), ignSignInAndCsrf, auth.IntrospectOAuth)
|
m.Methods("POST, OPTIONS", "/introspect", optionsCorsHandler(), web.Bind(forms.IntrospectTokenForm{}), ignSignInAndCsrf, auth.IntrospectOAuth)
|
||||||
|
}, oauth2Enabled)
|
||||||
|
|
||||||
m.Group("/user/settings", func() {
|
m.Group("/user/settings", func() {
|
||||||
m.Get("", user_setting.Profile)
|
m.Get("", user_setting.Profile)
|
||||||
|
@ -567,17 +576,24 @@ func registerRoutes(m *web.Route) {
|
||||||
}, openIDSignInEnabled)
|
}, openIDSignInEnabled)
|
||||||
m.Post("/account_link", linkAccountEnabled, security.DeleteAccountLink)
|
m.Post("/account_link", linkAccountEnabled, security.DeleteAccountLink)
|
||||||
})
|
})
|
||||||
m.Group("/applications/oauth2", func() {
|
|
||||||
|
m.Group("/applications", func() {
|
||||||
|
// oauth2 applications
|
||||||
|
m.Group("/oauth2", func() {
|
||||||
m.Get("/{id}", user_setting.OAuth2ApplicationShow)
|
m.Get("/{id}", user_setting.OAuth2ApplicationShow)
|
||||||
m.Post("/{id}", web.Bind(forms.EditOAuth2ApplicationForm{}), user_setting.OAuthApplicationsEdit)
|
m.Post("/{id}", web.Bind(forms.EditOAuth2ApplicationForm{}), user_setting.OAuthApplicationsEdit)
|
||||||
m.Post("/{id}/regenerate_secret", user_setting.OAuthApplicationsRegenerateSecret)
|
m.Post("/{id}/regenerate_secret", user_setting.OAuthApplicationsRegenerateSecret)
|
||||||
m.Post("", web.Bind(forms.EditOAuth2ApplicationForm{}), user_setting.OAuthApplicationsPost)
|
m.Post("", web.Bind(forms.EditOAuth2ApplicationForm{}), user_setting.OAuthApplicationsPost)
|
||||||
m.Post("/{id}/delete", user_setting.DeleteOAuth2Application)
|
m.Post("/{id}/delete", user_setting.DeleteOAuth2Application)
|
||||||
m.Post("/{id}/revoke/{grantId}", user_setting.RevokeOAuth2Grant)
|
m.Post("/{id}/revoke/{grantId}", user_setting.RevokeOAuth2Grant)
|
||||||
})
|
}, oauth2Enabled)
|
||||||
m.Combo("/applications").Get(user_setting.Applications).
|
|
||||||
|
// access token applications
|
||||||
|
m.Combo("").Get(user_setting.Applications).
|
||||||
Post(web.Bind(forms.NewAccessTokenForm{}), user_setting.ApplicationsPost)
|
Post(web.Bind(forms.NewAccessTokenForm{}), user_setting.ApplicationsPost)
|
||||||
m.Post("/applications/delete", user_setting.DeleteApplication)
|
m.Post("/delete", user_setting.DeleteApplication)
|
||||||
|
})
|
||||||
|
|
||||||
m.Combo("/keys").Get(user_setting.Keys).
|
m.Combo("/keys").Get(user_setting.Keys).
|
||||||
Post(web.Bind(forms.AddKeyForm{}), user_setting.KeysPost)
|
Post(web.Bind(forms.AddKeyForm{}), user_setting.KeysPost)
|
||||||
m.Post("/keys/delete", user_setting.DeleteKey)
|
m.Post("/keys/delete", user_setting.DeleteKey)
|
||||||
|
@ -755,12 +771,7 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Post("/regenerate_secret", admin.ApplicationsRegenerateSecret)
|
m.Post("/regenerate_secret", admin.ApplicationsRegenerateSecret)
|
||||||
m.Post("/delete", admin.DeleteApplication)
|
m.Post("/delete", admin.DeleteApplication)
|
||||||
})
|
})
|
||||||
}, func(ctx *context.Context) {
|
}, oauth2Enabled)
|
||||||
if !setting.OAuth2.Enabled {
|
|
||||||
ctx.Error(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
m.Group("/actions", func() {
|
m.Group("/actions", func() {
|
||||||
m.Get("", admin.RedirectToDefaultSetting)
|
m.Get("", admin.RedirectToDefaultSetting)
|
||||||
|
@ -883,12 +894,7 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Post("/regenerate_secret", org.OAuthApplicationsRegenerateSecret)
|
m.Post("/regenerate_secret", org.OAuthApplicationsRegenerateSecret)
|
||||||
m.Post("/delete", org.DeleteOAuth2Application)
|
m.Post("/delete", org.DeleteOAuth2Application)
|
||||||
})
|
})
|
||||||
}, func(ctx *context.Context) {
|
}, oauth2Enabled)
|
||||||
if !setting.OAuth2.Enabled {
|
|
||||||
ctx.Error(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
m.Group("/hooks", func() {
|
m.Group("/hooks", func() {
|
||||||
m.Get("", org.Webhooks)
|
m.Get("", org.Webhooks)
|
||||||
|
|
|
@ -68,6 +68,9 @@ func grantAdditionalScopes(grantScopes string) string {
|
||||||
// CheckOAuthAccessToken returns uid of user from oauth token
|
// CheckOAuthAccessToken returns uid of user from oauth token
|
||||||
// + non default openid scopes requested
|
// + non default openid scopes requested
|
||||||
func CheckOAuthAccessToken(ctx context.Context, accessToken string) (int64, string) {
|
func CheckOAuthAccessToken(ctx context.Context, accessToken string) (int64, string) {
|
||||||
|
if !setting.OAuth2.Enabled {
|
||||||
|
return 0, ""
|
||||||
|
}
|
||||||
// JWT tokens require a "."
|
// JWT tokens require a "."
|
||||||
if !strings.Contains(accessToken, ".") {
|
if !strings.Contains(accessToken, ".") {
|
||||||
return 0, ""
|
return 0, ""
|
||||||
|
|
|
@ -26,7 +26,6 @@ type Organization struct {
|
||||||
Organization *organization.Organization
|
Organization *organization.Organization
|
||||||
OrgLink string
|
OrgLink string
|
||||||
CanCreateOrgRepo bool
|
CanCreateOrgRepo bool
|
||||||
PublicMemberOnly bool // Only display public members
|
|
||||||
|
|
||||||
Team *organization.Team
|
Team *organization.Team
|
||||||
Teams []*organization.Team
|
Teams []*organization.Team
|
||||||
|
@ -176,10 +175,10 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
|
||||||
ctx.Data["OrgLink"] = ctx.Org.OrgLink
|
ctx.Data["OrgLink"] = ctx.Org.OrgLink
|
||||||
|
|
||||||
// Member
|
// Member
|
||||||
ctx.Org.PublicMemberOnly = ctx.Doer == nil || !ctx.Org.IsMember && !ctx.Doer.IsAdmin
|
|
||||||
opts := &organization.FindOrgMembersOpts{
|
opts := &organization.FindOrgMembersOpts{
|
||||||
|
Doer: ctx.Doer,
|
||||||
OrgID: org.ID,
|
OrgID: org.ID,
|
||||||
PublicOnly: ctx.Org.PublicMemberOnly,
|
IsDoerMember: ctx.Org.IsMember,
|
||||||
}
|
}
|
||||||
ctx.Data["NumMembers"], err = organization.CountOrgMembers(ctx, opts)
|
ctx.Data["NumMembers"], err = organization.CountOrgMembers(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
70
services/doctor/actions.go
Normal file
70
services/doctor/actions.go
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package doctor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/optional"
|
||||||
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
func disableMirrorActionsUnit(ctx context.Context, logger log.Logger, autofix bool) error {
|
||||||
|
var reposToFix []*repo_model.Repository
|
||||||
|
|
||||||
|
for page := 1; ; page++ {
|
||||||
|
repos, _, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
|
||||||
|
ListOptions: db.ListOptions{
|
||||||
|
PageSize: repo_model.RepositoryListDefaultPageSize,
|
||||||
|
Page: page,
|
||||||
|
},
|
||||||
|
Mirror: optional.Some(true),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("SearchRepository: %w", err)
|
||||||
|
}
|
||||||
|
if len(repos) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, repo := range repos {
|
||||||
|
if repo.UnitEnabled(ctx, unit_model.TypeActions) {
|
||||||
|
reposToFix = append(reposToFix, repo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(reposToFix) == 0 {
|
||||||
|
logger.Info("Found no mirror with actions unit enabled")
|
||||||
|
} else {
|
||||||
|
logger.Warn("Found %d mirrors with actions unit enabled", len(reposToFix))
|
||||||
|
}
|
||||||
|
if !autofix || len(reposToFix) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, repo := range reposToFix {
|
||||||
|
if err := repo_service.UpdateRepositoryUnits(ctx, repo, nil, []unit_model.Type{unit_model.TypeActions}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Info("Fixed %d mirrors with actions unit enabled", len(reposToFix))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(&Check{
|
||||||
|
Title: "Disable the actions unit for all mirrors",
|
||||||
|
Name: "disable-mirror-actions-unit",
|
||||||
|
IsDefault: false,
|
||||||
|
Run: disableMirrorActionsUnit,
|
||||||
|
Priority: 9,
|
||||||
|
})
|
||||||
|
}
|
|
@ -151,6 +151,7 @@ func CreateRelease(gitRepo *git.Repository, rel *repo_model.Release, msg string,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rel.Title, _ = util.SplitStringAtByteN(rel.Title, 255)
|
||||||
rel.LowerTagName = strings.ToLower(rel.TagName)
|
rel.LowerTagName = strings.ToLower(rel.TagName)
|
||||||
if err = db.Insert(gitRepo.Ctx, rel); err != nil {
|
if err = db.Insert(gitRepo.Ctx, rel); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -69,7 +69,7 @@ func (e RepoRefNotFoundError) Is(err error) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequest creates an archival request, based on the URI. The
|
// NewRequest creates an archival request, based on the URI. The
|
||||||
// resulting ArchiveRequest is suitable for being passed to ArchiveRepository()
|
// resulting ArchiveRequest is suitable for being passed to Await()
|
||||||
// if it's determined that the request still needs to be satisfied.
|
// if it's determined that the request still needs to be satisfied.
|
||||||
func NewRequest(ctx context.Context, repoID int64, repo *git.Repository, uri string) (*ArchiveRequest, error) {
|
func NewRequest(ctx context.Context, repoID int64, repo *git.Repository, uri string) (*ArchiveRequest, error) {
|
||||||
r := &ArchiveRequest{
|
r := &ArchiveRequest{
|
||||||
|
@ -168,13 +168,14 @@ func (aReq *ArchiveRequest) Await(ctx context.Context) (*repo_model.RepoArchiver
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// doArchive satisfies the ArchiveRequest being passed in. Processing
|
||||||
|
// will occur in a separate goroutine, as this phase may take a while to
|
||||||
|
// complete. If the archive already exists, doArchive will not do
|
||||||
|
// anything. In all cases, the caller should be examining the *ArchiveRequest
|
||||||
|
// being returned for completion, as it may be different than the one they passed
|
||||||
|
// in.
|
||||||
func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver, error) {
|
func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver, error) {
|
||||||
txCtx, committer, err := db.TxContext(ctx)
|
ctx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("ArchiveRequest[%d]: %s", r.RepoID, r.GetArchiveName()))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
ctx, _, finished := process.GetManager().AddContext(txCtx, fmt.Sprintf("ArchiveRequest[%d]: %s", r.RepoID, r.GetArchiveName()))
|
|
||||||
defer finished()
|
defer finished()
|
||||||
|
|
||||||
archiver, err := repo_model.GetRepoArchiver(ctx, r.RepoID, r.Type, r.CommitID)
|
archiver, err := repo_model.GetRepoArchiver(ctx, r.RepoID, r.Type, r.CommitID)
|
||||||
|
@ -209,7 +210,7 @@ func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return archiver, committer.Commit()
|
return archiver, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
@ -278,17 +279,7 @@ func doArchive(ctx context.Context, r *ArchiveRequest) (*repo_model.RepoArchiver
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return archiver, committer.Commit()
|
return archiver, nil
|
||||||
}
|
|
||||||
|
|
||||||
// ArchiveRepository satisfies the ArchiveRequest being passed in. Processing
|
|
||||||
// will occur in a separate goroutine, as this phase may take a while to
|
|
||||||
// complete. If the archive already exists, ArchiveRepository will not do
|
|
||||||
// anything. In all cases, the caller should be examining the *ArchiveRequest
|
|
||||||
// being returned for completion, as it may be different than the one they passed
|
|
||||||
// in.
|
|
||||||
func ArchiveRepository(ctx context.Context, request *ArchiveRequest) (*repo_model.RepoArchiver, error) {
|
|
||||||
return doArchive(ctx, request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var archiverQueue *queue.WorkerPoolQueue[*ArchiveRequest]
|
var archiverQueue *queue.WorkerPoolQueue[*ArchiveRequest]
|
||||||
|
@ -298,8 +289,10 @@ func Init(ctx context.Context) error {
|
||||||
handler := func(items ...*ArchiveRequest) []*ArchiveRequest {
|
handler := func(items ...*ArchiveRequest) []*ArchiveRequest {
|
||||||
for _, archiveReq := range items {
|
for _, archiveReq := range items {
|
||||||
log.Trace("ArchiverData Process: %#v", archiveReq)
|
log.Trace("ArchiverData Process: %#v", archiveReq)
|
||||||
if _, err := doArchive(ctx, archiveReq); err != nil {
|
if archiver, err := doArchive(ctx, archiveReq); err != nil {
|
||||||
log.Error("Archive %v failed: %v", archiveReq, err)
|
log.Error("Archive %v failed: %v", archiveReq, err)
|
||||||
|
} else {
|
||||||
|
log.Trace("ArchiverData Success: %#v", archiver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -81,13 +81,13 @@ func TestArchive_Basic(t *testing.T) {
|
||||||
inFlight[1] = tgzReq
|
inFlight[1] = tgzReq
|
||||||
inFlight[2] = secondReq
|
inFlight[2] = secondReq
|
||||||
|
|
||||||
ArchiveRepository(db.DefaultContext, zipReq)
|
doArchive(db.DefaultContext, zipReq)
|
||||||
ArchiveRepository(db.DefaultContext, tgzReq)
|
doArchive(db.DefaultContext, tgzReq)
|
||||||
ArchiveRepository(db.DefaultContext, secondReq)
|
doArchive(db.DefaultContext, secondReq)
|
||||||
|
|
||||||
// Make sure sending an unprocessed request through doesn't affect the queue
|
// Make sure sending an unprocessed request through doesn't affect the queue
|
||||||
// count.
|
// count.
|
||||||
ArchiveRepository(db.DefaultContext, zipReq)
|
doArchive(db.DefaultContext, zipReq)
|
||||||
|
|
||||||
// Sleep two seconds to make sure the queue doesn't change.
|
// Sleep two seconds to make sure the queue doesn't change.
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
@ -102,7 +102,7 @@ func TestArchive_Basic(t *testing.T) {
|
||||||
// We still have the other three stalled at completion, waiting to remove
|
// We still have the other three stalled at completion, waiting to remove
|
||||||
// from archiveInProgress. Try to submit this new one before its
|
// from archiveInProgress. Try to submit this new one before its
|
||||||
// predecessor has cleared out of the queue.
|
// predecessor has cleared out of the queue.
|
||||||
ArchiveRepository(db.DefaultContext, zipReq2)
|
doArchive(db.DefaultContext, zipReq2)
|
||||||
|
|
||||||
// Now we'll submit a request and TimedWaitForCompletion twice, before and
|
// Now we'll submit a request and TimedWaitForCompletion twice, before and
|
||||||
// after we release it. We should trigger both the timeout and non-timeout
|
// after we release it. We should trigger both the timeout and non-timeout
|
||||||
|
@ -110,7 +110,7 @@ func TestArchive_Basic(t *testing.T) {
|
||||||
timedReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".tar.gz")
|
timedReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".tar.gz")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, timedReq)
|
assert.NotNil(t, timedReq)
|
||||||
ArchiveRepository(db.DefaultContext, timedReq)
|
doArchive(db.DefaultContext, timedReq)
|
||||||
|
|
||||||
zipReq2, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
zipReq2, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -75,8 +75,9 @@ func (countTest *userCountTest) Init(t *testing.T, doerID, userID int64) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
countTest.memberCount, err = organization.CountOrgMembers(db.DefaultContext, &organization.FindOrgMembersOpts{
|
countTest.memberCount, err = organization.CountOrgMembers(db.DefaultContext, &organization.FindOrgMembersOpts{
|
||||||
|
Doer: countTest.doer,
|
||||||
OrgID: org.ID,
|
OrgID: org.ID,
|
||||||
PublicOnly: !isMember,
|
IsDoerMember: isMember,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue