From 8a421b1fd702d99e8438f6ef6f4ee339f1eaa130 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 4 Feb 2017 23:53:46 +0800 Subject: [PATCH] Add units concept for modulable functions of a repository (#742) * Add units concept for modulable functions of a repository * remove unused comment codes & fix lints and tests * remove unused comment codes * use struct config instead of map * fix lint * rm wrong files * fix tests --- cmd/web.go | 8 +- models/migrations/migrations.go | 6 +- models/migrations/v16.go | 117 +++++++++++++++++++++ models/models.go | 1 + models/repo.go | 150 +++++++++++++++++++++------ models/repo_test.go | 24 +++-- models/repo_unit.go | 137 ++++++++++++++++++++++++ models/unit.go | 137 ++++++++++++++++++++++++ modules/context/repo.go | 15 +++ routers/api/v1/api.go | 2 +- routers/repo/issue.go | 8 +- routers/repo/setting.go | 80 +++++++++++--- routers/repo/wiki.go | 8 +- routers/user/home.go | 2 +- templates/repo/header.tmpl | 24 ++++- templates/repo/settings/options.tmpl | 38 +++---- 16 files changed, 668 insertions(+), 89 deletions(-) create mode 100644 models/migrations/v16.go create mode 100644 models/repo_unit.go create mode 100644 models/unit.go diff --git a/cmd/web.go b/cmd/web.go index 026b938f21..0d18e86313 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -441,7 +441,7 @@ func runWeb(ctx *cli.Context) error { }, func(ctx *context.Context) { ctx.Data["PageIsSettings"] = true - }) + }, context.UnitTypes()) }, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef()) m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action) @@ -535,7 +535,7 @@ func runWeb(ctx *cli.Context) error { return } }) - }, reqSignIn, context.RepoAssignment(), repo.MustBeNotBare) + }, reqSignIn, context.RepoAssignment(), repo.MustBeNotBare, context.UnitTypes()) m.Group("/:username/:reponame", func() { m.Group("", func() { @@ -581,7 +581,7 @@ func runWeb(ctx *cli.Context) error { m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.RawDiff) m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.CompareDiff) - }, ignSignIn, context.RepoAssignment(), repo.MustBeNotBare) + }, ignSignIn, context.RepoAssignment(), repo.MustBeNotBare, context.UnitTypes()) m.Group("/:username/:reponame", func() { m.Get("/stars", repo.Stars) m.Get("/watchers", repo.Watchers) @@ -591,7 +591,7 @@ func runWeb(ctx *cli.Context) error { m.Group("/:reponame", func() { m.Get("", repo.SetEditorconfigIfExists, repo.Home) m.Get("\\.git$", repo.SetEditorconfigIfExists, repo.Home) - }, ignSignIn, context.RepoAssignment(true), context.RepoRef()) + }, ignSignIn, context.RepoAssignment(true), context.RepoRef(), context.UnitTypes()) m.Group("/:reponame", func() { m.Group("/info/lfs", func() { diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 5844e3f923..9832cdca92 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -76,10 +76,12 @@ var migrations = []Migration{ // v13 -> v14:v0.9.87 NewMigration("set comment updated with created", setCommentUpdatedWithCreated), - // v14 + // v14 -> v15 NewMigration("create user column diff view style", createUserColumnDiffViewStyle), - // v15 + // v15 -> v16 NewMigration("create user column allow create organization", createAllowCreateOrganizationColumn), + // V16 -> v17 + NewMigration("create repo unit table and add units for all repos", addUnitsToTables), } // Migrate database to current version diff --git a/models/migrations/v16.go b/models/migrations/v16.go new file mode 100644 index 0000000000..fca8311cbd --- /dev/null +++ b/models/migrations/v16.go @@ -0,0 +1,117 @@ +package migrations + +import ( + "fmt" + "time" + + "code.gitea.io/gitea/modules/markdown" + + "github.com/go-xorm/xorm" +) + +// RepoUnit describes all units of a repository +type RepoUnit struct { + ID int64 + RepoID int64 `xorm:"INDEX(s)"` + Type int `xorm:"INDEX(s)"` + Index int + Config map[string]string `xorm:"JSON"` + CreatedUnix int64 `xorm:"INDEX CREATED"` + Created time.Time `xorm:"-"` +} + +// Enumerate all the unit types +const ( + UnitTypeCode = iota + 1 // 1 code + UnitTypeIssues // 2 issues + UnitTypePRs // 3 PRs + UnitTypeCommits // 4 Commits + UnitTypeReleases // 5 Releases + UnitTypeWiki // 6 Wiki + UnitTypeSettings // 7 Settings + UnitTypeExternalWiki // 8 ExternalWiki + UnitTypeExternalTracker // 9 ExternalTracker +) + +// Repo describes a repository +type Repo struct { + ID int64 + EnableWiki, EnableExternalWiki, EnableIssues, EnableExternalTracker, EnablePulls bool + ExternalWikiURL, ExternalTrackerURL, ExternalTrackerFormat, ExternalTrackerStyle string +} + +func addUnitsToTables(x *xorm.Engine) error { + var repos []Repo + err := x.Table("repository").Find(&repos) + if err != nil { + return fmt.Errorf("Query repositories: %v", err) + } + + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + var repoUnit RepoUnit + if err := sess.CreateTable(&repoUnit); err != nil { + return fmt.Errorf("CreateTable RepoUnit: %v", err) + } + + if err := sess.CreateUniques(&repoUnit); err != nil { + return fmt.Errorf("CreateUniques RepoUnit: %v", err) + } + + if err := sess.CreateIndexes(&repoUnit); err != nil { + return fmt.Errorf("CreateIndexes RepoUnit: %v", err) + } + + for _, repo := range repos { + for i := 1; i <= 9; i++ { + if (i == UnitTypeWiki || i == UnitTypeExternalWiki) && !repo.EnableWiki { + continue + } + if i == UnitTypeExternalWiki && !repo.EnableExternalWiki { + continue + } + if i == UnitTypePRs && !repo.EnablePulls { + continue + } + if (i == UnitTypeIssues || i == UnitTypeExternalTracker) && !repo.EnableIssues { + continue + } + if i == UnitTypeExternalTracker && !repo.EnableExternalTracker { + continue + } + + var config = make(map[string]string) + switch i { + case UnitTypeExternalTracker: + config["ExternalTrackerURL"] = repo.ExternalTrackerURL + config["ExternalTrackerFormat"] = repo.ExternalTrackerFormat + if len(repo.ExternalTrackerStyle) == 0 { + repo.ExternalTrackerStyle = markdown.IssueNameStyleNumeric + } + config["ExternalTrackerStyle"] = repo.ExternalTrackerStyle + case UnitTypeExternalWiki: + config["ExternalWikiURL"] = repo.ExternalWikiURL + } + + if _, err = sess.Insert(&RepoUnit{ + RepoID: repo.ID, + Type: i, + Index: i, + Config: config, + }); err != nil { + return fmt.Errorf("Insert repo unit: %v", err) + } + } + } + + if err := sess.Commit(); err != nil { + return err + } + + return nil +} diff --git a/models/models.go b/models/models.go index 7353c11598..624badda17 100644 --- a/models/models.go +++ b/models/models.go @@ -106,6 +106,7 @@ func init() { new(IssueUser), new(LFSMetaObject), new(TwoFactor), + new(RepoUnit), ) gonicNames := []string{"SSL", "UID"} diff --git a/models/repo.go b/models/repo.go index 3a503d8953..a16b7a86f0 100644 --- a/models/repo.go +++ b/models/repo.go @@ -200,17 +200,8 @@ type Repository struct { IsMirror bool `xorm:"INDEX"` *Mirror `xorm:"-"` - // Advanced settings - EnableWiki bool `xorm:"NOT NULL DEFAULT true"` - EnableExternalWiki bool - ExternalWikiURL string - EnableIssues bool `xorm:"NOT NULL DEFAULT true"` - EnableExternalTracker bool - ExternalTrackerURL string - ExternalTrackerFormat string - ExternalTrackerStyle string - ExternalMetas map[string]string `xorm:"-"` - EnablePulls bool `xorm:"NOT NULL DEFAULT true"` + ExternalMetas map[string]string `xorm:"-"` + Units []*RepoUnit `xorm:"-"` IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"` ForkID int64 `xorm:"INDEX"` @@ -247,10 +238,6 @@ func (repo *Repository) AfterSet(colName string, _ xorm.Cell) { repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls case "num_closed_milestones": repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones - case "external_tracker_style": - if len(repo.ExternalTrackerStyle) == 0 { - repo.ExternalTrackerStyle = markdown.IssueNameStyleNumeric - } case "created_unix": repo.Created = time.Unix(repo.CreatedUnix, 0).Local() case "updated_unix": @@ -307,6 +294,72 @@ func (repo *Repository) APIFormat(mode AccessMode) *api.Repository { } } +func (repo *Repository) getUnits(e Engine) (err error) { + if repo.Units != nil { + return nil + } + + repo.Units, err = getUnitsByRepoID(e, repo.ID) + return err +} + +func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) { + return units, e.Where("repo_id = ?", repoID).Find(&units) +} + +// EnableUnit if this repository enabled some unit +func (repo *Repository) EnableUnit(tp UnitType) bool { + repo.getUnits(x) + for _, unit := range repo.Units { + if unit.Type == tp { + return true + } + } + return false +} + +var ( + // ErrUnitNotExist organization does not exist + ErrUnitNotExist = errors.New("Unit does not exist") +) + +// MustGetUnit always returns a RepoUnit object +func (repo *Repository) MustGetUnit(tp UnitType) *RepoUnit { + ru, err := repo.GetUnit(tp) + if err == nil { + return ru + } + + if tp == UnitTypeExternalWiki { + return &RepoUnit{ + Type: tp, + Config: new(ExternalWikiConfig), + } + } else if tp == UnitTypeExternalTracker { + return &RepoUnit{ + Type: tp, + Config: new(ExternalTrackerConfig), + } + } + return &RepoUnit{ + Type: tp, + Config: new(UnitConfig), + } +} + +// GetUnit returns a RepoUnit object +func (repo *Repository) GetUnit(tp UnitType) (*RepoUnit, error) { + if err := repo.getUnits(x); err != nil { + return nil, err + } + for _, unit := range repo.Units { + if unit.Type == tp { + return unit, nil + } + } + return nil, ErrUnitNotExist +} + func (repo *Repository) getOwner(e Engine) (err error) { if repo.Owner != nil { return nil @@ -334,15 +387,18 @@ func (repo *Repository) mustOwner(e Engine) *User { // ComposeMetas composes a map of metas for rendering external issue tracker URL. func (repo *Repository) ComposeMetas() map[string]string { - if !repo.EnableExternalTracker { + unit, err := repo.GetUnit(UnitTypeExternalTracker) + if err != nil { return nil - } else if repo.ExternalMetas == nil { + } + + if repo.ExternalMetas == nil { repo.ExternalMetas = map[string]string{ - "format": repo.ExternalTrackerFormat, + "format": unit.ExternalTrackerConfig().ExternalTrackerFormat, "user": repo.MustOwner().Name, "repo": repo.Name, } - switch repo.ExternalTrackerStyle { + switch unit.ExternalTrackerConfig().ExternalTrackerStyle { case markdown.IssueNameStyleAlphanumeric: repo.ExternalMetas["style"] = markdown.IssueNameStyleAlphanumeric default: @@ -359,6 +415,8 @@ func (repo *Repository) DeleteWiki() { for _, wikiPath := range wikiPaths { RemoveAllWithNotice("Delete repository wiki", wikiPath) } + + x.Where("repo_id = ?", repo.ID).And("type = ?", UnitTypeWiki).Delete(new(RepoUnit)) } func (repo *Repository) getAssignees(e Engine) (_ []*User, err error) { @@ -482,7 +540,7 @@ func (repo *Repository) CanEnablePulls() bool { // AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled. func (repo *Repository) AllowsPulls() bool { - return repo.CanEnablePulls() && repo.EnablePulls + return repo.CanEnablePulls() && repo.EnableUnit(UnitTypePullRequests) } // CanEnableEditor returns true if repository meets the requirements of web editor. @@ -997,6 +1055,20 @@ func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) { return err } + // insert units for repo + var units = make([]RepoUnit, 0, len(defaultRepoUnits)) + for i, tp := range defaultRepoUnits { + units = append(units, RepoUnit{ + RepoID: repo.ID, + Type: tp, + Index: i, + }) + } + + if _, err = e.Insert(&units); err != nil { + return err + } + u.NumRepos++ // Remember visibility preference. u.LastRepoVisibility = repo.IsPrivate @@ -1035,15 +1107,12 @@ func CreateRepository(u *User, opts CreateRepoOptions) (_ *Repository, err error } repo := &Repository{ - OwnerID: u.ID, - Owner: u, - Name: opts.Name, - LowerName: strings.ToLower(opts.Name), - Description: opts.Description, - IsPrivate: opts.IsPrivate, - EnableWiki: true, - EnableIssues: true, - EnablePulls: true, + OwnerID: u.ID, + Owner: u, + Name: opts.Name, + LowerName: strings.ToLower(opts.Name), + Description: opts.Description, + IsPrivate: opts.IsPrivate, } sess := x.NewSession() @@ -1380,6 +1449,25 @@ func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) { return sess.Commit() } +// UpdateRepositoryUnits updates a repository's units +func UpdateRepositoryUnits(repo *Repository, units []RepoUnit) (err error) { + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + + if _, err = sess.Where("repo_id = ?", repo.ID).Delete(new(RepoUnit)); err != nil { + return err + } + + if _, err = sess.Insert(units); err != nil { + return err + } + + return sess.Commit() +} + // DeleteRepository deletes a repository for a user or organization. func DeleteRepository(uid, repoID int64) error { repo := &Repository{ID: repoID, OwnerID: uid} @@ -1467,6 +1555,10 @@ func DeleteRepository(uid, repoID int64) error { return err } + if _, err = sess.Where("repo_id = ?", repoID).Delete(new(RepoUnit)); err != nil { + return err + } + if repo.IsFork { if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil { return fmt.Errorf("decrease fork count: %v", err) diff --git a/models/repo_test.go b/models/repo_test.go index 70ad1c1f0e..42bde62b46 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -14,34 +14,42 @@ func TestRepo(t *testing.T) { repo.Name = "testrepo" repo.Owner = new(User) repo.Owner.Name = "testuser" - repo.ExternalTrackerFormat = "https://someurl.com/{user}/{repo}/{issue}" + externalTracker := RepoUnit{ + Type: UnitTypeExternalTracker, + Config: &ExternalTrackerConfig{ + ExternalTrackerFormat: "https://someurl.com/{user}/{repo}/{issue}", + }, + } + repo.Units = []*RepoUnit{ + &externalTracker, + } Convey("When no external tracker is configured", func() { Convey("It should be nil", func() { - repo.EnableExternalTracker = false + repo.Units = nil So(repo.ComposeMetas(), ShouldEqual, map[string]string(nil)) }) Convey("It should be nil even if other settings are present", func() { - repo.EnableExternalTracker = false - repo.ExternalTrackerFormat = "http://someurl.com/{user}/{repo}/{issue}" - repo.ExternalTrackerStyle = markdown.IssueNameStyleNumeric + repo.Units = nil So(repo.ComposeMetas(), ShouldEqual, map[string]string(nil)) }) }) Convey("When an external issue tracker is configured", func() { - repo.EnableExternalTracker = true + repo.Units = []*RepoUnit{ + &externalTracker, + } Convey("It should default to numeric issue style", func() { metas := repo.ComposeMetas() So(metas["style"], ShouldEqual, markdown.IssueNameStyleNumeric) }) Convey("It should pass through numeric issue style setting", func() { - repo.ExternalTrackerStyle = markdown.IssueNameStyleNumeric + externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markdown.IssueNameStyleNumeric metas := repo.ComposeMetas() So(metas["style"], ShouldEqual, markdown.IssueNameStyleNumeric) }) Convey("It should pass through alphanumeric issue style setting", func() { - repo.ExternalTrackerStyle = markdown.IssueNameStyleAlphanumeric + externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markdown.IssueNameStyleAlphanumeric metas := repo.ComposeMetas() So(metas["style"], ShouldEqual, markdown.IssueNameStyleAlphanumeric) }) diff --git a/models/repo_unit.go b/models/repo_unit.go new file mode 100644 index 0000000000..ee61ef4c9d --- /dev/null +++ b/models/repo_unit.go @@ -0,0 +1,137 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +import ( + "encoding/json" + "time" + + "github.com/Unknwon/com" + "github.com/go-xorm/core" + "github.com/go-xorm/xorm" +) + +// RepoUnit describes all units of a repository +type RepoUnit struct { + ID int64 + RepoID int64 `xorm:"INDEX(s)"` + Type UnitType `xorm:"INDEX(s)"` + Index int + Config core.Conversion `xorm:"TEXT"` + CreatedUnix int64 `xorm:"INDEX CREATED"` + Created time.Time `xorm:"-"` +} + +// UnitConfig describes common unit config +type UnitConfig struct { +} + +// FromDB fills up a UnitConfig from serialized format. +func (cfg *UnitConfig) FromDB(bs []byte) error { + return json.Unmarshal(bs, &cfg) +} + +// ToDB exports a UnitConfig to a serialized format. +func (cfg *UnitConfig) ToDB() ([]byte, error) { + return json.Marshal(cfg) +} + +// ExternalWikiConfig describes external wiki config +type ExternalWikiConfig struct { + ExternalWikiURL string +} + +// FromDB fills up a ExternalWikiConfig from serialized format. +func (cfg *ExternalWikiConfig) FromDB(bs []byte) error { + return json.Unmarshal(bs, &cfg) +} + +// ToDB exports a ExternalWikiConfig to a serialized format. +func (cfg *ExternalWikiConfig) ToDB() ([]byte, error) { + return json.Marshal(cfg) +} + +// ExternalTrackerConfig describes external tracker config +type ExternalTrackerConfig struct { + ExternalTrackerURL string + ExternalTrackerFormat string + ExternalTrackerStyle string +} + +// FromDB fills up a ExternalTrackerConfig from serialized format. +func (cfg *ExternalTrackerConfig) FromDB(bs []byte) error { + return json.Unmarshal(bs, &cfg) +} + +// ToDB exports a ExternalTrackerConfig to a serialized format. +func (cfg *ExternalTrackerConfig) ToDB() ([]byte, error) { + return json.Marshal(cfg) +} + +// 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 { + case "type": + switch UnitType(Cell2Int64(val)) { + case UnitTypeCode, UnitTypeIssues, UnitTypePullRequests, UnitTypeCommits, UnitTypeReleases, + UnitTypeWiki, UnitTypeSettings: + r.Config = new(UnitConfig) + case UnitTypeExternalWiki: + r.Config = new(ExternalWikiConfig) + case UnitTypeExternalTracker: + r.Config = new(ExternalTrackerConfig) + default: + panic("unrecognized repo unit type: " + com.ToStr(*val)) + } + } +} + +// AfterSet is invoked from XORM after setting the value of a field of this object. +func (r *RepoUnit) AfterSet(colName string, _ xorm.Cell) { + switch colName { + case "created_unix": + r.Created = time.Unix(r.CreatedUnix, 0).Local() + } +} + +// Unit returns Unit +func (r *RepoUnit) Unit() Unit { + return Units[r.Type] +} + +// CodeConfig returns config for UnitTypeCode +func (r *RepoUnit) CodeConfig() *UnitConfig { + return r.Config.(*UnitConfig) +} + +// IssuesConfig returns config for UnitTypeIssues +func (r *RepoUnit) IssuesConfig() *UnitConfig { + return r.Config.(*UnitConfig) +} + +// PullRequestsConfig returns config for UnitTypePullRequests +func (r *RepoUnit) PullRequestsConfig() *UnitConfig { + return r.Config.(*UnitConfig) +} + +// CommitsConfig returns config for UnitTypeCommits +func (r *RepoUnit) CommitsConfig() *UnitConfig { + return r.Config.(*UnitConfig) +} + +// ReleasesConfig returns config for UnitTypeReleases +func (r *RepoUnit) ReleasesConfig() *UnitConfig { + return r.Config.(*UnitConfig) +} + +// ExternalWikiConfig returns config for UnitTypeExternalWiki +func (r *RepoUnit) ExternalWikiConfig() *ExternalWikiConfig { + return r.Config.(*ExternalWikiConfig) +} + +// ExternalTrackerConfig returns config for UnitTypeExternalTracker +func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig { + return r.Config.(*ExternalTrackerConfig) +} diff --git a/models/unit.go b/models/unit.go new file mode 100644 index 0000000000..54bb928ba7 --- /dev/null +++ b/models/unit.go @@ -0,0 +1,137 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +// UnitType is Unit's Type +type UnitType int + +// Enumerate all the unit types +const ( + UnitTypeCode UnitType = iota + 1 // 1 code + UnitTypeIssues // 2 issues + UnitTypePullRequests // 3 PRs + UnitTypeCommits // 4 Commits + UnitTypeReleases // 5 Releases + UnitTypeWiki // 6 Wiki + UnitTypeSettings // 7 Settings + UnitTypeExternalWiki // 8 ExternalWiki + UnitTypeExternalTracker // 9 ExternalTracker +) + +// Unit is a tab page of one repository +type Unit struct { + Type UnitType + NameKey string + URI string + DescKey string + Idx int +} + +// Enumerate all the units +var ( + UnitCode = Unit{ + UnitTypeCode, + "repo.code", + "/", + "repo.code_desc", + 0, + } + + UnitIssues = Unit{ + UnitTypeIssues, + "repo.issues", + "/issues", + "repo.issues_desc", + 1, + } + + UnitExternalTracker = Unit{ + UnitTypeExternalTracker, + "repo.issues", + "/issues", + "repo.issues_desc", + 1, + } + + UnitPullRequests = Unit{ + UnitTypePullRequests, + "repo.pulls", + "/pulls", + "repo.pulls_desc", + 2, + } + + UnitCommits = Unit{ + UnitTypeCommits, + "repo.commits", + "/commits/master", + "repo.commits_desc", + 3, + } + + UnitReleases = Unit{ + UnitTypeReleases, + "repo.releases", + "/releases", + "repo.releases_desc", + 4, + } + + UnitWiki = Unit{ + UnitTypeWiki, + "repo.wiki", + "/wiki", + "repo.wiki_desc", + 5, + } + + UnitExternalWiki = Unit{ + UnitTypeExternalWiki, + "repo.wiki", + "/wiki", + "repo.wiki_desc", + 5, + } + + UnitSettings = Unit{ + UnitTypeSettings, + "repo.settings", + "/settings", + "repo.settings_desc", + 6, + } + + // defaultRepoUnits contains all the default unit types + defaultRepoUnits = []UnitType{ + UnitTypeCode, + UnitTypeIssues, + UnitTypePullRequests, + UnitTypeCommits, + UnitTypeReleases, + UnitTypeWiki, + UnitTypeSettings, + } + + // MustRepoUnits contains the units could be disabled currently + MustRepoUnits = []UnitType{ + UnitTypeCode, + UnitTypeCommits, + UnitTypeReleases, + UnitTypeSettings, + } + + // Units contains all the units + Units = map[UnitType]Unit{ + UnitTypeCode: UnitCode, + UnitTypeIssues: UnitIssues, + UnitTypeExternalTracker: UnitExternalTracker, + UnitTypePullRequests: UnitPullRequests, + UnitTypeCommits: UnitCommits, + UnitTypeReleases: UnitReleases, + UnitTypeWiki: UnitWiki, + UnitTypeExternalWiki: UnitExternalWiki, + UnitTypeSettings: UnitSettings, + } +) diff --git a/modules/context/repo.go b/modules/context/repo.go index aca1e1b63c..8de5b9821b 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -477,3 +477,18 @@ func GitHookService() macaron.Handler { } } } + +// UnitTypes returns a macaron middleware to set unit types to context variables. +func UnitTypes() macaron.Handler { + return func(ctx *Context) { + ctx.Data["UnitTypeCode"] = models.UnitTypeCode + ctx.Data["UnitTypeIssues"] = models.UnitTypeIssues + ctx.Data["UnitTypePullRequests"] = models.UnitTypePullRequests + ctx.Data["UnitTypeCommits"] = models.UnitTypeCommits + ctx.Data["UnitTypeReleases"] = models.UnitTypeReleases + ctx.Data["UnitTypeWiki"] = models.UnitTypeWiki + ctx.Data["UnitTypeSettings"] = models.UnitTypeSettings + ctx.Data["UnitTypeExternalWiki"] = models.UnitTypeExternalWiki + ctx.Data["UnitTypeExternalTracker"] = models.UnitTypeExternalTracker + } +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 07180c9a5c..afdb2c0045 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -205,7 +205,7 @@ func orgAssignment(args ...bool) macaron.Handler { } func mustEnableIssues(ctx *context.APIContext) { - if !ctx.Repo.Repository.EnableIssues || ctx.Repo.Repository.EnableExternalTracker { + if !ctx.Repo.Repository.EnableUnit(models.UnitTypeIssues) { ctx.Status(404) return } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 8e8d058f2c..bc9e72a1ac 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -59,13 +59,15 @@ var ( // MustEnableIssues check if repository enable internal issues func MustEnableIssues(ctx *context.Context) { - if !ctx.Repo.Repository.EnableIssues { + if !ctx.Repo.Repository.EnableUnit(models.UnitTypeIssues) && + !ctx.Repo.Repository.EnableUnit(models.UnitTypeExternalTracker) { ctx.Handle(404, "MustEnableIssues", nil) return } - if ctx.Repo.Repository.EnableExternalTracker { - ctx.Redirect(ctx.Repo.Repository.ExternalTrackerURL) + unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker) + if err == nil { + ctx.Redirect(unit.ExternalTrackerConfig().ExternalTrackerURL) return } } diff --git a/routers/repo/setting.go b/routers/repo/setting.go index e29f0fcb1f..9a426c1598 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -143,18 +143,70 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { ctx.Redirect(repo.Link() + "/settings") case "advanced": - repo.EnableWiki = form.EnableWiki - repo.EnableExternalWiki = form.EnableExternalWiki - repo.ExternalWikiURL = form.ExternalWikiURL - repo.EnableIssues = form.EnableIssues - repo.EnableExternalTracker = form.EnableExternalTracker - repo.ExternalTrackerURL = form.ExternalTrackerURL - repo.ExternalTrackerFormat = form.TrackerURLFormat - repo.ExternalTrackerStyle = form.TrackerIssueStyle - repo.EnablePulls = form.EnablePulls + var units []models.RepoUnit - if err := models.UpdateRepository(repo, false); err != nil { - ctx.Handle(500, "UpdateRepository", err) + for _, tp := range models.MustRepoUnits { + units = append(units, models.RepoUnit{ + RepoID: repo.ID, + Type: tp, + Index: int(tp), + Config: new(models.UnitConfig), + }) + } + + if form.EnableWiki { + if form.EnableExternalWiki { + units = append(units, models.RepoUnit{ + RepoID: repo.ID, + Type: models.UnitTypeExternalWiki, + Index: int(models.UnitTypeExternalWiki), + Config: &models.ExternalWikiConfig{ + ExternalWikiURL: form.ExternalWikiURL, + }, + }) + } else { + units = append(units, models.RepoUnit{ + RepoID: repo.ID, + Type: models.UnitTypeWiki, + Index: int(models.UnitTypeWiki), + Config: new(models.UnitConfig), + }) + } + } + + if form.EnableIssues { + if form.EnableExternalTracker { + units = append(units, models.RepoUnit{ + RepoID: repo.ID, + Type: models.UnitTypeExternalWiki, + Index: int(models.UnitTypeExternalWiki), + Config: &models.ExternalTrackerConfig{ + ExternalTrackerURL: form.ExternalTrackerURL, + ExternalTrackerFormat: form.TrackerURLFormat, + ExternalTrackerStyle: form.TrackerIssueStyle, + }, + }) + } else { + units = append(units, models.RepoUnit{ + RepoID: repo.ID, + Type: models.UnitTypeIssues, + Index: int(models.UnitTypeIssues), + Config: new(models.UnitConfig), + }) + } + } + + if form.EnablePulls { + units = append(units, models.RepoUnit{ + RepoID: repo.ID, + Type: models.UnitTypePullRequests, + Index: int(models.UnitTypePullRequests), + Config: new(models.UnitConfig), + }) + } + + if err := models.UpdateRepositoryUnits(repo, units); err != nil { + ctx.Handle(500, "UpdateRepositoryUnits", err) return } log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) @@ -281,12 +333,6 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { repo.DeleteWiki() log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name) - repo.EnableWiki = false - if err := models.UpdateRepository(repo, false); err != nil { - ctx.Handle(500, "UpdateRepository", err) - return - } - ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success")) ctx.Redirect(ctx.Repo.RepoLink + "/settings") diff --git a/routers/repo/wiki.go b/routers/repo/wiki.go index ac979c1a9c..6e491f73a4 100644 --- a/routers/repo/wiki.go +++ b/routers/repo/wiki.go @@ -27,13 +27,15 @@ const ( // MustEnableWiki check if wiki is enabled, if external then redirect func MustEnableWiki(ctx *context.Context) { - if !ctx.Repo.Repository.EnableWiki { + if !ctx.Repo.Repository.EnableUnit(models.UnitTypeWiki) && + !ctx.Repo.Repository.EnableUnit(models.UnitTypeExternalWiki) { ctx.Handle(404, "MustEnableWiki", nil) return } - if ctx.Repo.Repository.EnableExternalWiki { - ctx.Redirect(ctx.Repo.Repository.ExternalWikiURL) + unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalWiki) + if err == nil { + ctx.Redirect(unit.ExternalWikiConfig().ExternalWikiURL) return } } diff --git a/routers/user/home.go b/routers/user/home.go index 09711f4bc0..f5f6a0950d 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -236,7 +236,7 @@ func Issues(ctx *context.Context) { for _, repo := range repos { if (isPullList && repo.NumPulls == 0) || (!isPullList && - (!repo.EnableIssues || repo.EnableExternalTracker || repo.NumIssues == 0)) { + (!repo.EnableUnit(models.UnitTypeIssues) || repo.NumIssues == 0)) { continue } diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index a46bf5b807..24ddb46d08 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -49,30 +49,48 @@ {{if not (or .IsBareRepo .IsDiffCompare)}}