* 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>tags/v1.12.0-dev
@@ -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 { | |||||
units = append(units, models.RepoUnit{ | |||||
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 { | |||||
if opts.HasIssues != nil { | |||||
if *opts.HasIssues && opts.ExternalTracker != nil && !models.UnitTypeExternalTracker.UnitGlobalDisabled() { | |||||
// 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 HasWiki setting not touched, rewrite existing repo unit | |||||
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 { | |||||
if opts.HasWiki != nil { | |||||
if *opts.HasWiki && opts.ExternalWiki != nil && !models.UnitTypeExternalWiki.UnitGlobalDisabled() { | |||||
// 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 HasPullRequest setting not touched, rewrite existing repo unit | |||||
if unit, err := repo.GetUnit(models.UnitTypePullRequests); err == nil { | |||||
units = append(units, *unit) | |||||
} | |||||
} else if *opts.HasPullRequests { | |||||
// We do allow setting individual PR settings through the API, so | |||||
// we get the config settings and then set them | |||||
// if those settings were provided in the opts. | |||||
unit, err := repo.GetUnit(models.UnitTypePullRequests) | |||||
var config *models.PullRequestsConfig | |||||
if err != nil { | |||||
// Unit type doesn't exist so we make a new config file with default values | |||||
config = &models.PullRequestsConfig{ | |||||
IgnoreWhitespaceConflicts: false, | |||||
AllowMerge: true, | |||||
AllowRebase: true, | |||||
AllowRebaseMerge: true, | |||||
AllowSquash: true, | |||||
if opts.HasPullRequests != nil { | |||||
if *opts.HasPullRequests && !models.UnitTypePullRequests.UnitGlobalDisabled() { | |||||
// We do allow setting individual PR settings through the API, so | |||||
// we get the config settings and then set them | |||||
// if those settings were provided in the opts. | |||||
unit, err := repo.GetUnit(models.UnitTypePullRequests) | |||||
var config *models.PullRequestsConfig | |||||
if err != nil { | |||||
// Unit type doesn't exist so we make a new config file with default values | |||||
config = &models.PullRequestsConfig{ | |||||
IgnoreWhitespaceConflicts: false, | |||||
AllowMerge: true, | |||||
AllowRebase: true, | |||||
AllowRebaseMerge: true, | |||||
AllowSquash: true, | |||||
} | |||||
} else { | |||||
config = unit.PullRequestsConfig() | |||||
} | } | ||||
} else { | |||||
config = unit.PullRequestsConfig() | |||||
} | |||||
if opts.IgnoreWhitespaceConflicts != nil { | |||||
config.IgnoreWhitespaceConflicts = *opts.IgnoreWhitespaceConflicts | |||||
} | |||||
if opts.AllowMerge != nil { | |||||
config.AllowMerge = *opts.AllowMerge | |||||
} | |||||
if opts.AllowRebase != nil { | |||||
config.AllowRebase = *opts.AllowRebase | |||||
} | |||||
if opts.AllowRebaseMerge != nil { | |||||
config.AllowRebaseMerge = *opts.AllowRebaseMerge | |||||
} | |||||
if opts.AllowSquash != nil { | |||||
config.AllowSquash = *opts.AllowSquash | |||||
} | |||||
if opts.IgnoreWhitespaceConflicts != nil { | |||||
config.IgnoreWhitespaceConflicts = *opts.IgnoreWhitespaceConflicts | |||||
} | |||||
if opts.AllowMerge != nil { | |||||
config.AllowMerge = *opts.AllowMerge | |||||
} | |||||
if opts.AllowRebase != nil { | |||||
config.AllowRebase = *opts.AllowRebase | |||||
} | |||||
if opts.AllowRebaseMerge != nil { | |||||
config.AllowRebaseMerge = *opts.AllowRebaseMerge | |||||
} | |||||
if opts.AllowSquash != nil { | |||||
config.AllowSquash = *opts.AllowSquash | |||||
} | |||||
units = append(units, models.RepoUnit{ | |||||
RepoID: repo.ID, | |||||
Type: models.UnitTypePullRequests, | |||||
Config: config, | |||||
}) | |||||
units = append(units, models.RepoUnit{ | |||||
RepoID: repo.ID, | |||||
Type: models.UnitTypePullRequests, | |||||
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), | ||||
}) | }) | ||||
} | |||||
if form.EnableWiki { | |||||
if form.EnableExternalWiki { | |||||
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, | |||||
}, | |||||
}) | |||||
} else { | |||||
units = append(units, models.RepoUnit{ | |||||
RepoID: repo.ID, | |||||
Type: models.UnitTypeWiki, | |||||
Config: new(models.UnitConfig), | |||||
}) | |||||
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalWiki) | |||||
} else { | |||||
if !models.UnitTypeExternalWiki.UnitGlobalDisabled() { | |||||
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeExternalWiki) | |||||
} | |||||
if !models.UnitTypeWiki.UnitGlobalDisabled() { | |||||
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeWiki) | |||||
} | } | ||||
} | } | ||||
if form.EnableIssues { | |||||
if form.EnableExternalTracker { | |||||
if !validation.IsValidExternalURL(form.ExternalTrackerURL) { | |||||
ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error")) | |||||
ctx.Redirect(repo.Link() + "/settings") | |||||
return | |||||
} | |||||
if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(form.TrackerURLFormat) { | |||||
ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error")) | |||||
ctx.Redirect(repo.Link() + "/settings") | |||||
return | |||||
} | |||||
units = append(units, models.RepoUnit{ | |||||
RepoID: repo.ID, | |||||
Type: models.UnitTypeExternalTracker, | |||||
Config: &models.ExternalTrackerConfig{ | |||||
ExternalTrackerURL: form.ExternalTrackerURL, | |||||
ExternalTrackerFormat: form.TrackerURLFormat, | |||||
ExternalTrackerStyle: form.TrackerIssueStyle, | |||||
}, | |||||
}) | |||||
} else { | |||||
units = append(units, models.RepoUnit{ | |||||
RepoID: repo.ID, | |||||
Type: models.UnitTypeIssues, | |||||
Config: &models.IssuesConfig{ | |||||
EnableTimetracker: form.EnableTimetracker, | |||||
AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, | |||||
EnableDependencies: form.EnableIssueDependencies, | |||||
}, | |||||
}) | |||||
if form.EnableIssues && form.EnableExternalTracker && !models.UnitTypeExternalTracker.UnitGlobalDisabled() { | |||||
if !validation.IsValidExternalURL(form.ExternalTrackerURL) { | |||||
ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error")) | |||||
ctx.Redirect(repo.Link() + "/settings") | |||||
return | |||||
} | |||||
if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(form.TrackerURLFormat) { | |||||
ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error")) | |||||
ctx.Redirect(repo.Link() + "/settings") | |||||
return | |||||
} | |||||
units = append(units, models.RepoUnit{ | |||||
RepoID: repo.ID, | |||||
Type: models.UnitTypeExternalTracker, | |||||
Config: &models.ExternalTrackerConfig{ | |||||
ExternalTrackerURL: form.ExternalTrackerURL, | |||||
ExternalTrackerFormat: form.TrackerURLFormat, | |||||
ExternalTrackerStyle: form.TrackerIssueStyle, | |||||
}, | |||||
}) | |||||
deleteUnitTypes = append(deleteUnitTypes, models.UnitTypeIssues) | |||||
} else if form.EnableIssues && !form.EnableExternalTracker && !models.UnitTypeIssues.UnitGlobalDisabled() { | |||||
units = append(units, models.RepoUnit{ | |||||
RepoID: repo.ID, | |||||
Type: models.UnitTypeIssues, | |||||
Config: &models.IssuesConfig{ | |||||
EnableTimetracker: form.EnableTimetracker, | |||||
AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, | |||||
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> | ||||