feat: vendor gitea 1.16.2
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestArchiveType(t *testing.T) {
|
||||
name, archiveType := SplitArchiveNameType("test.tar.gz")
|
||||
assert.Equal(t, "test", name)
|
||||
assert.Equal(t, "tar.gz", archiveType.String())
|
||||
|
||||
name, archiveType = SplitArchiveNameType("a/b/test.zip")
|
||||
assert.Equal(t, "a/b/test", name)
|
||||
assert.Equal(t, "zip", archiveType.String())
|
||||
|
||||
name, archiveType = SplitArchiveNameType("1234.bundle")
|
||||
assert.Equal(t, "1234", name)
|
||||
assert.Equal(t, "bundle", archiveType.String())
|
||||
|
||||
name, archiveType = SplitArchiveNameType("test")
|
||||
assert.Equal(t, "test", name)
|
||||
assert.Equal(t, "unknown", archiveType.String())
|
||||
|
||||
name, archiveType = SplitArchiveNameType("test.xz")
|
||||
assert.Equal(t, "test.xz", name)
|
||||
assert.Equal(t, "unknown", archiveType.String())
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
@@ -27,11 +26,46 @@ const (
|
||||
ArchiverReady // it's ready
|
||||
)
|
||||
|
||||
// ArchiveType archive types
|
||||
type ArchiveType int
|
||||
|
||||
const (
|
||||
ArchiveUnknown ArchiveType = iota
|
||||
ArchiveZip // 1
|
||||
ArchiveTarGz // 2
|
||||
ArchiveBundle // 3
|
||||
)
|
||||
|
||||
// String converts an ArchiveType to string: the extension of the archive file without prefix dot
|
||||
func (a ArchiveType) String() string {
|
||||
switch a {
|
||||
case ArchiveZip:
|
||||
return "zip"
|
||||
case ArchiveTarGz:
|
||||
return "tar.gz"
|
||||
case ArchiveBundle:
|
||||
return "bundle"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func SplitArchiveNameType(s string) (string, ArchiveType) {
|
||||
switch {
|
||||
case strings.HasSuffix(s, ".zip"):
|
||||
return strings.TrimSuffix(s, ".zip"), ArchiveZip
|
||||
case strings.HasSuffix(s, ".tar.gz"):
|
||||
return strings.TrimSuffix(s, ".tar.gz"), ArchiveTarGz
|
||||
case strings.HasSuffix(s, ".bundle"):
|
||||
return strings.TrimSuffix(s, ".bundle"), ArchiveBundle
|
||||
}
|
||||
return s, ArchiveUnknown
|
||||
}
|
||||
|
||||
// RepoArchiver represents all archivers
|
||||
type RepoArchiver struct { //revive:disable-line:exported
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"index unique(s)"`
|
||||
Type git.ArchiveType `xorm:"unique(s)"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"index unique(s)"`
|
||||
Type ArchiveType `xorm:"unique(s)"`
|
||||
Status ArchiverStatus
|
||||
CommitID string `xorm:"VARCHAR(64) unique(s)"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"`
|
||||
@@ -56,15 +90,15 @@ func repoArchiverForRelativePath(relativePath string) (*RepoArchiver, error) {
|
||||
if err != nil {
|
||||
return nil, util.NewInvalidArgumentErrorf("invalid storage path: invalid repo id")
|
||||
}
|
||||
commitID, archiveType := git.SplitArchiveNameType(parts[2])
|
||||
if archiveType == git.ArchiveUnknown {
|
||||
commitID, archiveType := SplitArchiveNameType(parts[2])
|
||||
if archiveType == ArchiveUnknown {
|
||||
return nil, util.NewInvalidArgumentErrorf("invalid storage path: invalid archive type")
|
||||
}
|
||||
return &RepoArchiver{RepoID: repoID, CommitID: commitID, Type: archiveType}, nil
|
||||
}
|
||||
|
||||
// GetRepoArchiver get an archiver
|
||||
func GetRepoArchiver(ctx context.Context, repoID int64, tp git.ArchiveType, commitID string) (*RepoArchiver, error) {
|
||||
func GetRepoArchiver(ctx context.Context, repoID int64, tp ArchiveType, commitID string) (*RepoArchiver, error) {
|
||||
var archiver RepoArchiver
|
||||
has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("`type`=?", tp).And("commit_id=?", commitID).Get(&archiver)
|
||||
if err != nil {
|
||||
@@ -73,7 +107,7 @@ func GetRepoArchiver(ctx context.Context, repoID int64, tp git.ArchiveType, comm
|
||||
if has {
|
||||
return &archiver, nil
|
||||
}
|
||||
return nil, nil
|
||||
return nil, nil //nolint:nilnil // return nil to indicate that the object does not exist
|
||||
}
|
||||
|
||||
// ExistsRepoArchiverWithStoragePath checks if there is a RepoArchiver for a given storage path
|
||||
|
||||
@@ -41,20 +41,14 @@ func generateRandomAvatar(ctx context.Context, repo *Repository) error {
|
||||
idToString := strconv.FormatInt(repo.ID, 10)
|
||||
|
||||
seed := idToString
|
||||
img, err := avatar.RandomImage([]byte(seed))
|
||||
if err != nil {
|
||||
return fmt.Errorf("RandomImage: %w", err)
|
||||
}
|
||||
img := avatar.RandomImageDefaultSize([]byte(seed))
|
||||
|
||||
repo.Avatar = idToString
|
||||
|
||||
if err := storage.SaveFrom(storage.RepoAvatars, repo.CustomAvatarRelativePath(), func(w io.Writer) error {
|
||||
if err := png.Encode(w, img); err != nil {
|
||||
log.Error("Encode: %v", err)
|
||||
}
|
||||
return err
|
||||
return png.Encode(w, img)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("Failed to create dir %s: %w", repo.CustomAvatarRelativePath(), err)
|
||||
return fmt.Errorf("failed to create dir %s: %w", repo.CustomAvatarRelativePath(), err)
|
||||
}
|
||||
|
||||
log.Info("New random avatar created for repository: %d", repo.ID)
|
||||
|
||||
@@ -49,7 +49,7 @@ func GetUserFork(ctx context.Context, repoID, userID int64) (*Repository, error)
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, nil
|
||||
return nil, nil //nolint:nilnil // return nil to indicate that the object does not exist
|
||||
}
|
||||
return &forkedRepo, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
func (repo *Repository) GetPullRequestTargetBranch(ctx context.Context) string {
|
||||
unitPRConfig := repo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig()
|
||||
return util.IfZero(unitPRConfig.DefaultTargetBranch, repo.DefaultBranch)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDefaultTargetBranchSelection(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
ctx := t.Context()
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: 1})
|
||||
|
||||
assert.Equal(t, repo.DefaultBranch, repo.GetPullRequestTargetBranch(ctx))
|
||||
|
||||
repo.Units = nil
|
||||
prUnit, err := repo.GetUnit(ctx, unit.TypePullRequests)
|
||||
assert.NoError(t, err)
|
||||
prConfig := prUnit.PullRequestsConfig()
|
||||
prConfig.DefaultTargetBranch = "branch2"
|
||||
prUnit.Config = prConfig
|
||||
assert.NoError(t, UpdateRepoUnitConfig(ctx, prUnit))
|
||||
repo.Units = nil
|
||||
assert.Equal(t, "branch2", repo.GetPullRequestTargetBranch(ctx))
|
||||
}
|
||||
@@ -229,10 +229,6 @@ func RelativePath(ownerName, repoName string) string {
|
||||
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".git"
|
||||
}
|
||||
|
||||
func RelativeWikiPath(ownerName, repoName string) string {
|
||||
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".wiki.git"
|
||||
}
|
||||
|
||||
// RelativePath should be an unix style path like username/reponame.git
|
||||
func (repo *Repository) RelativePath() string {
|
||||
return RelativePath(repo.OwnerName, repo.Name)
|
||||
@@ -245,12 +241,6 @@ func (sr StorageRepo) RelativePath() string {
|
||||
return string(sr)
|
||||
}
|
||||
|
||||
// WikiStorageRepo returns the storage repo for the wiki
|
||||
// The wiki repository should have the same object format as the code repository
|
||||
func (repo *Repository) WikiStorageRepo() StorageRepo {
|
||||
return StorageRepo(RelativeWikiPath(repo.OwnerName, repo.Name))
|
||||
}
|
||||
|
||||
// SanitizedOriginalURL returns a sanitized OriginalURL
|
||||
func (repo *Repository) SanitizedOriginalURL() string {
|
||||
if repo.OriginalURL == "" {
|
||||
@@ -291,7 +281,7 @@ func (repo *Repository) SizeDetailsString() string {
|
||||
var str strings.Builder
|
||||
sizeDetails := repo.SizeDetails()
|
||||
for _, detail := range sizeDetails {
|
||||
str.WriteString(fmt.Sprintf("%s: %s, ", detail.Name, base.FileSize(detail.Size)))
|
||||
fmt.Fprintf(&str, "%s: %s, ", detail.Name, base.FileSize(detail.Size))
|
||||
}
|
||||
return strings.TrimSuffix(str.String(), ", ")
|
||||
}
|
||||
@@ -432,52 +422,37 @@ func (repo *Repository) UnitEnabled(ctx context.Context, tp unit.Type) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// MustGetUnit always returns a RepoUnit object
|
||||
// MustGetUnit always returns a RepoUnit object even if the unit doesn't exist (not enabled)
|
||||
func (repo *Repository) MustGetUnit(ctx context.Context, tp unit.Type) *RepoUnit {
|
||||
ru, err := repo.GetUnit(ctx, tp)
|
||||
if err == nil {
|
||||
return ru
|
||||
}
|
||||
|
||||
if !errors.Is(err, util.ErrNotExist) {
|
||||
setting.PanicInDevOrTesting("Failed to get unit %v for repository %d: %v", tp, repo.ID, err)
|
||||
}
|
||||
ru = &RepoUnit{RepoID: repo.ID, Type: tp}
|
||||
switch tp {
|
||||
case unit.TypeExternalWiki:
|
||||
return &RepoUnit{
|
||||
Type: tp,
|
||||
Config: new(ExternalWikiConfig),
|
||||
}
|
||||
ru.Config = new(ExternalWikiConfig)
|
||||
case unit.TypeExternalTracker:
|
||||
return &RepoUnit{
|
||||
Type: tp,
|
||||
Config: new(ExternalTrackerConfig),
|
||||
}
|
||||
ru.Config = new(ExternalTrackerConfig)
|
||||
case unit.TypePullRequests:
|
||||
return &RepoUnit{
|
||||
Type: tp,
|
||||
Config: new(PullRequestsConfig),
|
||||
}
|
||||
ru.Config = new(PullRequestsConfig)
|
||||
case unit.TypeIssues:
|
||||
return &RepoUnit{
|
||||
Type: tp,
|
||||
Config: new(IssuesConfig),
|
||||
}
|
||||
ru.Config = new(IssuesConfig)
|
||||
case unit.TypeActions:
|
||||
return &RepoUnit{
|
||||
Type: tp,
|
||||
Config: new(ActionsConfig),
|
||||
}
|
||||
ru.Config = new(ActionsConfig)
|
||||
case unit.TypeProjects:
|
||||
cfg := new(ProjectsConfig)
|
||||
cfg.ProjectsMode = ProjectsModeNone
|
||||
return &RepoUnit{
|
||||
Type: tp,
|
||||
Config: cfg,
|
||||
ru.Config = new(ProjectsConfig)
|
||||
default: // other units don't have config
|
||||
}
|
||||
if ru.Config != nil {
|
||||
if err = ru.Config.FromDB(nil); err != nil {
|
||||
setting.PanicInDevOrTesting("Failed to load default config for unit %v of repository %d: %v", tp, repo.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return &RepoUnit{
|
||||
Type: tp,
|
||||
Config: new(UnitConfig),
|
||||
}
|
||||
return ru
|
||||
}
|
||||
|
||||
// GetUnit returns a RepoUnit object
|
||||
@@ -605,7 +580,7 @@ func (repo *Repository) IsGenerated() bool {
|
||||
|
||||
// RepoPath returns repository path by given user and repository name.
|
||||
func RepoPath(userName, repoName string) string { //revive:disable-line:exported
|
||||
return filepath.Join(user_model.UserPath(userName), strings.ToLower(repoName)+".git")
|
||||
return filepath.Join(setting.RepoRootPath, filepath.Clean(strings.ToLower(userName)), filepath.Clean(strings.ToLower(repoName)+".git"))
|
||||
}
|
||||
|
||||
// RepoPath returns the repository path
|
||||
@@ -623,16 +598,13 @@ func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) strin
|
||||
return fmt.Sprintf("%s/%s/compare/%s...%s", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name), util.PathEscapeSegments(oldCommitID), util.PathEscapeSegments(newCommitID))
|
||||
}
|
||||
|
||||
func (repo *Repository) ComposeBranchCompareURL(baseRepo *Repository, branchName string) string {
|
||||
if baseRepo == nil {
|
||||
baseRepo = repo
|
||||
}
|
||||
func (repo *Repository) ComposeBranchCompareURL(baseRepo *Repository, baseBranch, branchName string) string {
|
||||
var cmpBranchEscaped string
|
||||
if repo.ID != baseRepo.ID {
|
||||
cmpBranchEscaped = fmt.Sprintf("%s/%s:", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name))
|
||||
}
|
||||
cmpBranchEscaped = fmt.Sprintf("%s%s", cmpBranchEscaped, util.PathEscapeSegments(branchName))
|
||||
return fmt.Sprintf("%s/compare/%s...%s", baseRepo.Link(), util.PathEscapeSegments(baseRepo.DefaultBranch), cmpBranchEscaped)
|
||||
return fmt.Sprintf("%s/compare/%s...%s", baseRepo.Link(), util.PathEscapeSegments(baseBranch), cmpBranchEscaped)
|
||||
}
|
||||
|
||||
// IsOwnedBy returns true when user owns this repository
|
||||
@@ -879,16 +851,6 @@ func GetRepositoriesMapByIDs(ctx context.Context, ids []int64) (map[int64]*Repos
|
||||
return repos, db.GetEngine(ctx).In("id", ids).Find(&repos)
|
||||
}
|
||||
|
||||
// IsRepositoryModelOrDirExist returns true if the repository with given name under user has already existed.
|
||||
func IsRepositoryModelOrDirExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
|
||||
has, err := IsRepositoryModelExist(ctx, u, repoName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
isDir, err := util.IsDir(RepoPath(u.Name, repoName))
|
||||
return has || isDir, err
|
||||
}
|
||||
|
||||
func IsRepositoryModelExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
|
||||
return db.GetEngine(ctx).Get(&Repository{
|
||||
OwnerID: u.ID,
|
||||
@@ -901,7 +863,7 @@ func IsRepositoryModelExist(ctx context.Context, u *user_model.User, repoName st
|
||||
// non-generated repositories, and TemplateRepo will be left untouched)
|
||||
func GetTemplateRepo(ctx context.Context, repo *Repository) (*Repository, error) {
|
||||
if !repo.IsGenerated() {
|
||||
return nil, nil
|
||||
return nil, nil //nolint:nilnil // return nil for non-generated repositories
|
||||
}
|
||||
|
||||
return GetRepositoryByID(ctx, repo.TemplateID)
|
||||
|
||||
@@ -212,6 +212,13 @@ type SearchRepoOptions struct {
|
||||
OnlyShowRelevant bool
|
||||
}
|
||||
|
||||
func (opts *SearchRepoOptions) ApplyPublicOnly(publicOnly bool) {
|
||||
if publicOnly {
|
||||
opts.Private = false
|
||||
opts.AllLimited = false
|
||||
}
|
||||
}
|
||||
|
||||
// UserOwnedRepoCond returns user ownered repositories
|
||||
func UserOwnedRepoCond(userID int64) builder.Cond {
|
||||
return builder.Eq{
|
||||
@@ -778,3 +785,11 @@ func GetUserRepositories(ctx context.Context, opts SearchRepoOptions) (Repositor
|
||||
repos := make(RepositoryList, 0, opts.PageSize)
|
||||
return repos, count, db.SetSessionPagination(sess, &opts).Find(&repos)
|
||||
}
|
||||
|
||||
func GetOwnerRepositoriesByIDs(ctx context.Context, ownerID int64, repoIDs []int64) (RepositoryList, error) {
|
||||
if len(repoIDs) == 0 {
|
||||
return RepositoryList{}, nil
|
||||
}
|
||||
repos := make(RepositoryList, 0, len(repoIDs))
|
||||
return repos, db.GetEngine(ctx).Where(builder.Eq{"owner_id": ownerID}).In("id", repoIDs).Find(&repos)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
@@ -131,12 +129,29 @@ type PullRequestsConfig struct {
|
||||
DefaultDeleteBranchAfterMerge bool
|
||||
DefaultMergeStyle MergeStyle
|
||||
DefaultAllowMaintainerEdit bool
|
||||
DefaultTargetBranch string
|
||||
}
|
||||
|
||||
func DefaultPullRequestsConfig() *PullRequestsConfig {
|
||||
cfg := &PullRequestsConfig{
|
||||
AllowMerge: true,
|
||||
AllowRebase: true,
|
||||
AllowRebaseMerge: true,
|
||||
AllowSquash: true,
|
||||
AllowFastForwardOnly: true,
|
||||
AllowRebaseUpdate: true,
|
||||
DefaultAllowMaintainerEdit: true,
|
||||
}
|
||||
cfg.DefaultDeleteBranchAfterMerge = setting.Repository.PullRequest.DefaultDeleteBranchAfterMerge
|
||||
cfg.DefaultMergeStyle = MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle)
|
||||
cfg.DefaultMergeStyle = util.IfZero(cfg.DefaultMergeStyle, MergeStyleMerge)
|
||||
return cfg
|
||||
}
|
||||
|
||||
// FromDB fills up a PullRequestsConfig from serialized format.
|
||||
func (cfg *PullRequestsConfig) FromDB(bs []byte) error {
|
||||
// AllowRebaseUpdate = true as default for existing PullRequestConfig in DB
|
||||
cfg.AllowRebaseUpdate = true
|
||||
// set default values for existing PullRequestConfig in DB
|
||||
*cfg = *DefaultPullRequestsConfig()
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||
}
|
||||
|
||||
@@ -155,51 +170,8 @@ func (cfg *PullRequestsConfig) IsMergeStyleAllowed(mergeStyle MergeStyle) bool {
|
||||
mergeStyle == MergeStyleManuallyMerged && cfg.AllowManualMerge
|
||||
}
|
||||
|
||||
// GetDefaultMergeStyle returns the default merge style for this pull request
|
||||
func (cfg *PullRequestsConfig) GetDefaultMergeStyle() MergeStyle {
|
||||
if len(cfg.DefaultMergeStyle) != 0 {
|
||||
return cfg.DefaultMergeStyle
|
||||
}
|
||||
|
||||
if setting.Repository.PullRequest.DefaultMergeStyle != "" {
|
||||
return MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle)
|
||||
}
|
||||
|
||||
return MergeStyleMerge
|
||||
}
|
||||
|
||||
type ActionsConfig struct {
|
||||
DisabledWorkflows []string
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) EnableWorkflow(file string) {
|
||||
cfg.DisabledWorkflows = util.SliceRemoveAll(cfg.DisabledWorkflows, file)
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) ToString() string {
|
||||
return strings.Join(cfg.DisabledWorkflows, ",")
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) IsWorkflowDisabled(file string) bool {
|
||||
return slices.Contains(cfg.DisabledWorkflows, file)
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) DisableWorkflow(file string) {
|
||||
if slices.Contains(cfg.DisabledWorkflows, file) {
|
||||
return
|
||||
}
|
||||
|
||||
cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file)
|
||||
}
|
||||
|
||||
// FromDB fills up a ActionsConfig from serialized format.
|
||||
func (cfg *ActionsConfig) FromDB(bs []byte) error {
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||
}
|
||||
|
||||
// ToDB exports a ActionsConfig to a serialized format.
|
||||
func (cfg *ActionsConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
func DefaultPullRequestsUnit(repoID int64) RepoUnit {
|
||||
return RepoUnit{RepoID: repoID, Type: unit.TypePullRequests, Config: DefaultPullRequestsConfig()}
|
||||
}
|
||||
|
||||
// ProjectsMode represents the projects enabled for a repository
|
||||
@@ -223,6 +195,8 @@ type ProjectsConfig struct {
|
||||
|
||||
// FromDB fills up a ProjectsConfig from serialized format.
|
||||
func (cfg *ProjectsConfig) FromDB(bs []byte) error {
|
||||
// TODO: remove GetProjectsMode, only use ProjectsMode
|
||||
cfg.ProjectsMode = ProjectsModeAll
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||
}
|
||||
|
||||
@@ -253,7 +227,12 @@ func (cfg *ProjectsConfig) IsProjectsAllowed(m ProjectsMode) bool {
|
||||
func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
|
||||
switch colName {
|
||||
case "type":
|
||||
switch unit.Type(db.Cell2Int64(val)) {
|
||||
var err error
|
||||
r.Type, _, err = db.CellToInt(val, unit.TypeInvalid)
|
||||
if err != nil {
|
||||
setting.PanicInDevOrTesting("Unable to convert repo unit (id=%d) type: %v", r.ID, err)
|
||||
}
|
||||
switch r.Type {
|
||||
case unit.TypeExternalWiki:
|
||||
r.Config = new(ExternalWikiConfig)
|
||||
case unit.TypeExternalTracker:
|
||||
@@ -271,6 +250,11 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
|
||||
default:
|
||||
r.Config = new(UnitConfig)
|
||||
}
|
||||
case "config":
|
||||
if *val == nil {
|
||||
// XROM doesn't call FromDB if the value is nil, but we need to set default values for the config fields
|
||||
_ = r.Config.FromDB(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,9 +318,9 @@ func getUnitsByRepoID(ctx context.Context, repoID int64) (units []*RepoUnit, err
|
||||
return units, nil
|
||||
}
|
||||
|
||||
// UpdateRepoUnit updates the provided repo unit
|
||||
func UpdateRepoUnit(ctx context.Context, unit *RepoUnit) error {
|
||||
_, err := db.GetEngine(ctx).ID(unit.ID).Update(unit)
|
||||
// UpdateRepoUnitConfig updates the config of the provided repo unit
|
||||
func UpdateRepoUnitConfig(ctx context.Context, unit *RepoUnit) error {
|
||||
_, err := db.GetEngine(ctx).ID(unit.ID).Cols("config").Update(unit)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// ActionsTokenPermissionMode defines the default permission mode for Actions tokens
|
||||
type ActionsTokenPermissionMode string
|
||||
|
||||
const (
|
||||
// ActionsTokenPermissionModePermissive - write access by default (current behavior, backwards compatible)
|
||||
ActionsTokenPermissionModePermissive ActionsTokenPermissionMode = "permissive"
|
||||
// ActionsTokenPermissionModeRestricted - read access by default
|
||||
ActionsTokenPermissionModeRestricted ActionsTokenPermissionMode = "restricted"
|
||||
)
|
||||
|
||||
func (ActionsTokenPermissionMode) EnumValues() []ActionsTokenPermissionMode {
|
||||
return []ActionsTokenPermissionMode{ActionsTokenPermissionModePermissive /* default */, ActionsTokenPermissionModeRestricted}
|
||||
}
|
||||
|
||||
// ActionsTokenPermissions defines the permissions for different repository units
|
||||
type ActionsTokenPermissions struct {
|
||||
UnitAccessModes map[unit.Type]perm.AccessMode `json:"unit_access_modes,omitempty"`
|
||||
}
|
||||
|
||||
var ActionsTokenUnitTypes = []unit.Type{
|
||||
unit.TypeCode,
|
||||
unit.TypeIssues,
|
||||
unit.TypePullRequests,
|
||||
unit.TypePackages,
|
||||
unit.TypeActions,
|
||||
unit.TypeWiki,
|
||||
unit.TypeReleases,
|
||||
unit.TypeProjects,
|
||||
}
|
||||
|
||||
func MakeActionsTokenPermissions(unitAccessMode perm.AccessMode) (ret ActionsTokenPermissions) {
|
||||
ret.UnitAccessModes = make(map[unit.Type]perm.AccessMode)
|
||||
for _, u := range ActionsTokenUnitTypes {
|
||||
ret.UnitAccessModes[u] = unitAccessMode
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// ClampActionsTokenPermissions ensures that the given permissions don't exceed the maximum
|
||||
func ClampActionsTokenPermissions(p1, p2 ActionsTokenPermissions) (ret ActionsTokenPermissions) {
|
||||
ret.UnitAccessModes = make(map[unit.Type]perm.AccessMode)
|
||||
for _, ut := range ActionsTokenUnitTypes {
|
||||
ret.UnitAccessModes[ut] = min(p1.UnitAccessModes[ut], p2.UnitAccessModes[ut])
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// MakeRestrictedPermissions returns the restricted permissions
|
||||
func MakeRestrictedPermissions() ActionsTokenPermissions {
|
||||
ret := MakeActionsTokenPermissions(perm.AccessModeNone)
|
||||
ret.UnitAccessModes[unit.TypeCode] = perm.AccessModeRead
|
||||
ret.UnitAccessModes[unit.TypePackages] = perm.AccessModeRead
|
||||
ret.UnitAccessModes[unit.TypeReleases] = perm.AccessModeRead
|
||||
return ret
|
||||
}
|
||||
|
||||
type ActionsConfig struct {
|
||||
DisabledWorkflows []string
|
||||
// CollaborativeOwnerIDs is a list of owner IDs used to share actions from private repos.
|
||||
// Only workflows from the private repos whose owners are in CollaborativeOwnerIDs can access the current repo's actions.
|
||||
CollaborativeOwnerIDs []int64
|
||||
// TokenPermissionMode defines the default permission mode (permissive, restricted, or custom)
|
||||
TokenPermissionMode ActionsTokenPermissionMode `json:"token_permission_mode,omitempty"`
|
||||
// MaxTokenPermissions defines the absolute maximum permissions any token can have in this context.
|
||||
// Workflow YAML "permissions" keywords can reduce permissions but never exceed this ceiling.
|
||||
MaxTokenPermissions *ActionsTokenPermissions `json:"max_token_permissions,omitempty"`
|
||||
// OverrideOwnerConfig indicates if this repository should override the owner-level configuration (User or Org)
|
||||
OverrideOwnerConfig bool `json:"override_owner_config,omitempty"`
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) EnableWorkflow(file string) {
|
||||
cfg.DisabledWorkflows = util.SliceRemoveAll(cfg.DisabledWorkflows, file)
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) IsWorkflowDisabled(file string) bool {
|
||||
return slices.Contains(cfg.DisabledWorkflows, file)
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) DisableWorkflow(file string) {
|
||||
if slices.Contains(cfg.DisabledWorkflows, file) {
|
||||
return
|
||||
}
|
||||
|
||||
cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file)
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) AddCollaborativeOwner(ownerID int64) {
|
||||
if !slices.Contains(cfg.CollaborativeOwnerIDs, ownerID) {
|
||||
cfg.CollaborativeOwnerIDs = append(cfg.CollaborativeOwnerIDs, ownerID)
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) RemoveCollaborativeOwner(ownerID int64) {
|
||||
cfg.CollaborativeOwnerIDs = util.SliceRemoveAll(cfg.CollaborativeOwnerIDs, ownerID)
|
||||
}
|
||||
|
||||
func (cfg *ActionsConfig) IsCollaborativeOwner(ownerID int64) bool {
|
||||
return slices.Contains(cfg.CollaborativeOwnerIDs, ownerID)
|
||||
}
|
||||
|
||||
// GetDefaultTokenPermissions returns the default token permissions by its TokenPermissionMode.
|
||||
// It does not apply MaxTokenPermissions; callers must clamp if needed.
|
||||
func (cfg *ActionsConfig) GetDefaultTokenPermissions() ActionsTokenPermissions {
|
||||
switch cfg.TokenPermissionMode {
|
||||
case ActionsTokenPermissionModeRestricted:
|
||||
return MakeRestrictedPermissions()
|
||||
case ActionsTokenPermissionModePermissive:
|
||||
return MakeActionsTokenPermissions(perm.AccessModeWrite)
|
||||
default:
|
||||
return ActionsTokenPermissions{}
|
||||
}
|
||||
}
|
||||
|
||||
// GetMaxTokenPermissions returns the maximum allowed permissions
|
||||
func (cfg *ActionsConfig) GetMaxTokenPermissions() ActionsTokenPermissions {
|
||||
if cfg.MaxTokenPermissions != nil {
|
||||
return *cfg.MaxTokenPermissions
|
||||
}
|
||||
// Default max is write for everything
|
||||
return MakeActionsTokenPermissions(perm.AccessModeWrite)
|
||||
}
|
||||
|
||||
// ClampPermissions ensures that the given permissions don't exceed the maximum
|
||||
func (cfg *ActionsConfig) ClampPermissions(perms ActionsTokenPermissions) ActionsTokenPermissions {
|
||||
maxPerms := cfg.GetMaxTokenPermissions()
|
||||
return ClampActionsTokenPermissions(perms, maxPerms)
|
||||
}
|
||||
|
||||
// FromDB fills up a ActionsConfig from serialized format.
|
||||
func (cfg *ActionsConfig) FromDB(bs []byte) error {
|
||||
_ = json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||
cfg.TokenPermissionMode, _ = util.EnumValue(cfg.TokenPermissionMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports a ActionsConfig to a serialized format.
|
||||
func (cfg *ActionsConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
@@ -4,8 +4,12 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -26,5 +30,75 @@ func TestActionsConfig(t *testing.T) {
|
||||
cfg.DisableWorkflow("test1.yaml")
|
||||
cfg.DisableWorkflow("test2.yaml")
|
||||
cfg.DisableWorkflow("test3.yaml")
|
||||
assert.Equal(t, "test1.yaml,test2.yaml,test3.yaml", cfg.ToString())
|
||||
assert.Equal(t, "test1.yaml,test2.yaml,test3.yaml", strings.Join(cfg.DisabledWorkflows, ","))
|
||||
}
|
||||
|
||||
func TestActionsConfigTokenPermissions(t *testing.T) {
|
||||
t.Run("Default Permission Mode", func(t *testing.T) {
|
||||
cfg := &ActionsConfig{TokenPermissionMode: "invalid-value"}
|
||||
_ = cfg.FromDB(nil)
|
||||
assert.Equal(t, ActionsTokenPermissionModePermissive, cfg.TokenPermissionMode)
|
||||
assert.Equal(t, perm.AccessModeWrite, cfg.GetDefaultTokenPermissions().UnitAccessModes[unit.TypeCode])
|
||||
})
|
||||
|
||||
t.Run("Explicit Permission Mode", func(t *testing.T) {
|
||||
cfg := &ActionsConfig{
|
||||
TokenPermissionMode: ActionsTokenPermissionModeRestricted,
|
||||
}
|
||||
assert.Equal(t, ActionsTokenPermissionModeRestricted, cfg.TokenPermissionMode)
|
||||
})
|
||||
|
||||
t.Run("Effective Permissions - Permissive Mode", func(t *testing.T) {
|
||||
cfg := &ActionsConfig{
|
||||
TokenPermissionMode: ActionsTokenPermissionModePermissive,
|
||||
}
|
||||
defaultPerms := cfg.GetDefaultTokenPermissions()
|
||||
perms := cfg.ClampPermissions(defaultPerms)
|
||||
assert.Equal(t, perm.AccessModeWrite, perms.UnitAccessModes[unit.TypeCode])
|
||||
assert.Equal(t, perm.AccessModeWrite, perms.UnitAccessModes[unit.TypeIssues])
|
||||
assert.Equal(t, perm.AccessModeWrite, perms.UnitAccessModes[unit.TypePackages])
|
||||
})
|
||||
|
||||
t.Run("Effective Permissions - Restricted Mode", func(t *testing.T) {
|
||||
cfg := &ActionsConfig{
|
||||
TokenPermissionMode: ActionsTokenPermissionModeRestricted,
|
||||
}
|
||||
defaultPerms := cfg.GetDefaultTokenPermissions()
|
||||
perms := cfg.ClampPermissions(defaultPerms)
|
||||
assert.Equal(t, perm.AccessModeRead, perms.UnitAccessModes[unit.TypeCode])
|
||||
assert.Equal(t, perm.AccessModeNone, perms.UnitAccessModes[unit.TypeIssues])
|
||||
assert.Equal(t, perm.AccessModeRead, perms.UnitAccessModes[unit.TypePackages])
|
||||
})
|
||||
|
||||
t.Run("Clamp Permissions", func(t *testing.T) {
|
||||
cfg := &ActionsConfig{
|
||||
MaxTokenPermissions: &ActionsTokenPermissions{
|
||||
UnitAccessModes: map[unit.Type]perm.AccessMode{
|
||||
unit.TypeCode: perm.AccessModeRead,
|
||||
unit.TypeIssues: perm.AccessModeWrite,
|
||||
unit.TypePullRequests: perm.AccessModeRead,
|
||||
unit.TypePackages: perm.AccessModeRead,
|
||||
unit.TypeActions: perm.AccessModeNone,
|
||||
unit.TypeWiki: perm.AccessModeWrite,
|
||||
},
|
||||
},
|
||||
}
|
||||
input := ActionsTokenPermissions{
|
||||
UnitAccessModes: map[unit.Type]perm.AccessMode{
|
||||
unit.TypeCode: perm.AccessModeWrite, // Should be clamped to Read
|
||||
unit.TypeIssues: perm.AccessModeWrite, // Should stay Write
|
||||
unit.TypePullRequests: perm.AccessModeWrite, // Should be clamped to Read
|
||||
unit.TypePackages: perm.AccessModeWrite, // Should be clamped to Read
|
||||
unit.TypeActions: perm.AccessModeRead, // Should be clamped to None
|
||||
unit.TypeWiki: perm.AccessModeRead, // Should stay Read
|
||||
},
|
||||
}
|
||||
clamped := cfg.ClampPermissions(input)
|
||||
assert.Equal(t, perm.AccessModeRead, clamped.UnitAccessModes[unit.TypeCode])
|
||||
assert.Equal(t, perm.AccessModeWrite, clamped.UnitAccessModes[unit.TypeIssues])
|
||||
assert.Equal(t, perm.AccessModeRead, clamped.UnitAccessModes[unit.TypePullRequests])
|
||||
assert.Equal(t, perm.AccessModeRead, clamped.UnitAccessModes[unit.TypePackages])
|
||||
assert.Equal(t, perm.AccessModeNone, clamped.UnitAccessModes[unit.TypeActions])
|
||||
assert.Equal(t, perm.AccessModeRead, clamped.UnitAccessModes[unit.TypeWiki])
|
||||
})
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ func DeleteTopic(ctx context.Context, repoID int64, topicName string) (*Topic, e
|
||||
}
|
||||
if topic == nil {
|
||||
// Repo doesn't have topic, can't be removed
|
||||
return nil, nil
|
||||
return nil, nil //nolint:nilnil // return nil to indicate that the topic does not exist
|
||||
}
|
||||
|
||||
return db.WithTx2(ctx, func(ctx context.Context) (*Topic, error) {
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
@@ -106,35 +104,6 @@ func (err ErrRepoFilesAlreadyExist) Unwrap() error {
|
||||
return util.ErrAlreadyExist
|
||||
}
|
||||
|
||||
// CheckCreateRepository check if doer could create a repository in new owner
|
||||
func CheckCreateRepository(ctx context.Context, doer, owner *user_model.User, name string, overwriteOrAdopt bool) error {
|
||||
if !doer.CanCreateRepoIn(owner) {
|
||||
return ErrReachLimitOfRepo{owner.MaxRepoCreation}
|
||||
}
|
||||
|
||||
if err := IsUsableRepoName(name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
has, err := IsRepositoryModelOrDirExist(ctx, owner, name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("IsRepositoryExist: %w", err)
|
||||
} else if has {
|
||||
return ErrRepoAlreadyExist{owner.Name, name}
|
||||
}
|
||||
|
||||
repoPath := RepoPath(owner.Name, name)
|
||||
isExist, err := util.IsExist(repoPath)
|
||||
if err != nil {
|
||||
log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
|
||||
return err
|
||||
}
|
||||
if !overwriteOrAdopt && isExist {
|
||||
return ErrRepoFilesAlreadyExist{owner.Name, name}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRepoSize updates the repository size, calculating it using getDirectorySize
|
||||
func UpdateRepoSize(ctx context.Context, repoID, gitSize, lfsSize int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(repoID).Cols("size", "git_size", "lfs_size").NoAutoTime().Update(&Repository{
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@@ -23,6 +24,12 @@ type StarredReposOptions struct {
|
||||
IncludePrivate bool
|
||||
}
|
||||
|
||||
func (opts *StarredReposOptions) ApplyPublicOnly(publicOnly bool) {
|
||||
if publicOnly {
|
||||
opts.IncludePrivate = false
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *StarredReposOptions) ToConds() builder.Cond {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"star.uid": opts.StarrerID,
|
||||
@@ -61,6 +68,12 @@ type WatchedReposOptions struct {
|
||||
IncludePrivate bool
|
||||
}
|
||||
|
||||
func (opts *WatchedReposOptions) ApplyPublicOnly(publicOnly bool) {
|
||||
if publicOnly {
|
||||
opts.IncludePrivate = false
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *WatchedReposOptions) ToConds() builder.Cond {
|
||||
var cond builder.Cond = builder.Eq{
|
||||
"watch.user_id": opts.WatcherID,
|
||||
@@ -94,8 +107,7 @@ func GetWatchedRepos(ctx context.Context, opts *WatchedReposOptions) ([]*Reposit
|
||||
return db.FindAndCount[Repository](ctx, opts)
|
||||
}
|
||||
|
||||
// GetRepoAssignees returns all users that have write access and can be assigned to issues
|
||||
// of the repository,
|
||||
// GetRepoAssignees returns all users that have write access and can be assigned to issues or pull-requests of the repository,
|
||||
func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.User, err error) {
|
||||
if err = repo.LoadOwner(ctx); err != nil {
|
||||
return nil, err
|
||||
@@ -114,15 +126,9 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
|
||||
uniqueUserIDs.AddMultiple(userIDs...)
|
||||
|
||||
if repo.Owner.IsOrganization() {
|
||||
additionalUserIDs := make([]int64, 0, 10)
|
||||
if err = e.Table("team_user").
|
||||
Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id").
|
||||
Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
|
||||
Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
|
||||
repo.ID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests).
|
||||
Distinct("`team_user`.uid").
|
||||
Select("`team_user`.uid").
|
||||
Find(&additionalUserIDs); err != nil {
|
||||
// issues and pull requests both need "assignee list"
|
||||
additionalUserIDs, err := organization.GetTeamUserIDsWithAccessToAnyRepoUnit(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead, unit.TypeIssues, unit.TypePullRequests)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uniqueUserIDs.AddMultiple(additionalUserIDs...)
|
||||
@@ -147,19 +153,21 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
|
||||
}
|
||||
|
||||
// GetIssuePostersWithSearch returns users with limit of 30 whose username started with prefix that have authored an issue/pull request for the given repository
|
||||
// If isShowFullName is set to true, also include full name prefix search
|
||||
func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) {
|
||||
// It searches with the "user.name" and "user.full_name" fields case-insensitively.
|
||||
func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string) ([]*user_model.User, error) {
|
||||
users := make([]*user_model.User, 0, 30)
|
||||
var prefixCond builder.Cond = builder.Like{"lower_name", strings.ToLower(search) + "%"}
|
||||
if isShowFullName {
|
||||
prefixCond = prefixCond.Or(db.BuildCaseInsensitiveLike("full_name", "%"+search+"%"))
|
||||
}
|
||||
|
||||
cond := builder.In("`user`.id",
|
||||
builder.Select("poster_id").From("issue").Where(
|
||||
builder.Eq{"repo_id": repo.ID}.
|
||||
And(builder.Eq{"is_pull": isPull}),
|
||||
).GroupBy("poster_id")).And(prefixCond)
|
||||
).GroupBy("poster_id"))
|
||||
|
||||
if search != "" {
|
||||
var prefixCond builder.Cond = builder.Like{"lower_name", strings.ToLower(search) + "%"}
|
||||
prefixCond = prefixCond.Or(db.BuildCaseInsensitiveLike("full_name", "%"+search+"%"))
|
||||
cond = cond.And(prefixCond)
|
||||
}
|
||||
|
||||
return users, db.GetEngine(ctx).
|
||||
Where(cond).
|
||||
|
||||
@@ -6,7 +6,12 @@ package repo_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
perm_model "code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
@@ -14,9 +19,14 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRepoAssignees(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
func TestUserRepo(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
t.Run("GetIssuePostersWithSearch", testUserRepoGetIssuePostersWithSearch)
|
||||
t.Run("Assignees", testUserRepoAssignees)
|
||||
t.Run("AssigneesNoTeamUnit", testRepoAssigneesNoTeamUnit)
|
||||
}
|
||||
|
||||
func testUserRepoAssignees(t *testing.T) {
|
||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
users, err := repo_model.GetRepoAssignees(t.Context(), repo2)
|
||||
assert.NoError(t, err)
|
||||
@@ -39,17 +49,37 @@ func TestRepoAssignees(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIssuePostersWithSearch(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
func testRepoAssigneesNoTeamUnit(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 32})
|
||||
require.NoError(t, repo.LoadOwner(ctx))
|
||||
require.True(t, repo.Owner.IsOrganization())
|
||||
|
||||
require.NoError(t, db.TruncateBeans(ctx, &organization.Team{}, &organization.TeamUser{}, &organization.TeamRepo{}, &organization.TeamUnit{}, &access_model.Access{}))
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
team := &organization.Team{OrgID: repo.OwnerID, LowerName: "admin-team", AccessMode: perm_model.AccessModeAdmin}
|
||||
require.NoError(t, db.Insert(ctx, team))
|
||||
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: repo.OwnerID, TeamID: team.ID, UID: user.ID}))
|
||||
require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: repo.OwnerID, TeamID: team.ID, RepoID: repo.ID}))
|
||||
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: repo.OwnerID, TeamID: team.ID, Type: unit.TypePullRequests, AccessMode: perm_model.AccessModeNone}))
|
||||
|
||||
users, err := repo_model.GetRepoAssignees(ctx, repo)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, users, 1)
|
||||
assert.ElementsMatch(t, []int64{4}, []int64{users[0].ID})
|
||||
}
|
||||
|
||||
func testUserRepoGetIssuePostersWithSearch(t *testing.T) {
|
||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
|
||||
users, err := repo_model.GetIssuePostersWithSearch(t.Context(), repo2, false, "USER", false /* full name */)
|
||||
users, err := repo_model.GetIssuePostersWithSearch(t.Context(), repo2, false, "USER")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, users, 1)
|
||||
assert.Equal(t, "user2", users[0].Name)
|
||||
|
||||
users, err = repo_model.GetIssuePostersWithSearch(t.Context(), repo2, false, "TW%O", true /* full name */)
|
||||
users, err = repo_model.GetIssuePostersWithSearch(t.Context(), repo2, false, "TW%O")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, users, 1)
|
||||
assert.Equal(t, "user2", users[0].Name)
|
||||
|
||||
@@ -7,7 +7,6 @@ package repo
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
@@ -76,12 +75,12 @@ func (repo *Repository) WikiCloneLink(ctx context.Context, doer *user_model.User
|
||||
return repo.cloneLink(ctx, doer, repo.Name+".wiki")
|
||||
}
|
||||
|
||||
// WikiPath returns wiki data path by given user and repository name.
|
||||
func WikiPath(userName, repoName string) string {
|
||||
return filepath.Join(user_model.UserPath(userName), strings.ToLower(repoName)+".wiki.git")
|
||||
func RelativeWikiPath(ownerName, repoName string) string {
|
||||
return strings.ToLower(ownerName) + "/" + strings.ToLower(repoName) + ".wiki.git"
|
||||
}
|
||||
|
||||
// WikiPath returns wiki data path for given repository.
|
||||
func (repo *Repository) WikiPath() string {
|
||||
return WikiPath(repo.OwnerName, repo.Name)
|
||||
// WikiStorageRepo returns the storage repo for the wiki
|
||||
// The wiki repository should have the same object format as the code repository
|
||||
func (repo *Repository) WikiStorageRepo() StorageRepo {
|
||||
return StorageRepo(RelativeWikiPath(repo.OwnerName, repo.Name))
|
||||
}
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
package repo_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -23,15 +21,10 @@ func TestRepository_WikiCloneLink(t *testing.T) {
|
||||
assert.Equal(t, "https://try.gitea.io/user2/repo1.wiki.git", cloneLink.HTTPS)
|
||||
}
|
||||
|
||||
func TestWikiPath(t *testing.T) {
|
||||
func TestRepository_RelativeWikiPath(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git")
|
||||
assert.Equal(t, expected, repo_model.WikiPath("user2", "repo1"))
|
||||
}
|
||||
|
||||
func TestRepository_WikiPath(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
expected := filepath.Join(setting.RepoRootPath, "user2/repo1.wiki.git")
|
||||
assert.Equal(t, expected, repo.WikiPath())
|
||||
assert.Equal(t, "user2/repo1.wiki.git", repo_model.RelativeWikiPath(repo.OwnerName, repo.Name))
|
||||
assert.Equal(t, "user2/repo1.wiki.git", repo.WikiStorageRepo().RelativePath())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user