mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 20:36:07 +01:00 
			
		
		
		
	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:
		@@ -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]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user