| @@ -5,7 +5,7 @@ Gogs - Go Git Service [ |  | ||||
| ##### 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 | ||||
| @@ -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() | |||||
| Repository.ForcePrivate = sec.Key("FORCE_PRIVATE").MustBool() | |||||
| Repository.PullRequestQueueLength = sec.Key("PULL_REQUEST_QUEUE_LENGTH").MustInt(10000) | |||||
| if err = Cfg.Section("repository").MapTo(&Repository); err != nil { | |||||
| log.Fatal(4, "Fail to map Repository settings: %v", err) | |||||
| } | |||||
| // 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> | ||||