Add setting to set default and global disabled repository units. (#8788)

* Add possibility to global disable repo units.

* Add Default Repo Unit app.ini setting.

* Hide units

* Hide disabled repo units

* Minor fixes

* Indicate disabled units in team settings.

Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: zeripath <art27@cantab.net>
mj
David Svantesson 4 years ago committed by Lauris BH
parent 36943e56d6
commit 3c07d03c03

@ -42,6 +42,13 @@ DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
; Allow users to push local repositories to Gitea and have them automatically created for a user or an org ; Allow users to push local repositories to Gitea and have them automatically created for a user or an org
ENABLE_PUSH_CREATE_USER = false ENABLE_PUSH_CREATE_USER = false
ENABLE_PUSH_CREATE_ORG = false ENABLE_PUSH_CREATE_ORG = false
; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki
DISABLED_REPO_UNITS =
; Comma separated list of default repo units. Allowed values: repo.code, repo.releases, repo.issues, repo.pulls, repo.wiki.
; Note: Code and Releases can currently not be deactivated. If you specify default repo units you should still list them for future compatibility.
; External wiki and issue tracker can't be enabled by default as it requires additional settings.
; Disabled repo units will not be added to new repositories regardless if it is in the default list.
DEFAULT_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki
[repository.editor] [repository.editor]
; List of file extensions for which lines should be wrapped in the CodeMirror editor ; List of file extensions for which lines should be wrapped in the CodeMirror editor

@ -128,6 +128,7 @@ func loadRepoConfig() {
// NewRepoContext creates a new repository context // NewRepoContext creates a new repository context
func NewRepoContext() { func NewRepoContext() {
loadRepoConfig() loadRepoConfig()
loadUnitConfig()
RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp")) RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp"))
} }
@ -393,6 +394,7 @@ func (repo *Repository) getUnits(e Engine) (err error) {
} }
repo.Units, err = getUnitsByRepoID(e, repo.ID) repo.Units, err = getUnitsByRepoID(e, repo.ID)
log.Trace("repo.Units: %-+v", repo.Units)
return err return err
} }
@ -1442,14 +1444,19 @@ func UpdateRepositoryUpdatedTime(repoID int64, updateTime time.Time) error {
} }
// UpdateRepositoryUnits updates a repository's units // UpdateRepositoryUnits updates a repository's units
func UpdateRepositoryUnits(repo *Repository, units []RepoUnit) (err error) { func UpdateRepositoryUnits(repo *Repository, units []RepoUnit, deleteUnitTypes []UnitType) (err error) {
sess := x.NewSession() sess := x.NewSession()
defer sess.Close() defer sess.Close()
if err = sess.Begin(); err != nil { if err = sess.Begin(); err != nil {
return err return err
} }
if _, err = sess.Where("repo_id = ?", repo.ID).Delete(new(RepoUnit)); err != nil { // Delete existing settings of units before adding again
for _, u := range units {
deleteUnitTypes = append(deleteUnitTypes, u.Type)
}
if _, err = sess.Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(RepoUnit)); err != nil {
return err return err
} }

@ -170,5 +170,16 @@ func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig {
} }
func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) { func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) {
return units, e.Where("repo_id = ?", repoID).Find(&units) var tmpUnits []*RepoUnit
if err := e.Where("repo_id = ?", repoID).Find(&tmpUnits); err != nil {
return nil, err
}
for _, u := range tmpUnits {
if !u.Type.UnitGlobalDisabled() {
units = append(units, u)
}
}
return units, nil
} }

@ -9,6 +9,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
) )
// UnitType is Unit's Type // UnitType is Unit's Type
@ -78,13 +79,89 @@ var (
UnitTypeWiki, UnitTypeWiki,
} }
// NotAllowedDefaultRepoUnits contains units that can't be default
NotAllowedDefaultRepoUnits = []UnitType{
UnitTypeExternalWiki,
UnitTypeExternalTracker,
}
// MustRepoUnits contains the units could not be disabled currently // MustRepoUnits contains the units could not be disabled currently
MustRepoUnits = []UnitType{ MustRepoUnits = []UnitType{
UnitTypeCode, UnitTypeCode,
UnitTypeReleases, UnitTypeReleases,
} }
// DisabledRepoUnits contains the units that have been globally disabled
DisabledRepoUnits = []UnitType{}
) )
func loadUnitConfig() {
setDefaultRepoUnits := FindUnitTypes(setting.Repository.DefaultRepoUnits...)
// Default repo units set if setting is not empty
if len(setDefaultRepoUnits) > 0 {
// MustRepoUnits required as default
DefaultRepoUnits = make([]UnitType, len(MustRepoUnits))
copy(DefaultRepoUnits, MustRepoUnits)
for _, defaultU := range setDefaultRepoUnits {
if !defaultU.CanBeDefault() {
log.Warn("Not allowed as default unit: %s", defaultU.String())
continue
}
// MustRepoUnits already added
if defaultU.CanDisable() {
DefaultRepoUnits = append(DefaultRepoUnits, defaultU)
}
}
}
DisabledRepoUnits = FindUnitTypes(setting.Repository.DisabledRepoUnits...)
// Check that must units are not disabled
for i, disabledU := range DisabledRepoUnits {
if !disabledU.CanDisable() {
log.Warn("Not allowed to global disable unit %s", disabledU.String())
DisabledRepoUnits = append(DisabledRepoUnits[:i], DisabledRepoUnits[i+1:]...)
}
}
// Remove disabled units from default units
for _, disabledU := range DisabledRepoUnits {
for i, defaultU := range DefaultRepoUnits {
if defaultU == disabledU {
DefaultRepoUnits = append(DefaultRepoUnits[:i], DefaultRepoUnits[i+1:]...)
}
}
}
}
// UnitGlobalDisabled checks if unit type is global disabled
func (u UnitType) UnitGlobalDisabled() bool {
for _, ud := range DisabledRepoUnits {
if u == ud {
return true
}
}
return false
}
// CanDisable checks if this unit type can be disabled.
func (u *UnitType) CanDisable() bool {
for _, mu := range MustRepoUnits {
if *u == mu {
return false
}
}
return true
}
// CanBeDefault checks if the unit type can be a default repo unit
func (u *UnitType) CanBeDefault() bool {
for _, nadU := range NotAllowedDefaultRepoUnits {
if *u == nadU {
return false
}
}
return true
}
// Unit is a section of one repository // Unit is a section of one repository
type Unit struct { type Unit struct {
Type UnitType Type UnitType
@ -96,7 +173,7 @@ type Unit struct {
// CanDisable returns if this unit could be disabled. // CanDisable returns if this unit could be disabled.
func (u *Unit) CanDisable() bool { func (u *Unit) CanDisable() bool {
return true return u.Type.CanDisable()
} }
// IsLessThan compares order of two units // IsLessThan compares order of two units

@ -622,6 +622,7 @@ func (u *User) GetRepositories(page, pageSize int) (err error) {
} }
// GetRepositoryIDs returns repositories IDs where user owned and has unittypes // GetRepositoryIDs returns repositories IDs where user owned and has unittypes
// Caller shall check that units is not globally disabled
func (u *User) GetRepositoryIDs(units ...UnitType) ([]int64, error) { func (u *User) GetRepositoryIDs(units ...UnitType) ([]int64, error) {
var ids []int64 var ids []int64
@ -636,6 +637,7 @@ func (u *User) GetRepositoryIDs(units ...UnitType) ([]int64, error) {
} }
// GetOrgRepositoryIDs returns repositories IDs where user's team owned and has unittypes // GetOrgRepositoryIDs returns repositories IDs where user's team owned and has unittypes
// Caller shall check that units is not globally disabled
func (u *User) GetOrgRepositoryIDs(units ...UnitType) ([]int64, error) { func (u *User) GetOrgRepositoryIDs(units ...UnitType) ([]int64, error) {
var ids []int64 var ids []int64
@ -656,6 +658,7 @@ func (u *User) GetOrgRepositoryIDs(units ...UnitType) ([]int64, error) {
} }
// GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations // GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations
// Caller shall check that units is not globally disabled
func (u *User) GetAccessRepoIDs(units ...UnitType) ([]int64, error) { func (u *User) GetAccessRepoIDs(units ...UnitType) ([]int64, error) {
ids, err := u.GetRepositoryIDs(units...) ids, err := u.GetRepositoryIDs(units...)
if err != nil { if err != nil {

@ -37,6 +37,8 @@ var (
DefaultCloseIssuesViaCommitsInAnyBranch bool DefaultCloseIssuesViaCommitsInAnyBranch bool
EnablePushCreateUser bool EnablePushCreateUser bool
EnablePushCreateOrg bool EnablePushCreateOrg bool
DisabledRepoUnits []string
DefaultRepoUnits []string
// Repository editor settings // Repository editor settings
Editor struct { Editor struct {
@ -98,6 +100,8 @@ var (
DefaultCloseIssuesViaCommitsInAnyBranch: false, DefaultCloseIssuesViaCommitsInAnyBranch: false,
EnablePushCreateUser: false, EnablePushCreateUser: false,
EnablePushCreateOrg: false, EnablePushCreateOrg: false,
DisabledRepoUnits: []string{},
DefaultRepoUnits: []string{},
// Repository editor settings // Repository editor settings
Editor: struct { Editor: struct {

@ -636,6 +636,7 @@ stargazers = Stargazers
forks = Forks forks = Forks
pick_reaction = Pick your reaction pick_reaction = Pick your reaction
reactions_more = and %d more reactions_more = and %d more
unit_disabled = The site administrator has disabled this repository section.
template.items = Template Items template.items = Template Items
template.git_content = Git Content (Default Branch) template.git_content = Git Content (Default Branch)
@ -1613,6 +1614,7 @@ team_desc_helper = Describe the purpose or role of the team.
team_access_desc = Repository access team_access_desc = Repository access
team_permission_desc = Permission team_permission_desc = Permission
team_unit_desc = Allow Access to Repository Sections team_unit_desc = Allow Access to Repository Sections
team_unit_disabled = (Disabled)
form.name_reserved = The organization name '%s' is reserved. form.name_reserved = The organization name '%s' is reserved.
form.name_pattern_not_allowed = The pattern '%s' is not allowed in an organization name. form.name_pattern_not_allowed = The pattern '%s' is not allowed in an organization name.

@ -757,25 +757,10 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
var units []models.RepoUnit var units []models.RepoUnit
var deleteUnitTypes []models.UnitType
for _, tp := range models.MustRepoUnits { if opts.HasIssues != nil {
units = append(units, models.RepoUnit{ if *opts.HasIssues && opts.ExternalTracker != nil && !models.UnitTypeExternalTracker.UnitGlobalDisabled() {
RepoID: repo.ID,
Type: tp,
Config: new(models.UnitConfig),
})
}
if opts.HasIssues == nil {
// If HasIssues setting not touched, rewrite existing repo unit
if unit, err := repo.GetUnit(models.UnitTypeIssues); err == nil {
units = append(units, *unit)
} else if unit, err := repo.GetUnit(models.UnitTypeExternalTracker); err == nil {
units = append(units, *unit)
}
} else if *opts.HasIssues {
if opts.ExternalTracker != nil {
// Check that values are valid // Check that values are valid
if !validation.IsValidExternalURL(opts.ExternalTracker.ExternalTrackerURL) { if !validation.IsValidExternalURL(opts.ExternalTracker.ExternalTrackerURL) {
err := fmt.Errorf("External tracker URL not valid") err := fmt.Errorf("External tracker URL not valid")
@ -797,7 +782,8 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
ExternalTrackerStyle: opts.ExternalTracker.ExternalTrackerStyle, ExternalTrackerStyle: opts.ExternalTracker.ExternalTrackerStyle,
}, },
}) })
} else { deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeIssues)
} else if *opts.HasIssues && opts.ExternalTracker == nil && !models.UnitTypeIssues.UnitGlobalDisabled() {
// Default to built-in tracker // Default to built-in tracker
var config *models.IssuesConfig var config *models.IssuesConfig
@ -823,19 +809,19 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
Type: models.UnitTypeIssues, Type: models.UnitTypeIssues,
Config: config, Config: config,
}) })
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalTracker)
} else if !*opts.HasIssues {
if !models.UnitTypeExternalTracker.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalTracker)
}
if !models.UnitTypeIssues.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeIssues)
}
} }
} }
if opts.HasWiki == nil { if opts.HasWiki != nil {
// If HasWiki setting not touched, rewrite existing repo unit if *opts.HasWiki && opts.ExternalWiki != nil && !models.UnitTypeExternalWiki.UnitGlobalDisabled() {
if unit, err := repo.GetUnit(models.UnitTypeWiki); err == nil {
units = append(units, *unit)
} else if unit, err := repo.GetUnit(models.UnitTypeExternalWiki); err == nil {
units = append(units, *unit)
}
} else if *opts.HasWiki {
if opts.ExternalWiki != nil {
// Check that values are valid // Check that values are valid
if !validation.IsValidExternalURL(opts.ExternalWiki.ExternalWikiURL) { if !validation.IsValidExternalURL(opts.ExternalWiki.ExternalWikiURL) {
err := fmt.Errorf("External wiki URL not valid") err := fmt.Errorf("External wiki URL not valid")
@ -850,64 +836,72 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
ExternalWikiURL: opts.ExternalWiki.ExternalWikiURL, ExternalWikiURL: opts.ExternalWiki.ExternalWikiURL,
}, },
}) })
} else { deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeWiki)
} else if *opts.HasWiki && opts.ExternalWiki == nil && !models.UnitTypeWiki.UnitGlobalDisabled() {
config := &models.UnitConfig{} config := &models.UnitConfig{}
units = append(units, models.RepoUnit{ units = append(units, models.RepoUnit{
RepoID: repo.ID, RepoID: repo.ID,
Type: models.UnitTypeWiki, Type: models.UnitTypeWiki,
Config: config, Config: config,
}) })
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalWiki)
} else if !*opts.HasWiki {
if !models.UnitTypeExternalWiki.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalWiki)
}
if !models.UnitTypeWiki.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeWiki)
}
} }
} }
if opts.HasPullRequests == nil { if opts.HasPullRequests != nil {
// If HasPullRequest setting not touched, rewrite existing repo unit if *opts.HasPullRequests && !models.UnitTypePullRequests.UnitGlobalDisabled() {
if unit, err := repo.GetUnit(models.UnitTypePullRequests); err == nil { // We do allow setting individual PR settings through the API, so
units = append(units, *unit) // we get the config settings and then set them
} // if those settings were provided in the opts.
} else if *opts.HasPullRequests { unit, err := repo.GetUnit(models.UnitTypePullRequests)
// We do allow setting individual PR settings through the API, so var config *models.PullRequestsConfig
// we get the config settings and then set them if err != nil {
// if those settings were provided in the opts. // Unit type doesn't exist so we make a new config file with default values
unit, err := repo.GetUnit(models.UnitTypePullRequests) config = &models.PullRequestsConfig{
var config *models.PullRequestsConfig IgnoreWhitespaceConflicts: false,
if err != nil { AllowMerge: true,
// Unit type doesn't exist so we make a new config file with default values AllowRebase: true,
config = &models.PullRequestsConfig{ AllowRebaseMerge: true,
IgnoreWhitespaceConflicts: false, AllowSquash: true,
AllowMerge: true, }
AllowRebase: true, } else {
AllowRebaseMerge: true, config = unit.PullRequestsConfig()
AllowSquash: true,
} }
} else {
config = unit.PullRequestsConfig()
}
if opts.IgnoreWhitespaceConflicts != nil { if opts.IgnoreWhitespaceConflicts != nil {
config.IgnoreWhitespaceConflicts = *opts.IgnoreWhitespaceConflicts config.IgnoreWhitespaceConflicts = *opts.IgnoreWhitespaceConflicts
} }
if opts.AllowMerge != nil { if opts.AllowMerge != nil {
config.AllowMerge = *opts.AllowMerge config.AllowMerge = *opts.AllowMerge
} }
if opts.AllowRebase != nil { if opts.AllowRebase != nil {
config.AllowRebase = *opts.AllowRebase config.AllowRebase = *opts.AllowRebase
} }
if opts.AllowRebaseMerge != nil { if opts.AllowRebaseMerge != nil {
config.AllowRebaseMerge = *opts.AllowRebaseMerge config.AllowRebaseMerge = *opts.AllowRebaseMerge
} }
if opts.AllowSquash != nil { if opts.AllowSquash != nil {
config.AllowSquash = *opts.AllowSquash config.AllowSquash = *opts.AllowSquash
} }
units = append(units, models.RepoUnit{ units = append(units, models.RepoUnit{
RepoID: repo.ID, RepoID: repo.ID,
Type: models.UnitTypePullRequests, Type: models.UnitTypePullRequests,
Config: config, Config: config,
}) })
} else if !*opts.HasPullRequests && !models.UnitTypePullRequests.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypePullRequests)
}
} }
if err := models.UpdateRepositoryUnits(repo, units); err != nil { if err := models.UpdateRepositoryUnits(repo, units, deleteUnitTypes); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err) ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
return err return err
} }

@ -205,78 +205,85 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
case "advanced": case "advanced":
var units []models.RepoUnit var units []models.RepoUnit
var deleteUnitTypes []models.UnitType
// This section doesn't require repo_name/RepoName to be set in the form, don't show it // This section doesn't require repo_name/RepoName to be set in the form, don't show it
// as an error on the UI for this action // as an error on the UI for this action
ctx.Data["Err_RepoName"] = nil ctx.Data["Err_RepoName"] = nil
for _, tp := range models.MustRepoUnits { if form.EnableWiki && form.EnableExternalWiki && !models.UnitTypeExternalWiki.UnitGlobalDisabled() {
if !validation.IsValidExternalURL(form.ExternalWikiURL) {
ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error"))
ctx.Redirect(repo.Link() + "/settings")
return
}
units = append(units, models.RepoUnit{
RepoID: repo.ID,
Type: models.UnitTypeExternalWiki,
Config: &models.ExternalWikiConfig{
ExternalWikiURL: form.ExternalWikiURL,
},
})
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeWiki)
} else if form.EnableWiki && !form.EnableExternalWiki && !models.UnitTypeWiki.UnitGlobalDisabled() {
units = append(units, models.RepoUnit{ units = append(units, models.RepoUnit{
RepoID: repo.ID, RepoID: repo.ID,
Type: tp, Type: models.UnitTypeWiki,
Config: new(models.UnitConfig), Config: new(models.UnitConfig),
}) })
} deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalWiki)
} else {
if form.EnableWiki { if !models.UnitTypeExternalWiki.UnitGlobalDisabled() {
if form.EnableExternalWiki { deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalWiki)
if !validation.IsValidExternalURL(form.ExternalWikiURL) { }
ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error")) if !models.UnitTypeWiki.UnitGlobalDisabled() {
ctx.Redirect(repo.Link() + "/settings") deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeWiki)
return
}
units = append(units, models.RepoUnit{
RepoID: repo.ID,
Type: models.UnitTypeExternalWiki,
Config: &models.ExternalWikiConfig{
ExternalWikiURL: form.ExternalWikiURL,
},
})
} else {
units = append(units, models.RepoUnit{
RepoID: repo.ID,
Type: models.UnitTypeWiki,
Config: new(models.UnitConfig),
})
} }
} }
if form.EnableIssues { if form.EnableIssues && form.EnableExternalTracker && !models.UnitTypeExternalTracker.UnitGlobalDisabled() {
if form.EnableExternalTracker { if !validation.IsValidExternalURL(form.ExternalTrackerURL) {
if !validation.IsValidExternalURL(form.ExternalTrackerURL) { ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error"))
ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error")) ctx.Redirect(repo.Link() + "/settings")
ctx.Redirect(repo.Link() + "/settings") return
return }
} if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(form.TrackerURLFormat) {
if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(form.TrackerURLFormat) { ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error"))
ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error")) ctx.Redirect(repo.Link() + "/settings")
ctx.Redirect(repo.Link() + "/settings") return
return }
} units = append(units, models.RepoUnit{
units = append(units, models.RepoUnit{ RepoID: repo.ID,
RepoID: repo.ID, Type: models.UnitTypeExternalTracker,
Type: models.UnitTypeExternalTracker, Config: &models.ExternalTrackerConfig{
Config: &models.ExternalTrackerConfig{ ExternalTrackerURL: form.ExternalTrackerURL,
ExternalTrackerURL: form.ExternalTrackerURL, ExternalTrackerFormat: form.TrackerURLFormat,
ExternalTrackerFormat: form.TrackerURLFormat, ExternalTrackerStyle: form.TrackerIssueStyle,
ExternalTrackerStyle: form.TrackerIssueStyle, },
}, })
}) deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeIssues)
} else { } else if form.EnableIssues && !form.EnableExternalTracker && !models.UnitTypeIssues.UnitGlobalDisabled() {
units = append(units, models.RepoUnit{ units = append(units, models.RepoUnit{
RepoID: repo.ID, RepoID: repo.ID,
Type: models.UnitTypeIssues, Type: models.UnitTypeIssues,
Config: &models.IssuesConfig{ Config: &models.IssuesConfig{
EnableTimetracker: form.EnableTimetracker, EnableTimetracker: form.EnableTimetracker,
AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
EnableDependencies: form.EnableIssueDependencies, EnableDependencies: form.EnableIssueDependencies,
}, },
}) })
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalTracker)
} else {
if !models.UnitTypeExternalTracker.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalTracker)
}
if !models.UnitTypeIssues.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeIssues)
} }
} }
if form.EnablePulls { if form.EnablePulls && !models.UnitTypePullRequests.UnitGlobalDisabled() {
units = append(units, models.RepoUnit{ units = append(units, models.RepoUnit{
RepoID: repo.ID, RepoID: repo.ID,
Type: models.UnitTypePullRequests, Type: models.UnitTypePullRequests,
@ -288,9 +295,11 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
AllowSquash: form.PullsAllowSquash, AllowSquash: form.PullsAllowSquash,
}, },
}) })
} else if !models.UnitTypePullRequests.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypePullRequests)
} }
if err := models.UpdateRepositoryUnits(repo, units); err != nil { if err := models.UpdateRepositoryUnits(repo, units, deleteUnitTypes); err != nil {
ctx.ServerError("UpdateRepositoryUnits", err) ctx.ServerError("UpdateRepositoryUnits", err)
return return
} }

@ -261,6 +261,11 @@ func RegisterRoutes(m *macaron.Macaron) {
} }
m.Use(user.GetNotificationCount) m.Use(user.GetNotificationCount)
m.Use(func(ctx *context.Context) {
ctx.Data["UnitWikiGlobalDisabled"] = models.UnitTypeWiki.UnitGlobalDisabled()
ctx.Data["UnitIssuesGlobalDisabled"] = models.UnitTypeIssues.UnitGlobalDisabled()
ctx.Data["UnitPullsGlobalDisabled"] = models.UnitTypePullRequests.UnitGlobalDisabled()
})
// FIXME: not all routes need go through same middlewares. // FIXME: not all routes need go through same middlewares.
// Especially some AJAX requests, we can reduce middleware number to improve performance. // Especially some AJAX requests, we can reduce middleware number to improve performance.

@ -158,6 +158,12 @@ func Dashboard(ctx *context.Context) {
// Milestones render the user milestones page // Milestones render the user milestones page
func Milestones(ctx *context.Context) { func Milestones(ctx *context.Context) {
if models.UnitTypeIssues.UnitGlobalDisabled() && models.UnitTypePullRequests.UnitGlobalDisabled() {
log.Debug("Milestones overview page not available as both issues and pull requests are globally disabled")
ctx.Status(404)
return
}
ctx.Data["Title"] = ctx.Tr("milestones") ctx.Data["Title"] = ctx.Tr("milestones")
ctx.Data["PageIsMilestonesDashboard"] = true ctx.Data["PageIsMilestonesDashboard"] = true
@ -335,10 +341,22 @@ func Issues(ctx *context.Context) {
isPullList := ctx.Params(":type") == "pulls" isPullList := ctx.Params(":type") == "pulls"
unitType := models.UnitTypeIssues unitType := models.UnitTypeIssues
if isPullList { if isPullList {
if models.UnitTypePullRequests.UnitGlobalDisabled() {
log.Debug("Pull request overview page not available as it is globally disabled.")
ctx.Status(404)
return
}
ctx.Data["Title"] = ctx.Tr("pull_requests") ctx.Data["Title"] = ctx.Tr("pull_requests")
ctx.Data["PageIsPulls"] = true ctx.Data["PageIsPulls"] = true
unitType = models.UnitTypePullRequests unitType = models.UnitTypePullRequests
} else { } else {
if models.UnitTypeIssues.UnitGlobalDisabled() {
log.Debug("Issues overview page not available as it is globally disabled.")
ctx.Status(404)
return
}
ctx.Data["Title"] = ctx.Tr("issues") ctx.Data["Title"] = ctx.Tr("issues")
ctx.Data["PageIsIssues"] = true ctx.Data["PageIsIssues"] = true
} }

@ -10,9 +10,15 @@
{{if .IsSigned}} {{if .IsSigned}}
<a class="item {{if .PageIsDashboard}}active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "dashboard"}}</a> <a class="item {{if .PageIsDashboard}}active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "dashboard"}}</a>
{{if not .UnitIssuesGlobalDisabled}}
<a class="item {{if .PageIsIssues}}active{{end}}" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a> <a class="item {{if .PageIsIssues}}active{{end}}" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
{{end}}
{{if not .UnitPullsGlobalDisabled}}
<a class="item {{if .PageIsPulls}}active{{end}}" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a> <a class="item {{if .PageIsPulls}}active{{end}}" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
{{end}}
{{if not (and .UnitIssuesGlobalDisabled .UnitPullsGlobalDisabled)}}
{{if .ShowMilestonesDashboardPage}}<a class="item {{if .PageIsMilestonesDashboard}}active{{end}}" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>{{end}} {{if .ShowMilestonesDashboardPage}}<a class="item {{if .PageIsMilestonesDashboard}}active{{end}}" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>{{end}}
{{end}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "explore"}}</a> <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "explore"}}</a>
{{else if .IsLandingPageHome}} {{else if .IsLandingPageHome}}
<a class="item {{if .PageIsHome}}active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "home"}}</a> <a class="item {{if .PageIsHome}}active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "home"}}</a>

@ -81,10 +81,14 @@
<label>{{.i18n.Tr "org.team_unit_desc"}}</label> <label>{{.i18n.Tr "org.team_unit_desc"}}</label>
<br> <br>
{{range $t, $unit := $.Units}} {{range $t, $unit := $.Units}}
{{if $unit.Type.UnitGlobalDisabled}}
<div class="field poping up" data-content="{{$.i18n.Tr "repo.unit_disabled"}}">
{{else}}
<div class="field"> <div class="field">
{{end}}
<div class="ui toggle checkbox"> <div class="ui toggle checkbox">
<input type="checkbox" class="hidden" name="units" value="{{$unit.Type.Value}}"{{if or (eq $.Team.ID 0) ($.Team.UnitEnabled $unit.Type)}} checked{{end}}> <input type="checkbox" class="hidden" name="units" value="{{$unit.Type.Value}}"{{if or (eq $.Team.ID 0) ($.Team.UnitEnabled $unit.Type)}} checked{{end}}>
<label>{{$.i18n.Tr $unit.NameKey}}</label> <label>{{$.i18n.Tr $unit.NameKey}}{{if $unit.Type.UnitGlobalDisabled}} {{$.i18n.Tr "org.team_unit_disabled"}}{{end}}</label>
<span class="help">{{$.i18n.Tr $unit.DescKey}}</span> <span class="help">{{$.i18n.Tr $unit.DescKey}}</span>
</div> </div>
</div> </div>

@ -144,20 +144,32 @@
{{$isWikiEnabled := or (.Repository.UnitEnabled $.UnitTypeWiki) (.Repository.UnitEnabled $.UnitTypeExternalWiki)}} {{$isWikiEnabled := or (.Repository.UnitEnabled $.UnitTypeWiki) (.Repository.UnitEnabled $.UnitTypeExternalWiki)}}
<div class="inline field"> <div class="inline field">
<label>{{.i18n.Tr "repo.wiki"}}</label> <label>{{.i18n.Tr "repo.wiki"}}</label>
{{if and (.UnitTypeWiki.UnitGlobalDisabled) (.UnitTypeExternalWiki.UnitGlobalDisabled)}}
<div class="ui checkbox poping up disabled" data-content="{{.i18n.Tr "repo.unit_disabled"}}">
{{else}}
<div class="ui checkbox"> <div class="ui checkbox">
{{end}}
<input class="enable-system" name="enable_wiki" type="checkbox" data-target="#wiki_box" {{if $isWikiEnabled}}checked{{end}}> <input class="enable-system" name="enable_wiki" type="checkbox" data-target="#wiki_box" {{if $isWikiEnabled}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.wiki_desc"}}</label> <label>{{.i18n.Tr "repo.settings.wiki_desc"}}</label>
</div> </div>
</div> </div>
<div class="field {{if not $isWikiEnabled}}disabled{{end}}" id="wiki_box"> <div class="field {{if not $isWikiEnabled}}disabled{{end}}" id="wiki_box">
<div class="field"> <div class="field">
{{if .UnitTypeWiki.UnitGlobalDisabled}}
<div class="ui radio checkbox poping up disabled" data-content="{{.i18n.Tr "repo.unit_disabled"}}">
{{else}}
<div class="ui radio checkbox"> <div class="ui radio checkbox">
{{end}}
<input class="hidden enable-system-radio" tabindex="0" name="enable_external_wiki" type="radio" value="false" data-target="#external_wiki_box" {{if not (.Repository.UnitEnabled $.UnitTypeExternalWiki)}}checked{{end}}/> <input class="hidden enable-system-radio" tabindex="0" name="enable_external_wiki" type="radio" value="false" data-target="#external_wiki_box" {{if not (.Repository.UnitEnabled $.UnitTypeExternalWiki)}}checked{{end}}/>
<label>{{.i18n.Tr "repo.settings.use_internal_wiki"}}</label> <label>{{.i18n.Tr "repo.settings.use_internal_wiki"}}</label>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
{{if .UnitTypeExternalWiki.UnitGlobalDisabled}}
<div class="ui radio checkbox poping up disabled" data-content="{{.i18n.Tr "repo.unit_disabled"}}">
{{else}}
<div class="ui radio checkbox"> <div class="ui radio checkbox">
{{end}}
<input class="hidden enable-system-radio" tabindex="0" name="enable_external_wiki" type="radio" value="true" data-target="#external_wiki_box" {{if .Repository.UnitEnabled $.UnitTypeExternalWiki}}checked{{end}}/> <input class="hidden enable-system-radio" tabindex="0" name="enable_external_wiki" type="radio" value="true" data-target="#external_wiki_box" {{if .Repository.UnitEnabled $.UnitTypeExternalWiki}}checked{{end}}/>
<label>{{.i18n.Tr "repo.settings.use_external_wiki"}}</label> <label>{{.i18n.Tr "repo.settings.use_external_wiki"}}</label>
</div> </div>
@ -174,14 +186,22 @@
{{$isIssuesEnabled := or (.Repository.UnitEnabled $.UnitTypeIssues) (.Repository.UnitEnabled $.UnitTypeExternalTracker)}} {{$isIssuesEnabled := or (.Repository.UnitEnabled $.UnitTypeIssues) (.Repository.UnitEnabled $.UnitTypeExternalTracker)}}
<div class="inline field"> <div class="inline field">
<label>{{.i18n.Tr "repo.issues"}}</label> <label>{{.i18n.Tr "repo.issues"}}</label>
{{if and (.UnitTypeIssues.UnitGlobalDisabled) (.UnitTypeExternalTracker.UnitGlobalDisabled)}}
<div class="ui checkbox poping up disabled" data-content="{{.i18n.Tr "repo.unit_disabled"}}">
{{else}}
<div class="ui checkbox"> <div class="ui checkbox">
{{end}}
<input class="enable-system" name="enable_issues" type="checkbox" data-target="#issue_box" {{if $isIssuesEnabled}}checked{{end}}> <input class="enable-system" name="enable_issues" type="checkbox" data-target="#issue_box" {{if $isIssuesEnabled}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.issues_desc"}}</label> <label>{{.i18n.Tr "repo.settings.issues_desc"}}</label>
</div> </div>
</div> </div>
<div class="field {{if not $isIssuesEnabled}}disabled{{end}}" id="issue_box"> <div class="field {{if not $isIssuesEnabled}}disabled{{end}}" id="issue_box">
<div class="field"> <div class="field">
{{if .UnitTypeIssues.UnitGlobalDisabled}}
<div class="ui radio checkbox poping up disabled" data-content="{{.i18n.Tr "repo.unit_disabled"}}">
{{else}}
<div class="ui radio checkbox"> <div class="ui radio checkbox">
{{end}}
<input class="hidden enable-system-radio" tabindex="0" name="enable_external_tracker" type="radio" value="false" data-context="#internal_issue_box" data-target="#external_issue_box" {{if not (.Repository.UnitEnabled $.UnitTypeExternalTracker)}}checked{{end}}/> <input class="hidden enable-system-radio" tabindex="0" name="enable_external_tracker" type="radio" value="false" data-context="#internal_issue_box" data-target="#external_issue_box" {{if not (.Repository.UnitEnabled $.UnitTypeExternalTracker)}}checked{{end}}/>
<label>{{.i18n.Tr "repo.settings.use_internal_issue_tracker"}}</label> <label>{{.i18n.Tr "repo.settings.use_internal_issue_tracker"}}</label>
</div> </div>
@ -209,7 +229,11 @@
</div> </div>
</div> </div>
<div class="field"> <div class="field">
{{if .UnitTypeExternalTracker.UnitGlobalDisabled}}
<div class="ui radio checkbox poping up disabled" data-content="{{.i18n.Tr "repo.unit_disabled"}}">
{{else}}
<div class="ui radio checkbox"> <div class="ui radio checkbox">
{{end}}
<input class="hidden enable-system-radio" tabindex="0" name="enable_external_tracker" type="radio" value="true" data-context="#internal_issue_box" data-target="#external_issue_box" {{if .Repository.UnitEnabled $.UnitTypeExternalTracker}}checked{{end}}/> <input class="hidden enable-system-radio" tabindex="0" name="enable_external_tracker" type="radio" value="true" data-context="#internal_issue_box" data-target="#external_issue_box" {{if .Repository.UnitEnabled $.UnitTypeExternalTracker}}checked{{end}}/>
<label>{{.i18n.Tr "repo.settings.use_external_issue_tracker"}}</label> <label>{{.i18n.Tr "repo.settings.use_external_issue_tracker"}}</label>
</div> </div>
@ -251,7 +275,11 @@
{{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}} {{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}}
<div class="inline field"> <div class="inline field">
<label>{{.i18n.Tr "repo.pulls"}}</label> <label>{{.i18n.Tr "repo.pulls"}}</label>
{{if .UnitTypePullRequests.UnitGlobalDisabled}}
<div class="ui checkbox poping up disabled" data-content="{{.i18n.Tr "repo.unit_disabled"}}">
{{else}}
<div class="ui checkbox"> <div class="ui checkbox">
{{end}}
<input class="enable-system" name="enable_pulls" type="checkbox" data-target="#pull_box" {{if $pullRequestEnabled}}checked{{end}}> <input class="enable-system" name="enable_pulls" type="checkbox" data-target="#pull_box" {{if $pullRequestEnabled}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.pulls_desc"}}</label> <label>{{.i18n.Tr "repo.settings.pulls_desc"}}</label>
</div> </div>

Loading…
Cancel
Save