Browse Source

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
Lunny Xiao 3 years ago
parent
commit
8a421b1fd7

+ 4 - 4
cmd/web.go

@@ -441,7 +441,7 @@ func runWeb(ctx *cli.Context) error {
441 441
 
442 442
 		}, func(ctx *context.Context) {
443 443
 			ctx.Data["PageIsSettings"] = true
444
-		})
444
+		}, context.UnitTypes())
445 445
 	}, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef())
446 446
 
447 447
 	m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action)
@@ -535,7 +535,7 @@ func runWeb(ctx *cli.Context) error {
535 535
 				return
536 536
 			}
537 537
 		})
538
-	}, reqSignIn, context.RepoAssignment(), repo.MustBeNotBare)
538
+	}, reqSignIn, context.RepoAssignment(), repo.MustBeNotBare, context.UnitTypes())
539 539
 
540 540
 	m.Group("/:username/:reponame", func() {
541 541
 		m.Group("", func() {
@@ -581,7 +581,7 @@ func runWeb(ctx *cli.Context) error {
581 581
 		m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.RawDiff)
582 582
 
583 583
 		m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.CompareDiff)
584
-	}, ignSignIn, context.RepoAssignment(), repo.MustBeNotBare)
584
+	}, ignSignIn, context.RepoAssignment(), repo.MustBeNotBare, context.UnitTypes())
585 585
 	m.Group("/:username/:reponame", func() {
586 586
 		m.Get("/stars", repo.Stars)
587 587
 		m.Get("/watchers", repo.Watchers)
@@ -591,7 +591,7 @@ func runWeb(ctx *cli.Context) error {
591 591
 		m.Group("/:reponame", func() {
592 592
 			m.Get("", repo.SetEditorconfigIfExists, repo.Home)
593 593
 			m.Get("\\.git$", repo.SetEditorconfigIfExists, repo.Home)
594
-		}, ignSignIn, context.RepoAssignment(true), context.RepoRef())
594
+		}, ignSignIn, context.RepoAssignment(true), context.RepoRef(), context.UnitTypes())
595 595
 
596 596
 		m.Group("/:reponame", func() {
597 597
 			m.Group("/info/lfs", func() {

+ 4 - 2
models/migrations/migrations.go

@@ -76,10 +76,12 @@ var migrations = []Migration{
76 76
 
77 77
 	// v13 -> v14:v0.9.87
78 78
 	NewMigration("set comment updated with created", setCommentUpdatedWithCreated),
79
-	// v14
79
+	// v14 -> v15
80 80
 	NewMigration("create user column diff view style", createUserColumnDiffViewStyle),
81
-	// v15
81
+	// v15 -> v16
82 82
 	NewMigration("create user column allow create organization", createAllowCreateOrganizationColumn),
83
+	// V16 -> v17
84
+	NewMigration("create repo unit table and add units for all repos", addUnitsToTables),
83 85
 }
84 86
 
85 87
 // Migrate database to current version

+ 117 - 0
models/migrations/v16.go

@@ -0,0 +1,117 @@
1
+package migrations
2
+
3
+import (
4
+	"fmt"
5
+	"time"
6
+
7
+	"code.gitea.io/gitea/modules/markdown"
8
+
9
+	"github.com/go-xorm/xorm"
10
+)
11
+
12
+// RepoUnit describes all units of a repository
13
+type RepoUnit struct {
14
+	ID          int64
15
+	RepoID      int64 `xorm:"INDEX(s)"`
16
+	Type        int   `xorm:"INDEX(s)"`
17
+	Index       int
18
+	Config      map[string]string `xorm:"JSON"`
19
+	CreatedUnix int64             `xorm:"INDEX CREATED"`
20
+	Created     time.Time         `xorm:"-"`
21
+}
22
+
23
+// Enumerate all the unit types
24
+const (
25
+	UnitTypeCode            = iota + 1 // 1 code
26
+	UnitTypeIssues                     // 2 issues
27
+	UnitTypePRs                        // 3 PRs
28
+	UnitTypeCommits                    // 4 Commits
29
+	UnitTypeReleases                   // 5 Releases
30
+	UnitTypeWiki                       // 6 Wiki
31
+	UnitTypeSettings                   // 7 Settings
32
+	UnitTypeExternalWiki               // 8 ExternalWiki
33
+	UnitTypeExternalTracker            // 9 ExternalTracker
34
+)
35
+
36
+// Repo describes a repository
37
+type Repo struct {
38
+	ID                                                                               int64
39
+	EnableWiki, EnableExternalWiki, EnableIssues, EnableExternalTracker, EnablePulls bool
40
+	ExternalWikiURL, ExternalTrackerURL, ExternalTrackerFormat, ExternalTrackerStyle string
41
+}
42
+
43
+func addUnitsToTables(x *xorm.Engine) error {
44
+	var repos []Repo
45
+	err := x.Table("repository").Find(&repos)
46
+	if err != nil {
47
+		return fmt.Errorf("Query repositories: %v", err)
48
+	}
49
+
50
+	sess := x.NewSession()
51
+	defer sess.Close()
52
+
53
+	if err := sess.Begin(); err != nil {
54
+		return err
55
+	}
56
+
57
+	var repoUnit RepoUnit
58
+	if err := sess.CreateTable(&repoUnit); err != nil {
59
+		return fmt.Errorf("CreateTable RepoUnit: %v", err)
60
+	}
61
+
62
+	if err := sess.CreateUniques(&repoUnit); err != nil {
63
+		return fmt.Errorf("CreateUniques RepoUnit: %v", err)
64
+	}
65
+
66
+	if err := sess.CreateIndexes(&repoUnit); err != nil {
67
+		return fmt.Errorf("CreateIndexes RepoUnit: %v", err)
68
+	}
69
+
70
+	for _, repo := range repos {
71
+		for i := 1; i <= 9; i++ {
72
+			if (i == UnitTypeWiki || i == UnitTypeExternalWiki) && !repo.EnableWiki {
73
+				continue
74
+			}
75
+			if i == UnitTypeExternalWiki && !repo.EnableExternalWiki {
76
+				continue
77
+			}
78
+			if i == UnitTypePRs && !repo.EnablePulls {
79
+				continue
80
+			}
81
+			if (i == UnitTypeIssues || i == UnitTypeExternalTracker) && !repo.EnableIssues {
82
+				continue
83
+			}
84
+			if i == UnitTypeExternalTracker && !repo.EnableExternalTracker {
85
+				continue
86
+			}
87
+
88
+			var config = make(map[string]string)
89
+			switch i {
90
+			case UnitTypeExternalTracker:
91
+				config["ExternalTrackerURL"] = repo.ExternalTrackerURL
92
+				config["ExternalTrackerFormat"] = repo.ExternalTrackerFormat
93
+				if len(repo.ExternalTrackerStyle) == 0 {
94
+					repo.ExternalTrackerStyle = markdown.IssueNameStyleNumeric
95
+				}
96
+				config["ExternalTrackerStyle"] = repo.ExternalTrackerStyle
97
+			case UnitTypeExternalWiki:
98
+				config["ExternalWikiURL"] = repo.ExternalWikiURL
99
+			}
100
+
101
+			if _, err = sess.Insert(&RepoUnit{
102
+				RepoID: repo.ID,
103
+				Type:   i,
104
+				Index:  i,
105
+				Config: config,
106
+			}); err != nil {
107
+				return fmt.Errorf("Insert repo unit: %v", err)
108
+			}
109
+		}
110
+	}
111
+
112
+	if err := sess.Commit(); err != nil {
113
+		return err
114
+	}
115
+
116
+	return nil
117
+}

+ 1 - 0
models/models.go

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

+ 121 - 29
models/repo.go

@@ -200,17 +200,8 @@ type Repository struct {
200 200
 	IsMirror bool `xorm:"INDEX"`
201 201
 	*Mirror  `xorm:"-"`
202 202
 
203
-	// Advanced settings
204
-	EnableWiki            bool `xorm:"NOT NULL DEFAULT true"`
205
-	EnableExternalWiki    bool
206
-	ExternalWikiURL       string
207
-	EnableIssues          bool `xorm:"NOT NULL DEFAULT true"`
208
-	EnableExternalTracker bool
209
-	ExternalTrackerURL    string
210
-	ExternalTrackerFormat string
211
-	ExternalTrackerStyle  string
212
-	ExternalMetas         map[string]string `xorm:"-"`
213
-	EnablePulls           bool              `xorm:"NOT NULL DEFAULT true"`
203
+	ExternalMetas map[string]string `xorm:"-"`
204
+	Units         []*RepoUnit       `xorm:"-"`
214 205
 
215 206
 	IsFork   bool        `xorm:"INDEX NOT NULL DEFAULT false"`
216 207
 	ForkID   int64       `xorm:"INDEX"`
@@ -247,10 +238,6 @@ func (repo *Repository) AfterSet(colName string, _ xorm.Cell) {
247 238
 		repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls
248 239
 	case "num_closed_milestones":
249 240
 		repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
250
-	case "external_tracker_style":
251
-		if len(repo.ExternalTrackerStyle) == 0 {
252
-			repo.ExternalTrackerStyle = markdown.IssueNameStyleNumeric
253
-		}
254 241
 	case "created_unix":
255 242
 		repo.Created = time.Unix(repo.CreatedUnix, 0).Local()
256 243
 	case "updated_unix":
@@ -307,6 +294,72 @@ func (repo *Repository) APIFormat(mode AccessMode) *api.Repository {
307 294
 	}
308 295
 }
309 296
 
297
+func (repo *Repository) getUnits(e Engine) (err error) {
298
+	if repo.Units != nil {
299
+		return nil
300
+	}
301
+
302
+	repo.Units, err = getUnitsByRepoID(e, repo.ID)
303
+	return err
304
+}
305
+
306
+func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) {
307
+	return units, e.Where("repo_id = ?", repoID).Find(&units)
308
+}
309
+
310
+// EnableUnit if this repository enabled some unit
311
+func (repo *Repository) EnableUnit(tp UnitType) bool {
312
+	repo.getUnits(x)
313
+	for _, unit := range repo.Units {
314
+		if unit.Type == tp {
315
+			return true
316
+		}
317
+	}
318
+	return false
319
+}
320
+
321
+var (
322
+	// ErrUnitNotExist organization does not exist
323
+	ErrUnitNotExist = errors.New("Unit does not exist")
324
+)
325
+
326
+// MustGetUnit always returns a RepoUnit object
327
+func (repo *Repository) MustGetUnit(tp UnitType) *RepoUnit {
328
+	ru, err := repo.GetUnit(tp)
329
+	if err == nil {
330
+		return ru
331
+	}
332
+
333
+	if tp == UnitTypeExternalWiki {
334
+		return &RepoUnit{
335
+			Type:   tp,
336
+			Config: new(ExternalWikiConfig),
337
+		}
338
+	} else if tp == UnitTypeExternalTracker {
339
+		return &RepoUnit{
340
+			Type:   tp,
341
+			Config: new(ExternalTrackerConfig),
342
+		}
343
+	}
344
+	return &RepoUnit{
345
+		Type:   tp,
346
+		Config: new(UnitConfig),
347
+	}
348
+}
349
+
350
+// GetUnit returns a RepoUnit object
351
+func (repo *Repository) GetUnit(tp UnitType) (*RepoUnit, error) {
352
+	if err := repo.getUnits(x); err != nil {
353
+		return nil, err
354
+	}
355
+	for _, unit := range repo.Units {
356
+		if unit.Type == tp {
357
+			return unit, nil
358
+		}
359
+	}
360
+	return nil, ErrUnitNotExist
361
+}
362
+
310 363
 func (repo *Repository) getOwner(e Engine) (err error) {
311 364
 	if repo.Owner != nil {
312 365
 		return nil
@@ -334,15 +387,18 @@ func (repo *Repository) mustOwner(e Engine) *User {
334 387
 
335 388
 // ComposeMetas composes a map of metas for rendering external issue tracker URL.
336 389
 func (repo *Repository) ComposeMetas() map[string]string {
337
-	if !repo.EnableExternalTracker {
390
+	unit, err := repo.GetUnit(UnitTypeExternalTracker)
391
+	if err != nil {
338 392
 		return nil
339
-	} else if repo.ExternalMetas == nil {
393
+	}
394
+
395
+	if repo.ExternalMetas == nil {
340 396
 		repo.ExternalMetas = map[string]string{
341
-			"format": repo.ExternalTrackerFormat,
397
+			"format": unit.ExternalTrackerConfig().ExternalTrackerFormat,
342 398
 			"user":   repo.MustOwner().Name,
343 399
 			"repo":   repo.Name,
344 400
 		}
345
-		switch repo.ExternalTrackerStyle {
401
+		switch unit.ExternalTrackerConfig().ExternalTrackerStyle {
346 402
 		case markdown.IssueNameStyleAlphanumeric:
347 403
 			repo.ExternalMetas["style"] = markdown.IssueNameStyleAlphanumeric
348 404
 		default:
@@ -359,6 +415,8 @@ func (repo *Repository) DeleteWiki() {
359 415
 	for _, wikiPath := range wikiPaths {
360 416
 		RemoveAllWithNotice("Delete repository wiki", wikiPath)
361 417
 	}
418
+
419
+	x.Where("repo_id = ?", repo.ID).And("type = ?", UnitTypeWiki).Delete(new(RepoUnit))
362 420
 }
363 421
 
364 422
 func (repo *Repository) getAssignees(e Engine) (_ []*User, err error) {
@@ -482,7 +540,7 @@ func (repo *Repository) CanEnablePulls() bool {
482 540
 
483 541
 // AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled.
484 542
 func (repo *Repository) AllowsPulls() bool {
485
-	return repo.CanEnablePulls() && repo.EnablePulls
543
+	return repo.CanEnablePulls() && repo.EnableUnit(UnitTypePullRequests)
486 544
 }
487 545
 
488 546
 // 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) {
997 1055
 		return err
998 1056
 	}
999 1057
 
1058
+	// insert units for repo
1059
+	var units = make([]RepoUnit, 0, len(defaultRepoUnits))
1060
+	for i, tp := range defaultRepoUnits {
1061
+		units = append(units, RepoUnit{
1062
+			RepoID: repo.ID,
1063
+			Type:   tp,
1064
+			Index:  i,
1065
+		})
1066
+	}
1067
+
1068
+	if _, err = e.Insert(&units); err != nil {
1069
+		return err
1070
+	}
1071
+
1000 1072
 	u.NumRepos++
1001 1073
 	// Remember visibility preference.
1002 1074
 	u.LastRepoVisibility = repo.IsPrivate
@@ -1035,15 +1107,12 @@ func CreateRepository(u *User, opts CreateRepoOptions) (_ *Repository, err error
1035 1107
 	}
1036 1108
 
1037 1109
 	repo := &Repository{
1038
-		OwnerID:      u.ID,
1039
-		Owner:        u,
1040
-		Name:         opts.Name,
1041
-		LowerName:    strings.ToLower(opts.Name),
1042
-		Description:  opts.Description,
1043
-		IsPrivate:    opts.IsPrivate,
1044
-		EnableWiki:   true,
1045
-		EnableIssues: true,
1046
-		EnablePulls:  true,
1110
+		OwnerID:     u.ID,
1111
+		Owner:       u,
1112
+		Name:        opts.Name,
1113
+		LowerName:   strings.ToLower(opts.Name),
1114
+		Description: opts.Description,
1115
+		IsPrivate:   opts.IsPrivate,
1047 1116
 	}
1048 1117
 
1049 1118
 	sess := x.NewSession()
@@ -1380,6 +1449,25 @@ func UpdateRepository(repo *Repository, visibilityChanged bool) (err error) {
1380 1449
 	return sess.Commit()
1381 1450
 }
1382 1451
 
1452
+// UpdateRepositoryUnits updates a repository's units
1453
+func UpdateRepositoryUnits(repo *Repository, units []RepoUnit) (err error) {
1454
+	sess := x.NewSession()
1455
+	defer sess.Close()
1456
+	if err = sess.Begin(); err != nil {
1457
+		return err
1458
+	}
1459
+
1460
+	if _, err = sess.Where("repo_id = ?", repo.ID).Delete(new(RepoUnit)); err != nil {
1461
+		return err
1462
+	}
1463
+
1464
+	if _, err = sess.Insert(units); err != nil {
1465
+		return err
1466
+	}
1467
+
1468
+	return sess.Commit()
1469
+}
1470
+
1383 1471
 // DeleteRepository deletes a repository for a user or organization.
1384 1472
 func DeleteRepository(uid, repoID int64) error {
1385 1473
 	repo := &Repository{ID: repoID, OwnerID: uid}
@@ -1467,6 +1555,10 @@ func DeleteRepository(uid, repoID int64) error {
1467 1555
 		return err
1468 1556
 	}
1469 1557
 
1558
+	if _, err = sess.Where("repo_id = ?", repoID).Delete(new(RepoUnit)); err != nil {
1559
+		return err
1560
+	}
1561
+
1470 1562
 	if repo.IsFork {
1471 1563
 		if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil {
1472 1564
 			return fmt.Errorf("decrease fork count: %v", err)

+ 16 - 8
models/repo_test.go

@@ -14,34 +14,42 @@ func TestRepo(t *testing.T) {
14 14
 		repo.Name = "testrepo"
15 15
 		repo.Owner = new(User)
16 16
 		repo.Owner.Name = "testuser"
17
-		repo.ExternalTrackerFormat = "https://someurl.com/{user}/{repo}/{issue}"
17
+		externalTracker := RepoUnit{
18
+			Type: UnitTypeExternalTracker,
19
+			Config: &ExternalTrackerConfig{
20
+				ExternalTrackerFormat: "https://someurl.com/{user}/{repo}/{issue}",
21
+			},
22
+		}
23
+		repo.Units = []*RepoUnit{
24
+			&externalTracker,
25
+		}
18 26
 
19 27
 		Convey("When no external tracker is configured", func() {
20 28
 			Convey("It should be nil", func() {
21
-				repo.EnableExternalTracker = false
29
+				repo.Units = nil
22 30
 				So(repo.ComposeMetas(), ShouldEqual, map[string]string(nil))
23 31
 			})
24 32
 			Convey("It should be nil even if other settings are present", func() {
25
-				repo.EnableExternalTracker = false
26
-				repo.ExternalTrackerFormat = "http://someurl.com/{user}/{repo}/{issue}"
27
-				repo.ExternalTrackerStyle = markdown.IssueNameStyleNumeric
33
+				repo.Units = nil
28 34
 				So(repo.ComposeMetas(), ShouldEqual, map[string]string(nil))
29 35
 			})
30 36
 		})
31 37
 
32 38
 		Convey("When an external issue tracker is configured", func() {
33
-			repo.EnableExternalTracker = true
39
+			repo.Units = []*RepoUnit{
40
+				&externalTracker,
41
+			}
34 42
 			Convey("It should default to numeric issue style", func() {
35 43
 				metas := repo.ComposeMetas()
36 44
 				So(metas["style"], ShouldEqual, markdown.IssueNameStyleNumeric)
37 45
 			})
38 46
 			Convey("It should pass through numeric issue style setting", func() {
39
-				repo.ExternalTrackerStyle = markdown.IssueNameStyleNumeric
47
+				externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markdown.IssueNameStyleNumeric
40 48
 				metas := repo.ComposeMetas()
41 49
 				So(metas["style"], ShouldEqual, markdown.IssueNameStyleNumeric)
42 50
 			})
43 51
 			Convey("It should pass through alphanumeric issue style setting", func() {
44
-				repo.ExternalTrackerStyle = markdown.IssueNameStyleAlphanumeric
52
+				externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markdown.IssueNameStyleAlphanumeric
45 53
 				metas := repo.ComposeMetas()
46 54
 				So(metas["style"], ShouldEqual, markdown.IssueNameStyleAlphanumeric)
47 55
 			})

+ 137 - 0
models/repo_unit.go

@@ -0,0 +1,137 @@
1
+// Copyright 2017 The Gitea Authors. All rights reserved.
2
+// Use of this source code is governed by a MIT-style
3
+// license that can be found in the LICENSE file.
4
+
5
+package models
6
+
7
+import (
8
+	"encoding/json"
9
+	"time"
10
+
11
+	"github.com/Unknwon/com"
12
+	"github.com/go-xorm/core"
13
+	"github.com/go-xorm/xorm"
14
+)
15
+
16
+// RepoUnit describes all units of a repository
17
+type RepoUnit struct {
18
+	ID          int64
19
+	RepoID      int64    `xorm:"INDEX(s)"`
20
+	Type        UnitType `xorm:"INDEX(s)"`
21
+	Index       int
22
+	Config      core.Conversion `xorm:"TEXT"`
23
+	CreatedUnix int64           `xorm:"INDEX CREATED"`
24
+	Created     time.Time       `xorm:"-"`
25
+}
26
+
27
+// UnitConfig describes common unit config
28
+type UnitConfig struct {
29
+}
30
+
31
+// FromDB fills up a UnitConfig from serialized format.
32
+func (cfg *UnitConfig) FromDB(bs []byte) error {
33
+	return json.Unmarshal(bs, &cfg)
34
+}
35
+
36
+// ToDB exports a UnitConfig to a serialized format.
37
+func (cfg *UnitConfig) ToDB() ([]byte, error) {
38
+	return json.Marshal(cfg)
39
+}
40
+
41
+// ExternalWikiConfig describes external wiki config
42
+type ExternalWikiConfig struct {
43
+	ExternalWikiURL string
44
+}
45
+
46
+// FromDB fills up a ExternalWikiConfig from serialized format.
47
+func (cfg *ExternalWikiConfig) FromDB(bs []byte) error {
48
+	return json.Unmarshal(bs, &cfg)
49
+}
50
+
51
+// ToDB exports a ExternalWikiConfig to a serialized format.
52
+func (cfg *ExternalWikiConfig) ToDB() ([]byte, error) {
53
+	return json.Marshal(cfg)
54
+}
55
+
56
+// ExternalTrackerConfig describes external tracker config
57
+type ExternalTrackerConfig struct {
58
+	ExternalTrackerURL    string
59
+	ExternalTrackerFormat string
60
+	ExternalTrackerStyle  string
61
+}
62
+
63
+// FromDB fills up a ExternalTrackerConfig from serialized format.
64
+func (cfg *ExternalTrackerConfig) FromDB(bs []byte) error {
65
+	return json.Unmarshal(bs, &cfg)
66
+}
67
+
68
+// ToDB exports a ExternalTrackerConfig to a serialized format.
69
+func (cfg *ExternalTrackerConfig) ToDB() ([]byte, error) {
70
+	return json.Marshal(cfg)
71
+}
72
+
73
+// BeforeSet is invoked from XORM before setting the value of a field of this object.
74
+func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) {
75
+	switch colName {
76
+	case "type":
77
+		switch UnitType(Cell2Int64(val)) {
78
+		case UnitTypeCode, UnitTypeIssues, UnitTypePullRequests, UnitTypeCommits, UnitTypeReleases,
79
+			UnitTypeWiki, UnitTypeSettings:
80
+			r.Config = new(UnitConfig)
81
+		case UnitTypeExternalWiki:
82
+			r.Config = new(ExternalWikiConfig)
83
+		case UnitTypeExternalTracker:
84
+			r.Config = new(ExternalTrackerConfig)
85
+		default:
86
+			panic("unrecognized repo unit type: " + com.ToStr(*val))
87
+		}
88
+	}
89
+}
90
+
91
+// AfterSet is invoked from XORM after setting the value of a field of this object.
92
+func (r *RepoUnit) AfterSet(colName string, _ xorm.Cell) {
93
+	switch colName {
94
+	case "created_unix":
95
+		r.Created = time.Unix(r.CreatedUnix, 0).Local()
96
+	}
97
+}
98
+
99
+// Unit returns Unit
100
+func (r *RepoUnit) Unit() Unit {
101
+	return Units[r.Type]
102
+}
103
+
104
+// CodeConfig returns config for UnitTypeCode
105
+func (r *RepoUnit) CodeConfig() *UnitConfig {
106
+	return r.Config.(*UnitConfig)
107
+}
108
+
109
+// IssuesConfig returns config for UnitTypeIssues
110
+func (r *RepoUnit) IssuesConfig() *UnitConfig {
111
+	return r.Config.(*UnitConfig)
112
+}
113
+
114
+// PullRequestsConfig returns config for UnitTypePullRequests
115
+func (r *RepoUnit) PullRequestsConfig() *UnitConfig {
116
+	return r.Config.(*UnitConfig)
117
+}
118
+
119
+// CommitsConfig returns config for UnitTypeCommits
120
+func (r *RepoUnit) CommitsConfig() *UnitConfig {
121
+	return r.Config.(*UnitConfig)
122
+}
123
+
124
+// ReleasesConfig returns config for UnitTypeReleases
125
+func (r *RepoUnit) ReleasesConfig() *UnitConfig {
126
+	return r.Config.(*UnitConfig)
127
+}
128
+
129
+// ExternalWikiConfig returns config for UnitTypeExternalWiki
130
+func (r *RepoUnit) ExternalWikiConfig() *ExternalWikiConfig {
131
+	return r.Config.(*ExternalWikiConfig)
132
+}
133
+
134
+// ExternalTrackerConfig returns config for UnitTypeExternalTracker
135
+func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig {
136
+	return r.Config.(*ExternalTrackerConfig)
137
+}

+ 137 - 0
models/unit.go

@@ -0,0 +1,137 @@
1
+// Copyright 2017 The Gitea Authors. All rights reserved.
2
+// Use of this source code is governed by a MIT-style
3
+// license that can be found in the LICENSE file.
4
+
5
+package models
6
+
7
+// UnitType is Unit's Type
8
+type UnitType int
9
+
10
+// Enumerate all the unit types
11
+const (
12
+	UnitTypeCode            UnitType = iota + 1 // 1 code
13
+	UnitTypeIssues                              // 2 issues
14
+	UnitTypePullRequests                        // 3 PRs
15
+	UnitTypeCommits                             // 4 Commits
16
+	UnitTypeReleases                            // 5 Releases
17
+	UnitTypeWiki                                // 6 Wiki
18
+	UnitTypeSettings                            // 7 Settings
19
+	UnitTypeExternalWiki                        // 8 ExternalWiki
20
+	UnitTypeExternalTracker                     // 9 ExternalTracker
21
+)
22
+
23
+// Unit is a tab page of one repository
24
+type Unit struct {
25
+	Type    UnitType
26
+	NameKey string
27
+	URI     string
28
+	DescKey string
29
+	Idx     int
30
+}
31
+
32
+// Enumerate all the units
33
+var (
34
+	UnitCode = Unit{
35
+		UnitTypeCode,
36
+		"repo.code",
37
+		"/",
38
+		"repo.code_desc",
39
+		0,
40
+	}
41
+
42
+	UnitIssues = Unit{
43
+		UnitTypeIssues,
44
+		"repo.issues",
45
+		"/issues",
46
+		"repo.issues_desc",
47
+		1,
48
+	}
49
+
50
+	UnitExternalTracker = Unit{
51
+		UnitTypeExternalTracker,
52
+		"repo.issues",
53
+		"/issues",
54
+		"repo.issues_desc",
55
+		1,
56
+	}
57
+
58
+	UnitPullRequests = Unit{
59
+		UnitTypePullRequests,
60
+		"repo.pulls",
61
+		"/pulls",
62
+		"repo.pulls_desc",
63
+		2,
64
+	}
65
+
66
+	UnitCommits = Unit{
67
+		UnitTypeCommits,
68
+		"repo.commits",
69
+		"/commits/master",
70
+		"repo.commits_desc",
71
+		3,
72
+	}
73
+
74
+	UnitReleases = Unit{
75
+		UnitTypeReleases,
76
+		"repo.releases",
77
+		"/releases",
78
+		"repo.releases_desc",
79
+		4,
80
+	}
81
+
82
+	UnitWiki = Unit{
83
+		UnitTypeWiki,
84
+		"repo.wiki",
85
+		"/wiki",
86
+		"repo.wiki_desc",
87
+		5,
88
+	}
89
+
90
+	UnitExternalWiki = Unit{
91
+		UnitTypeExternalWiki,
92
+		"repo.wiki",
93
+		"/wiki",
94
+		"repo.wiki_desc",
95
+		5,
96
+	}
97
+
98
+	UnitSettings = Unit{
99
+		UnitTypeSettings,
100
+		"repo.settings",
101
+		"/settings",
102
+		"repo.settings_desc",
103
+		6,
104
+	}
105
+
106
+	// defaultRepoUnits contains all the default unit types
107
+	defaultRepoUnits = []UnitType{
108
+		UnitTypeCode,
109
+		UnitTypeIssues,
110
+		UnitTypePullRequests,
111
+		UnitTypeCommits,
112
+		UnitTypeReleases,
113
+		UnitTypeWiki,
114
+		UnitTypeSettings,
115
+	}
116
+
117
+	// MustRepoUnits contains the units could be disabled currently
118
+	MustRepoUnits = []UnitType{
119
+		UnitTypeCode,
120
+		UnitTypeCommits,
121
+		UnitTypeReleases,
122
+		UnitTypeSettings,
123
+	}
124
+
125
+	// Units contains all the units
126
+	Units = map[UnitType]Unit{
127
+		UnitTypeCode:            UnitCode,
128
+		UnitTypeIssues:          UnitIssues,
129
+		UnitTypeExternalTracker: UnitExternalTracker,
130
+		UnitTypePullRequests:    UnitPullRequests,
131
+		UnitTypeCommits:         UnitCommits,
132
+		UnitTypeReleases:        UnitReleases,
133
+		UnitTypeWiki:            UnitWiki,
134
+		UnitTypeExternalWiki:    UnitExternalWiki,
135
+		UnitTypeSettings:        UnitSettings,
136
+	}
137
+)

+ 15 - 0
modules/context/repo.go

@@ -477,3 +477,18 @@ func GitHookService() macaron.Handler {
477 477
 		}
478 478
 	}
479 479
 }
480
+
481
+// UnitTypes returns a macaron middleware to set unit types to context variables.
482
+func UnitTypes() macaron.Handler {
483
+	return func(ctx *Context) {
484
+		ctx.Data["UnitTypeCode"] = models.UnitTypeCode
485
+		ctx.Data["UnitTypeIssues"] = models.UnitTypeIssues
486
+		ctx.Data["UnitTypePullRequests"] = models.UnitTypePullRequests
487
+		ctx.Data["UnitTypeCommits"] = models.UnitTypeCommits
488
+		ctx.Data["UnitTypeReleases"] = models.UnitTypeReleases
489
+		ctx.Data["UnitTypeWiki"] = models.UnitTypeWiki
490
+		ctx.Data["UnitTypeSettings"] = models.UnitTypeSettings
491
+		ctx.Data["UnitTypeExternalWiki"] = models.UnitTypeExternalWiki
492
+		ctx.Data["UnitTypeExternalTracker"] = models.UnitTypeExternalTracker
493
+	}
494
+}

+ 1 - 1
routers/api/v1/api.go

@@ -205,7 +205,7 @@ func orgAssignment(args ...bool) macaron.Handler {
205 205
 }
206 206
 
207 207
 func mustEnableIssues(ctx *context.APIContext) {
208
-	if !ctx.Repo.Repository.EnableIssues || ctx.Repo.Repository.EnableExternalTracker {
208
+	if !ctx.Repo.Repository.EnableUnit(models.UnitTypeIssues) {
209 209
 		ctx.Status(404)
210 210
 		return
211 211
 	}

+ 5 - 3
routers/repo/issue.go

@@ -59,13 +59,15 @@ var (
59 59
 
60 60
 // MustEnableIssues check if repository enable internal issues
61 61
 func MustEnableIssues(ctx *context.Context) {
62
-	if !ctx.Repo.Repository.EnableIssues {
62
+	if !ctx.Repo.Repository.EnableUnit(models.UnitTypeIssues) &&
63
+		!ctx.Repo.Repository.EnableUnit(models.UnitTypeExternalTracker) {
63 64
 		ctx.Handle(404, "MustEnableIssues", nil)
64 65
 		return
65 66
 	}
66 67
 
67
-	if ctx.Repo.Repository.EnableExternalTracker {
68
-		ctx.Redirect(ctx.Repo.Repository.ExternalTrackerURL)
68
+	unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker)
69
+	if err == nil {
70
+		ctx.Redirect(unit.ExternalTrackerConfig().ExternalTrackerURL)
69 71
 		return
70 72
 	}
71 73
 }

+ 64 - 18
routers/repo/setting.go

@@ -143,18 +143,70 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
143 143
 		ctx.Redirect(repo.Link() + "/settings")
144 144
 
145 145
 	case "advanced":
146
-		repo.EnableWiki = form.EnableWiki
147
-		repo.EnableExternalWiki = form.EnableExternalWiki
148
-		repo.ExternalWikiURL = form.ExternalWikiURL
149
-		repo.EnableIssues = form.EnableIssues
150
-		repo.EnableExternalTracker = form.EnableExternalTracker
151
-		repo.ExternalTrackerURL = form.ExternalTrackerURL
152
-		repo.ExternalTrackerFormat = form.TrackerURLFormat
153
-		repo.ExternalTrackerStyle = form.TrackerIssueStyle
154
-		repo.EnablePulls = form.EnablePulls
155
-
156
-		if err := models.UpdateRepository(repo, false); err != nil {
157
-			ctx.Handle(500, "UpdateRepository", err)
146
+		var units []models.RepoUnit
147
+
148
+		for _, tp := range models.MustRepoUnits {
149
+			units = append(units, models.RepoUnit{
150
+				RepoID: repo.ID,
151
+				Type:   tp,
152
+				Index:  int(tp),
153
+				Config: new(models.UnitConfig),
154
+			})
155
+		}
156
+
157
+		if form.EnableWiki {
158
+			if form.EnableExternalWiki {
159
+				units = append(units, models.RepoUnit{
160
+					RepoID: repo.ID,
161
+					Type:   models.UnitTypeExternalWiki,
162
+					Index:  int(models.UnitTypeExternalWiki),
163
+					Config: &models.ExternalWikiConfig{
164
+						ExternalWikiURL: form.ExternalWikiURL,
165
+					},
166
+				})
167
+			} else {
168
+				units = append(units, models.RepoUnit{
169
+					RepoID: repo.ID,
170
+					Type:   models.UnitTypeWiki,
171
+					Index:  int(models.UnitTypeWiki),
172
+					Config: new(models.UnitConfig),
173
+				})
174
+			}
175
+		}
176
+
177
+		if form.EnableIssues {
178
+			if form.EnableExternalTracker {
179
+				units = append(units, models.RepoUnit{
180
+					RepoID: repo.ID,
181
+					Type:   models.UnitTypeExternalWiki,
182
+					Index:  int(models.UnitTypeExternalWiki),
183
+					Config: &models.ExternalTrackerConfig{
184
+						ExternalTrackerURL:    form.ExternalTrackerURL,
185
+						ExternalTrackerFormat: form.TrackerURLFormat,
186
+						ExternalTrackerStyle:  form.TrackerIssueStyle,
187
+					},
188
+				})
189
+			} else {
190
+				units = append(units, models.RepoUnit{
191
+					RepoID: repo.ID,
192
+					Type:   models.UnitTypeIssues,
193
+					Index:  int(models.UnitTypeIssues),
194
+					Config: new(models.UnitConfig),
195
+				})
196
+			}
197
+		}
198
+
199
+		if form.EnablePulls {
200
+			units = append(units, models.RepoUnit{
201
+				RepoID: repo.ID,
202
+				Type:   models.UnitTypePullRequests,
203
+				Index:  int(models.UnitTypePullRequests),
204
+				Config: new(models.UnitConfig),
205
+			})
206
+		}
207
+
208
+		if err := models.UpdateRepositoryUnits(repo, units); err != nil {
209
+			ctx.Handle(500, "UpdateRepositoryUnits", err)
158 210
 			return
159 211
 		}
160 212
 		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) {
281 333
 		repo.DeleteWiki()
282 334
 		log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
283 335
 
284
-		repo.EnableWiki = false
285
-		if err := models.UpdateRepository(repo, false); err != nil {
286
-			ctx.Handle(500, "UpdateRepository", err)
287
-			return
288
-		}
289
-
290 336
 		ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success"))
291 337
 		ctx.Redirect(ctx.Repo.RepoLink + "/settings")
292 338
 

+ 5 - 3
routers/repo/wiki.go

@@ -27,13 +27,15 @@ const (
27 27
 
28 28
 // MustEnableWiki check if wiki is enabled, if external then redirect
29 29
 func MustEnableWiki(ctx *context.Context) {
30
-	if !ctx.Repo.Repository.EnableWiki {
30
+	if !ctx.Repo.Repository.EnableUnit(models.UnitTypeWiki) &&
31
+		!ctx.Repo.Repository.EnableUnit(models.UnitTypeExternalWiki) {
31 32
 		ctx.Handle(404, "MustEnableWiki", nil)
32 33
 		return
33 34
 	}
34 35
 
35
-	if ctx.Repo.Repository.EnableExternalWiki {
36
-		ctx.Redirect(ctx.Repo.Repository.ExternalWikiURL)
36
+	unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalWiki)
37
+	if err == nil {
38
+		ctx.Redirect(unit.ExternalWikiConfig().ExternalWikiURL)
37 39
 		return
38 40
 	}
39 41
 }

+ 1 - 1
routers/user/home.go

@@ -236,7 +236,7 @@ func Issues(ctx *context.Context) {
236 236
 	for _, repo := range repos {
237 237
 		if (isPullList && repo.NumPulls == 0) ||
238 238
 			(!isPullList &&
239
-				(!repo.EnableIssues || repo.EnableExternalTracker || repo.NumIssues == 0)) {
239
+				(!repo.EnableUnit(models.UnitTypeIssues) || repo.NumIssues == 0)) {
240 240
 			continue
241 241
 		}
242 242
 

+ 21 - 3
templates/repo/header.tmpl

@@ -49,30 +49,48 @@
49 49
 {{if not (or .IsBareRepo .IsDiffCompare)}}
50 50
 	<div class="ui tabs container">
51 51
 		<div class="ui tabular menu navbar">
52
+			{{if .Repository.EnableUnit $.UnitTypeCode}}
52 53
 			<a class="{{if .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}">
53 54
 				<i class="octicon octicon-code"></i> {{.i18n.Tr "repo.code"}}
54 55
 			</a>
55
-			{{if .Repository.EnableIssues}}
56
+			{{end}}
57
+
58
+			{{if .Repository.EnableUnit $.UnitTypeIssues}}
59
+				<a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues">
60
+					<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>
61
+				</a>
62
+			{{end}}
63
+
64
+			{{if .Repository.EnableUnit $.UnitTypeExternalTracker}}
56 65
 				<a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues">
57
-					<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>
66
+					<i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues"}} </span>
58 67
 				</a>
59 68
 			{{end}}
69
+			
60 70
 			{{if .Repository.AllowsPulls}}
61 71
 				<a class="{{if .PageIsPullList}}active{{end}} item" href="{{.RepoLink}}/pulls">
62 72
 					<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>
63 73
 				</a>
64 74
 			{{end}}
75
+
76
+			{{if .Repository.EnableUnit $.UnitTypeCommits}}
65 77
 			<a class="{{if (or (.PageIsCommits) (.PageIsDiff))}}active{{end}} item" href="{{.RepoLink}}/commits/{{EscapePound .BranchName}}">
66 78
 				<i class="octicon octicon-history"></i> {{.i18n.Tr "repo.commits"}} <span class="ui {{if not .CommitsCount}}gray{{else}}blue{{end}} small label">{{.CommitsCount}}</span>
67 79
 			</a>
80
+			{{end}}
81
+
82
+			{{if .Repository.EnableUnit $.UnitTypeReleases}}
68 83
 			<a class="{{if .PageIsReleaseList}}active{{end}} item" href="{{.RepoLink}}/releases">
69 84
 				<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>
70 85
 			</a>
71
-			{{if .Repository.EnableWiki}}
86
+			{{end}}
87
+
88
+			{{if or (.Repository.EnableUnit $.UnitTypeWiki) (.Repository.EnableUnit $.UnitTypeExternalWiki)}}
72 89
 				<a class="{{if .PageIsWiki}}active{{end}} item" href="{{.RepoLink}}/wiki">
73 90
 					<i class="octicon octicon-book"></i> {{.i18n.Tr "repo.wiki"}}
74 91
 				</a>
75 92
 			{{end}}
93
+
76 94
 			{{if .IsRepositoryAdmin}}
77 95
 				<div class="right menu">
78 96
 					<a class="{{if .PageIsSettings}}active{{end}} item" href="{{.RepoLink}}/settings">

+ 20 - 18
templates/repo/settings/options.tmpl

@@ -114,26 +114,26 @@
114 114
 						<div class="inline field">
115 115
 							<label>{{.i18n.Tr "repo.wiki"}}</label>
116 116
 							<div class="ui checkbox">
117
-								<input class="enable-system" name="enable_wiki" type="checkbox" data-target="#wiki_box" {{if .Repository.EnableWiki}}checked{{end}}>
117
+								<input class="enable-system" name="enable_wiki" type="checkbox" data-target="#wiki_box" {{if or (.Repository.EnableUnit $.UnitTypeWiki) (.Repository.EnableUnit $.UnitTypeExternalWiki)}}checked{{end}}>
118 118
 								<label>{{.i18n.Tr "repo.settings.wiki_desc"}}</label>
119 119
 							</div>
120 120
 						</div>
121
-						<div class="field {{if not .Repository.EnableWiki}}disabled{{end}}" id="wiki_box">
121
+						<div class="field {{if not (.Repository.EnableUnit $.UnitTypeWiki)}}disabled{{end}}" id="wiki_box">
122 122
 							<div class="field">
123 123
 								<div class="ui radio checkbox">
124
-									<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}}/>
124
+									<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}}/>
125 125
 									<label>{{.i18n.Tr "repo.settings.use_internal_wiki"}}</label>
126 126
 								</div>
127 127
 							</div>
128 128
 							<div class="field">
129 129
 								<div class="ui radio checkbox">
130
-									<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}}/>
130
+									<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}}/>
131 131
 									<label>{{.i18n.Tr "repo.settings.use_external_wiki"}}</label>
132 132
 								</div>
133 133
 							</div>
134
-							<div class="field {{if not .Repository.EnableExternalWiki}}disabled{{end}}" id="external_wiki_box">
134
+							<div class="field {{if not (.Repository.EnableUnit $.UnitTypeExternalWiki)}}disabled{{end}}" id="external_wiki_box">
135 135
 								<label for="external_wiki_url">{{.i18n.Tr "repo.settings.external_wiki_url"}}</label>
136
-								<input id="external_wiki_url" name="external_wiki_url" type="url" value="{{.Repository.ExternalWikiURL}}">
136
+								<input id="external_wiki_url" name="external_wiki_url" type="url" value="{{(.Repository.MustGetUnit $.UnitTypeExternalWiki).ExternalWikiConfig.ExternalWikiURL}}">
137 137
 								<p class="help">{{.i18n.Tr "repo.settings.external_wiki_url_desc"}}</p>
138 138
 							</div>
139 139
 						</div>
@@ -143,45 +143,47 @@
143 143
 						<div class="inline field">
144 144
 							<label>{{.i18n.Tr "repo.issues"}}</label>
145 145
 							<div class="ui checkbox">
146
-								<input class="enable-system" name="enable_issues" type="checkbox" data-target="#issue_box" {{if .Repository.EnableIssues}}checked{{end}}>
146
+								<input class="enable-system" name="enable_issues" type="checkbox" data-target="#issue_box" {{if or (.Repository.EnableUnit $.UnitTypeIssues) (.Repository.EnableUnit $.UnitTypeExternalTracker)}}checked{{end}}>
147 147
 								<label>{{.i18n.Tr "repo.settings.issues_desc"}}</label>
148 148
 							</div>
149 149
 						</div>
150
-						<div class="field {{if not .Repository.EnableIssues}}disabled{{end}}" id="issue_box">
150
+						<div class="field {{if not (.Repository.EnableUnit $.UnitTypeIssues)}}disabled{{end}}" id="issue_box">
151 151
 							<div class="field">
152 152
 								<div class="ui radio checkbox">
153
-									<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}}/>
153
+									<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}}/>
154 154
 									<label>{{.i18n.Tr "repo.settings.use_internal_issue_tracker"}}</label>
155 155
 								</div>
156 156
 							</div>
157 157
 							<div class="field">
158 158
 								<div class="ui radio checkbox">
159
-									<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}}/>
159
+									<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}}/>
160 160
 									<label>{{.i18n.Tr "repo.settings.use_external_issue_tracker"}}</label>
161 161
 								</div>
162 162
 							</div>
163
-							<div class="field {{if not .Repository.EnableExternalTracker}}disabled{{end}}" id="external_issue_box">
163
+							<div class="field {{if not (.Repository.EnableUnit $.UnitTypeExternalTracker)}}disabled{{end}}" id="external_issue_box">
164 164
 								<div class="field">
165 165
 									<label for="external_tracker_url">{{.i18n.Tr "repo.settings.external_tracker_url"}}</label>
166
-									<input id="external_tracker_url" name="external_tracker_url" type="url" value="{{.Repository.ExternalTrackerURL}}">
166
+									<input id="external_tracker_url" name="external_tracker_url" type="url" value="{{(.Repository.MustGetUnit $.UnitTypeExternalTracker).ExternalTrackerConfig.ExternalTrackerURL}}">
167 167
 									<p class="help">{{.i18n.Tr "repo.settings.external_tracker_url_desc"}}</p>
168 168
 								</div>
169 169
 								<div class="field">
170 170
 									<label for="tracker_url_format">{{.i18n.Tr "repo.settings.tracker_url_format"}}</label>
171
-									<input id="tracker_url_format" name="tracker_url_format" type="url" value="{{.Repository.ExternalTrackerFormat}}" placeholder="e.g. https://github.com/{user}/{repo}/issues/{index}">
171
+									<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}">
172 172
 									<p class="help">{{.i18n.Tr "repo.settings.tracker_url_format_desc" | Str2html}}</p>
173 173
 								</div>
174 174
 								<div class="inline fields">
175 175
 									<label for="issue_style">{{.i18n.Tr "repo.settings.tracker_issue_style"}}</label>
176 176
 									<div class="field">
177 177
 										<div class="ui radio checkbox">
178
-											<input class="hidden" tabindex="0" name="tracker_issue_style" type="radio" value="numeric"  {{if eq .Repository.ExternalTrackerStyle "numeric"}}checked=""{{end}}/>
178
+										{{$externalTracker := (.Repository.MustGetUnit $.UnitTypeExternalTracker)}}
179
+										{{$externalTrackerStyle := $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle}}
180
+											<input class="hidden" tabindex="0" name="tracker_issue_style" type="radio" value="numeric" {{if $externalTrackerStyle}}{{if eq $externalTrackerStyle "numeric"}}checked=""{{end}}{{end}}/>
179 181
 											<label>{{.i18n.Tr "repo.settings.tracker_issue_style.numeric"}} <span class="ui light grey text">(#1234)</span></label>
180 182
 										</div>
181 183
 									</div>
182 184
 									<div class="field">
183 185
 										<div class="ui radio checkbox">
184
-											<input class="hidden" tabindex="0" name="tracker_issue_style" type="radio" value="alphanumeric"  {{if eq .Repository.ExternalTrackerStyle "alphanumeric"}}checked=""{{end}}/>
186
+											<input class="hidden" tabindex="0" name="tracker_issue_style" type="radio" value="alphanumeric" {{if $externalTrackerStyle}}{{if eq $externalTracker.ExternalTrackerConfig.ExternalTrackerStyle "alphanumeric"}}checked=""{{end}}{{end}} />
185 187
 											<label>{{.i18n.Tr "repo.settings.tracker_issue_style.alphanumeric"}} <span class="ui light grey text">(ABC-123, DEFG-234)</span></label>
186 188
 										</div>
187 189
 									</div>
@@ -195,7 +197,7 @@
195 197
 							<div class="inline field">
196 198
 								<label>{{.i18n.Tr "repo.pulls"}}</label>
197 199
 								<div class="ui checkbox">
198
-									<input name="enable_pulls" type="checkbox" {{if .Repository.EnablePulls}}checked{{end}}>
200
+									<input name="enable_pulls" type="checkbox" {{if .Repository.EnableUnit $.UnitTypePullRequests}}checked{{end}}>
199 201
 									<label>{{.i18n.Tr "repo.settings.pulls_desc"}}</label>
200 202
 								</div>
201 203
 							</div>
@@ -236,7 +238,7 @@
236 238
 						</div>
237 239
 					</div>
238 240
 
239
-					{{if .Repository.EnableWiki}}
241
+					{{if .Repository.EnableUnit $.UnitTypeWiki}}
240 242
 						<div class="ui divider"></div>
241 243
 
242 244
 						<div class="item">
@@ -370,7 +372,7 @@
370 372
 		</div>
371 373
 	</div>
372 374
 
373
-	{{if .Repository.EnableWiki}}
375
+	{{if .Repository.EnableUnit $.UnitTypeWiki}}
374 376
 	<div class="ui small modal" id="delete-wiki-modal">
375 377
 		<div class="header">
376 378
 			{{.i18n.Tr "repo.settings.wiki-delete"}}