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
This commit is contained in:
Lunny Xiao 2017-02-04 23:53:46 +08:00 committed by GitHub
parent 49fa03bf42
commit 8a421b1fd7
16 changed files with 668 additions and 89 deletions

View File

@ -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() {

View File

@ -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

117
models/migrations/v16.go Normal file
View File

@ -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
}

View File

@ -106,6 +106,7 @@ func init() {
new(IssueUser),
new(LFSMetaObject),
new(TwoFactor),
new(RepoUnit),
)
gonicNames := []string{"SSL", "UID"}

View File

@ -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)

View File

@ -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)
})

137
models/repo_unit.go Normal file
View File

@ -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)
}

137
models/unit.go Normal file
View File

@ -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,
}
)

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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")

View File

@ -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
}
}

View File

@ -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
}

View File

@ -49,30 +49,48 @@
{{if not (or .IsBareRepo .IsDiffCompare)}}
<div class="ui tabs container">
<div class="ui tabular menu navbar">
{{if .Repository.EnableUnit $.UnitTypeCode}}
<a class="{{if .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}">
<i class="octicon octicon-code"></i> {{.i18n.Tr "repo.code"}}
</a>
{{if .Repository.EnableIssues}}
{{end}}
{{if .Repository.EnableUnit $.UnitTypeIssues}}
<a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues">
<i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues"}} {{if not .Repository.EnableExternalTracker}}<span class="ui {{if not .Repository.NumOpenIssues}}gray{{else}}blue{{end}} small label">{{.Repository.NumOpenIssues}}{{end}}</span>
<i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues"}} <span class="ui {{if not .Repository.NumOpenIssues}}gray{{else}}blue{{end}} small label">{{.Repository.NumOpenIssues}}</span>
</a>
{{end}}
{{if .Repository.EnableUnit $.UnitTypeExternalTracker}}
<a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues">
<i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues"}} </span>
</a>
{{end}}
{{if .Repository.AllowsPulls}}
<a class="{{if .PageIsPullList}}active{{end}} item" href="{{.RepoLink}}/pulls">
<i class="octicon octicon-git-pull-request"></i> {{.i18n.Tr "repo.pulls"}} <span class="ui {{if not .Repository.NumOpenPulls}}gray{{else}}blue{{end}} small label">{{.Repository.NumOpenPulls}}</span>
</a>
{{end}}
{{if .Repository.EnableUnit $.UnitTypeCommits}}
<a class="{{if (or (.PageIsCommits) (.PageIsDiff))}}active{{end}} item" href="{{.RepoLink}}/commits/{{EscapePound .BranchName}}">
<i class="octicon octicon-history"></i> {{.i18n.Tr "repo.commits"}} <span class="ui {{if not .CommitsCount}}gray{{else}}blue{{end}} small label">{{.CommitsCount}}</span>
</a>
{{end}}
{{if .Repository.EnableUnit $.UnitTypeReleases}}
<a class="{{if .PageIsReleaseList}}active{{end}} item" href="{{.RepoLink}}/releases">
<i class="octicon octicon-tag"></i> {{.i18n.Tr "repo.releases"}} <span class="ui {{if not .Repository.NumTags}}gray{{else}}blue{{end}} small label">{{.Repository.NumTags}}</span>
</a>
{{if .Repository.EnableWiki}}
{{end}}
{{if or (.Repository.EnableUnit $.UnitTypeWiki) (.Repository.EnableUnit $.UnitTypeExternalWiki)}}
<a class="{{if .PageIsWiki}}active{{end}} item" href="{{.RepoLink}}/wiki">
<i class="octicon octicon-book"></i> {{.i18n.Tr "repo.wiki"}}
</a>
{{end}}
{{if .IsRepositoryAdmin}}
<div class="right menu">
<a class="{{if .PageIsSettings}}active{{end}} item" href="{{.RepoLink}}/settings">

View File

@ -114,26 +114,26 @@
<div class="inline field">
<label>{{.i18n.Tr "repo.wiki"}}</label>
<div class="ui checkbox">
<input class="enable-system" name="enable_wiki" type="checkbox" data-target="#wiki_box" {{if .Repository.EnableWiki}}checked{{end}}>
<input class="enable-system" name="enable_wiki" type="checkbox" data-target="#wiki_box" {{if or (.Repository.EnableUnit $.UnitTypeWiki) (.Repository.EnableUnit $.UnitTypeExternalWiki)}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.wiki_desc"}}</label>
</div>
</div>
<div class="field {{if not .Repository.EnableWiki}}disabled{{end}}" id="wiki_box">
<div class="field {{if not (.Repository.EnableUnit $.UnitTypeWiki)}}disabled{{end}}" id="wiki_box">
<div class="field">
<div class="ui radio checkbox">
<input class="hidden enable-system-radio" tabindex="0" name="enable_external_wiki" type="radio" value="false" data-target="#external_wiki_box" {{if not .Repository.EnableExternalWiki}}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.EnableUnit $.UnitTypeExternalWiki)}}checked{{end}}/>
<label>{{.i18n.Tr "repo.settings.use_internal_wiki"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input class="hidden enable-system-radio" tabindex="0" name="enable_external_wiki" type="radio" value="true" data-target="#external_wiki_box" {{if .Repository.EnableExternalWiki}}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.EnableUnit $.UnitTypeExternalWiki}}checked{{end}}/>
<label>{{.i18n.Tr "repo.settings.use_external_wiki"}}</label>
</div>
</div>
<div class="field {{if not .Repository.EnableExternalWiki}}disabled{{end}}" id="external_wiki_box">
<div class="field {{if not (.Repository.EnableUnit $.UnitTypeExternalWiki)}}disabled{{end}}" id="external_wiki_box">
<label for="external_wiki_url">{{.i18n.Tr "repo.settings.external_wiki_url"}}</label>
<input id="external_wiki_url" name="external_wiki_url" type="url" value="{{.Repository.ExternalWikiURL}}">
<input id="external_wiki_url" name="external_wiki_url" type="url" value="{{(.Repository.MustGetUnit $.UnitTypeExternalWiki).ExternalWikiConfig.ExternalWikiURL}}">
<p class="help">{{.i18n.Tr "repo.settings.external_wiki_url_desc"}}</p>
</div>
</div>
@ -143,45 +143,47 @@
<div class="inline field">
<label>{{.i18n.Tr "repo.issues"}}</label>
<div class="ui checkbox">
<input class="enable-system" name="enable_issues" type="checkbox" data-target="#issue_box" {{if .Repository.EnableIssues}}checked{{end}}>
<input class="enable-system" name="enable_issues" type="checkbox" data-target="#issue_box" {{if or (.Repository.EnableUnit $.UnitTypeIssues) (.Repository.EnableUnit $.UnitTypeExternalTracker)}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.issues_desc"}}</label>
</div>
</div>
<div class="field {{if not .Repository.EnableIssues}}disabled{{end}}" id="issue_box">
<div class="field {{if not (.Repository.EnableUnit $.UnitTypeIssues)}}disabled{{end}}" id="issue_box">
<div class="field">
<div class="ui radio checkbox">
<input class="hidden enable-system-radio" tabindex="0" name="enable_external_tracker" type="radio" value="false" data-target="#external_issue_box" {{if not .Repository.EnableExternalTracker}}checked{{end}}/>
<input class="hidden enable-system-radio" tabindex="0" name="enable_external_tracker" type="radio" value="false" data-target="#external_issue_box" {{if not (.Repository.EnableUnit $.UnitTypeExternalTracker)}}checked{{end}}/>
<label>{{.i18n.Tr "repo.settings.use_internal_issue_tracker"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input class="hidden enable-system-radio" tabindex="0" name="enable_external_tracker" type="radio" value="true" data-target="#external_issue_box" {{if .Repository.EnableExternalTracker}}checked{{end}}/>
<input class="hidden enable-system-radio" tabindex="0" name="enable_external_tracker" type="radio" value="true" data-target="#external_issue_box" {{if .Repository.EnableUnit $.UnitTypeExternalTracker}}checked{{end}}/>
<label>{{.i18n.Tr "repo.settings.use_external_issue_tracker"}}</label>
</div>
</div>
<div class="field {{if not .Repository.EnableExternalTracker}}disabled{{end}}" id="external_issue_box">
<div class="field {{if not (.Repository.EnableUnit $.UnitTypeExternalTracker)}}disabled{{end}}" id="external_issue_box">
<div class="field">
<label for="external_tracker_url">{{.i18n.Tr "repo.settings.external_tracker_url"}}</label>
<input id="external_tracker_url" name="external_tracker_url" type="url" value="{{.Repository.ExternalTrackerURL}}">
<input id="external_tracker_url" name="external_tracker_url" type="url" value="{{(.Repository.MustGetUnit $.UnitTypeExternalTracker).ExternalTrackerConfig.ExternalTrackerURL}}">
<p class="help">{{.i18n.Tr "repo.settings.external_tracker_url_desc"}}</p>
</div>
<div class="field">
<label for="tracker_url_format">{{.i18n.Tr "repo.settings.tracker_url_format"}}</label>
<input id="tracker_url_format" name="tracker_url_format" type="url" value="{{.Repository.ExternalTrackerFormat}}" placeholder="e.g. https://github.com/{user}/{repo}/issues/{index}">
<input id="tracker_url_format" name="tracker_url_format" type="url" value="{{(.Repository.MustGetUnit $.UnitTypeExternalTracker).ExternalTrackerConfig.ExternalTrackerFormat}}" placeholder="e.g. https://github.com/{user}/{repo}/issues/{index}">
<p class="help">{{.i18n.Tr "repo.settings.tracker_url_format_desc" | Str2html}}</p>
</div>
<div class="inline fields">
<label for="issue_style">{{.i18n.Tr "repo.settings.tracker_issue_style"}}</label>
<div class="field">
<div class="ui radio checkbox">
<input class="hidden" tabindex="0" name="tracker_issue_style" type="radio" value="numeric" {{if eq .Repository.ExternalTrackerStyle "numeric"}}checked=""{{end}}/>
{{$externalTracker := (.Repository.MustGetUnit $.UnitTypeExternalTracker)}}
{{$externalTrackerStyle := $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle}}
<input class="hidden" tabindex="0" name="tracker_issue_style" type="radio" value="numeric" {{if $externalTrackerStyle}}{{if eq $externalTrackerStyle "numeric"}}checked=""{{end}}{{end}}/>
<label>{{.i18n.Tr "repo.settings.tracker_issue_style.numeric"}} <span class="ui light grey text">(#1234)</span></label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input class="hidden" tabindex="0" name="tracker_issue_style" type="radio" value="alphanumeric" {{if eq .Repository.ExternalTrackerStyle "alphanumeric"}}checked=""{{end}}/>
<input class="hidden" tabindex="0" name="tracker_issue_style" type="radio" value="alphanumeric" {{if $externalTrackerStyle}}{{if eq $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle "alphanumeric"}}checked=""{{end}}{{end}} />
<label>{{.i18n.Tr "repo.settings.tracker_issue_style.alphanumeric"}} <span class="ui light grey text">(ABC-123, DEFG-234)</span></label>
</div>
</div>
@ -195,7 +197,7 @@
<div class="inline field">
<label>{{.i18n.Tr "repo.pulls"}}</label>
<div class="ui checkbox">
<input name="enable_pulls" type="checkbox" {{if .Repository.EnablePulls}}checked{{end}}>
<input name="enable_pulls" type="checkbox" {{if .Repository.EnableUnit $.UnitTypePullRequests}}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.pulls_desc"}}</label>
</div>
</div>
@ -236,7 +238,7 @@
</div>
</div>
{{if .Repository.EnableWiki}}
{{if .Repository.EnableUnit $.UnitTypeWiki}}
<div class="ui divider"></div>
<div class="item">
@ -370,7 +372,7 @@
</div>
</div>
{{if .Repository.EnableWiki}}
{{if .Repository.EnableUnit $.UnitTypeWiki}}
<div class="ui small modal" id="delete-wiki-modal">
<div class="header">
{{.i18n.Tr "repo.settings.wiki-delete"}}