@ -27,19 +27,8 @@ func getRepoWorkingLockKey(repoID int64) string {
return fmt . Sprintf ( "repo_working_%d" , repoID )
return fmt . Sprintf ( "repo_working_%d" , repoID )
}
}
// TransferOwnership transfers all corresponding setting from old user to new one.
// AcceptTransferOwnership transfers all corresponding setting from old user to new one.
func TransferOwnership ( ctx context . Context , doer , newOwner * user_model . User , repo * repo_model . Repository , teams [ ] * organization . Team ) error {
func AcceptTransferOwnership ( ctx context . Context , repo * repo_model . Repository , doer * user_model . User ) error {
if err := repo . LoadOwner ( ctx ) ; err != nil {
return err
}
for _ , team := range teams {
if newOwner . ID != team . OrgID {
return fmt . Errorf ( "team %d does not belong to organization" , team . ID )
}
}
oldOwner := repo . Owner
releaser , err := globallock . Lock ( ctx , getRepoWorkingLockKey ( repo . ID ) )
releaser , err := globallock . Lock ( ctx , getRepoWorkingLockKey ( repo . ID ) )
if err != nil {
if err != nil {
log . Error ( "lock.Lock(): %v" , err )
log . Error ( "lock.Lock(): %v" , err )
@ -47,29 +36,44 @@ func TransferOwnership(ctx context.Context, doer, newOwner *user_model.User, rep
}
}
defer releaser ( )
defer releaser ( )
if err := transferOwnership ( ctx , doer , newOwner . Name , repo ) ; err != nil {
repoTransfer , err := repo_model . GetPendingRepositoryTransfer ( ctx , repo )
if err != nil {
return err
return err
}
}
releaser ( )
newRepo , err := repo_model . GetRepositoryByID ( ctx , repo . ID )
oldOwnerName := repo . OwnerName
if err != nil {
if err := db . WithTx ( ctx , func ( ctx context . Context ) error {
if err := repoTransfer . LoadAttributes ( ctx ) ; err != nil {
return err
return err
}
}
for _ , team := range teams {
if ! repoTransfer . CanUserAcceptOrRejectTransfer ( ctx , doer ) {
if err := addRepositoryToTeam ( ctx , team , newRepo ) ; err != nil {
return util . ErrPermissionDenied
}
if err := repo . LoadOwner ( ctx ) ; err != nil {
return err
return err
}
}
for _ , team := range repoTransfer . Teams {
if repoTransfer . Recipient . ID != team . OrgID {
return fmt . Errorf ( "team %d does not belong to organization" , team . ID )
}
}
}
notify_service . TransferRepository ( ctx , doer , repo , oldOwner . Name )
return transferOwnership ( ctx , repoTransfer . Doer , repoTransfer . Recipient . Name , repo , repoTransfer . Teams )
} ) ; err != nil {
return err
}
releaser ( )
notify_service . TransferRepository ( ctx , doer , repo , oldOwnerName )
return nil
return nil
}
}
// transferOwnership transfers all corresponding repository items from old user to new one.
// transferOwnership transfers all corresponding repository items from old user to new one.
func transferOwnership ( ctx context . Context , doer * user_model . User , newOwnerName string , repo * repo_model . Repository ) ( err error ) {
func transferOwnership ( ctx context . Context , doer * user_model . User , newOwnerName string , repo * repo_model . Repository , teams [ ] * organization . Team ) ( err error ) {
repoRenamed := false
repoRenamed := false
wikiRenamed := false
wikiRenamed := false
oldOwnerName := doer . Name
oldOwnerName := doer . Name
@ -138,7 +142,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
repo . OwnerName = newOwner . Name
repo . OwnerName = newOwner . Name
// Update repository.
// Update repository.
if _, err := sess . ID ( repo . ID ) . Update ( repo ) ; err != nil {
if err := repo_model . UpdateRepositoryCols ( ctx , repo , "owner_id" , "owner_name" ) ; err != nil {
return fmt . Errorf ( "update owner: %w" , err )
return fmt . Errorf ( "update owner: %w" , err )
}
}
@ -174,15 +178,13 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
collaboration . UserID = 0
collaboration . UserID = 0
}
}
// Remove old team-repository relations.
if oldOwner . IsOrganization ( ) {
if oldOwner . IsOrganization ( ) {
// Remove old team-repository relations.
if err := organization . RemoveOrgRepo ( ctx , oldOwner . ID , repo . ID ) ; err != nil {
if err := organization . RemoveOrgRepo ( ctx , oldOwner . ID , repo . ID ) ; err != nil {
return fmt . Errorf ( "removeOrgRepo: %w" , err )
return fmt . Errorf ( "removeOrgRepo: %w" , err )
}
}
}
// Remove project's issues that belong to old organization's projects
// Remove project's issues that belong to old organization's projects
if oldOwner . IsOrganization ( ) {
projects , err := project_model . GetAllProjectsIDsByOwnerIDAndType ( ctx , oldOwner . ID , project_model . TypeOrganization )
projects , err := project_model . GetAllProjectsIDsByOwnerIDAndType ( ctx , oldOwner . ID , project_model . TypeOrganization )
if err != nil {
if err != nil {
return fmt . Errorf ( "Unable to find old org projects: %w" , err )
return fmt . Errorf ( "Unable to find old org projects: %w" , err )
@ -225,15 +227,13 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
return fmt . Errorf ( "watchRepo: %w" , err )
return fmt . Errorf ( "watchRepo: %w" , err )
}
}
// Remove watch for organization.
if oldOwner . IsOrganization ( ) {
if oldOwner . IsOrganization ( ) {
// Remove watch for organization.
if err := repo_model . WatchRepo ( ctx , oldOwner , repo , false ) ; err != nil {
if err := repo_model . WatchRepo ( ctx , oldOwner , repo , false ) ; err != nil {
return fmt . Errorf ( "watchRepo [false]: %w" , err )
return fmt . Errorf ( "watchRepo [false]: %w" , err )
}
}
}
// Delete labels that belong to the old organization and comments that added these labels
// Delete labels that belong to the old organization and comments that added these labels
if oldOwner . IsOrganization ( ) {
if _ , err := sess . Exec ( ` DELETE FROM issue_label WHERE issue_label . id IN (
if _ , err := sess . Exec ( ` DELETE FROM issue_label WHERE issue_label . id IN (
SELECT il_too . id FROM (
SELECT il_too . id FROM (
SELECT il_too_too . id
SELECT il_too_too . id
@ -261,7 +261,6 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
// Rename remote repository to new path and delete local copy.
// Rename remote repository to new path and delete local copy.
dir := user_model . UserPath ( newOwner . Name )
dir := user_model . UserPath ( newOwner . Name )
if err := os . MkdirAll ( dir , os . ModePerm ) ; err != nil {
if err := os . MkdirAll ( dir , os . ModePerm ) ; err != nil {
return fmt . Errorf ( "Failed to create dir %s: %w" , dir , err )
return fmt . Errorf ( "Failed to create dir %s: %w" , dir , err )
}
}
@ -273,7 +272,6 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
// Rename remote wiki repository to new path and delete local copy.
// Rename remote wiki repository to new path and delete local copy.
wikiPath := repo_model . WikiPath ( oldOwner . Name , repo . Name )
wikiPath := repo_model . WikiPath ( oldOwner . Name , repo . Name )
if isExist , err := util . IsExist ( wikiPath ) ; err != nil {
if isExist , err := util . IsExist ( wikiPath ) ; err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , wikiPath , err )
log . Error ( "Unable to check if %s exists. Error: %v" , wikiPath , err )
return err
return err
@ -301,6 +299,17 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
return fmt . Errorf ( "repo_model.NewRedirect: %w" , err )
return fmt . Errorf ( "repo_model.NewRedirect: %w" , err )
}
}
newRepo , err := repo_model . GetRepositoryByID ( ctx , repo . ID )
if err != nil {
return err
}
for _ , team := range teams {
if err := addRepositoryToTeam ( ctx , team , newRepo ) ; err != nil {
return err
}
}
return committer . Commit ( )
return committer . Commit ( )
}
}
@ -343,17 +352,9 @@ func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newR
}
}
}
}
ctx , committer , err := db . TxContext ( ctx )
return db . WithTx ( ctx , func ( ctx context . Context ) error {
if err != nil {
return repo_model . NewRedirect ( ctx , repo . Owner . ID , repo . ID , oldRepoName , newRepoName )
return err
} )
}
defer committer . Close ( )
if err := repo_model . NewRedirect ( ctx , repo . Owner . ID , repo . ID , oldRepoName , newRepoName ) ; err != nil {
return err
}
return committer . Commit ( )
}
}
// ChangeRepositoryName changes all corresponding setting from old repository name to new one.
// ChangeRepositoryName changes all corresponding setting from old repository name to new one.
@ -387,13 +388,25 @@ func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo
// StartRepositoryTransfer transfer a repo from one owner to a new one.
// StartRepositoryTransfer transfer a repo from one owner to a new one.
// it make repository into pending transfer state, if doer can not create repo for new owner.
// it make repository into pending transfer state, if doer can not create repo for new owner.
func StartRepositoryTransfer ( ctx context . Context , doer , newOwner * user_model . User , repo * repo_model . Repository , teams [ ] * organization . Team ) error {
func StartRepositoryTransfer ( ctx context . Context , doer , newOwner * user_model . User , repo * repo_model . Repository , teams [ ] * organization . Team ) error {
releaser , err := globallock . Lock ( ctx , getRepoWorkingLockKey ( repo . ID ) )
if err != nil {
return fmt . Errorf ( "lock.Lock: %w" , err )
}
defer releaser ( )
if err := repo_model . TestRepositoryReadyForTransfer ( repo . Status ) ; err != nil {
if err := repo_model . TestRepositoryReadyForTransfer ( repo . Status ) ; err != nil {
return err
return err
}
}
// Admin is always allowed to transfer || user transfer repo back to his account
var isDirectTransfer bool
oldOwnerName := repo . OwnerName
if err := db . WithTx ( ctx , func ( ctx context . Context ) error {
// Admin is always allowed to transfer || user transfer repo back to his account,
// then it will transfer directly without acceptance.
if doer . IsAdmin || doer . ID == newOwner . ID {
if doer . IsAdmin || doer . ID == newOwner . ID {
return TransferOwnership ( ctx , doer , newOwner , repo , teams )
isDirectTransfer = true
return transferOwnership ( ctx , doer , newOwner . Name , repo , teams )
}
}
if user_model . IsUserBlockedBy ( ctx , doer , newOwner . ID ) {
if user_model . IsUserBlockedBy ( ctx , doer , newOwner . ID ) {
@ -407,7 +420,8 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use
return err
return err
}
}
if allowed {
if allowed {
return TransferOwnership ( ctx , doer , newOwner , repo , teams )
isDirectTransfer = true
return transferOwnership ( ctx , doer , newOwner . Name , repo , teams )
}
}
}
}
@ -424,33 +438,92 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use
// Make repo as pending for transfer
// Make repo as pending for transfer
repo . Status = repo_model . RepositoryPendingTransfer
repo . Status = repo_model . RepositoryPendingTransfer
if err := repo_model . CreatePendingRepositoryTransfer ( ctx , doer , newOwner , repo . ID , teams ) ; err != nil {
return repo_model . CreatePendingRepositoryTransfer ( ctx , doer , newOwner , repo . ID , teams )
} ) ; err != nil {
return err
return err
}
}
if isDirectTransfer {
notify_service . TransferRepository ( ctx , doer , repo , oldOwnerName )
} else {
// notify users who are able to accept / reject transfer
// notify users who are able to accept / reject transfer
notify_service . RepoPendingTransfer ( ctx , doer , newOwner , repo )
notify_service . RepoPendingTransfer ( ctx , doer , newOwner , repo )
}
return nil
return nil
}
}
// Cancel RepositoryTransfer marks the repository as ready and remove pending transfer entry,
// Reject RepositoryTransfer marks the repository as ready and remove pending transfer entry,
// thus cancel the transfer process.
// thus cancel the transfer process.
func CancelRepositoryTransfer ( ctx context . Context , repo * repo_model . Repository ) error {
// The accepter can reject the transfer.
ctx , committer , err := db . TxContext ( ctx )
func RejectRepositoryTransfer ( ctx context . Context , repo * repo_model . Repository , doer * user_model . User ) error {
return db . WithTx ( ctx , func ( ctx context . Context ) error {
repoTransfer , err := repo_model . GetPendingRepositoryTransfer ( ctx , repo )
if err != nil {
if err != nil {
return err
return err
}
}
defer committer . Close ( )
if err := repoTransfer . LoadAttributes ( ctx ) ; err != nil {
return err
}
if ! repoTransfer . CanUserAcceptOrRejectTransfer ( ctx , doer ) {
return util . ErrPermissionDenied
}
repo . Status = repo_model . RepositoryReady
repo . Status = repo_model . RepositoryReady
if err := repo_model . UpdateRepositoryCols ( ctx , repo , "status" ) ; err != nil {
if err := repo_model . UpdateRepositoryCols ( ctx , repo , "status" ) ; err != nil {
return err
return err
}
}
if err := repo_model . DeleteRepositoryTransfer ( ctx , repo . ID ) ; err != nil {
return repo_model . DeleteRepositoryTransfer ( ctx , repo . ID )
} )
}
func canUserCancelTransfer ( ctx context . Context , r * repo_model . RepoTransfer , u * user_model . User ) bool {
if u . IsAdmin || u . ID == r . DoerID {
return true
}
if err := r . LoadAttributes ( ctx ) ; err != nil {
log . Error ( "LoadAttributes: %v" , err )
return false
}
if err := r . Repo . LoadOwner ( ctx ) ; err != nil {
log . Error ( "LoadOwner: %v" , err )
return false
}
if ! r . Repo . Owner . IsOrganization ( ) {
return r . Repo . OwnerID == u . ID
}
perm , err := access_model . GetUserRepoPermission ( ctx , r . Repo , u )
if err != nil {
log . Error ( "GetUserRepoPermission: %v" , err )
return false
}
return perm . IsOwner ( )
}
// CancelRepositoryTransfer cancels the repository transfer process. The sender or
// the users who have admin permission of the original repository can cancel the transfer
func CancelRepositoryTransfer ( ctx context . Context , repoTransfer * repo_model . RepoTransfer , doer * user_model . User ) error {
return db . WithTx ( ctx , func ( ctx context . Context ) error {
if err := repoTransfer . LoadAttributes ( ctx ) ; err != nil {
return err
return err
}
}
return committer . Commit ( )
if ! canUserCancelTransfer ( ctx , repoTransfer , doer ) {
return util . ErrPermissionDenied
}
repoTransfer . Repo . Status = repo_model . RepositoryReady
if err := repo_model . UpdateRepositoryCols ( ctx , repoTransfer . Repo , "status" ) ; err != nil {
return err
}
return repo_model . DeleteRepositoryTransfer ( ctx , repoTransfer . RepoID )
} )
}
}