summaryrefslogtreecommitdiffstats
path: root/models/unit
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
committerDaniel Baumann <daniel@debian.org>2024-12-12 23:57:56 +0100
commite68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch)
tree97775d6c13b0f416af55314eb6a89ef792474615 /models/unit
parentInitial commit. (diff)
downloadforgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.tar.xz
forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.zip
Adding upstream version 9.0.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'models/unit')
-rw-r--r--models/unit/unit.go437
-rw-r--r--models/unit/unit_test.go96
2 files changed, 533 insertions, 0 deletions
diff --git a/models/unit/unit.go b/models/unit/unit.go
new file mode 100644
index 0000000..5a8b911
--- /dev/null
+++ b/models/unit/unit.go
@@ -0,0 +1,437 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package unit
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "sync/atomic"
+
+ "code.gitea.io/gitea/models/perm"
+ "code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+)
+
+// Type is Unit's Type
+type Type int
+
+// Enumerate all the unit types
+const (
+ TypeInvalid Type = iota // 0 invalid
+ TypeCode // 1 code
+ TypeIssues // 2 issues
+ TypePullRequests // 3 PRs
+ TypeReleases // 4 Releases
+ TypeWiki // 5 Wiki
+ TypeExternalWiki // 6 ExternalWiki
+ TypeExternalTracker // 7 ExternalTracker
+ TypeProjects // 8 Projects
+ TypePackages // 9 Packages
+ TypeActions // 10 Actions
+)
+
+// Value returns integer value for unit type
+func (u Type) Value() int {
+ return int(u)
+}
+
+func (u Type) String() string {
+ switch u {
+ case TypeCode:
+ return "TypeCode"
+ case TypeIssues:
+ return "TypeIssues"
+ case TypePullRequests:
+ return "TypePullRequests"
+ case TypeReleases:
+ return "TypeReleases"
+ case TypeWiki:
+ return "TypeWiki"
+ case TypeExternalWiki:
+ return "TypeExternalWiki"
+ case TypeExternalTracker:
+ return "TypeExternalTracker"
+ case TypeProjects:
+ return "TypeProjects"
+ case TypePackages:
+ return "TypePackages"
+ case TypeActions:
+ return "TypeActions"
+ }
+ return fmt.Sprintf("Unknown Type %d", u)
+}
+
+func (u Type) LogString() string {
+ return fmt.Sprintf("<UnitType:%d:%s>", u, u.String())
+}
+
+var (
+ // AllRepoUnitTypes contains all the unit types
+ AllRepoUnitTypes = []Type{
+ TypeCode,
+ TypeIssues,
+ TypePullRequests,
+ TypeReleases,
+ TypeWiki,
+ TypeExternalWiki,
+ TypeExternalTracker,
+ TypeProjects,
+ TypePackages,
+ TypeActions,
+ }
+
+ // DefaultRepoUnits contains the default unit types
+ DefaultRepoUnits = []Type{
+ TypeCode,
+ TypeIssues,
+ TypePullRequests,
+ TypeReleases,
+ TypeWiki,
+ TypeProjects,
+ TypePackages,
+ TypeActions,
+ }
+
+ // ForkRepoUnits contains the default unit types for forks
+ DefaultForkRepoUnits = []Type{
+ TypeCode,
+ TypePullRequests,
+ }
+
+ // NotAllowedDefaultRepoUnits contains units that can't be default
+ NotAllowedDefaultRepoUnits = []Type{
+ TypeExternalWiki,
+ TypeExternalTracker,
+ }
+
+ disabledRepoUnitsAtomic atomic.Pointer[[]Type] // the units that have been globally disabled
+
+ // AllowedRepoUnitGroups contains the units that have been globally enabled,
+ // with mutually exclusive units grouped together.
+ AllowedRepoUnitGroups = [][]Type{}
+)
+
+// DisabledRepoUnitsGet returns the globally disabled units, it is a quick patch to fix data-race during testing.
+// Because the queue worker might read when a test is mocking the value. FIXME: refactor to a clear solution later.
+func DisabledRepoUnitsGet() []Type {
+ v := disabledRepoUnitsAtomic.Load()
+ if v == nil {
+ return nil
+ }
+ return *v
+}
+
+func DisabledRepoUnitsSet(v []Type) {
+ disabledRepoUnitsAtomic.Store(&v)
+}
+
+// Get valid set of default repository units from settings
+func validateDefaultRepoUnits(defaultUnits, settingDefaultUnits []Type) []Type {
+ units := defaultUnits
+
+ // Use setting if not empty
+ if len(settingDefaultUnits) > 0 {
+ units = make([]Type, 0, len(settingDefaultUnits))
+ for _, settingUnit := range settingDefaultUnits {
+ if !settingUnit.CanBeDefault() {
+ log.Warn("Not allowed as default unit: %s", settingUnit.String())
+ continue
+ }
+ units = append(units, settingUnit)
+ }
+ }
+
+ // Remove disabled units
+ for _, disabledUnit := range DisabledRepoUnitsGet() {
+ for i, unit := range units {
+ if unit == disabledUnit {
+ units = append(units[:i], units[i+1:]...)
+ }
+ }
+ }
+
+ return units
+}
+
+// LoadUnitConfig load units from settings
+func LoadUnitConfig() error {
+ disabledRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DisabledRepoUnits...)
+ if len(invalidKeys) > 0 {
+ log.Warn("Invalid keys in disabled repo units: %s", strings.Join(invalidKeys, ", "))
+ }
+ DisabledRepoUnitsSet(disabledRepoUnits)
+
+ setDefaultRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultRepoUnits...)
+ if len(invalidKeys) > 0 {
+ log.Warn("Invalid keys in default repo units: %s", strings.Join(invalidKeys, ", "))
+ }
+ DefaultRepoUnits = validateDefaultRepoUnits(DefaultRepoUnits, setDefaultRepoUnits)
+ if len(DefaultRepoUnits) == 0 {
+ return errors.New("no default repository units found")
+ }
+ setDefaultForkRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultForkRepoUnits...)
+ if len(invalidKeys) > 0 {
+ log.Warn("Invalid keys in default fork repo units: %s", strings.Join(invalidKeys, ", "))
+ }
+ DefaultForkRepoUnits = validateDefaultRepoUnits(DefaultForkRepoUnits, setDefaultForkRepoUnits)
+ if len(DefaultForkRepoUnits) == 0 {
+ return errors.New("no default fork repository units found")
+ }
+
+ // Collect the allowed repo unit groups. Mutually exclusive units are
+ // grouped together.
+ AllowedRepoUnitGroups = [][]Type{}
+ for _, unit := range []Type{
+ TypeCode,
+ TypePullRequests,
+ TypeProjects,
+ TypePackages,
+ TypeActions,
+ } {
+ // If unit is globally disabled, ignore it.
+ if unit.UnitGlobalDisabled() {
+ continue
+ }
+
+ // If it is allowed, add it to the group list.
+ AllowedRepoUnitGroups = append(AllowedRepoUnitGroups, []Type{unit})
+ }
+
+ addMutuallyExclusiveGroup := func(unit1, unit2 Type) {
+ var list []Type
+
+ if !unit1.UnitGlobalDisabled() {
+ list = append(list, unit1)
+ }
+
+ if !unit2.UnitGlobalDisabled() {
+ list = append(list, unit2)
+ }
+
+ if len(list) > 0 {
+ AllowedRepoUnitGroups = append(AllowedRepoUnitGroups, list)
+ }
+ }
+
+ addMutuallyExclusiveGroup(TypeIssues, TypeExternalTracker)
+ addMutuallyExclusiveGroup(TypeWiki, TypeExternalWiki)
+
+ return nil
+}
+
+// UnitGlobalDisabled checks if unit type is global disabled
+func (u Type) UnitGlobalDisabled() bool {
+ for _, ud := range DisabledRepoUnitsGet() {
+ if u == ud {
+ return true
+ }
+ }
+ return false
+}
+
+// CanBeDefault checks if the unit type can be a default repo unit
+func (u *Type) CanBeDefault() bool {
+ for _, nadU := range NotAllowedDefaultRepoUnits {
+ if *u == nadU {
+ return false
+ }
+ }
+ return true
+}
+
+// Unit is a section of one repository
+type Unit struct {
+ Type Type
+ Name string
+ NameKey string
+ URI string
+ DescKey string
+ Idx int
+ MaxAccessMode perm.AccessMode // The max access mode of the unit. i.e. Read means this unit can only be read.
+}
+
+// IsLessThan compares order of two units
+func (u Unit) IsLessThan(unit Unit) bool {
+ if (u.Type == TypeExternalTracker || u.Type == TypeExternalWiki) && unit.Type != TypeExternalTracker && unit.Type != TypeExternalWiki {
+ return false
+ }
+ return u.Idx < unit.Idx
+}
+
+// MaxPerm returns the max perms of this unit
+func (u Unit) MaxPerm() perm.AccessMode {
+ if u.Type == TypeExternalTracker || u.Type == TypeExternalWiki {
+ return perm.AccessModeRead
+ }
+ return perm.AccessModeAdmin
+}
+
+// Enumerate all the units
+var (
+ UnitCode = Unit{
+ TypeCode,
+ "code",
+ "repo.code",
+ "/",
+ "repo.code.desc",
+ 0,
+ perm.AccessModeOwner,
+ }
+
+ UnitIssues = Unit{
+ TypeIssues,
+ "issues",
+ "repo.issues",
+ "/issues",
+ "repo.issues.desc",
+ 1,
+ perm.AccessModeOwner,
+ }
+
+ UnitExternalTracker = Unit{
+ TypeExternalTracker,
+ "ext_issues",
+ "repo.ext_issues",
+ "/issues",
+ "repo.ext_issues.desc",
+ 1,
+ perm.AccessModeRead,
+ }
+
+ UnitPullRequests = Unit{
+ TypePullRequests,
+ "pulls",
+ "repo.pulls",
+ "/pulls",
+ "repo.pulls.desc",
+ 2,
+ perm.AccessModeOwner,
+ }
+
+ UnitReleases = Unit{
+ TypeReleases,
+ "releases",
+ "repo.releases",
+ "/releases",
+ "repo.releases.desc",
+ 3,
+ perm.AccessModeOwner,
+ }
+
+ UnitWiki = Unit{
+ TypeWiki,
+ "wiki",
+ "repo.wiki",
+ "/wiki",
+ "repo.wiki.desc",
+ 4,
+ perm.AccessModeOwner,
+ }
+
+ UnitExternalWiki = Unit{
+ TypeExternalWiki,
+ "ext_wiki",
+ "repo.ext_wiki",
+ "/wiki",
+ "repo.ext_wiki.desc",
+ 4,
+ perm.AccessModeRead,
+ }
+
+ UnitProjects = Unit{
+ TypeProjects,
+ "projects",
+ "repo.projects",
+ "/projects",
+ "repo.projects.desc",
+ 5,
+ perm.AccessModeOwner,
+ }
+
+ UnitPackages = Unit{
+ TypePackages,
+ "packages",
+ "repo.packages",
+ "/packages",
+ "packages.desc",
+ 6,
+ perm.AccessModeRead,
+ }
+
+ UnitActions = Unit{
+ TypeActions,
+ "actions",
+ "repo.actions",
+ "/actions",
+ "actions.unit.desc",
+ 7,
+ perm.AccessModeOwner,
+ }
+
+ // Units contains all the units
+ Units = map[Type]Unit{
+ TypeCode: UnitCode,
+ TypeIssues: UnitIssues,
+ TypeExternalTracker: UnitExternalTracker,
+ TypePullRequests: UnitPullRequests,
+ TypeReleases: UnitReleases,
+ TypeWiki: UnitWiki,
+ TypeExternalWiki: UnitExternalWiki,
+ TypeProjects: UnitProjects,
+ TypePackages: UnitPackages,
+ TypeActions: UnitActions,
+ }
+)
+
+// FindUnitTypes give the unit key names and return valid unique units and invalid keys
+func FindUnitTypes(nameKeys ...string) (res []Type, invalidKeys []string) {
+ m := make(container.Set[Type])
+ for _, key := range nameKeys {
+ t := TypeFromKey(key)
+ if t == TypeInvalid {
+ invalidKeys = append(invalidKeys, key)
+ } else if m.Add(t) {
+ res = append(res, t)
+ }
+ }
+ return res, invalidKeys
+}
+
+// TypeFromKey give the unit key name and return unit
+func TypeFromKey(nameKey string) Type {
+ for t, u := range Units {
+ if strings.EqualFold(nameKey, u.NameKey) {
+ return t
+ }
+ }
+ return TypeInvalid
+}
+
+// AllUnitKeyNames returns all unit key names
+func AllUnitKeyNames() []string {
+ res := make([]string, 0, len(Units))
+ for _, u := range Units {
+ res = append(res, u.NameKey)
+ }
+ return res
+}
+
+// MinUnitAccessMode returns the minial permission of the permission map
+func MinUnitAccessMode(unitsMap map[Type]perm.AccessMode) perm.AccessMode {
+ res := perm.AccessModeNone
+ for t, mode := range unitsMap {
+ // Don't allow `TypeExternal{Tracker,Wiki}` to influence this as they can only be set to READ perms.
+ if t == TypeExternalTracker || t == TypeExternalWiki {
+ continue
+ }
+
+ // get the minial permission great than AccessModeNone except all are AccessModeNone
+ if mode > perm.AccessModeNone && (res == perm.AccessModeNone || mode < res) {
+ res = mode
+ }
+ }
+ return res
+}
diff --git a/models/unit/unit_test.go b/models/unit/unit_test.go
new file mode 100644
index 0000000..a739677
--- /dev/null
+++ b/models/unit/unit_test.go
@@ -0,0 +1,96 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package unit
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestLoadUnitConfig(t *testing.T) {
+ t.Run("regular", func(t *testing.T) {
+ defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
+ DisabledRepoUnitsSet(disabledRepoUnits)
+ DefaultRepoUnits = defaultRepoUnits
+ DefaultForkRepoUnits = defaultForkRepoUnits
+ }(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
+ defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
+ setting.Repository.DisabledRepoUnits = disabledRepoUnits
+ setting.Repository.DefaultRepoUnits = defaultRepoUnits
+ setting.Repository.DefaultForkRepoUnits = defaultForkRepoUnits
+ }(setting.Repository.DisabledRepoUnits, setting.Repository.DefaultRepoUnits, setting.Repository.DefaultForkRepoUnits)
+
+ setting.Repository.DisabledRepoUnits = []string{"repo.issues"}
+ setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls"}
+ setting.Repository.DefaultForkRepoUnits = []string{"repo.releases"}
+ require.NoError(t, LoadUnitConfig())
+ assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
+ assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
+ assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
+ })
+ t.Run("invalid", func(t *testing.T) {
+ defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
+ DisabledRepoUnitsSet(disabledRepoUnits)
+ DefaultRepoUnits = defaultRepoUnits
+ DefaultForkRepoUnits = defaultForkRepoUnits
+ }(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
+ defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
+ setting.Repository.DisabledRepoUnits = disabledRepoUnits
+ setting.Repository.DefaultRepoUnits = defaultRepoUnits
+ setting.Repository.DefaultForkRepoUnits = defaultForkRepoUnits
+ }(setting.Repository.DisabledRepoUnits, setting.Repository.DefaultRepoUnits, setting.Repository.DefaultForkRepoUnits)
+
+ setting.Repository.DisabledRepoUnits = []string{"repo.issues", "invalid.1"}
+ setting.Repository.DefaultRepoUnits = []string{"repo.code", "invalid.2", "repo.releases", "repo.issues", "repo.pulls"}
+ setting.Repository.DefaultForkRepoUnits = []string{"invalid.3", "repo.releases"}
+ require.NoError(t, LoadUnitConfig())
+ assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
+ assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
+ assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
+ })
+ t.Run("duplicate", func(t *testing.T) {
+ defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
+ DisabledRepoUnitsSet(disabledRepoUnits)
+ DefaultRepoUnits = defaultRepoUnits
+ DefaultForkRepoUnits = defaultForkRepoUnits
+ }(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
+ defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
+ setting.Repository.DisabledRepoUnits = disabledRepoUnits
+ setting.Repository.DefaultRepoUnits = defaultRepoUnits
+ setting.Repository.DefaultForkRepoUnits = defaultForkRepoUnits
+ }(setting.Repository.DisabledRepoUnits, setting.Repository.DefaultRepoUnits, setting.Repository.DefaultForkRepoUnits)
+
+ setting.Repository.DisabledRepoUnits = []string{"repo.issues", "repo.issues"}
+ setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls", "repo.code"}
+ setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"}
+ require.NoError(t, LoadUnitConfig())
+ assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
+ assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits)
+ assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
+ })
+ t.Run("empty_default", func(t *testing.T) {
+ defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []Type) {
+ DisabledRepoUnitsSet(disabledRepoUnits)
+ DefaultRepoUnits = defaultRepoUnits
+ DefaultForkRepoUnits = defaultForkRepoUnits
+ }(DisabledRepoUnitsGet(), DefaultRepoUnits, DefaultForkRepoUnits)
+ defer func(disabledRepoUnits, defaultRepoUnits, defaultForkRepoUnits []string) {
+ setting.Repository.DisabledRepoUnits = disabledRepoUnits
+ setting.Repository.DefaultRepoUnits = defaultRepoUnits
+ setting.Repository.DefaultForkRepoUnits = defaultForkRepoUnits
+ }(setting.Repository.DisabledRepoUnits, setting.Repository.DefaultRepoUnits, setting.Repository.DefaultForkRepoUnits)
+
+ setting.Repository.DisabledRepoUnits = []string{"repo.issues", "repo.issues"}
+ setting.Repository.DefaultRepoUnits = []string{}
+ setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"}
+ require.NoError(t, LoadUnitConfig())
+ assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet())
+ assert.ElementsMatch(t, []Type{TypeCode, TypePullRequests, TypeReleases, TypeWiki, TypePackages, TypeProjects, TypeActions}, DefaultRepoUnits)
+ assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits)
+ })
+}