#1575 Limit repo creation

This commit is contained in:
Unknwon 2015-12-10 12:37:53 -05:00
parent c6083c335e
commit 2a0bb1fa90
15 changed files with 73 additions and 14 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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)
}
// __ __.__ __ .__ // __ __.__ __ .__
// / \ / \__| | _|__| // / \ / \__| | _|__|
// \ \/\/ / | |/ / | // \ \/\/ / | |/ / |

View file

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

View file

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

View file

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

File diff suppressed because one or more lines are too long

View file

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

View file

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

View file

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

View file

@ -1 +1 @@
0.7.38.1210 Beta 0.7.39.1210 Beta

View file

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