diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 96582200ce..95160dd8e5 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1081,22 +1081,17 @@ LEVEL = Info ;; Allow to fork repositories without maximum number limit ;ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT = true ; -;; Specify a global repository size limit in bytes to apply for each repository. -1 - Disabled, 0 - Enabled with no limit -;; If repository has it's own limit set in repositiry settings UI it will override the global setting +;; Specify a global repository Git size limit in bytes. -1 - Disabled, 0 - limit to zero bytes ;; Standard units of measurements for size can be used like B, KB, KiB, ... , EB, EiB, etc or if not provided - bytes +;; If limit is reached and operation doesn't increase disk consumption operation would be allowed ;; This is experimental and subject to change -;REPO_SIZE_LIMIT = -1 +;GIT_SIZE_MAX = -1 -;; Specify a global LFS size limit in bytes to apply for each repository. -1 - Disabled, 0 - Enabled with no limit -;; If repository has it's own limit set in repository settings UI it will override the global setting +;; Specify a global repository LFS size limit in bytes. -1 - Disabled, 0 - limit to zero bytes ;; Standard units of measurements for size can be used like B, KB, KiB, ... , EB, EiB, etc or if not provided - bytes +;; If limit is reached and operation doesn't increase disk consumption operation would be allowed ;; This is experimental and subject to change -;LFS_SIZE_LIMIT = -1 -;; -;; If true, LFS size will be included in the repository size calculation. -;; Which means even if LFS_SIZE_LIMIT is not set (-1) pushes will be rejected if LFS size + repository size exceeds REPO_SIZE_LIMIT -;; This is experimental and subject to change -;LFS_SIZE_IN_REPO_SIZE = false +;LFS_SIZE_MAX = -1 ;; Allow to fork repositories into the same owner (user or organization) ;; This feature is experimental, not fully tested, and may be changed in the future diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index cc801479fb..9975729fd6 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -400,7 +400,6 @@ func prepareMigrationTasks() []*migration { newMigration(323, "Add support for actions concurrency", v1_26.AddActionsConcurrency), newMigration(324, "Fix closed milestone completeness for milestones with no issues", v1_26.FixClosedMilestoneCompleteness), newMigration(325, "Fix missed repo_id when migrate attachments", v1_26.FixMissedRepoIDWhenMigrateAttachments), - newMigration(326, "Add support for repository size limit - SizeLimit and LFSSizeLimit columns to repository table", v1_26.AddSizeLimitOnRepo), } return preparedMigrations } diff --git a/models/migrations/v1_26/v326.go b/models/migrations/v1_26/v326.go deleted file mode 100644 index 5fc9774cdb..0000000000 --- a/models/migrations/v1_26/v326.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package v1_26 - -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"` - LFSSizeLimit int64 `xorm:"NOT NULL DEFAULT 0"` - } - - return x.Sync2(new(Repository)) -} diff --git a/models/repo/repo.go b/models/repo/repo.go index 59b4fe627e..6ee0e9c860 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -201,11 +201,9 @@ type Repository struct { BaseRepo *Repository `xorm:"-"` IsTemplate bool `xorm:"INDEX NOT NULL DEFAULT false"` TemplateID int64 `xorm:"INDEX"` - SizeLimit int64 `xorm:"NOT NULL DEFAULT 0"` Size int64 `xorm:"NOT NULL DEFAULT 0"` GitSize int64 `xorm:"NOT NULL DEFAULT 0"` LFSSize int64 `xorm:"NOT NULL DEFAULT 0"` - LFSSizeLimit int64 `xorm:"NOT NULL DEFAULT 0"` CodeIndexerStatus *RepoIndexerStatus `xorm:"-"` StatsIndexerStatus *RepoIndexerStatus `xorm:"-"` IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"` @@ -633,58 +631,43 @@ func (repo *Repository) IsOwnedBy(userID int64) bool { } // GetActualSizeLimit returns repository size limit in bytes -// or global repository limit setting if per repository size limit is not set func (repo *Repository) GetActualSizeLimit() int64 { - sizeLimit := repo.SizeLimit - if setting.RepoSizeLimit > 0 && sizeLimit == 0 { - sizeLimit = setting.RepoSizeLimit - } - return sizeLimit + return setting.Repository.GitSizeMax } // RepoSizeIsOversized return true if is over size limitation func (repo *Repository) IsRepoSizeOversized(additionalSize int64) bool { - return repo.ShouldCheckRepoSize() && repo.GitSize+additionalSize > repo.GetActualSizeLimit() + limit := repo.GetActualSizeLimit() + if limit < 0 { + return false + } + newSize := repo.GitSize + additionalSize + return newSize > limit && newSize > repo.GitSize } -// ShouldCheckRepoSize returns true if size limit checking is enabled and limit is non zero for this specific repository -// this is used to enable size checking during pre-receive hook +// ShouldCheckRepoSize returns true if size limit checking is enabled func (repo *Repository) ShouldCheckRepoSize() bool { - return setting.RepoSizeLimit > -1 && repo.GetActualSizeLimit() > 0 + return setting.Repository.GitSizeMax > -1 } // GetActualLFSSizeLimit returns repository LFS size limit in bytes -// or global LFS size limit setting if per repository LFS size limit is not set func (repo *Repository) GetActualLFSSizeLimit() int64 { - lfsSizeLimit := repo.LFSSizeLimit - if setting.LFSSizeLimit > 0 && lfsSizeLimit == 0 { - lfsSizeLimit = setting.LFSSizeLimit - } - return lfsSizeLimit + return setting.Repository.LFSSizeMax } -// ShouldCheckLFSSize returns true if LFS size limit checking is enabled for this repository +// ShouldCheckLFSSize returns true if LFS size limit checking is enabled func (repo *Repository) ShouldCheckLFSSize() bool { - return setting.LFSSizeLimit > -1 && repo.GetActualLFSSizeLimit() > 0 + return setting.Repository.LFSSizeMax > -1 } // IsLFSSizeOversized returns true if adding additionalSize would exceed the LFS size limit func (repo *Repository) IsLFSSizeOversized(additionalSize int64) bool { - return repo.ShouldCheckLFSSize() && - repo.LFSSize+additionalSize > repo.GetActualLFSSizeLimit() -} - -// IsRepoAndLFSSizeOversized checks if combined repo + LFS size exceeds repo size limit -// This is used when LFS_SIZE_IN_REPO_SIZE is enabled -func (repo *Repository) IsRepoAndLFSSizeOversized(additionalGitSize, additionalLFSSize int64) bool { - if !setting.LFSSizeInRepoSize || setting.RepoSizeLimit == -1 { + limit := repo.GetActualLFSSizeLimit() + if limit < 0 { return false } - limit := repo.GetActualSizeLimit() - if limit == 0 { - return false - } - return (repo.GitSize+additionalGitSize)+(repo.LFSSize+additionalLFSSize) > limit + newSize := repo.LFSSize + additionalSize + return newSize > limit && newSize > repo.LFSSize } // CanCreateBranch returns true if repository meets the requirements for creating new branches. diff --git a/modules/base/tool.go b/modules/base/tool.go index bae62c491c..8446f6904c 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -93,21 +93,18 @@ func CreateTimeLimitCode[T time.Time | string](data string, minutes int, startTi // FileSize calculates the file size and generate user-friendly string. func FileSize(s int64) string { - if s == -1 { - return "-1" - } return humanize.IBytes(uint64(s)) } -// Get FileSize bytes value from String. +// GetFileSize gets FileSize bytes value from String. func GetFileSize(s string) (int64, error) { s = strings.TrimSpace(s) - if s == "-1" { - return -1, nil + // default to bytes if no unit is provided + if _, err := strconv.ParseInt(s, 10, 64); err == nil { + s += " B" } v, err := humanize.ParseBytes(s) - iv := int64(v) - return iv, err + return int64(v), err } // StringsToInt64s converts a slice of string to a slice of int64. diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 1a09d3cd89..4e925ccc5a 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -56,6 +56,8 @@ var ( DisableDownloadSourceArchives bool AllowForkWithoutMaximumLimit bool AllowForkIntoSameOwner bool + GitSizeMax int64 `ini:"GIT_SIZE_MAX"` + LFSSizeMax int64 `ini:"LFS_SIZE_MAX"` // StreamArchives makes Gitea stream git archive files to the client directly instead of creating an archive first. // Ideally all users should use this streaming method. However, at the moment we don't know whether there are @@ -276,36 +278,33 @@ var ( } RepoRootPath string ScriptType = "bash" - - // Repository size limits - RepoSizeLimit int64 = -1 - LFSSizeLimit int64 = -1 - LFSSizeInRepoSize bool ) -func SaveGlobalRepositorySetting(repoSizeLimit, lfsSizeLimit int64, lfsSizeInRepoSize bool) error { - RepoSizeLimit = repoSizeLimit - LFSSizeLimit = lfsSizeLimit - LFSSizeInRepoSize = lfsSizeInRepoSize +func UpdateGlobalRepositoryLimit(gitSizeMax, lfsSizeMax int64) { + Repository.GitSizeMax = gitSizeMax + Repository.LFSSizeMax = lfsSizeMax +} - cfg, err := CfgProvider.PrepareSaving() - if err != nil { - return err +// FormatRepositorySizeLimit returns "-1" for disabled limits, otherwise returns human-readable size. +func FormatRepositorySizeLimit(sizeInBytes int64) string { + if sizeInBytes == -1 { + return "-1" } + return humanize.IBytes(uint64(sizeInBytes)) +} - sec := cfg.Section("repository") - if RepoSizeLimit == -1 { - sec.Key("REPO_SIZE_LIMIT").SetValue("-1") - } else { - sec.Key("REPO_SIZE_LIMIT").SetValue(humanize.Bytes(uint64(RepoSizeLimit))) +// ParseRepositorySizeLimit accepts "-1" to disable the limit, otherwise parses as a byte size. +func ParseRepositorySizeLimit(s string) (int64, error) { + s = strings.TrimSpace(s) + if s == "-1" { + return -1, nil } - if LFSSizeLimit == -1 { - sec.Key("LFS_SIZE_LIMIT").SetValue("-1") - } else { - sec.Key("LFS_SIZE_LIMIT").SetValue(humanize.Bytes(uint64(LFSSizeLimit))) + // default to bytes if no unit is provided + if _, err := strconv.ParseInt(s, 10, 64); err == nil { + s += " B" } - sec.Key("LFS_SIZE_IN_REPO_SIZE").SetValue(strconv.FormatBool(LFSSizeInRepoSize)) - return cfg.Save() + v, err := humanize.ParseBytes(s) + return int64(v), err } func parseSize(sec ConfigSection, key string, def int64) int64 { @@ -328,9 +327,8 @@ func loadRepositoryFrom(rootCfg ConfigProvider) { // Determine and create root git repository path. sec := rootCfg.Section("repository") - RepoSizeLimit = parseSize(sec, "REPO_SIZE_LIMIT", -1) - LFSSizeLimit = parseSize(sec, "LFS_SIZE_LIMIT", -1) - LFSSizeInRepoSize = sec.Key("LFS_SIZE_IN_REPO_SIZE").MustBool(false) + Repository.GitSizeMax = parseSize(sec, "GIT_SIZE_MAX", -1) + Repository.LFSSizeMax = parseSize(sec, "LFS_SIZE_MAX", -1) Repository.DisableHTTPGit = sec.Key("DISABLE_HTTP_GIT").MustBool() Repository.UseCompatSSHURI = sec.Key("USE_COMPAT_SSH_URI").MustBool() diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 0dcc0096da..90ac1688e4 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -153,10 +153,6 @@ type CreateRepoOption struct { // ObjectFormatName of the underlying git repository // enum: sha1,sha256 ObjectFormatName string `json:"object_format_name" binding:"MaxSize(6)"` - // SizeLimit of the repository - SizeLimit int64 `json:"size_limit"` - // LFSSizeLimit of the repository - LFSSizeLimit int64 `json:"lfs_size_limit"` } // EditRepoOption options when editing a repository's properties @@ -227,11 +223,7 @@ type EditRepoOption struct { DefaultAllowMaintainerEdit *bool `json:"default_allow_maintainer_edit,omitempty"` // set to `true` to archive this repository. Archived *bool `json:"archived,omitempty"` - // SizeLimit of the repository. - SizeLimit *int64 `json:"size_limit,omitempty"` - // LFSSizeLimit of the repository. - LFSSizeLimit *int64 `json:"lfs_size_limit,omitempty"` - // set to a string like `8h30m0s` to set the mirror interval time + // set a string like `8h30m0s` to set the mirror interval time MirrorInterval *string `json:"mirror_interval,omitempty"` // enable prune - remove obsolete remote-tracking references when mirroring EnablePrune *bool `json:"enable_prune,omitempty"` diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index f336605066..83338ad7fe 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -1082,6 +1082,7 @@ "repo.form.name_reserved": "The repository name \"%s\" is reserved.", "repo.form.name_pattern_not_allowed": "The pattern \"%s\" is not allowed in a repository name.", "repo.need_auth": "Authorization", + "repo.lfs_size": "LFS Size", "repo.migrate_options": "Migration Options", "repo.migrate_service": "Migration Service", "repo.migrate_options_mirror_helper": "This repository will be a mirror", @@ -3025,8 +3026,15 @@ "admin.repos.name": "Name", "admin.repos.private": "Private", "admin.repos.issues": "Issues", - "admin.repos.size": "Size", + "admin.repos.size": "Git Size", "admin.repos.lfs_size": "LFS Size", + "admin.repos.settings": "Global Repository Settings", + "admin.repos.git_size_max": "Max Git Size (Global), Bytes", + "admin.repos.git_size_max_helper": "Maximum Git size allowed for a single repository. Set to -1 for unlimited.", + "admin.repos.lfs_size_max": "Max LFS Size (Global), Bytes", + "admin.repos.lfs_size_max_helper": "Maximum LFS size allowed for a single repository. Set to -1 for unlimited.", + "admin.repos.update_settings": "Update Settings", + "admin.repos.update_success": "Global repository limits have been updated.", "admin.packages.package_manage_panel": "Package Management", "admin.packages.total_size": "Total Size: %s", "admin.packages.unreferenced_size": "Unreferenced Size: %s", @@ -3729,4 +3737,4 @@ "git.filemode.executable_file": "Executable", "git.filemode.symbolic_link": "Symlink", "git.filemode.submodule": "Submodule" -} +} \ No newline at end of file diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index ada70dda4c..bb6bda587d 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -263,8 +263,6 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre TrustModel: repo_model.ToTrustModel(opt.TrustModel), IsTemplate: opt.Template, ObjectFormatName: opt.ObjectFormatName, - SizeLimit: opt.SizeLimit, - LFSSizeLimit: opt.LFSSizeLimit, }) if err != nil { if repo_model.IsErrRepoAlreadyExist(err) { @@ -751,13 +749,6 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err } } - if opts.SizeLimit != nil { - repo.SizeLimit = *opts.SizeLimit - } - if opts.LFSSizeLimit != nil { - repo.LFSSizeLimit = *opts.LFSSizeLimit - } - if err := repo_service.UpdateRepository(ctx, repo, visibilityChanged); err != nil { ctx.APIErrorInternal(err) return err diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 0a78a45fc9..449769d085 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -396,8 +396,6 @@ func sumLFSSizes(m map[string]int64) int64 { return s } -// scanLFSPointersFromObjectIDs finds LFS pointer blobs among objectIDs and returns map[oid]size. -// It only reads small blobs via cat-file, so it stays bounded. // scanLFSPointersFromObjectIDs finds LFS pointer blobs among objectIDs and returns map[oid]size. // It only reads small blobs via cat-file, so it stays bounded. func scanLFSPointersFromObjectIDs(ctx *gitea_context.PrivateContext, repoPath string, env, objectIDs []string, maxBlobSize int64) (map[string]int64, error) { @@ -524,13 +522,6 @@ func scanLFSPointersFromObjectIDs(ctx *gitea_context.PrivateContext, repoPath st return out, nil } -func maxInt64(a, b int64) int64 { - if a > b { - return a - } - return b -} - // HookPreReceive checks whether a individual commit is acceptable func HookPreReceive(ctx *gitea_context.PrivateContext) { startTime := time.Now() @@ -561,7 +552,7 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { var duration time.Duration needGitDelta := repo.ShouldCheckRepoSize() - needLFSDelta := repo.ShouldCheckLFSSize() || setting.LFSSizeInRepoSize + needLFSDelta := repo.ShouldCheckLFSSize() // Only do CountObjects (push/repo) when we're doing the repo-size limit at all if needGitDelta { @@ -801,7 +792,6 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { currentGit := repo.GitSize currentLFS := repo.LFSSize - currentCombined := currentGit + currentLFS gitDelta := addedSize - removedSize predictedGitAfter := currentGit + gitDelta @@ -809,44 +799,30 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { lfsDelta := incomingNewToRepoLFS - removedLFSSize predictedLFSAfter := currentLFS + lfsDelta - predictedCombinedAfter := predictedGitAfter - if setting.LFSSizeInRepoSize { - predictedCombinedAfter = predictedGitAfter + predictedLFSAfter - } - - // Avoid nil panic when repo-size check is disabled but LFS delta is enabled (combined mode / LFS limit) - pushBytes := int64(0) - if pushSize != nil { - pushBytes = maxInt64(0, pushSize.Size+pushSize.SizePack) - } - // One summary line (time included here only) if repo.ShouldCheckRepoSize() || repo.ShouldCheckLFSSize() { log.Warn( - "SizeCheck summary: took=%s repo=%s/%s git(pred=%s cur=%s delta=%s) lfs(pred=%s cur=%s delta=%s) combined(pred=%s) limits(repo=%s lfs=%s) LFSSizeInRepoSize=%v push=%s", + "SizeCheck summary: took=%s repo=%s/%s git(pred=%s cur=%s delta=%s) lfs(pred=%s cur=%s delta=%s) limits(git=%s lfs=%s)", duration, repo.OwnerName, repo.Name, base.FileSize(predictedGitAfter), base.FileSize(currentGit), base.FileSize(gitDelta), base.FileSize(predictedLFSAfter), base.FileSize(currentLFS), base.FileSize(lfsDelta), - base.FileSize(predictedCombinedAfter), - base.FileSize(repo.GetActualSizeLimit()), - base.FileSize(repo.GetActualLFSSizeLimit()), - setting.LFSSizeInRepoSize, - base.FileSize(pushBytes), + setting.FormatRepositorySizeLimit(setting.Repository.GitSizeMax), + setting.FormatRepositorySizeLimit(setting.Repository.LFSSizeMax), ) } - // 1) LFS-only limit: compare against predicted LFS after push + // 1) LFS size limit: compare against predicted LFS after push if repo.ShouldCheckLFSSize() { lfsLimit := repo.GetActualLFSSizeLimit() - if lfsLimit > 0 && predictedLFSAfter > lfsLimit && predictedLFSAfter > currentLFS { - log.Warn("Forbidden: LFS limit exceeded: %s > %s for repo %-v", + if lfsLimit >= 0 && predictedLFSAfter > lfsLimit && predictedLFSAfter > currentLFS { + log.Warn("Forbidden: LFS size limit exceeded: %s > %s for repo %-v", base.FileSize(predictedLFSAfter), base.FileSize(lfsLimit), repo, ) ctx.JSON(http.StatusForbidden, private.Response{ - UserMsg: fmt.Sprintf("LFS size limit exceeded: %s > then limit of %s", + UserMsg: fmt.Sprintf("LFS size limit exceeded: %s > than limit of %s", base.FileSize(predictedLFSAfter), base.FileSize(lfsLimit), ), @@ -855,17 +831,17 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { } } - // 2) Repo (git) size limit when NOT counting LFS into repo size - if repo.ShouldCheckRepoSize() && !setting.LFSSizeInRepoSize { + // 2) Git size limit + if repo.ShouldCheckRepoSize() { limit := repo.GetActualSizeLimit() - if limit > 0 && predictedGitAfter > limit && predictedGitAfter > currentGit { - log.Warn("Forbidden: repository size limit exceeded: %s > %s for repo %-v", + if limit >= 0 && predictedGitAfter > limit && predictedGitAfter > currentGit { + log.Warn("Forbidden: Repository git size limit exceeded: %s > %s for repo %-v", base.FileSize(predictedGitAfter), base.FileSize(limit), repo, ) ctx.JSON(http.StatusForbidden, private.Response{ - UserMsg: fmt.Sprintf("Repository size limit exceeded: %s > then limit of %s", + UserMsg: fmt.Sprintf("Repository git size limit exceeded: %s > then limit of %s", base.FileSize(predictedGitAfter), base.FileSize(limit), ), @@ -874,25 +850,6 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { } } - // 3) Combined limit when LFS is counted in repo size - if setting.LFSSizeInRepoSize && repo.ShouldCheckRepoSize() { - limit := repo.GetActualSizeLimit() - if limit > 0 && predictedCombinedAfter > limit && predictedCombinedAfter > currentCombined { - log.Warn("Forbidden: combined repo and LFS size limit exceeded: %s > %s for repo %-v", - base.FileSize(predictedCombinedAfter), - base.FileSize(limit), - repo, - ) - ctx.JSON(http.StatusForbidden, private.Response{ - UserMsg: fmt.Sprintf("Combined repository+LFS size limit exceeded: %s > then limit of %s", - base.FileSize(predictedCombinedAfter), - base.FileSize(limit), - ), - }) - return - } - } - ctx.PlainText(http.StatusOK, "ok") } diff --git a/routers/web/admin/repos.go b/routers/web/admin/repos.go index 0697b27cf2..7b04340576 100644 --- a/routers/web/admin/repos.go +++ b/routers/web/admin/repos.go @@ -11,7 +11,6 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -33,9 +32,11 @@ func Repos(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.repositories") ctx.Data["PageIsAdminRepositories"] = true - ctx.Data["RepoSizeLimit"] = base.FileSize(setting.RepoSizeLimit) - ctx.Data["LFSSizeLimit"] = base.FileSize(setting.LFSSizeLimit) - ctx.Data["LFSSizeInRepoSize"] = setting.LFSSizeInRepoSize + gitSizeStr := setting.FormatRepositorySizeLimit(setting.Repository.GitSizeMax) + lfsSizeStr := setting.FormatRepositorySizeLimit(setting.Repository.LFSSizeMax) + log.Trace("Repos: GitSizeMax=%d -> %s, LFSSizeMax=%d -> %s", setting.Repository.GitSizeMax, gitSizeStr, setting.Repository.LFSSizeMax, lfsSizeStr) + ctx.Data["GitSizeMax"] = gitSizeStr + ctx.Data["LFSSizeMax"] = lfsSizeStr explore.RenderRepoSearch(ctx, &explore.RepoSearchOptions{ Private: true, @@ -46,28 +47,16 @@ func Repos(ctx *context.Context) { } func UpdateRepoPost(ctx *context.Context) { - temp := web.GetForm(ctx) - if temp == nil { - ctx.Data["Err_Repo_Size_Limit"] = "" - explore.RenderRepoSearch(ctx, &explore.RepoSearchOptions{ - Private: true, - PageSize: setting.UI.Admin.RepoPagingNum, - TplName: tplRepos, - OnlyShowRelevant: false, - }) - return - } - form := temp.(*forms.UpdateGlobalRepoFrom) + form := web.GetForm(ctx).(*forms.UpdateGlobalRepoFrom) ctx.Data["Title"] = ctx.Tr("admin.repositories") ctx.Data["PageIsAdminRepositories"] = true - ctx.Data["RepoSizeLimit"] = form.RepoSizeLimit - ctx.Data["LFSSizeLimit"] = form.LFSSizeLimit - ctx.Data["LFSSizeInRepoSize"] = form.LFSSizeInRepoSize + ctx.Data["GitSizeMax"] = form.GitSizeMax + ctx.Data["LFSSizeMax"] = form.LFSSizeMax - repoSizeLimit, err := base.GetFileSize(form.RepoSizeLimit) + gitSizeMax, err := setting.ParseRepositorySizeLimit(form.GitSizeMax) if err != nil { - ctx.Data["Err_Repo_Size_Limit"] = form.RepoSizeLimit + ctx.Data["Err_Git_Size_Max"] = form.GitSizeMax explore.RenderRepoSearch(ctx, &explore.RepoSearchOptions{ Private: true, PageSize: setting.UI.Admin.RepoPagingNum, @@ -77,9 +66,9 @@ func UpdateRepoPost(ctx *context.Context) { return } - lfsSizeLimit, err := base.GetFileSize(form.LFSSizeLimit) + lfsSizeMax, err := setting.ParseRepositorySizeLimit(form.LFSSizeMax) if err != nil { - ctx.Data["Err_LFS_Size_Limit"] = form.LFSSizeLimit + ctx.Data["Err_LFS_Size_Max"] = form.LFSSizeMax explore.RenderRepoSearch(ctx, &explore.RepoSearchOptions{ Private: true, PageSize: setting.UI.Admin.RepoPagingNum, @@ -89,19 +78,10 @@ func UpdateRepoPost(ctx *context.Context) { return } - err = setting.SaveGlobalRepositorySetting(repoSizeLimit, lfsSizeLimit, form.LFSSizeInRepoSize) - if err != nil { - ctx.Data["Err_Repo_Size_Save"] = err.Error() - explore.RenderRepoSearch(ctx, &explore.RepoSearchOptions{ - Private: true, - PageSize: setting.UI.Admin.RepoPagingNum, - TplName: tplRepos, - OnlyShowRelevant: false, - }) - return - } + setting.UpdateGlobalRepositoryLimit(gitSizeMax, lfsSizeMax) + log.Trace("UpdateRepoPost: After update, setting.Repository.GitSizeMax=%d, LFSSizeMax=%d", setting.Repository.GitSizeMax, setting.Repository.LFSSizeMax) - ctx.Flash.Success(ctx.Tr("admin.config.repository_setting_success")) + ctx.Flash.Success(ctx.Tr("admin.repos.update_success")) ctx.Redirect(setting.AppSubURL + "/-/admin/repos") } diff --git a/routers/web/admin/repos_test.go b/routers/web/admin/repos_test.go deleted file mode 100644 index 17594fad2e..0000000000 --- a/routers/web/admin/repos_test.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package admin - -import ( - "testing" - - "code.gitea.io/gitea/models/unittest" - "code.gitea.io/gitea/services/contexttest" - - "github.com/stretchr/testify/assert" -) - -func TestUpdateRepoPost(t *testing.T) { - unittest.PrepareTestEnv(t) - ctx, _ := contexttest.MockContext(t, "admin/repos") - contexttest.LoadUser(t, ctx, 1) - - ctx.Req.Form.Set("enable_size_limit", "on") - ctx.Req.Form.Set("repo_size_limit", "222 kcmcm") - - UpdateRepoPost(ctx) - - assert.NotEmpty(t, ctx.Flash.ErrorMsg) -} diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 2e4682c246..bc2b0264c0 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -288,7 +288,6 @@ func CreatePost(ctx *context.Context) { IsTemplate: form.Template, TrustModel: repo_model.DefaultTrustModel, ObjectFormatName: form.ObjectFormatName, - SizeLimit: form.SizeLimit, }) if err == nil { log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index ab3d28514e..2f18e5bf28 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -15,7 +15,6 @@ import ( repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/indexer/code" @@ -62,11 +61,8 @@ func SettingsCtxData(ctx *context.Context) { ctx.Data["MinimumMirrorInterval"] = setting.Mirror.MinInterval ctx.Data["CanConvertFork"] = ctx.Repo.Repository.IsFork && ctx.Doer.CanCreateRepoIn(ctx.Repo.Repository.Owner) ctx.Data["Err_RepoSize"] = ctx.Repo.Repository.IsRepoSizeOversized(ctx.Repo.Repository.GetActualSizeLimit() / 10) // less than 10% left - ctx.Data["ActualSizeLimit"] = ctx.Repo.Repository.GetActualSizeLimit() - ctx.Data["ActualLFSSizeLimit"] = ctx.Repo.Repository.GetActualLFSSizeLimit() - ctx.Data["RepoSizeLimit"] = setting.RepoSizeLimit - ctx.Data["LFSSizeLimit"] = setting.LFSSizeLimit - ctx.Data["LFSSizeInRepoSize"] = setting.LFSSizeInRepoSize + ctx.Data["GitSizeMax"] = ctx.Repo.Repository.GetActualSizeLimit() + ctx.Data["LFSSizeMax"] = ctx.Repo.Repository.GetActualLFSSizeLimit() signing, _ := gitrepo.GetSigningKey(ctx) ctx.Data["SigningKeyAvailable"] = signing != nil @@ -214,56 +210,9 @@ func handleSettingsPostUpdate(ctx *context.Context) { repo.Name = newRepoName repo.LowerName = strings.ToLower(newRepoName) repo.Description = form.Description - ctx.Data["RepoSizeLimitText"] = form.RepoSizeLimit - ctx.Data["LFSSizeLimitText"] = form.LFSSizeLimit repo.Website = form.Website repo.IsTemplate = form.Template - var repoSizeLimit int64 - var err error - if form.RepoSizeLimit != "" { - repoSizeLimit, err = base.GetFileSize(form.RepoSizeLimit) - if err != nil { - ctx.Data["Err_RepoSizeLimit"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.invalid_repo_size_limit_repo"), tplSettingsOptions, &form) - return - } - } - if repoSizeLimit < 0 { - ctx.Data["Err_RepoSizeLimit"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.invalid_repo_size_limit_repo"), tplSettingsOptions, &form) - return - } - - if !ctx.Doer.IsAdmin && repo.SizeLimit != repoSizeLimit { - ctx.Data["Err_RepoSizeLimit"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.repo_size_limit_only_by_admins"), tplSettingsOptions, &form) - return - } - repo.SizeLimit = repoSizeLimit - - // Handle LFS size limit (admin-only) - var lfsSizeLimit int64 - if form.LFSSizeLimit != "" { - lfsSizeLimit, err = base.GetFileSize(form.LFSSizeLimit) - if err != nil { - ctx.Data["Err_LFSSizeLimit"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.invalid_lfs_size_limit_repo"), tplSettingsOptions, &form) - return - } - } - if lfsSizeLimit < 0 { - ctx.Data["Err_LFSSizeLimit"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.invalid_lfs_size_limit_repo"), tplSettingsOptions, &form) - return - } - if !ctx.Doer.IsAdmin && repo.LFSSizeLimit != lfsSizeLimit { - ctx.Data["Err_LFSSizeLimit"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.lfs_size_limit_only_by_admins"), tplSettingsOptions, &form) - return - } - repo.LFSSizeLimit = lfsSizeLimit - if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { ctx.ServerError("UpdateRepository", err) return diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index ac5980984c..4d51a36ca3 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -18,13 +18,6 @@ import ( "gitea.com/go-chi/binding" ) -// UpdateGlobalRepoFrom for updating global repository setting -type UpdateGlobalRepoFrom struct { - RepoSizeLimit string - LFSSizeLimit string - LFSSizeInRepoSize bool -} - // CreateRepoForm form for creating repository type CreateRepoForm struct { UID int64 `binding:"Required"` @@ -50,7 +43,11 @@ type CreateRepoForm struct { ForkSingleBranch string ObjectFormatName string - SizeLimit int64 +} + +type UpdateGlobalRepoFrom struct { + GitSizeMax string `form:"GitSizeMax"` + LFSSizeMax string `form:"LFSSizeMax"` } // Validate validates the fields @@ -114,8 +111,6 @@ type RepoSettingForm struct { PushMirrorInterval string Template bool EnablePrune bool - RepoSizeLimit string - LFSSizeLimit string // Advanced settings EnableCode bool diff --git a/services/lfs/server.go b/services/lfs/server.go index 2958c5eb7f..e375e0dd57 100644 --- a/services/lfs/server.go +++ b/services/lfs/server.go @@ -186,13 +186,13 @@ func DownloadHandler(ctx *context.Context) { } } -// ---- tracing helpers ---- - +// traceBatchDecision outputs a trace message for a batch decision func traceBatchDecision(rc *requestContext, op, msg string, args ...any) { prefix := fmt.Sprintf("LFS[BATCH][%s/%s][op=%s] ", rc.User, rc.Repo, op) log.Trace(prefix+msg, args...) } +// batchReqID returns a unique ID for a batch request func batchReqID(br *lfs_module.BatchRequest) string { h := sha1.New() h.Write([]byte(br.Operation)) @@ -244,92 +244,50 @@ func BatchHandler(ctx *context.Context) { // Create content store once, reuse for tracing + normal logic below. contentStore := lfs_module.NewContentStore() - currCombinedTotal := repository.GitSize + repository.LFSSize - // Baseline repo stats and limits traceBatchDecision(rc, br.Operation, - "req=%s auth=%t isUpload=%t repoID=%d sizes: git=%s lfs=%s combined=%s limits: repo=%s lfs=%s LFSSizeInRepoSize=%v", + "req=%s auth=%t isUpload=%t repoID=%d sizes: git=%s lfs=%s limits: git=%s lfs=%s", reqID, ctx.IsSigned || ctx.Doer != nil, isUpload, repository.ID, base.FileSize(repository.GitSize), base.FileSize(repository.LFSSize), - base.FileSize(currCombinedTotal), - base.FileSize(repository.GetActualSizeLimit()), - base.FileSize(repository.GetActualLFSSizeLimit()), - setting.LFSSizeInRepoSize, + setting.FormatRepositorySizeLimit(repository.GetActualSizeLimit()), + setting.FormatRepositorySizeLimit(repository.GetActualLFSSizeLimit()), ) // Check LFS size limits for upload operations - if isUpload && (repository.ShouldCheckLFSSize() || (setting.LFSSizeInRepoSize && repository.ShouldCheckRepoSize())) { + if isUpload && repository.ShouldCheckLFSSize() { // Sum sizes of objects that are NEW TO THIS REPO (no meta row) var incomingNewToRepoLFS int64 - var invalidCount, metaMissingCount, metaPresentCount, storeExistsCount int64 for _, p := range br.Objects { if !p.IsValid() { - invalidCount++ continue } meta, _ := git_model.GetLFSMetaObjectByOid(ctx, repository.ID, p.Oid) - exists, _ := contentStore.Exists(p) - if exists { - storeExistsCount++ - } - if meta == nil { - metaMissingCount++ incomingNewToRepoLFS += p.Size - } else { - metaPresentCount++ } } predictedLFS := repository.LFSSize + incomingNewToRepoLFS - predictedTotal := repository.GitSize + predictedLFS - - traceBatchDecision(rc, br.Operation, - "req=%s accounting: objects=%d invalid=%d metaMissing=%d metaPresent=%d storeExists=%d incomingNewToRepoLFS=%s predictedLFS=%s predictedTotal=%s", - reqID, - len(br.Objects), - invalidCount, - metaMissingCount, - metaPresentCount, - storeExistsCount, - base.FileSize(incomingNewToRepoLFS), - base.FileSize(predictedLFS), - base.FileSize(predictedTotal), - ) // LFS-only limit if we are over, but size doesn't increase allow - if repository.ShouldCheckLFSSize() && predictedLFS > repository.GetActualLFSSizeLimit() && predictedLFS > repository.LFSSize { + if predictedLFS > repository.GetActualLFSSizeLimit() && predictedLFS > repository.LFSSize { traceBatchDecision(rc, br.Operation, - "req=%s DECISION=FORBID reason=LFS_LIMIT predictedLFS=%s limit=%s", - reqID, base.FileSize(predictedLFS), base.FileSize(repository.GetActualLFSSizeLimit()), + "req=%s DECISION=FORBID reason=LFS_LIMIT predictedLFS=%s limit=%s (NewObjects=%d ObjectsPresentInStore=%d MetaPresent=%d StoreExists=%d Invalid=%d)", + reqID, base.FileSize(predictedLFS), setting.FormatRepositorySizeLimit(repository.GetActualLFSSizeLimit()), ) writeStatusMessage(ctx, http.StatusForbidden, fmt.Sprintf("LFS size %s would exceed limit %s", - base.FileSize(predictedLFS), base.FileSize(repository.GetActualLFSSizeLimit()))) + base.FileSize(predictedLFS), setting.FormatRepositorySizeLimit(repository.GetActualLFSSizeLimit()))) return } - // Combined limit (conservative: ignores git delta/removals) if we are over, but combined size doesn't increase allow - if setting.LFSSizeInRepoSize && repository.ShouldCheckRepoSize() { - if predictedTotal > repository.GetActualSizeLimit() && predictedTotal > currCombinedTotal { - traceBatchDecision(rc, br.Operation, - "req=%s DECISION=FORBID reason=COMBINED_LIMIT predictedTotal=%s limit=%s", - reqID, base.FileSize(predictedTotal), base.FileSize(repository.GetActualSizeLimit()), - ) - writeStatusMessage(ctx, http.StatusForbidden, - fmt.Sprintf("Repository size %s after LFS addition would exceed limit %s", - base.FileSize(predictedTotal), base.FileSize(repository.GetActualSizeLimit()))) - return - } - } - traceBatchDecision(rc, br.Operation, "req=%s DECISION=ALLOW size-check passed", reqID) } diff --git a/services/repository/create.go b/services/repository/create.go index 129e7d1d54..bfac83419d 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -52,8 +52,6 @@ type CreateRepoOptions struct { TrustModel repo_model.TrustModelType MirrorInterval string ObjectFormatName string - SizeLimit int64 - LFSSizeLimit int64 } func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir string, opts CreateRepoOptions) error { @@ -249,8 +247,6 @@ func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User, Status: opts.Status, IsEmpty: !opts.AutoInit, TrustModel: opts.TrustModel, - SizeLimit: opts.SizeLimit, - LFSSizeLimit: opts.LFSSizeLimit, IsMirror: opts.IsMirror, DefaultBranch: opts.DefaultBranch, DefaultWikiBranch: setting.Repository.DefaultBranch, diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index 9e9bea8e31..c70dfa9b15 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -1,28 +1,20 @@ {{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin")}}