#1575 Limit repo creation
This commit is contained in:
Родитель
c6083c335e
Коммит
2a0bb1fa90
|
@ -5,7 +5,7 @@ Gogs - Go Git Service [![Build Status](https://travis-ci.org/gogits/gogs.svg?bra
|
||||||
|
|
||||||
![](public/img/gogs-large-resize.png)
|
![](public/img/gogs-large-resize.png)
|
||||||
|
|
||||||
##### Current version: 0.7.38 Beta
|
##### Current version: 0.7.39 Beta
|
||||||
|
|
||||||
| Web | UI | Preview |
|
| Web | UI | Preview |
|
||||||
|:-------------:|:-------:|:-------:|
|
|:-------------:|:-------:|:-------:|
|
||||||
|
|
|
@ -86,7 +86,7 @@ func checkVersion() {
|
||||||
{"github.com/go-macaron/i18n", i18n.Version, "0.2.0"},
|
{"github.com/go-macaron/i18n", i18n.Version, "0.2.0"},
|
||||||
{"github.com/go-macaron/session", session.Version, "0.1.6"},
|
{"github.com/go-macaron/session", session.Version, "0.1.6"},
|
||||||
{"github.com/go-macaron/toolbox", toolbox.Version, "0.1.0"},
|
{"github.com/go-macaron/toolbox", toolbox.Version, "0.1.0"},
|
||||||
{"gopkg.in/ini.v1", ini.Version, "1.8.1"},
|
{"gopkg.in/ini.v1", ini.Version, "1.8.3"},
|
||||||
{"gopkg.in/macaron.v1", macaron.Version, "0.8.0"},
|
{"gopkg.in/macaron.v1", macaron.Version, "0.8.0"},
|
||||||
{"github.com/gogits/git-shell", git.Version, "0.1.0"},
|
{"github.com/gogits/git-shell", git.Version, "0.1.0"},
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ SCRIPT_TYPE = bash
|
||||||
ANSI_CHARSET =
|
ANSI_CHARSET =
|
||||||
; Force every new repository to be private
|
; Force every new repository to be private
|
||||||
FORCE_PRIVATE = false
|
FORCE_PRIVATE = false
|
||||||
|
; Global maximum creation limit of repository per user, 0 means no limit
|
||||||
|
MAX_CREATION_LIMIT = 0
|
||||||
; Patch test queue length, make it as large as possible
|
; Patch test queue length, make it as large as possible
|
||||||
PULL_REQUEST_QUEUE_LENGTH = 10000
|
PULL_REQUEST_QUEUE_LENGTH = 10000
|
||||||
|
|
||||||
|
|
|
@ -359,6 +359,7 @@ watchers = Watchers
|
||||||
stargazers = Stargazers
|
stargazers = Stargazers
|
||||||
forks = Forks
|
forks = Forks
|
||||||
|
|
||||||
|
form.reach_limit_of_creation = The owner has reached maximum creation limit of %d repositories.
|
||||||
form.name_reserved = Repository name '%s' is reserved.
|
form.name_reserved = Repository name '%s' is reserved.
|
||||||
form.name_pattern_not_allowed = Repository name pattern '%s' is not allowed.
|
form.name_pattern_not_allowed = Repository name pattern '%s' is not allowed.
|
||||||
|
|
||||||
|
@ -855,6 +856,8 @@ users.auth_login_name = Authentication Login Name
|
||||||
users.password_helper = Leave it empty to remain unchanged.
|
users.password_helper = Leave it empty to remain unchanged.
|
||||||
users.update_profile_success = Account profile has been updated successfully.
|
users.update_profile_success = Account profile has been updated successfully.
|
||||||
users.edit_account = Edit Account
|
users.edit_account = Edit Account
|
||||||
|
users.max_repo_creation = Maximum Repository Creation Limit
|
||||||
|
users.max_repo_creation_desc = (Set 0 to use gloabl default limit)
|
||||||
users.is_activated = This account is activated
|
users.is_activated = This account is activated
|
||||||
users.is_admin = This account has administrator permissions
|
users.is_admin = This account has administrator permissions
|
||||||
users.allow_git_hook = This account has permissions to create Git hooks
|
users.allow_git_hook = This account has permissions to create Git hooks
|
||||||
|
|
2
gogs.go
2
gogs.go
|
@ -18,7 +18,7 @@ import (
|
||||||
"github.com/gogits/gogs/modules/setting"
|
"github.com/gogits/gogs/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
const APP_VER = "0.7.38.1210 Beta"
|
const APP_VER = "0.7.39.1210 Beta"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
|
|
@ -107,6 +107,19 @@ func (err ErrUserHasOrgs) Error() string {
|
||||||
return fmt.Sprintf("user still has membership of organizations [uid: %d]", err.UID)
|
return fmt.Sprintf("user still has membership of organizations [uid: %d]", err.UID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ErrReachLimitOfRepo struct {
|
||||||
|
Limit int
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsErrReachLimitOfRepo(err error) bool {
|
||||||
|
_, ok := err.(ErrReachLimitOfRepo)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrReachLimitOfRepo) Error() string {
|
||||||
|
return fmt.Sprintf("user has reached maximum limit of repositories [limit: %d]", err.Limit)
|
||||||
|
}
|
||||||
|
|
||||||
// __ __.__ __ .__
|
// __ __.__ __ .__
|
||||||
// / \ / \__| | _|__|
|
// / \ / \__| | _|__|
|
||||||
// \ \/\/ / | |/ / |
|
// \ \/\/ / | |/ / |
|
||||||
|
|
|
@ -900,6 +900,10 @@ func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) {
|
||||||
|
|
||||||
// CreateRepository creates a repository for given user or organization.
|
// CreateRepository creates a repository for given user or organization.
|
||||||
func CreateRepository(u *User, opts CreateRepoOptions) (_ *Repository, err error) {
|
func CreateRepository(u *User, opts CreateRepoOptions) (_ *Repository, err error) {
|
||||||
|
if !u.CanCreateRepo() {
|
||||||
|
return nil, ErrReachLimitOfRepo{u.MaxRepoCreation}
|
||||||
|
}
|
||||||
|
|
||||||
repo := &Repository{
|
repo := &Repository{
|
||||||
OwnerID: u.Id,
|
OwnerID: u.Id,
|
||||||
Owner: u,
|
Owner: u,
|
||||||
|
|
|
@ -75,6 +75,8 @@ type User struct {
|
||||||
|
|
||||||
// Remember visibility choice for convenience, true for private
|
// Remember visibility choice for convenience, true for private
|
||||||
LastRepoVisibility bool
|
LastRepoVisibility bool
|
||||||
|
// Maximum repository creation limit, 0 means use gloabl default
|
||||||
|
MaxRepoCreation int `xorm:"NOT NULL"`
|
||||||
|
|
||||||
// Permissions.
|
// Permissions.
|
||||||
IsActive bool
|
IsActive bool
|
||||||
|
@ -101,6 +103,12 @@ type User struct {
|
||||||
Members []*User `xorm:"-"`
|
Members []*User `xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) BeforeUpdate() {
|
||||||
|
if u.MaxRepoCreation < 0 {
|
||||||
|
u.MaxRepoCreation = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) AfterSet(colName string, _ xorm.Cell) {
|
func (u *User) AfterSet(colName string, _ xorm.Cell) {
|
||||||
switch colName {
|
switch colName {
|
||||||
case "full_name":
|
case "full_name":
|
||||||
|
@ -116,6 +124,20 @@ func (u *User) HasForkedRepo(repoID int64) bool {
|
||||||
return has
|
return has
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) RepoCreationNum() int {
|
||||||
|
if u.MaxRepoCreation == 0 {
|
||||||
|
return setting.Repository.MaxCreationLimit
|
||||||
|
}
|
||||||
|
return u.MaxRepoCreation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) CanCreateRepo() bool {
|
||||||
|
if u.MaxRepoCreation == 0 {
|
||||||
|
return u.NumRepos < setting.Repository.MaxCreationLimit
|
||||||
|
}
|
||||||
|
return u.NumRepos < u.MaxRepoCreation
|
||||||
|
}
|
||||||
|
|
||||||
// CanEditGitHook returns true if user can edit Git hooks.
|
// CanEditGitHook returns true if user can edit Git hooks.
|
||||||
func (u *User) CanEditGitHook() bool {
|
func (u *User) CanEditGitHook() bool {
|
||||||
return u.IsAdmin || u.AllowGitHook
|
return u.IsAdmin || u.AllowGitHook
|
||||||
|
|
|
@ -31,6 +31,7 @@ type AdminEditUserForm struct {
|
||||||
Password string `binding:"MaxSize(255)"`
|
Password string `binding:"MaxSize(255)"`
|
||||||
Website string `binding:"MaxSize(50)"`
|
Website string `binding:"MaxSize(50)"`
|
||||||
Location string `binding:"MaxSize(50)"`
|
Location string `binding:"MaxSize(50)"`
|
||||||
|
MaxRepoCreation int
|
||||||
Active bool
|
Active bool
|
||||||
Admin bool
|
Admin bool
|
||||||
AllowGitHook bool
|
AllowGitHook bool
|
||||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -98,6 +98,7 @@ var (
|
||||||
Repository struct {
|
Repository struct {
|
||||||
AnsiCharset string
|
AnsiCharset string
|
||||||
ForcePrivate bool
|
ForcePrivate bool
|
||||||
|
MaxCreationLimit int
|
||||||
PullRequestQueueLength int
|
PullRequestQueueLength int
|
||||||
}
|
}
|
||||||
RepoRootPath string
|
RepoRootPath string
|
||||||
|
@ -379,9 +380,9 @@ func NewContext() {
|
||||||
RepoRootPath = path.Clean(RepoRootPath)
|
RepoRootPath = path.Clean(RepoRootPath)
|
||||||
}
|
}
|
||||||
ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash")
|
ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash")
|
||||||
Repository.AnsiCharset = sec.Key("ANSI_CHARSET").String()
|
if err = Cfg.Section("repository").MapTo(&Repository); err != nil {
|
||||||
Repository.ForcePrivate = sec.Key("FORCE_PRIVATE").MustBool()
|
log.Fatal(4, "Fail to map Repository settings: %v", err)
|
||||||
Repository.PullRequestQueueLength = sec.Key("PULL_REQUEST_QUEUE_LENGTH").MustInt(10000)
|
}
|
||||||
|
|
||||||
// UI settings.
|
// UI settings.
|
||||||
sec = Cfg.Section("ui")
|
sec = Cfg.Section("ui")
|
||||||
|
|
|
@ -210,6 +210,7 @@ func EditUserPost(ctx *middleware.Context, form auth.AdminEditUserForm) {
|
||||||
u.Email = form.Email
|
u.Email = form.Email
|
||||||
u.Website = form.Website
|
u.Website = form.Website
|
||||||
u.Location = form.Location
|
u.Location = form.Location
|
||||||
|
u.MaxRepoCreation = form.MaxRepoCreation
|
||||||
u.IsActive = form.Active
|
u.IsActive = form.Active
|
||||||
u.IsAdmin = form.Admin
|
u.IsAdmin = form.Admin
|
||||||
u.AllowGitHook = form.AllowGitHook
|
u.AllowGitHook = form.AllowGitHook
|
||||||
|
|
|
@ -78,8 +78,10 @@ func Create(ctx *middleware.Context) {
|
||||||
ctx.HTML(200, CREATE)
|
ctx.HTML(200, CREATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCreateError(ctx *middleware.Context, err error, name string, tpl base.TplName, form interface{}) {
|
func handleCreateError(ctx *middleware.Context, owner *models.User, err error, name string, tpl base.TplName, form interface{}) {
|
||||||
switch {
|
switch {
|
||||||
|
case models.IsErrReachLimitOfRepo(err):
|
||||||
|
ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_creation", owner.RepoCreationNum()), tpl, form)
|
||||||
case models.IsErrRepoAlreadyExist(err):
|
case models.IsErrRepoAlreadyExist(err):
|
||||||
ctx.Data["Err_RepoName"] = true
|
ctx.Data["Err_RepoName"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form)
|
ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form)
|
||||||
|
@ -133,7 +135,7 @@ func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCreateError(ctx, err, "CreatePost", CREATE, &form)
|
handleCreateError(ctx, ctxUser, err, "CreatePost", CREATE, &form)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Migrate(ctx *middleware.Context) {
|
func Migrate(ctx *middleware.Context) {
|
||||||
|
@ -216,7 +218,7 @@ func MigratePost(ctx *middleware.Context, form auth.MigrateRepoForm) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCreateError(ctx, err, "MigratePost", MIGRATE, &form)
|
handleCreateError(ctx, ctxUser, err, "MigratePost", MIGRATE, &form)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Action(ctx *middleware.Context) {
|
func Action(ctx *middleware.Context) {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
0.7.38.1210 Beta
|
0.7.39.1210 Beta
|
|
@ -57,6 +57,16 @@
|
||||||
<input id="location" name="location" value="{{.User.Location}}">
|
<input id="location" name="location" value="{{.User.Location}}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
|
<div class="inline field {{if .Err_MaxRepoCreation}}error{{end}}">
|
||||||
|
<label for="max_repo_creation">{{.i18n.Tr "admin.users.max_repo_creation"}}</label>
|
||||||
|
<input id="max_repo_creation" name="max_repo_creation" type="number" value="{{.User.MaxRepoCreation}}">
|
||||||
|
<p class="help">{{.i18n.Tr "admin.users.max_repo_creation_desc"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
<div class="inline field">
|
<div class="inline field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<label><strong>{{.i18n.Tr "admin.users.is_activated"}}</strong></label>
|
<label><strong>{{.i18n.Tr "admin.users.is_activated"}}</strong></label>
|
||||||
|
|
Загрузка…
Ссылка в новой задаче