Add option to prohibit fork if user reached maximum limit of repositories (#21848)

If user has reached the maximum limit of repositories:

- Before
  - disallow create
  - allow fork without limit
- This patch:
  - disallow create
  - disallow fork
- Add option `ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT` (Default **true**) :
enable this allow user fork repositories without maximum number limit

fixed https://github.com/go-gitea/gitea/issues/21847

Signed-off-by: Xinyu Zhou <i@sourcehut.net>
This commit is contained in:
Xinyu Zhou 2022-12-28 05:21:14 +08:00 committed by GitHub
parent 22a6e97597
commit 7cc7db73b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 53 additions and 2 deletions

View file

@ -957,6 +957,9 @@ ROUTER = console
;; Don't allow download source archive files from UI ;; Don't allow download source archive files from UI
;DISABLE_DOWNLOAD_SOURCE_ARCHIVES = false ;DISABLE_DOWNLOAD_SOURCE_ARCHIVES = false
;; Allow fork repositories without maximum number limit
;ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT = true
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[repository.editor] ;[repository.editor]

View file

@ -112,6 +112,7 @@ In addition there is _`StaticRootPath`_ which can be set as a built-in at build
- `ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to adopt unadopted repositories - `ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to adopt unadopted repositories
- `ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to delete unadopted repositories - `ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to delete unadopted repositories
- `DISABLE_DOWNLOAD_SOURCE_ARCHIVES`: **false**: Don't allow download source archive files from UI - `DISABLE_DOWNLOAD_SOURCE_ARCHIVES`: **false**: Don't allow download source archive files from UI
- `ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT`: **true**: Allow fork repositories without maximum number limit
### Repository - Editor (`repository.editor`) ### Repository - Editor (`repository.editor`)

View file

@ -275,6 +275,15 @@ func (u *User) CanEditGitHook() bool {
return !setting.DisableGitHooks && (u.IsAdmin || u.AllowGitHook) return !setting.DisableGitHooks && (u.IsAdmin || u.AllowGitHook)
} }
// CanForkRepo returns if user login can fork a repository
// It checks especially that the user can create repos, and potentially more
func (u *User) CanForkRepo() bool {
if setting.Repository.AllowForkWithoutMaximumLimit {
return true
}
return u.CanCreateRepo()
}
// CanImportLocal returns true if user can migrate repository by local path. // CanImportLocal returns true if user can migrate repository by local path.
func (u *User) CanImportLocal() bool { func (u *User) CanImportLocal() bool {
if !setting.ImportLocalPaths || u == nil { if !setting.ImportLocalPaths || u == nil {

View file

@ -48,6 +48,7 @@ var (
AllowAdoptionOfUnadoptedRepositories bool AllowAdoptionOfUnadoptedRepositories bool
AllowDeleteOfUnadoptedRepositories bool AllowDeleteOfUnadoptedRepositories bool
DisableDownloadSourceArchives bool DisableDownloadSourceArchives bool
AllowForkWithoutMaximumLimit bool
// Repository editor settings // Repository editor settings
Editor struct { Editor struct {
@ -160,6 +161,7 @@ var (
DisableMigrations: false, DisableMigrations: false,
DisableStars: false, DisableStars: false,
DefaultBranch: "main", DefaultBranch: "main",
AllowForkWithoutMaximumLimit: true,
// Repository editor settings // Repository editor settings
Editor: struct { Editor: struct {

View file

@ -141,7 +141,7 @@ func CreateFork(ctx *context.APIContext) {
Description: repo.Description, Description: repo.Description,
}) })
if err != nil { if err != nil {
if repo_model.IsErrRepoAlreadyExist(err) { if repo_model.IsErrReachLimitOfRepo(err) || repo_model.IsErrRepoAlreadyExist(err) {
ctx.Error(http.StatusConflict, "ForkRepository", err) ctx.Error(http.StatusConflict, "ForkRepository", err)
} else { } else {
ctx.Error(http.StatusInternalServerError, "ForkRepository", err) ctx.Error(http.StatusInternalServerError, "ForkRepository", err)

View file

@ -182,6 +182,15 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository {
func Fork(ctx *context.Context) { func Fork(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("new_fork") ctx.Data["Title"] = ctx.Tr("new_fork")
if ctx.Doer.CanForkRepo() {
ctx.Data["CanForkRepo"] = true
} else {
maxCreationLimit := ctx.Doer.MaxCreationLimit()
msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
ctx.Data["Flash"] = ctx.Flash
ctx.Flash.Error(msg)
}
getForkRepository(ctx) getForkRepository(ctx)
if ctx.Written() { if ctx.Written() {
return return
@ -254,6 +263,10 @@ func ForkPost(ctx *context.Context) {
if err != nil { if err != nil {
ctx.Data["Err_RepoName"] = true ctx.Data["Err_RepoName"] = true
switch { switch {
case repo_model.IsErrReachLimitOfRepo(err):
maxCreationLimit := ctxUser.MaxCreationLimit()
msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
ctx.RenderWithErr(msg, tplFork, &form)
case repo_model.IsErrRepoAlreadyExist(err): case repo_model.IsErrRepoAlreadyExist(err):
ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form) ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
case db.IsErrNameReserved(err): case db.IsErrNameReserved(err):

View file

@ -51,6 +51,13 @@ type ForkRepoOptions struct {
// ForkRepository forks a repository // ForkRepository forks a repository
func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts ForkRepoOptions) (*repo_model.Repository, error) { func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts ForkRepoOptions) (*repo_model.Repository, error) {
// Fork is prohibited, if user has reached maximum limit of repositories
if !owner.CanForkRepo() {
return nil, repo_model.ErrReachLimitOfRepo{
Limit: owner.MaxRepoCreation,
}
}
forkedRepo, err := repo_model.GetUserFork(ctx, opts.BaseRepo.ID, owner.ID) forkedRepo, err := repo_model.GetUserFork(ctx, opts.BaseRepo.ID, owner.ID)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -10,6 +10,7 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -29,4 +30,19 @@ func TestForkRepository(t *testing.T) {
assert.Nil(t, fork) assert.Nil(t, fork)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrForkAlreadyExist(err)) assert.True(t, IsErrForkAlreadyExist(err))
// user not reached maximum limit of repositories
assert.False(t, repo_model.IsErrReachLimitOfRepo(err))
// change AllowForkWithoutMaximumLimit to false for the test
setting.Repository.AllowForkWithoutMaximumLimit = false
// user has reached maximum limit of repositories
user.MaxRepoCreation = 0
fork2, err := ForkRepository(git.DefaultContext, user, user, ForkRepoOptions{
BaseRepo: repo,
Name: "test",
Description: "test",
})
assert.Nil(t, fork2)
assert.True(t, repo_model.IsErrReachLimitOfRepo(err))
} }

View file

@ -58,7 +58,7 @@
<div class="inline field"> <div class="inline field">
<label></label> <label></label>
<button class="ui green button"> <button class="ui green button{{if not .CanForkRepo}} disabled{{end}}">
{{.locale.Tr "repo.fork_repo"}} {{.locale.Tr "repo.fork_repo"}}
</button> </button>
</div> </div>