mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-25 13:02:00 +00:00
fix(repo): preserve transfer recipient collaboration (#39042)
Remove temporary recipient access after a transfer ends while preserving existing collaboration. --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -424,6 +424,7 @@ func prepareMigrationTasks() []*migration {
|
|||||||
newMigration(348, "Recreate email_hash table for SHA256 avatar hashes", v28.RecreateEmailHashTable),
|
newMigration(348, "Recreate email_hash table for SHA256 avatar hashes", v28.RecreateEmailHashTable),
|
||||||
newMigration(349, "Expand action_schedule content column", v28.ExpandActionScheduleContent),
|
newMigration(349, "Expand action_schedule content column", v28.ExpandActionScheduleContent),
|
||||||
newMigration(350, "Add published_unix column to release", v28.AddPublishedUnixToRelease),
|
newMigration(350, "Add published_unix column to release", v28.AddPublishedUnixToRelease),
|
||||||
|
newMigration(351, "Track transfer recipient access grants", v28.AddRecipientAccessGrantedToRepoTransfer),
|
||||||
}
|
}
|
||||||
return preparedMigrations
|
return preparedMigrations
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v28
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitea.dev/modelmigration/base"
|
||||||
|
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddRecipientAccessGrantedToRepoTransfer(_ context.Context, x base.EngineMigration) error {
|
||||||
|
type RepoTransfer struct {
|
||||||
|
RecipientAccessGranted bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
}
|
||||||
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreConstrains: true,
|
||||||
|
IgnoreDropIndices: true,
|
||||||
|
}, new(RepoTransfer))
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -72,6 +72,8 @@ type RepoTransfer struct { //nolint:revive // export stutter
|
|||||||
TeamIDs []int64
|
TeamIDs []int64
|
||||||
Teams []*organization.Team `xorm:"-"`
|
Teams []*organization.Team `xorm:"-"`
|
||||||
|
|
||||||
|
RecipientAccessGranted bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"`
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"`
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL updated"`
|
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL updated"`
|
||||||
}
|
}
|
||||||
@@ -221,7 +223,7 @@ func TestRepositoryReadyForTransfer(status RepositoryStatus) error {
|
|||||||
|
|
||||||
// CreatePendingRepositoryTransfer transfer a repo from one owner to a new one.
|
// CreatePendingRepositoryTransfer transfer a repo from one owner to a new one.
|
||||||
// it marks the repository transfer as "pending"
|
// it marks the repository transfer as "pending"
|
||||||
func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.User, repoID int64, teams []*organization.Team) error {
|
func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.User, repoID int64, teams []*organization.Team, recipientAccessGranted bool) error {
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
repo, err := GetRepositoryByID(ctx, repoID)
|
repo, err := GetRepositoryByID(ctx, repoID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -270,6 +272,8 @@ func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_m
|
|||||||
UpdatedUnix: timeutil.TimeStampNow(),
|
UpdatedUnix: timeutil.TimeStampNow(),
|
||||||
DoerID: doer.ID,
|
DoerID: doer.ID,
|
||||||
TeamIDs: make([]int64, 0, len(teams)),
|
TeamIDs: make([]int64, 0, len(teams)),
|
||||||
|
|
||||||
|
RecipientAccessGranted: recipientAccessGranted,
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := range teams {
|
for k := range teams {
|
||||||
|
|||||||
@@ -67,31 +67,31 @@ func AddOrUpdateCollaborator(ctx context.Context, repo *repo_model.Repository, u
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteCollaboration removes collaboration relation between the user and repository.
|
// DeleteCollaboration removes collaboration relation between the user and repository.
|
||||||
func DeleteCollaboration(ctx context.Context, repo *repo_model.Repository, collaborator *user_model.User) (err error) {
|
func DeleteCollaboration(ctx context.Context, repo *repo_model.Repository, collaborator *user_model.User) error {
|
||||||
collaboration := &repo_model.Collaboration{
|
return deleteCollaboration(ctx, repo, collaborator, &repo_model.Collaboration{RepoID: repo.ID, UserID: collaborator.ID})
|
||||||
RepoID: repo.ID,
|
}
|
||||||
UserID: collaborator.ID,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func deleteCollaborationByMode(ctx context.Context, repo *repo_model.Repository, collaborator *user_model.User, mode perm.AccessMode) error {
|
||||||
|
return deleteCollaboration(ctx, repo, collaborator, &repo_model.Collaboration{
|
||||||
|
RepoID: repo.ID, UserID: collaborator.ID, Mode: mode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteCollaboration(ctx context.Context, repo *repo_model.Repository, collaborator *user_model.User, collaboration *repo_model.Collaboration) (err error) {
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
if has, err := db.GetEngine(ctx).Delete(collaboration); err != nil {
|
if deleted, err := db.GetEngine(ctx).Delete(collaboration); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if has == 0 {
|
} else if deleted == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo.LoadOwner(ctx); err != nil {
|
if err := repo.LoadOwner(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
|
if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = repo_model.WatchRepoAuto(ctx, collaborator, repo, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = ReconsiderWatches(ctx, repo, collaborator); err != nil {
|
if err = ReconsiderWatches(ctx, repo, collaborator); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,8 @@ func ReconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Reposito
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReconsiderWatches(ctx context.Context, repo *repo_model.Repository, user *user_model.User) error {
|
func ReconsiderWatches(ctx context.Context, repo *repo_model.Repository, user *user_model.User) error {
|
||||||
if has, err := access_model.HasAnyUnitAccess(ctx, user.ID, repo); err != nil || has {
|
permission, err := access_model.GetIndividualUserRepoPermission(ctx, repo, user)
|
||||||
|
if err != nil || permission.HasAnyUnitAccessOrPublicAccess() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := repo_model.WatchRepoAuto(ctx, user, repo, false); err != nil {
|
if err := repo_model.WatchRepoAuto(ctx, user, repo, false); err != nil {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"gitea.dev/models/perm"
|
"gitea.dev/models/perm"
|
||||||
access_model "gitea.dev/models/perm/access"
|
access_model "gitea.dev/models/perm/access"
|
||||||
repo_model "gitea.dev/models/repo"
|
repo_model "gitea.dev/models/repo"
|
||||||
|
"gitea.dev/models/unit"
|
||||||
"gitea.dev/models/unittest"
|
"gitea.dev/models/unittest"
|
||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
|
|
||||||
@@ -98,3 +99,22 @@ func TestRepository_DeleteCollaborationRemovesSubscriptionsAndStopwatches(t *tes
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, hasStopwatch)
|
assert.False(t, hasStopwatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRepository_DeleteCollaborationPreservesWatchWithPublicAccess(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
ctx := t.Context()
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||||
|
assert.NoError(t, repo_model.UpdateRepoUnitPublicAccess(ctx, &repo_model.RepoUnit{
|
||||||
|
RepoID: 2, Type: unit.TypeIssues, EveryoneAccessMode: perm.AccessModeRead,
|
||||||
|
}))
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
|
assert.NoError(t, repo.LoadOwner(ctx))
|
||||||
|
assert.NoError(t, AddOrUpdateCollaborator(ctx, repo, user, perm.AccessModeRead))
|
||||||
|
assert.NoError(t, repo_model.WatchRepoAuto(ctx, user, repo, true))
|
||||||
|
|
||||||
|
assert.NoError(t, DeleteCollaboration(ctx, repo, user))
|
||||||
|
watch, err := repo_model.GetWatch(ctx, user.ID, repo.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, repo_model.IsWatchModeWatching(watch.Mode))
|
||||||
|
}
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use
|
|||||||
return transferOwnership(ctx, doer, newOwner.Name, repo, teams)
|
return transferOwnership(ctx, doer, newOwner.Name, repo, teams)
|
||||||
}
|
}
|
||||||
|
|
||||||
if user_model.IsUserBlockedBy(ctx, doer, newOwner.ID) {
|
if user_model.IsUserBlockedBy(ctx, doer, newOwner.ID) || user_model.IsUserBlockedBy(ctx, newOwner, repo.OwnerID) {
|
||||||
return user_model.ErrBlockedUser
|
return user_model.ErrBlockedUser
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,15 +471,19 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !hasAccess {
|
grantRecipientTempAccess := !hasAccess
|
||||||
if err := AddOrUpdateCollaborator(ctx, repo, newOwner, perm.AccessModeRead); err != nil {
|
if grantRecipientTempAccess {
|
||||||
|
if err := db.Insert(ctx, &repo_model.Collaboration{RepoID: repo.ID, UserID: newOwner.ID, Mode: perm.AccessModeRead}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := access_model.RecalculateUserAccess(ctx, repo, newOwner.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make repo as pending for transfer
|
// Make repo as pending for transfer
|
||||||
repo.Status = repo_model.RepositoryPendingTransfer
|
repo.Status = repo_model.RepositoryPendingTransfer
|
||||||
return repo_model.CreatePendingRepositoryTransfer(ctx, doer, newOwner, repo.ID, teams)
|
return repo_model.CreatePendingRepositoryTransfer(ctx, doer, newOwner, repo.ID, teams, grantRecipientTempAccess)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -511,6 +515,9 @@ func RejectRepositoryTransfer(ctx context.Context, repo *repo_model.Repository,
|
|||||||
if !repoTransfer.CanUserAcceptOrRejectTransfer(ctx, doer) {
|
if !repoTransfer.CanUserAcceptOrRejectTransfer(ctx, doer) {
|
||||||
return util.ErrPermissionDenied
|
return util.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
if err := removeTransferRecipientCollaboration(ctx, repoTransfer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
repo.Status = repo_model.RepositoryReady
|
repo.Status = repo_model.RepositoryReady
|
||||||
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "status"); err != nil {
|
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "status"); err != nil {
|
||||||
@@ -521,6 +528,13 @@ func RejectRepositoryTransfer(ctx context.Context, repo *repo_model.Repository,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeTransferRecipientCollaboration(ctx context.Context, repoTransfer *repo_model.RepoTransfer) error {
|
||||||
|
if !repoTransfer.RecipientAccessGranted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return deleteCollaborationByMode(ctx, repoTransfer.Repo, repoTransfer.Recipient, perm.AccessModeRead)
|
||||||
|
}
|
||||||
|
|
||||||
func canUserCancelTransfer(ctx context.Context, r *repo_model.RepoTransfer, u *user_model.User) bool {
|
func canUserCancelTransfer(ctx context.Context, r *repo_model.RepoTransfer, u *user_model.User) bool {
|
||||||
if u.IsAdmin || u.ID == r.DoerID {
|
if u.IsAdmin || u.ID == r.DoerID {
|
||||||
return true
|
return true
|
||||||
@@ -559,6 +573,9 @@ func CancelRepositoryTransfer(ctx context.Context, repoTransfer *repo_model.Repo
|
|||||||
if !canUserCancelTransfer(ctx, repoTransfer, doer) {
|
if !canUserCancelTransfer(ctx, repoTransfer, doer) {
|
||||||
return util.ErrPermissionDenied
|
return util.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
if err := removeTransferRecipientCollaboration(ctx, repoTransfer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
repoTransfer.Repo.Status = repo_model.RepositoryReady
|
repoTransfer.Repo.Status = repo_model.RepositoryReady
|
||||||
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repoTransfer.Repo, "status"); err != nil {
|
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repoTransfer.Repo, "status"); err != nil {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
activities_model "gitea.dev/models/activities"
|
activities_model "gitea.dev/models/activities"
|
||||||
"gitea.dev/models/organization"
|
"gitea.dev/models/organization"
|
||||||
|
"gitea.dev/models/perm"
|
||||||
access_model "gitea.dev/models/perm/access"
|
access_model "gitea.dev/models/perm/access"
|
||||||
repo_model "gitea.dev/models/repo"
|
repo_model "gitea.dev/models/repo"
|
||||||
"gitea.dev/models/unittest"
|
"gitea.dev/models/unittest"
|
||||||
@@ -72,16 +73,60 @@ func TestStartRepositoryTransferSetPermission(t *testing.T) {
|
|||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
assert.NoError(t, repo.LoadOwner(t.Context()))
|
assert.NoError(t, repo.LoadOwner(t.Context()))
|
||||||
|
|
||||||
|
// the recipient doesn't have permission to access the repo
|
||||||
hasAccess, err := access_model.HasAnyUnitAccess(t.Context(), recipient.ID, repo)
|
hasAccess, err := access_model.HasAnyUnitAccess(t.Context(), recipient.ID, repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, hasAccess)
|
assert.False(t, hasAccess)
|
||||||
|
|
||||||
assert.NoError(t, StartRepositoryTransfer(t.Context(), doer, recipient, repo, nil))
|
t.Run("RevokeAccessAfterRejection", func(t *testing.T) {
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
hasAccess, err = access_model.HasAnyUnitAccess(t.Context(), recipient.ID, repo)
|
assert.NoError(t, repo.LoadOwner(t.Context()))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, StartRepositoryTransfer(t.Context(), doer, recipient, repo, nil))
|
||||||
assert.True(t, hasAccess)
|
hasAccess, err = access_model.HasAnyUnitAccess(t.Context(), recipient.ID, repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, hasAccess)
|
||||||
|
assert.NoError(t, RejectRepositoryTransfer(t.Context(), repo, recipient))
|
||||||
|
hasAccess, err = access_model.HasAnyUnitAccess(t.Context(), recipient.ID, repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, hasAccess)
|
||||||
|
})
|
||||||
|
t.Run("RevokeAccessAfterCancel", func(t *testing.T) {
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
|
assert.NoError(t, repo.LoadOwner(t.Context()))
|
||||||
|
assert.NoError(t, StartRepositoryTransfer(t.Context(), doer, recipient, repo, nil))
|
||||||
|
hasAccess, err = access_model.HasAnyUnitAccess(t.Context(), recipient.ID, repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, hasAccess)
|
||||||
|
transfer, err := repo_model.GetPendingRepositoryTransfer(t.Context(), repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, CancelRepositoryTransfer(t.Context(), transfer, doer))
|
||||||
|
hasAccess, err = access_model.HasAnyUnitAccess(t.Context(), recipient.ID, repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, hasAccess)
|
||||||
|
})
|
||||||
|
t.Run("KeepOriginAccessOnStop", func(t *testing.T) {
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
|
assert.NoError(t, repo.LoadOwner(t.Context()))
|
||||||
|
assert.NoError(t, AddOrUpdateCollaborator(t.Context(), repo, recipient, perm.AccessModeWrite))
|
||||||
|
assert.NoError(t, StartRepositoryTransfer(t.Context(), doer, recipient, repo, nil))
|
||||||
|
assert.NoError(t, RejectRepositoryTransfer(t.Context(), repo, recipient))
|
||||||
|
collaboration, err := repo_model.GetCollaboration(t.Context(), repo.ID, recipient.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, perm.AccessModeWrite, collaboration.Mode)
|
||||||
|
assert.NoError(t, DeleteCollaboration(t.Context(), repo, recipient))
|
||||||
|
})
|
||||||
|
t.Run("KeepNonReadAccessOnStop", func(t *testing.T) {
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
|
assert.NoError(t, repo.LoadOwner(t.Context()))
|
||||||
|
assert.NoError(t, StartRepositoryTransfer(t.Context(), doer, recipient, repo, nil))
|
||||||
|
transfer, err := repo_model.GetPendingRepositoryTransfer(t.Context(), repo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, AddOrUpdateCollaborator(t.Context(), repo, recipient, perm.AccessModeWrite))
|
||||||
|
assert.NoError(t, CancelRepositoryTransfer(t.Context(), transfer, doer))
|
||||||
|
collaboration, err := repo_model.GetCollaboration(t.Context(), repo.ID, recipient.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, perm.AccessModeWrite, collaboration.Mode)
|
||||||
|
})
|
||||||
unittest.CheckConsistencyFor(t, &repo_model.Repository{}, &user_model.User{}, &organization.Team{})
|
unittest.CheckConsistencyFor(t, &repo_model.Repository{}, &user_model.User{}, &organization.Team{})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +150,7 @@ func TestRepositoryTransfer(t *testing.T) {
|
|||||||
|
|
||||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
assert.NoError(t, repo_model.CreatePendingRepositoryTransfer(t.Context(), doer, user2, repo.ID, nil))
|
assert.NoError(t, repo_model.CreatePendingRepositoryTransfer(t.Context(), doer, user2, repo.ID, nil, false))
|
||||||
|
|
||||||
transfer, err = repo_model.GetPendingRepositoryTransfer(t.Context(), repo)
|
transfer, err = repo_model.GetPendingRepositoryTransfer(t.Context(), repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -115,13 +160,13 @@ func TestRepositoryTransfer(t *testing.T) {
|
|||||||
org6 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
org6 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
// Only transfer can be started at any given time
|
// Only transfer can be started at any given time
|
||||||
err = repo_model.CreatePendingRepositoryTransfer(t.Context(), doer, org6, repo.ID, nil)
|
err = repo_model.CreatePendingRepositoryTransfer(t.Context(), doer, org6, repo.ID, nil, false)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.True(t, repo_model.IsErrRepoTransferInProgress(err))
|
assert.True(t, repo_model.IsErrRepoTransferInProgress(err))
|
||||||
|
|
||||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
// Unknown user, transfer non-existent transfer repo id = 2
|
// Unknown user, transfer non-existent transfer repo id = 2
|
||||||
err = repo_model.CreatePendingRepositoryTransfer(t.Context(), doer, &user_model.User{ID: 1000, LowerName: "user1000"}, repo2.ID, nil)
|
err = repo_model.CreatePendingRepositoryTransfer(t.Context(), doer, &user_model.User{ID: 1000, LowerName: "user1000"}, repo2.ID, nil, false)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
||||||
// Reject transfer
|
// Reject transfer
|
||||||
|
|||||||
Reference in New Issue
Block a user