From a66c16dc1bca44d480317c6319144d33949bc724 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 14 Dec 2024 09:39:05 +0800 Subject: [PATCH] Allow to fork repository into the same owner (#32819) This feature is experimental, not fully tested, and may be changed in the future. It is only designed for users who really need it: set `[repository].ALLOW_FORK_INTO_SAME_OWNER=true` in your app.ini Doc: https://gitea.com/gitea/docs/pulls/122 ![image](https://github.com/user-attachments/assets/38d08c23-9cfc-49d8-9321-ff81edf65395) --- custom/conf/app.example.ini | 6 +++++- modules/repository/fork.go | 10 +++++++++- modules/repository/fork_test.go | 25 +++++++++++++++++++++++++ modules/setting/repository.go | 1 + routers/web/repo/fork.go | 7 ++++--- 5 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 modules/repository/fork_test.go diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 5c23f70d7ca..6377ebf9d2a 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1040,9 +1040,13 @@ LEVEL = Info ;; Don't allow download source archive files from UI ;DISABLE_DOWNLOAD_SOURCE_ARCHIVES = false -;; Allow fork repositories without maximum number limit +;; Allow to fork repositories without maximum number limit ;ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT = true +;; 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 +;ALLOW_FORK_INTO_SAME_OWNER = false + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[repository.editor] diff --git a/modules/repository/fork.go b/modules/repository/fork.go index fbf00087167..d5306340716 100644 --- a/modules/repository/fork.go +++ b/modules/repository/fork.go @@ -9,14 +9,22 @@ import ( "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" ) +func CanUserForkBetweenOwners(id1, id2 int64) bool { + if id1 != id2 { + return true + } + return setting.Repository.AllowForkIntoSameOwner +} + // CanUserForkRepo returns true if specified user can fork repository. func CanUserForkRepo(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (bool, error) { if user == nil { return false, nil } - if repo.OwnerID != user.ID && !repo_model.HasForkedRepo(ctx, user.ID, repo.ID) { + if CanUserForkBetweenOwners(repo.OwnerID, user.ID) && !repo_model.HasForkedRepo(ctx, user.ID, repo.ID) { return true, nil } ownedOrgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx, user.ID) diff --git a/modules/repository/fork_test.go b/modules/repository/fork_test.go new file mode 100644 index 00000000000..f8c76d942d1 --- /dev/null +++ b/modules/repository/fork_test.go @@ -0,0 +1,25 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "testing" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/assert" +) + +func TestCanUserForkBetweenOwners(t *testing.T) { + defer test.MockVariableValue(&setting.Repository.AllowForkIntoSameOwner) + + setting.Repository.AllowForkIntoSameOwner = true + assert.True(t, CanUserForkBetweenOwners(1, 1)) + assert.True(t, CanUserForkBetweenOwners(1, 2)) + + setting.Repository.AllowForkIntoSameOwner = false + assert.False(t, CanUserForkBetweenOwners(1, 1)) + assert.True(t, CanUserForkBetweenOwners(1, 2)) +} diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 14cf5805c02..c5619d0f048 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -53,6 +53,7 @@ var ( AllowDeleteOfUnadoptedRepositories bool DisableDownloadSourceArchives bool AllowForkWithoutMaximumLimit bool + AllowForkIntoSameOwner bool // Repository editor settings Editor struct { diff --git a/routers/web/repo/fork.go b/routers/web/repo/fork.go index 27e42a8f98e..86af7056176 100644 --- a/routers/web/repo/fork.go +++ b/routers/web/repo/fork.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" @@ -48,7 +49,7 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository { ctx.Data["repo_name"] = forkRepo.Name ctx.Data["description"] = forkRepo.Description ctx.Data["IsPrivate"] = forkRepo.IsPrivate || forkRepo.Owner.Visibility == structs.VisibleTypePrivate - canForkToUser := forkRepo.OwnerID != ctx.Doer.ID && !repo_model.HasForkedRepo(ctx, ctx.Doer.ID, forkRepo.ID) + canForkToUser := repository.CanUserForkBetweenOwners(forkRepo.OwnerID, ctx.Doer.ID) && !repo_model.HasForkedRepo(ctx, ctx.Doer.ID, forkRepo.ID) ctx.Data["ForkRepo"] = forkRepo @@ -66,7 +67,7 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository { traverseParentRepo := forkRepo for { - if ctx.Doer.ID == traverseParentRepo.OwnerID { + if !repository.CanUserForkBetweenOwners(ctx.Doer.ID, traverseParentRepo.OwnerID) { canForkToUser = false } else { for i, org := range orgs { @@ -162,7 +163,7 @@ func ForkPost(ctx *context.Context) { var err error traverseParentRepo := forkRepo for { - if ctxUser.ID == traverseParentRepo.OwnerID { + if !repository.CanUserForkBetweenOwners(ctxUser.ID, traverseParentRepo.OwnerID) { ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form) return }