Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 14 commits into from Dec 27, 2022
3 changes: 3 additions & 0 deletions custom/conf/app.example.ini
Expand Up @@ -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]
Expand Down
1 change: 1 addition & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Expand Up @@ -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`)

Expand Down
9 changes: 9 additions & 0 deletions models/user/user.go
Expand Up @@ -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
This conversation was marked as resolved.
Show resolved Hide resolved
// 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 {
Expand Down
2 changes: 2 additions & 0 deletions modules/setting/repository.go
Expand Up @@ -48,6 +48,7 @@ var (
AllowAdoptionOfUnadoptedRepositories bool
AllowDeleteOfUnadoptedRepositories bool
DisableDownloadSourceArchives bool
AllowForkWithoutMaximumLimit bool

// Repository editor settings
Editor struct {
Expand Down Expand Up @@ -160,6 +161,7 @@ var (
DisableMigrations: false,
DisableStars: false,
DefaultBranch: "main",
AllowForkWithoutMaximumLimit: true,

// Repository editor settings
Editor: struct {
Expand Down
2 changes: 1 addition & 1 deletion routers/api/v1/repo/fork.go
Expand Up @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions routers/web/repo/pull.go
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
7 changes: 7 additions & 0 deletions services/repository/fork.go
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions services/repository/fork_test.go
Expand Up @@ -29,4 +29,16 @@ func TestForkRepository(t *testing.T) {
assert.Nil(t, fork)
assert.Error(t, err)
assert.True(t, IsErrForkAlreadyExist(err))

This conversation was marked as resolved.
Show resolved Hide resolved
// user not reached maximum limit of repositories
assert.False(t, repo_model.IsErrReachLimitOfRepo(err))
// user has reached maximum limit of repositories
This conversation was marked as resolved.
Show resolved Hide resolved
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))
}
2 changes: 1 addition & 1 deletion templates/repo/pulls/fork.tmpl
Expand Up @@ -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>
Expand Down