id: 75
repo_id: 1
type: 8
+ config: "{\"ProjectsMode\":\"all\"}"
created_unix: 946684810
-
type: 2
created_unix: 946684810
--
- id: 98
- repo_id: 1
- type: 8
- created_unix: 946684810
-
-
id: 99
repo_id: 1
Type: tp,
Config: new(ActionsConfig),
}
+ } else if tp == unit.TypeProjects {
+ return &RepoUnit{
+ Type: tp,
+ Config: new(ProjectsConfig),
+ }
}
return &RepoUnit{
return json.Marshal(cfg)
}
+// ProjectsMode represents the projects enabled for a repository
+type ProjectsMode string
+
+const (
+ // ProjectsModeRepo allows only repo-level projects
+ ProjectsModeRepo ProjectsMode = "repo"
+ // ProjectsModeOwner allows only owner-level projects
+ ProjectsModeOwner ProjectsMode = "owner"
+ // ProjectsModeAll allows both kinds of projects
+ ProjectsModeAll ProjectsMode = "all"
+ // ProjectsModeNone doesn't allow projects
+ ProjectsModeNone ProjectsMode = "none"
+)
+
+// ProjectsConfig describes projects config
+type ProjectsConfig struct {
+ ProjectsMode ProjectsMode
+}
+
+// FromDB fills up a ProjectsConfig from serialized format.
+func (cfg *ProjectsConfig) FromDB(bs []byte) error {
+ return json.UnmarshalHandleDoubleEncode(bs, &cfg)
+}
+
+// ToDB exports a ProjectsConfig to a serialized format.
+func (cfg *ProjectsConfig) ToDB() ([]byte, error) {
+ return json.Marshal(cfg)
+}
+
+func (cfg *ProjectsConfig) GetProjectsMode() ProjectsMode {
+ if cfg.ProjectsMode != "" {
+ return cfg.ProjectsMode
+ }
+
+ return ProjectsModeNone
+}
+
+func (cfg *ProjectsConfig) IsProjectsAllowed(m ProjectsMode) bool {
+ projectsMode := cfg.GetProjectsMode()
+
+ if m == ProjectsModeNone {
+ return true
+ }
+
+ return projectsMode == m || projectsMode == ProjectsModeAll
+}
+
// BeforeSet is invoked from XORM before setting the value of a field of this object.
func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
switch colName {
r.Config = new(IssuesConfig)
case unit.TypeActions:
r.Config = new(ActionsConfig)
- case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects, unit.TypePackages:
+ case unit.TypeProjects:
+ r.Config = new(ProjectsConfig)
+ case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypePackages:
fallthrough
default:
r.Config = new(UnitConfig)
return r.Config.(*ActionsConfig)
}
+// ProjectsConfig returns config for unit.ProjectsConfig
+func (r *RepoUnit) ProjectsConfig() *ProjectsConfig {
+ return r.Config.(*ProjectsConfig)
+}
+
func getUnitsByRepoID(ctx context.Context, repoID int64) (units []*RepoUnit, err error) {
var tmpUnits []*RepoUnit
if err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Find(&tmpUnits); err != nil {
AllowRebaseUpdate: true,
},
})
+ } else if tp == unit.TypeProjects {
+ units = append(units, repo_model.RepoUnit{
+ RepoID: repo.ID,
+ Type: tp,
+ Config: &repo_model.ProjectsConfig{ProjectsMode: repo_model.ProjectsModeAll},
+ })
} else {
units = append(units, repo_model.RepoUnit{
RepoID: repo.ID,
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
HasPullRequests bool `json:"has_pull_requests"`
HasProjects bool `json:"has_projects"`
+ ProjectsMode string `json:"projects_mode"`
HasReleases bool `json:"has_releases"`
HasPackages bool `json:"has_packages"`
HasActions bool `json:"has_actions"`
HasPullRequests *bool `json:"has_pull_requests,omitempty"`
// either `true` to enable project unit, or `false` to disable them.
HasProjects *bool `json:"has_projects,omitempty"`
+ // `repo` to only allow repo-level projects, `owner` to only allow owner projects, `all` to allow both.
+ ProjectsMode *string `json:"projects_mode,omitempty" binding:"In(repo,owner,all)"`
// either `true` to enable releases unit, or `false` to disable them.
HasReleases *bool `json:"has_releases,omitempty"`
// either `true` to enable packages unit, or `false` to disable them.
settings.pulls.default_allow_edits_from_maintainers = Allow edits from maintainers by default
settings.releases_desc = Enable Repository Releases
settings.packages_desc = Enable Repository Packages Registry
-settings.projects_desc = Enable Repository Projects
+settings.projects_desc = Enable Projects
+settings.projects_mode_desc = Projects Mode (which kinds of projects to show)
+settings.projects_mode_repo = Repo projects only
+settings.projects_mode_owner = Only user or org projects
+settings.projects_mode_all = All projects
settings.actions_desc = Enable Repository Actions
settings.admin_settings = Administrator Settings
settings.admin_enable_health_check = Enable Repository Health Checks (git fsck)
}
}
- if opts.HasProjects != nil && !unit_model.TypeProjects.UnitGlobalDisabled() {
- if *opts.HasProjects {
+ currHasProjects := repo.UnitEnabled(ctx, unit_model.TypeProjects)
+ newHasProjects := currHasProjects
+ if opts.HasProjects != nil {
+ newHasProjects = *opts.HasProjects
+ }
+ if currHasProjects || newHasProjects {
+ if newHasProjects && !unit_model.TypeProjects.UnitGlobalDisabled() {
+ unit, err := repo.GetUnit(ctx, unit_model.TypeProjects)
+ var config *repo_model.ProjectsConfig
+ if err != nil {
+ config = &repo_model.ProjectsConfig{
+ ProjectsMode: repo_model.ProjectsModeAll,
+ }
+ } else {
+ config = unit.ProjectsConfig()
+ }
+
+ if opts.ProjectsMode != nil {
+ config.ProjectsMode = repo_model.ProjectsMode(*opts.ProjectsMode)
+ }
+
units = append(units, repo_model.RepoUnit{
RepoID: repo.ID,
Type: unit_model.TypeProjects,
+ Config: config,
})
- } else {
+ } else if !newHasProjects && !unit_model.TypeProjects.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects)
}
}
if repo.Owner.IsOrganization() {
repoOwnerType = project_model.TypeOrganization
}
- var err error
- projects, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{
- ListOptions: db.ListOptionsAll,
- RepoID: repo.ID,
- IsClosed: optional.Some(false),
- Type: project_model.TypeRepository,
- })
- if err != nil {
- ctx.ServerError("GetProjects", err)
- return
- }
- projects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{
- ListOptions: db.ListOptionsAll,
- OwnerID: repo.OwnerID,
- IsClosed: optional.Some(false),
- Type: repoOwnerType,
- })
- if err != nil {
- ctx.ServerError("GetProjects", err)
- return
- }
- ctx.Data["OpenProjects"] = append(projects, projects2...)
+ projectsUnit := repo.MustGetUnit(ctx, unit.TypeProjects)
- projects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{
- ListOptions: db.ListOptionsAll,
- RepoID: repo.ID,
- IsClosed: optional.Some(true),
- Type: project_model.TypeRepository,
- })
- if err != nil {
- ctx.ServerError("GetProjects", err)
- return
+ var openProjects []*project_model.Project
+ var closedProjects []*project_model.Project
+ var err error
+
+ if projectsUnit.ProjectsConfig().IsProjectsAllowed(repo_model.ProjectsModeRepo) {
+ openProjects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{
+ ListOptions: db.ListOptionsAll,
+ RepoID: repo.ID,
+ IsClosed: optional.Some(false),
+ Type: project_model.TypeRepository,
+ })
+ if err != nil {
+ ctx.ServerError("GetProjects", err)
+ return
+ }
+ closedProjects, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{
+ ListOptions: db.ListOptionsAll,
+ RepoID: repo.ID,
+ IsClosed: optional.Some(true),
+ Type: project_model.TypeRepository,
+ })
+ if err != nil {
+ ctx.ServerError("GetProjects", err)
+ return
+ }
}
- projects2, err = db.Find[project_model.Project](ctx, project_model.SearchOptions{
- ListOptions: db.ListOptionsAll,
- OwnerID: repo.OwnerID,
- IsClosed: optional.Some(true),
- Type: repoOwnerType,
- })
- if err != nil {
- ctx.ServerError("GetProjects", err)
- return
+
+ if projectsUnit.ProjectsConfig().IsProjectsAllowed(repo_model.ProjectsModeOwner) {
+ openProjects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{
+ ListOptions: db.ListOptionsAll,
+ OwnerID: repo.OwnerID,
+ IsClosed: optional.Some(false),
+ Type: repoOwnerType,
+ })
+ if err != nil {
+ ctx.ServerError("GetProjects", err)
+ return
+ }
+ openProjects = append(openProjects, openProjects2...)
+ closedProjects2, err := db.Find[project_model.Project](ctx, project_model.SearchOptions{
+ ListOptions: db.ListOptionsAll,
+ OwnerID: repo.OwnerID,
+ IsClosed: optional.Some(true),
+ Type: repoOwnerType,
+ })
+ if err != nil {
+ ctx.ServerError("GetProjects", err)
+ return
+ }
+ closedProjects = append(closedProjects, closedProjects2...)
}
- ctx.Data["ClosedProjects"] = append(projects, projects2...)
+ ctx.Data["OpenProjects"] = openProjects
+ ctx.Data["ClosedProjects"] = closedProjects
}
// repoReviewerSelection items to bee shown
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/perm"
project_model "code.gitea.io/gitea/models/project"
- attachment_model "code.gitea.io/gitea/models/repo"
+ repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/json"
tplProjectsView base.TplName = "repo/projects/view"
)
-// MustEnableProjects check if projects are enabled in settings
-func MustEnableProjects(ctx *context.Context) {
+// MustEnableRepoProjects check if repo projects are enabled in settings
+func MustEnableRepoProjects(ctx *context.Context) {
if unit.TypeProjects.UnitGlobalDisabled() {
ctx.NotFound("EnableKanbanBoard", nil)
return
}
if ctx.Repo.Repository != nil {
- if !ctx.Repo.CanRead(unit.TypeProjects) {
- ctx.NotFound("MustEnableProjects", nil)
+ projectsUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeProjects)
+ if !ctx.Repo.CanRead(unit.TypeProjects) || !projectsUnit.ProjectsConfig().IsProjectsAllowed(repo_model.ProjectsModeRepo) {
+ ctx.NotFound("MustEnableRepoProjects", nil)
return
}
}
}
if project.CardType != project_model.CardTypeTextOnly {
- issuesAttachmentMap := make(map[int64][]*attachment_model.Attachment)
+ issuesAttachmentMap := make(map[int64][]*repo_model.Attachment)
for _, issuesList := range issuesMap {
for _, issue := range issuesList {
- if issueAttachment, err := attachment_model.GetAttachmentsByIssueIDImagesLatest(ctx, issue.ID); err == nil {
+ if issueAttachment, err := repo_model.GetAttachmentsByIssueIDImagesLatest(ctx, issue.ID); err == nil {
issuesAttachmentMap[issue.ID] = issueAttachment
}
}
units = append(units, repo_model.RepoUnit{
RepoID: repo.ID,
Type: unit_model.TypeProjects,
+ Config: &repo_model.ProjectsConfig{
+ ProjectsMode: repo_model.ProjectsMode(form.ProjectsMode),
+ },
})
} else if !unit_model.TypeProjects.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects)
})
})
}, reqRepoProjectsWriter, context.RepoMustNotBeArchived())
- }, reqRepoProjectsReader, repo.MustEnableProjects)
+ }, reqRepoProjectsReader, repo.MustEnableRepoProjects)
m.Group("/actions", func() {
m.Get("", actions.List)
defaultAllowMaintainerEdit = config.DefaultAllowMaintainerEdit
}
hasProjects := false
- if _, err := repo.GetUnit(ctx, unit_model.TypeProjects); err == nil {
+ projectsMode := repo_model.ProjectsModeAll
+ if unit, err := repo.GetUnit(ctx, unit_model.TypeProjects); err == nil {
hasProjects = true
+ config := unit.ProjectsConfig()
+ projectsMode = config.ProjectsMode
}
hasReleases := false
InternalTracker: internalTracker,
HasWiki: hasWiki,
HasProjects: hasProjects,
+ ProjectsMode: string(projectsMode),
HasReleases: hasReleases,
HasPackages: hasPackages,
HasActions: hasActions,
ExternalTrackerRegexpPattern string
EnableCloseIssuesViaCommitInAnyBranch bool
EnableProjects bool
+ ProjectsMode string
EnableReleases bool
EnablePackages bool
EnablePulls bool
</a>
{{end}}
- {{if and (not .UnitProjectsGlobalDisabled) (.Permission.CanRead $.UnitTypeProjects)}}
+ {{$projectsUnit := .Repository.MustGetUnit $.Context $.UnitTypeProjects}}
+ {{if and (not .UnitProjectsGlobalDisabled) (.Permission.CanRead $.UnitTypeProjects) ($projectsUnit.ProjectsConfig.IsProjectsAllowed "repo")}}
<a href="{{.RepoLink}}/projects" class="{{if .IsProjectsPage}}active {{end}}item">
{{svg "octicon-project"}} {{ctx.Locale.Tr "repo.project_board"}}
{{if .Repository.NumOpenProjects}}
{{$isProjectsEnabled := .Repository.UnitEnabled $.Context $.UnitTypeProjects}}
{{$isProjectsGlobalDisabled := .UnitTypeProjects.UnitGlobalDisabled}}
+ {{$projectsUnit := .Repository.MustGetUnit $.Context $.UnitTypeProjects}}
<div class="inline field">
<label>{{ctx.Locale.Tr "repo.project_board"}}</label>
<div class="ui checkbox{{if $isProjectsGlobalDisabled}} disabled{{end}}"{{if $isProjectsGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
- <input class="enable-system" name="enable_projects" type="checkbox" {{if $isProjectsEnabled}}checked{{end}}>
+ <input class="enable-system" name="enable_projects" type="checkbox" data-target="#projects_box" {{if $isProjectsEnabled}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.projects_desc"}}</label>
</div>
</div>
+ <div class="field {{if not $isProjectsEnabled}} disabled{{end}} gt-pl-4" id="projects_box">
+ <p>
+ {{ctx.Locale.Tr "repo.settings.projects_mode_desc"}}
+ </p>
+ <div class="ui dropdown selection">
+ <select name="projects_mode">
+ <option value="repo" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.ProjectsMode "repo")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_repo"}}</option>
+ <option value="owner" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.ProjectsMode "owner")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_owner"}}</option>
+ <option value="all" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.ProjectsMode "all")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_all"}}</option>
+ </select>
+ {{svg "octicon-triangle-down" 14 "dropdown icon"}}
+ <div class="default text">
+ {{if (eq $projectsUnit.ProjectsConfig.ProjectsMode "repo")}}
+ {{ctx.Locale.Tr "repo.settings.projects_mode_repo"}}
+ {{end}}
+ {{if (eq $projectsUnit.ProjectsConfig.ProjectsMode "owner")}}
+ {{ctx.Locale.Tr "repo.settings.projects_mode_owner"}}
+ {{end}}
+ {{if (eq $projectsUnit.ProjectsConfig.ProjectsMode "all")}}
+ {{ctx.Locale.Tr "repo.settings.projects_mode_all"}}
+ {{end}}
+ </div>
+ <div class="menu">
+ <div class="item" data-value="repo">{{ctx.Locale.Tr "repo.settings.projects_mode_repo"}}</div>
+ <div class="item" data-value="owner">{{ctx.Locale.Tr "repo.settings.projects_mode_owner"}}</div>
+ <div class="item" data-value="all">{{ctx.Locale.Tr "repo.settings.projects_mode_all"}}</div>
+ </div>
+ </div>
+ </div>
+
+ <div class="divider"></div>
{{$isReleasesEnabled := .Repository.UnitEnabled $.Context $.UnitTypeReleases}}
{{$isReleasesGlobalDisabled := .UnitTypeReleases.UnitGlobalDisabled}}
"type": "boolean",
"x-go-name": "Private"
},
+ "projects_mode": {
+ "description": "`repo` to only allow repo-level projects, `owner` to only allow owner projects, `all` to allow both.",
+ "type": "string",
+ "x-go-name": "ProjectsMode"
+ },
"template": {
"description": "either `true` to make this repository a template or `false` to make it a normal repository",
"type": "boolean",
"type": "boolean",
"x-go-name": "Private"
},
+ "projects_mode": {
+ "type": "string",
+ "x-go-name": "ProjectsMode"
+ },
"release_counter": {
"type": "integer",
"format": "int64",