fix(repo): honor ALLOW_FORK_INTO_SAME_OWNER for organizations (#40025) (#40094)

Backport #40025 by @somaz94

With `[repository] ALLOW_FORK_INTO_SAME_OWNER = true` a user can fork
their own repository, but an organization still cannot fork a repository
it owns. The fork button check and the owner list on the fork page
compared the organization with the repository owner directly instead of
asking `CanUserForkBetweenOwners`, and the walk over parent repositories
dropped the owning organization whenever the setting was on.
Organizations now follow the setting the same way users already do, and
nothing changes when it is off.

Fixes https://github.com/go-gitea/gitea/issues/40021
Assisted-by: Claude Code:claude-opus-5-5
Co-authored-by: somaz <112675579+somaz94@users.noreply.github.com>
This commit is contained in:
Giteabot authored and GitHub committed 2026-10-30 18:06:59 +00:00
1 parent 9ad9f99060
commit 2b11ba861f
4 files changed
+42 -10

No files matched your search

+1 -1
View File
@@ -35,7 +35,7 @@ func CanUserForkRepo(ctx context.Context, user *user_model.User, repo *repo_mode
return false, err
}
for _, org := range ownedOrgs {
if repo.OwnerID != org.ID && !repo_model.HasForkedRepo(ctx, org.ID, repo.ID) {
if CanUserForkBetweenOwners(repo.OwnerID, org.ID) && !repo_model.HasForkedRepo(ctx, org.ID, repo.ID) {
return true, nil
}
}
+28 -1
View File
@@ -6,6 +6,10 @@ package repository
import (
"testing"
"gitea.dev/models/db"
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest"
user_model "gitea.dev/models/user"
"gitea.dev/modules/setting"
"gitea.dev/modules/test"
@@ -13,7 +17,7 @@ import (
)
func TestCanUserForkBetweenOwners(t *testing.T) {
defer test.MockVariableValue(&setting.Repository.AllowForkIntoSameOwner)
defer test.MockVariableValue(&setting.Repository.AllowForkIntoSameOwner)()
setting.Repository.AllowForkIntoSameOwner = true
assert.True(t, CanUserForkBetweenOwners(1, 1))
@@ -23,3 +27,26 @@ func TestCanUserForkBetweenOwners(t *testing.T) {
assert.False(t, CanUserForkBetweenOwners(1, 1))
assert.True(t, CanUserForkBetweenOwners(1, 2))
}
func TestCanUserForkRepoIntoOwningOrg(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
defer test.MockVariableValue(&setting.Repository.AllowForkIntoSameOwner)()
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
// user2's own fork leaves org3, the owner, as the only target
assert.NoError(t, db.Insert(t.Context(), &repo_model.Repository{
OwnerID: 2, Name: "repo3-fork", LowerName: "repo3-fork",
IsFork: true, ForkID: 3,
}))
setting.Repository.AllowForkIntoSameOwner = true
ok, err := CanUserForkRepo(t.Context(), user2, repo3)
assert.NoError(t, err)
assert.True(t, ok)
setting.Repository.AllowForkIntoSameOwner = false
ok, err = CanUserForkRepo(t.Context(), user2, repo3)
assert.NoError(t, err)
assert.False(t, ok)
}
+5 -8
View File
@@ -7,6 +7,7 @@ import (
"errors"
"net/http"
"net/url"
"slices"
"gitea.dev/models/db"
git_model "gitea.dev/models/git"
@@ -60,7 +61,7 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository {
}
var orgs []*organization.Organization
for _, org := range ownedOrgs {
if forkRepo.OwnerID != org.ID && !repo_model.HasForkedRepo(ctx, org.ID, forkRepo.ID) {
if repository.CanUserForkBetweenOwners(forkRepo.OwnerID, org.ID) && !repo_model.HasForkedRepo(ctx, org.ID, forkRepo.ID) {
orgs = append(orgs, org)
}
}
@@ -69,14 +70,10 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository {
for {
if !repository.CanUserForkBetweenOwners(ctx.Doer.ID, traverseParentRepo.OwnerID) {
canForkToUser = false
} else {
for i, org := range orgs {
if org.ID == traverseParentRepo.OwnerID {
orgs = append(orgs[:i], orgs[i+1:]...)
break
}
}
}
orgs = slices.DeleteFunc(orgs, func(org *organization.Organization) bool {
return !repository.CanUserForkBetweenOwners(org.ID, traverseParentRepo.OwnerID)
})
if !traverseParentRepo.IsFork {
break
+8
View File
@@ -13,6 +13,7 @@ import (
org_model "gitea.dev/models/organization"
"gitea.dev/models/unittest"
user_model "gitea.dev/models/user"
"gitea.dev/modules/setting"
"gitea.dev/modules/structs"
"gitea.dev/modules/test"
org_service "gitea.dev/services/org"
@@ -80,6 +81,13 @@ func TestRepoForkToOrg(t *testing.T) {
assert.False(t, exists, "Forking should not be allowed anymore")
}
func TestRepoForkIntoSameOrg(t *testing.T) {
defer tests.PrepareTestEnv(t)()
defer test.MockVariableValue(&setting.Repository.AllowForkIntoSameOwner, true)()
session := loginUser(t, "user2")
testRepoFork(t, session, "org3", "repo3", "org3", "repo3-fork", "")
}
func TestForkListLimitedAndPrivateRepos(t *testing.T) {
defer tests.PrepareTestEnv(t)()
forkItemSelector := ".fork-list .item"