rebase on master

This commit is contained in:
Antoine GIRARD 2020-11-07 22:52:16 +01:00
parent 0615b668dc
commit bb96349d09
13 changed files with 99 additions and 5 deletions

View File

@ -254,6 +254,8 @@ var migrations = []Migration{
NewMigration("code comment replies should have the commitID of the review they are replying to", updateCodeCommentReplies), NewMigration("code comment replies should have the commitID of the review they are replying to", updateCodeCommentReplies),
// v159 -> v160 // v159 -> v160
NewMigration("update reactions constraint", updateReactionConstraint), NewMigration("update reactions constraint", updateReactionConstraint),
// v160 -> v161
NewMigration("add size limit on repository", addSizeLimitOnRepo),
} }
// GetCurrentDBVersion returns the current db version // GetCurrentDBVersion returns the current db version

16
models/migrations/v160.go Normal file
View File

@ -0,0 +1,16 @@
// Copyright 2020 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 migrations
import "xorm.io/xorm"
func addSizeLimitOnRepo(x *xorm.Engine) error {
type Repository struct {
ID int64 `xorm:"pk autoincr"`
SizeLimit int64 `xorm:"NOT NULL DEFAULT 0"`
}
return x.Sync2(new(Repository))
}

View File

@ -230,6 +230,7 @@ type Repository struct {
TemplateID int64 `xorm:"INDEX"` TemplateID int64 `xorm:"INDEX"`
TemplateRepo *Repository `xorm:"-"` TemplateRepo *Repository `xorm:"-"`
Size int64 `xorm:"NOT NULL DEFAULT 0"` Size int64 `xorm:"NOT NULL DEFAULT 0"`
SizeLimit int64 `xorm:"NOT NULL DEFAULT 0"`
CodeIndexerStatus *RepoIndexerStatus `xorm:"-"` CodeIndexerStatus *RepoIndexerStatus `xorm:"-"`
StatsIndexerStatus *RepoIndexerStatus `xorm:"-"` StatsIndexerStatus *RepoIndexerStatus `xorm:"-"`
IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"` IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"`
@ -860,20 +861,29 @@ func (repo *Repository) IsOwnedBy(userID int64) bool {
return repo.OwnerID == userID return repo.OwnerID == userID
} }
func (repo *Repository) updateSize(e Engine) error { func (repo *Repository) computeSize() (int64, error) {
size, err := util.GetDirectorySize(repo.RepoPath()) size, err := util.GetDirectorySize(repo.RepoPath())
if err != nil { if err != nil {
return fmt.Errorf("updateSize: %v", err) return 0, fmt.Errorf("computeSize: %v", err)
} }
objs, err := repo.GetLFSMetaObjects(-1, 0) objs, err := repo.GetLFSMetaObjects(-1, 0)
if err != nil { if err != nil {
return fmt.Errorf("updateSize: GetLFSMetaObjects: %v", err) return 0, fmt.Errorf("computeSize: GetLFSMetaObjects: %v", err)
} }
for _, obj := range objs { for _, obj := range objs {
size += obj.Size size += obj.Size
} }
return size, nil
}
func (repo *Repository) updateSize(e Engine) error {
size, err := repo.computeSize()
if err != nil {
return fmt.Errorf("updateSize: %v", err)
}
repo.Size = size repo.Size = size
_, err = e.ID(repo.ID).Cols("size").Update(repo) _, err = e.ID(repo.ID).Cols("size").Update(repo)
return err return err
@ -884,6 +894,11 @@ func (repo *Repository) UpdateSize(ctx DBContext) error {
return repo.updateSize(ctx.e) return repo.updateSize(ctx.e)
} }
// RepoSizeIsOversized return if is over size limitation
func (repo *Repository) RepoSizeIsOversized(additionalSize int64) bool {
return repo.SizeLimit > 0 && repo.Size+additionalSize > repo.SizeLimit
}
// CanUserFork returns true if specified user can fork repository. // CanUserFork returns true if specified user can fork repository.
func (repo *Repository) CanUserFork(user *User) (bool, error) { func (repo *Repository) CanUserFork(user *User) (bool, error) {
if user == nil { if user == nil {
@ -1101,6 +1116,7 @@ type CreateRepoOptions struct {
AutoInit bool AutoInit bool
Status RepositoryStatus Status RepositoryStatus
TrustModel TrustModelType TrustModel TrustModelType
SizeLimit int64
} }
// GetRepoInitFile returns repository init files // GetRepoInitFile returns repository init files

View File

@ -48,6 +48,7 @@ type CreateRepoForm struct {
Avatar bool Avatar bool
Labels bool Labels bool
TrustModel string TrustModel string
SizeLimit int64
} }
// Validate validates the fields // Validate validates the fields
@ -126,6 +127,7 @@ type RepoSettingForm struct {
Private bool Private bool
Template bool Template bool
EnablePrune bool EnablePrune bool
RepoSizeLimit int64
// Advanced settings // Advanced settings
EnableWiki bool EnableWiki bool

View File

@ -352,8 +352,13 @@ const (
// CountObjects returns the results of git count-objects on the repoPath // CountObjects returns the results of git count-objects on the repoPath
func CountObjects(repoPath string) (*CountObject, error) { func CountObjects(repoPath string) (*CountObject, error) {
return CountObjectsWithEnv(repoPath, nil)
}
// CountObjectsWithEnv returns the results of git count-objects on the repoPath with custom env setup
func CountObjectsWithEnv(repoPath string, env []string) (*CountObject, error) {
cmd := NewCommand("count-objects", "-v") cmd := NewCommand("count-objects", "-v")
stdout, err := cmd.RunInDir(repoPath) stdout, err := cmd.RunInDirWithEnv(repoPath, env)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -45,6 +45,7 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (*mod
Status: opts.Status, Status: opts.Status,
IsEmpty: !opts.AutoInit, IsEmpty: !opts.AutoInit,
TrustModel: opts.TrustModel, TrustModel: opts.TrustModel,
SizeLimit: opts.SizeLimit,
} }
if err := models.WithTx(func(ctx models.DBContext) error { if err := models.WithTx(func(ctx models.DBContext) error {

View File

@ -122,6 +122,8 @@ type CreateRepoOption struct {
// TrustModel of the repository // TrustModel of the repository
// enum: default,collaborator,committer,collaboratorcommitter // enum: default,collaborator,committer,collaboratorcommitter
TrustModel string `json:"trust_model"` TrustModel string `json:"trust_model"`
// SizeLimit of the repository
SizeLimit int64 `json:"size_limit"`
} }
// EditRepoOption options when editing a repository's properties // EditRepoOption options when editing a repository's properties
@ -168,6 +170,8 @@ type EditRepoOption struct {
AllowSquash *bool `json:"allow_squash_merge,omitempty"` AllowSquash *bool `json:"allow_squash_merge,omitempty"`
// set to `true` to archive this repository. // set to `true` to archive this repository.
Archived *bool `json:"archived,omitempty"` Archived *bool `json:"archived,omitempty"`
// SizeLimit of the repository.
SizeLimit *int64 `json:"size_limit,omitempty"`
} }
// CreateBranchRepoOption options when creating a branch in a repository // CreateBranchRepoOption options when creating a branch in a repository

View File

@ -653,6 +653,7 @@ owner = Owner
repo_name = Repository Name repo_name = Repository Name
repo_name_helper = Good repository names use short, memorable and unique keywords. repo_name_helper = Good repository names use short, memorable and unique keywords.
repo_size = Repository Size repo_size = Repository Size
repo_size_limit = Repository Size Limit
template = Template template = Template
template_select = Select a template. template_select = Select a template.
template_helper = Make repository a template template_helper = Make repository a template
@ -733,6 +734,8 @@ archive.pull.nocomment = This repo is archived. You cannot comment on pull reque
form.reach_limit_of_creation = You have already reached your limit of %d repositories. form.reach_limit_of_creation = You have already reached your limit of %d repositories.
form.name_reserved = The repository name '%s' is reserved. form.name_reserved = The repository name '%s' is reserved.
form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name. form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name.
form.repo_size_limit_negative = Repository size limitation cannot be negative.
form.repo_size_limit_only_by_admins = Only administrators can change the repository size limitation.
need_auth = Clone Authorization need_auth = Clone Authorization
migrate_options = Migration Options migrate_options = Migration Options

View File

@ -246,6 +246,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateR
DefaultBranch: opt.DefaultBranch, DefaultBranch: opt.DefaultBranch,
TrustModel: models.ToTrustModel(opt.TrustModel), TrustModel: models.ToTrustModel(opt.TrustModel),
IsTemplate: opt.Template, IsTemplate: opt.Template,
SizeLimit: opt.SizeLimit,
}) })
if err != nil { if err != nil {
if models.IsErrRepoAlreadyExist(err) { if models.IsErrRepoAlreadyExist(err) {
@ -568,6 +569,9 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
repo.DefaultBranch = *opts.DefaultBranch repo.DefaultBranch = *opts.DefaultBranch
} }
if opts.SizeLimit != nil {
repo.SizeLimit = *opts.SizeLimit
}
if err := models.UpdateRepository(repo, visibilityChanged); err != nil { if err := models.UpdateRepository(repo, visibilityChanged); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepository", err) ctx.Error(http.StatusInternalServerError, "UpdateRepository", err)
return err return err

View File

@ -155,12 +155,33 @@ func HookPreReceive(ctx *macaron.Context, opts private.HookOptions) {
private.GitQuarantinePath+"="+opts.GitQuarantinePath) private.GitQuarantinePath+"="+opts.GitQuarantinePath)
} }
pushSize, err := git.CountObjectsWithEnv(repo.RepoPath(), env)
if err != nil {
log.Error("Unable to get repository size with env %v: %s Error: %v", repo.RepoPath(), env, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": err.Error(),
})
return
}
log.Trace("Push size %d", pushSize.Size)
// Iterate across the provided old commit IDs // Iterate across the provided old commit IDs
for i := range opts.OldCommitIDs { for i := range opts.OldCommitIDs {
oldCommitID := opts.OldCommitIDs[i] oldCommitID := opts.OldCommitIDs[i]
newCommitID := opts.NewCommitIDs[i] newCommitID := opts.NewCommitIDs[i]
refFullName := opts.RefFullNames[i] refFullName := opts.RefFullNames[i]
//Check size
if newCommitID != git.EmptySHA && repo.RepoSizeIsOversized(pushSize.Size) { //Check next size if we are not deleting a reference
log.Warn("Forbidden: new repo size is over limitation: %d", repo.SizeLimit)
ctx.JSON(http.StatusForbidden, map[string]interface{}{
"err": fmt.Sprintf("new repo size is over limitation: %d", repo.SizeLimit),
})
}
//TODO investigate why on force push some git objects are not cleaned on server side.
//TODO corner-case force push and branch creation -> git.EmptySHA == oldCommitID
//TODO calculate pushed LFS objects size
branchName := strings.TrimPrefix(refFullName, git.BranchPrefix) branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
if branchName == repo.DefaultBranch && newCommitID == git.EmptySHA { if branchName == repo.DefaultBranch && newCommitID == git.EmptySHA {
log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo) log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo)

View File

@ -250,6 +250,7 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) {
AutoInit: form.AutoInit, AutoInit: form.AutoInit,
IsTemplate: form.Template, IsTemplate: form.Template,
TrustModel: models.ToTrustModel(form.TrustModel), TrustModel: models.ToTrustModel(form.TrustModel),
SizeLimit: form.SizeLimit,
}) })
if err == nil { if err == nil {
log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)

View File

@ -50,6 +50,7 @@ func Settings(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsOptions"] = true ctx.Data["PageIsSettingsOptions"] = true
ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate
ctx.Data["Err_RepoSize"] = ctx.Repo.Repository.RepoSizeIsOversized(ctx.Repo.Repository.SizeLimit / 10) // less than 10% left
signing, _ := models.SigningKey(ctx.Repo.Repository.RepoPath()) signing, _ := models.SigningKey(ctx.Repo.Repository.RepoPath())
ctx.Data["SigningKeyAvailable"] = len(signing) > 0 ctx.Data["SigningKeyAvailable"] = len(signing) > 0
@ -64,6 +65,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
ctx.Data["PageIsSettingsOptions"] = true ctx.Data["PageIsSettingsOptions"] = true
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
ctx.Data["Err_RepoSize"] = repo.RepoSizeIsOversized(repo.SizeLimit / 10) // less than 10% left
switch ctx.Query("action") { switch ctx.Query("action") {
case "update": case "update":
@ -128,6 +130,19 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
return return
} }
if form.RepoSizeLimit < 0 {
ctx.Data["Err_RepoSizeLimit"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.repo_size_limit_negative"), tplSettingsOptions, &form)
return
}
if !ctx.User.IsAdmin && repo.SizeLimit != form.RepoSizeLimit {
ctx.Data["Err_RepoSizeLimit"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.repo_size_limit_only_by_admins"), tplSettingsOptions, &form)
return
}
repo.SizeLimit = form.RepoSizeLimit
repo.IsPrivate = form.Private repo.IsPrivate = form.Private
if err := models.UpdateRepository(repo, visibilityChanged); err != nil { if err := models.UpdateRepository(repo, visibilityChanged); err != nil {
ctx.ServerError("UpdateRepository", err) ctx.ServerError("UpdateRepository", err)

View File

@ -17,7 +17,11 @@
</div> </div>
<div class="inline field"> <div class="inline field">
<label>{{.i18n.Tr "repo.repo_size"}}</label> <label>{{.i18n.Tr "repo.repo_size"}}</label>
<span>{{SizeFmt .Repository.Size}}</span> <span {{if .Err_RepoSize}}class="ui text red"{{end}}>{{SizeFmt .Repository.Size}}{{if .Repository.SizeLimit}}/{{SizeFmt .Repository.SizeLimit}}{{end}}</span>
</div>
<div class="field {{if .Err_RepoSizeLimit}}error{{end}}" {{if not .IsAdmin}}style="display:none;"{{end}}>
<label for="repo_size_limit">{{.i18n.Tr "repo.repo_size_limit"}}</label>
<input id="repo_size_limit" name="repo_size_limit" type="number" value="{{.Repository.SizeLimit}}" data-repo-size-limit="{{.Repository.SizeLimit}}">
</div> </div>
<div class="inline field"> <div class="inline field">
<label>{{.i18n.Tr "repo.template"}}</label> <label>{{.i18n.Tr "repo.template"}}</label>