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>tags/v1.19.0-rc0
@@ -957,6 +957,9 @@ ROUTER = console | |||
;; Don't allow download source archive files from UI | |||
;DISABLE_DOWNLOAD_SOURCE_ARCHIVES = false | |||
;; Allow fork repositories without maximum number limit | |||
;ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT = true | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;[repository.editor] |
@@ -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_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 | |||
- `ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT`: **true**: Allow fork repositories without maximum number limit | |||
### Repository - Editor (`repository.editor`) | |||
@@ -275,6 +275,15 @@ func (u *User) CanEditGitHook() bool { | |||
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. | |||
func (u *User) CanImportLocal() bool { | |||
if !setting.ImportLocalPaths || u == nil { |
@@ -48,6 +48,7 @@ var ( | |||
AllowAdoptionOfUnadoptedRepositories bool | |||
AllowDeleteOfUnadoptedRepositories bool | |||
DisableDownloadSourceArchives bool | |||
AllowForkWithoutMaximumLimit bool | |||
// Repository editor settings | |||
Editor struct { | |||
@@ -160,6 +161,7 @@ var ( | |||
DisableMigrations: false, | |||
DisableStars: false, | |||
DefaultBranch: "main", | |||
AllowForkWithoutMaximumLimit: true, | |||
// Repository editor settings | |||
Editor: struct { |
@@ -141,7 +141,7 @@ func CreateFork(ctx *context.APIContext) { | |||
Description: repo.Description, | |||
}) | |||
if err != nil { | |||
if repo_model.IsErrRepoAlreadyExist(err) { | |||
if repo_model.IsErrReachLimitOfRepo(err) || repo_model.IsErrRepoAlreadyExist(err) { | |||
ctx.Error(http.StatusConflict, "ForkRepository", err) | |||
} else { | |||
ctx.Error(http.StatusInternalServerError, "ForkRepository", err) |
@@ -182,6 +182,15 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository { | |||
func Fork(ctx *context.Context) { | |||
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) | |||
if ctx.Written() { | |||
return | |||
@@ -254,6 +263,10 @@ func ForkPost(ctx *context.Context) { | |||
if err != nil { | |||
ctx.Data["Err_RepoName"] = true | |||
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): | |||
ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form) | |||
case db.IsErrNameReserved(err): |
@@ -51,6 +51,13 @@ type ForkRepoOptions struct { | |||
// ForkRepository forks a repository | |||
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) | |||
if err != nil { | |||
return nil, err |
@@ -10,6 +10,7 @@ import ( | |||
"code.gitea.io/gitea/models/unittest" | |||
user_model "code.gitea.io/gitea/models/user" | |||
"code.gitea.io/gitea/modules/git" | |||
"code.gitea.io/gitea/modules/setting" | |||
"github.com/stretchr/testify/assert" | |||
) | |||
@@ -29,4 +30,19 @@ func TestForkRepository(t *testing.T) { | |||
assert.Nil(t, fork) | |||
assert.Error(t, 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)) | |||
} |
@@ -58,7 +58,7 @@ | |||
<div class="inline field"> | |||
<label></label> | |||
<button class="ui green button"> | |||
<button class="ui green button{{if not .CanForkRepo}} disabled{{end}}"> | |||
{{.locale.Tr "repo.fork_repo"}} | |||
</button> | |||
</div> |