Skip to content

Commit bb96349

Browse files
committed
rebase on master
1 parent 0615b66 commit bb96349

File tree

13 files changed

+99
-5
lines changed

13 files changed

+99
-5
lines changed

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ var migrations = []Migration{
254254
NewMigration("code comment replies should have the commitID of the review they are replying to", updateCodeCommentReplies),
255255
// v159 -> v160
256256
NewMigration("update reactions constraint", updateReactionConstraint),
257+
// v160 -> v161
258+
NewMigration("add size limit on repository", addSizeLimitOnRepo),
257259
}
258260

259261
// GetCurrentDBVersion returns the current db version

models/migrations/v160.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2020 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 migrations
6+
7+
import "xorm.io/xorm"
8+
9+
func addSizeLimitOnRepo(x *xorm.Engine) error {
10+
type Repository struct {
11+
ID int64 `xorm:"pk autoincr"`
12+
SizeLimit int64 `xorm:"NOT NULL DEFAULT 0"`
13+
}
14+
15+
return x.Sync2(new(Repository))
16+
}

models/repo.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ type Repository struct {
230230
TemplateID int64 `xorm:"INDEX"`
231231
TemplateRepo *Repository `xorm:"-"`
232232
Size int64 `xorm:"NOT NULL DEFAULT 0"`
233+
SizeLimit int64 `xorm:"NOT NULL DEFAULT 0"`
233234
CodeIndexerStatus *RepoIndexerStatus `xorm:"-"`
234235
StatsIndexerStatus *RepoIndexerStatus `xorm:"-"`
235236
IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"`
@@ -860,20 +861,29 @@ func (repo *Repository) IsOwnedBy(userID int64) bool {
860861
return repo.OwnerID == userID
861862
}
862863

863-
func (repo *Repository) updateSize(e Engine) error {
864+
func (repo *Repository) computeSize() (int64, error) {
864865
size, err := util.GetDirectorySize(repo.RepoPath())
865866
if err != nil {
866-
return fmt.Errorf("updateSize: %v", err)
867+
return 0, fmt.Errorf("computeSize: %v", err)
867868
}
868869

869870
objs, err := repo.GetLFSMetaObjects(-1, 0)
870871
if err != nil {
871-
return fmt.Errorf("updateSize: GetLFSMetaObjects: %v", err)
872+
return 0, fmt.Errorf("computeSize: GetLFSMetaObjects: %v", err)
872873
}
873874
for _, obj := range objs {
874875
size += obj.Size
875876
}
876877

878+
return size, nil
879+
}
880+
881+
func (repo *Repository) updateSize(e Engine) error {
882+
size, err := repo.computeSize()
883+
if err != nil {
884+
return fmt.Errorf("updateSize: %v", err)
885+
}
886+
877887
repo.Size = size
878888
_, err = e.ID(repo.ID).Cols("size").Update(repo)
879889
return err
@@ -884,6 +894,11 @@ func (repo *Repository) UpdateSize(ctx DBContext) error {
884894
return repo.updateSize(ctx.e)
885895
}
886896

897+
// RepoSizeIsOversized return if is over size limitation
898+
func (repo *Repository) RepoSizeIsOversized(additionalSize int64) bool {
899+
return repo.SizeLimit > 0 && repo.Size+additionalSize > repo.SizeLimit
900+
}
901+
887902
// CanUserFork returns true if specified user can fork repository.
888903
func (repo *Repository) CanUserFork(user *User) (bool, error) {
889904
if user == nil {
@@ -1101,6 +1116,7 @@ type CreateRepoOptions struct {
11011116
AutoInit bool
11021117
Status RepositoryStatus
11031118
TrustModel TrustModelType
1119+
SizeLimit int64
11041120
}
11051121

11061122
// GetRepoInitFile returns repository init files

modules/auth/repo_form.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type CreateRepoForm struct {
4848
Avatar bool
4949
Labels bool
5050
TrustModel string
51+
SizeLimit int64
5152
}
5253

5354
// Validate validates the fields
@@ -126,6 +127,7 @@ type RepoSettingForm struct {
126127
Private bool
127128
Template bool
128129
EnablePrune bool
130+
RepoSizeLimit int64
129131

130132
// Advanced settings
131133
EnableWiki bool

modules/git/repo.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,13 @@ const (
352352

353353
// CountObjects returns the results of git count-objects on the repoPath
354354
func CountObjects(repoPath string) (*CountObject, error) {
355+
return CountObjectsWithEnv(repoPath, nil)
356+
}
357+
358+
// CountObjectsWithEnv returns the results of git count-objects on the repoPath with custom env setup
359+
func CountObjectsWithEnv(repoPath string, env []string) (*CountObject, error) {
355360
cmd := NewCommand("count-objects", "-v")
356-
stdout, err := cmd.RunInDir(repoPath)
361+
stdout, err := cmd.RunInDirWithEnv(repoPath, env)
357362
if err != nil {
358363
return nil, err
359364
}

modules/repository/create.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (*mod
4545
Status: opts.Status,
4646
IsEmpty: !opts.AutoInit,
4747
TrustModel: opts.TrustModel,
48+
SizeLimit: opts.SizeLimit,
4849
}
4950

5051
if err := models.WithTx(func(ctx models.DBContext) error {

modules/structs/repo.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ type CreateRepoOption struct {
122122
// TrustModel of the repository
123123
// enum: default,collaborator,committer,collaboratorcommitter
124124
TrustModel string `json:"trust_model"`
125+
// SizeLimit of the repository
126+
SizeLimit int64 `json:"size_limit"`
125127
}
126128

127129
// EditRepoOption options when editing a repository's properties
@@ -168,6 +170,8 @@ type EditRepoOption struct {
168170
AllowSquash *bool `json:"allow_squash_merge,omitempty"`
169171
// set to `true` to archive this repository.
170172
Archived *bool `json:"archived,omitempty"`
173+
// SizeLimit of the repository.
174+
SizeLimit *int64 `json:"size_limit,omitempty"`
171175
}
172176

173177
// CreateBranchRepoOption options when creating a branch in a repository

options/locale/locale_en-US.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,7 @@ owner = Owner
653653
repo_name = Repository Name
654654
repo_name_helper = Good repository names use short, memorable and unique keywords.
655655
repo_size = Repository Size
656+
repo_size_limit = Repository Size Limit
656657
template = Template
657658
template_select = Select a template.
658659
template_helper = Make repository a template
@@ -733,6 +734,8 @@ archive.pull.nocomment = This repo is archived. You cannot comment on pull reque
733734
form.reach_limit_of_creation = You have already reached your limit of %d repositories.
734735
form.name_reserved = The repository name '%s' is reserved.
735736
form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name.
737+
form.repo_size_limit_negative = Repository size limitation cannot be negative.
738+
form.repo_size_limit_only_by_admins = Only administrators can change the repository size limitation.
736739

737740
need_auth = Clone Authorization
738741
migrate_options = Migration Options

routers/api/v1/repo/repo.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateR
246246
DefaultBranch: opt.DefaultBranch,
247247
TrustModel: models.ToTrustModel(opt.TrustModel),
248248
IsTemplate: opt.Template,
249+
SizeLimit: opt.SizeLimit,
249250
})
250251
if err != nil {
251252
if models.IsErrRepoAlreadyExist(err) {
@@ -568,6 +569,9 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
568569
repo.DefaultBranch = *opts.DefaultBranch
569570
}
570571

572+
if opts.SizeLimit != nil {
573+
repo.SizeLimit = *opts.SizeLimit
574+
}
571575
if err := models.UpdateRepository(repo, visibilityChanged); err != nil {
572576
ctx.Error(http.StatusInternalServerError, "UpdateRepository", err)
573577
return err

routers/private/hook.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,33 @@ func HookPreReceive(ctx *macaron.Context, opts private.HookOptions) {
155155
private.GitQuarantinePath+"="+opts.GitQuarantinePath)
156156
}
157157

158+
pushSize, err := git.CountObjectsWithEnv(repo.RepoPath(), env)
159+
if err != nil {
160+
log.Error("Unable to get repository size with env %v: %s Error: %v", repo.RepoPath(), env, err)
161+
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
162+
"err": err.Error(),
163+
})
164+
return
165+
}
166+
log.Trace("Push size %d", pushSize.Size)
167+
158168
// Iterate across the provided old commit IDs
159169
for i := range opts.OldCommitIDs {
160170
oldCommitID := opts.OldCommitIDs[i]
161171
newCommitID := opts.NewCommitIDs[i]
162172
refFullName := opts.RefFullNames[i]
163173

174+
//Check size
175+
if newCommitID != git.EmptySHA && repo.RepoSizeIsOversized(pushSize.Size) { //Check next size if we are not deleting a reference
176+
log.Warn("Forbidden: new repo size is over limitation: %d", repo.SizeLimit)
177+
ctx.JSON(http.StatusForbidden, map[string]interface{}{
178+
"err": fmt.Sprintf("new repo size is over limitation: %d", repo.SizeLimit),
179+
})
180+
}
181+
//TODO investigate why on force push some git objects are not cleaned on server side.
182+
//TODO corner-case force push and branch creation -> git.EmptySHA == oldCommitID
183+
//TODO calculate pushed LFS objects size
184+
164185
branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
165186
if branchName == repo.DefaultBranch && newCommitID == git.EmptySHA {
166187
log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo)

routers/repo/repo.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) {
250250
AutoInit: form.AutoInit,
251251
IsTemplate: form.Template,
252252
TrustModel: models.ToTrustModel(form.TrustModel),
253+
SizeLimit: form.SizeLimit,
253254
})
254255
if err == nil {
255256
log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)

routers/repo/setting.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func Settings(ctx *context.Context) {
5050
ctx.Data["Title"] = ctx.Tr("repo.settings")
5151
ctx.Data["PageIsSettingsOptions"] = true
5252
ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate
53+
ctx.Data["Err_RepoSize"] = ctx.Repo.Repository.RepoSizeIsOversized(ctx.Repo.Repository.SizeLimit / 10) // less than 10% left
5354

5455
signing, _ := models.SigningKey(ctx.Repo.Repository.RepoPath())
5556
ctx.Data["SigningKeyAvailable"] = len(signing) > 0
@@ -64,6 +65,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
6465
ctx.Data["PageIsSettingsOptions"] = true
6566

6667
repo := ctx.Repo.Repository
68+
ctx.Data["Err_RepoSize"] = repo.RepoSizeIsOversized(repo.SizeLimit / 10) // less than 10% left
6769

6870
switch ctx.Query("action") {
6971
case "update":
@@ -128,6 +130,19 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
128130
return
129131
}
130132

133+
if form.RepoSizeLimit < 0 {
134+
ctx.Data["Err_RepoSizeLimit"] = true
135+
ctx.RenderWithErr(ctx.Tr("repo.form.repo_size_limit_negative"), tplSettingsOptions, &form)
136+
return
137+
}
138+
139+
if !ctx.User.IsAdmin && repo.SizeLimit != form.RepoSizeLimit {
140+
ctx.Data["Err_RepoSizeLimit"] = true
141+
ctx.RenderWithErr(ctx.Tr("repo.form.repo_size_limit_only_by_admins"), tplSettingsOptions, &form)
142+
return
143+
}
144+
repo.SizeLimit = form.RepoSizeLimit
145+
131146
repo.IsPrivate = form.Private
132147
if err := models.UpdateRepository(repo, visibilityChanged); err != nil {
133148
ctx.ServerError("UpdateRepository", err)

templates/repo/settings/options.tmpl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
</div>
1818
<div class="inline field">
1919
<label>{{.i18n.Tr "repo.repo_size"}}</label>
20-
<span>{{SizeFmt .Repository.Size}}</span>
20+
<span {{if .Err_RepoSize}}class="ui text red"{{end}}>{{SizeFmt .Repository.Size}}{{if .Repository.SizeLimit}}/{{SizeFmt .Repository.SizeLimit}}{{end}}</span>
21+
</div>
22+
<div class="field {{if .Err_RepoSizeLimit}}error{{end}}" {{if not .IsAdmin}}style="display:none;"{{end}}>
23+
<label for="repo_size_limit">{{.i18n.Tr "repo.repo_size_limit"}}</label>
24+
<input id="repo_size_limit" name="repo_size_limit" type="number" value="{{.Repository.SizeLimit}}" data-repo-size-limit="{{.Repository.SizeLimit}}">
2125
</div>
2226
<div class="inline field">
2327
<label>{{.i18n.Tr "repo.template"}}</label>

0 commit comments

Comments
 (0)