2021-03-01 03:47:30 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
2021-12-12 18:48:20 +03:00
"context"
2021-03-01 03:47:30 +03:00
"fmt"
"os"
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2022-06-13 12:37:59 +03:00
issues_model "code.gitea.io/gitea/models/issues"
2022-03-29 09:29:02 +03:00
"code.gitea.io/gitea/models/organization"
2022-05-11 13:09:36 +03:00
access_model "code.gitea.io/gitea/models/perm/access"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2021-03-01 03:47:30 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
)
// RepoTransfer is used to manage repository transfers
type RepoTransfer struct {
ID int64 ` xorm:"pk autoincr" `
DoerID int64
2021-11-24 12:49:20 +03:00
Doer * user_model . User ` xorm:"-" `
2021-03-01 03:47:30 +03:00
RecipientID int64
2021-11-24 12:49:20 +03:00
Recipient * user_model . User ` xorm:"-" `
2021-03-01 03:47:30 +03:00
RepoID int64
TeamIDs [ ] int64
2022-03-29 09:29:02 +03:00
Teams [ ] * organization . Team ` xorm:"-" `
2021-03-01 03:47:30 +03:00
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX NOT NULL created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"INDEX NOT NULL updated" `
}
2021-09-19 14:49:59 +03:00
func init ( ) {
db . RegisterModel ( new ( RepoTransfer ) )
}
2021-03-01 03:47:30 +03:00
// LoadAttributes fetches the transfer recipient from the database
func ( r * RepoTransfer ) LoadAttributes ( ) error {
if r . Recipient == nil {
2021-11-24 12:49:20 +03:00
u , err := user_model . GetUserByID ( r . RecipientID )
2021-03-01 03:47:30 +03:00
if err != nil {
return err
}
r . Recipient = u
}
if r . Recipient . IsOrganization ( ) && len ( r . TeamIDs ) != len ( r . Teams ) {
for _ , v := range r . TeamIDs {
2022-05-20 17:08:52 +03:00
team , err := organization . GetTeamByID ( db . DefaultContext , v )
2021-03-01 03:47:30 +03:00
if err != nil {
return err
}
if team . OrgID != r . Recipient . ID {
return fmt . Errorf ( "team %d belongs not to org %d" , v , r . Recipient . ID )
}
r . Teams = append ( r . Teams , team )
}
}
if r . Doer == nil {
2021-11-24 12:49:20 +03:00
u , err := user_model . GetUserByID ( r . DoerID )
2021-03-01 03:47:30 +03:00
if err != nil {
return err
}
r . Doer = u
}
return nil
}
// CanUserAcceptTransfer checks if the user has the rights to accept/decline a repo transfer.
// For user, it checks if it's himself
// For organizations, it checks if the user is able to create repos
2021-11-24 12:49:20 +03:00
func ( r * RepoTransfer ) CanUserAcceptTransfer ( u * user_model . User ) bool {
2021-03-01 03:47:30 +03:00
if err := r . LoadAttributes ( ) ; err != nil {
log . Error ( "LoadAttributes: %v" , err )
return false
}
if ! r . Recipient . IsOrganization ( ) {
return r . RecipientID == u . ID
}
2022-03-29 09:29:02 +03:00
allowed , err := organization . CanCreateOrgRepo ( r . RecipientID , u . ID )
2021-03-01 03:47:30 +03:00
if err != nil {
log . Error ( "CanCreateOrgRepo: %v" , err )
return false
}
return allowed
}
// GetPendingRepositoryTransfer fetches the most recent and ongoing transfer
// process for the repository
2021-12-10 04:27:50 +03:00
func GetPendingRepositoryTransfer ( repo * repo_model . Repository ) ( * RepoTransfer , error ) {
2021-03-14 21:52:12 +03:00
transfer := new ( RepoTransfer )
2021-03-01 03:47:30 +03:00
2021-09-23 18:45:36 +03:00
has , err := db . GetEngine ( db . DefaultContext ) . Where ( "repo_id = ? " , repo . ID ) . Get ( transfer )
2021-03-01 03:47:30 +03:00
if err != nil {
return nil , err
}
if ! has {
return nil , ErrNoPendingRepoTransfer { RepoID : repo . ID }
}
return transfer , nil
}
2021-12-12 18:48:20 +03:00
func deleteRepositoryTransfer ( ctx context . Context , repoID int64 ) error {
_ , err := db . GetEngine ( ctx ) . Where ( "repo_id = ?" , repoID ) . Delete ( & RepoTransfer { } )
2021-03-01 03:47:30 +03:00
return err
}
// CancelRepositoryTransfer marks the repository as ready and remove pending transfer entry,
// thus cancel the transfer process.
2021-12-10 04:27:50 +03:00
func CancelRepositoryTransfer ( repo * repo_model . Repository ) error {
2021-11-21 18:41:00 +03:00
ctx , committer , err := db . TxContext ( )
if err != nil {
2021-03-01 03:47:30 +03:00
return err
}
2021-11-21 18:41:00 +03:00
defer committer . Close ( )
2021-03-01 03:47:30 +03:00
2021-12-10 04:27:50 +03:00
repo . Status = repo_model . RepositoryReady
2022-05-20 17:08:52 +03:00
if err := repo_model . UpdateRepositoryCols ( ctx , repo , "status" ) ; err != nil {
2021-03-01 03:47:30 +03:00
return err
}
2021-12-12 18:48:20 +03:00
if err := deleteRepositoryTransfer ( ctx , repo . ID ) ; err != nil {
2021-03-01 03:47:30 +03:00
return err
}
2021-11-21 18:41:00 +03:00
return committer . Commit ( )
2021-03-01 03:47:30 +03:00
}
// TestRepositoryReadyForTransfer make sure repo is ready to transfer
2021-12-10 04:27:50 +03:00
func TestRepositoryReadyForTransfer ( status repo_model . RepositoryStatus ) error {
2021-03-01 03:47:30 +03:00
switch status {
2021-12-10 04:27:50 +03:00
case repo_model . RepositoryBeingMigrated :
2021-03-01 03:47:30 +03:00
return fmt . Errorf ( "repo is not ready, currently migrating" )
2021-12-10 04:27:50 +03:00
case repo_model . RepositoryPendingTransfer :
2021-03-01 03:47:30 +03:00
return ErrRepoTransferInProgress { }
}
return nil
}
// CreatePendingRepositoryTransfer transfer a repo from one owner to a new one.
// it marks the repository transfer as "pending"
2022-03-29 09:29:02 +03:00
func CreatePendingRepositoryTransfer ( doer , newOwner * user_model . User , repoID int64 , teams [ ] * organization . Team ) error {
2021-11-21 18:41:00 +03:00
ctx , committer , err := db . TxContext ( )
if err != nil {
2021-03-01 03:47:30 +03:00
return err
}
2021-11-21 18:41:00 +03:00
defer committer . Close ( )
2021-03-01 03:47:30 +03:00
2021-12-10 04:27:50 +03:00
repo , err := repo_model . GetRepositoryByIDCtx ( ctx , repoID )
2021-03-01 03:47:30 +03:00
if err != nil {
return err
}
// Make sure repo is ready to transfer
if err := TestRepositoryReadyForTransfer ( repo . Status ) ; err != nil {
return err
}
2021-12-10 04:27:50 +03:00
repo . Status = repo_model . RepositoryPendingTransfer
2022-05-20 17:08:52 +03:00
if err := repo_model . UpdateRepositoryCols ( ctx , repo , "status" ) ; err != nil {
2021-03-01 03:47:30 +03:00
return err
}
// Check if new owner has repository with same name.
2022-05-20 17:08:52 +03:00
if has , err := repo_model . IsRepositoryExist ( ctx , newOwner , repo . Name ) ; err != nil {
2021-03-01 03:47:30 +03:00
return fmt . Errorf ( "IsRepositoryExist: %v" , err )
} else if has {
2021-12-12 18:48:20 +03:00
return repo_model . ErrRepoAlreadyExist {
Uname : newOwner . LowerName ,
Name : repo . Name ,
}
2021-03-01 03:47:30 +03:00
}
transfer := & RepoTransfer {
RepoID : repo . ID ,
RecipientID : newOwner . ID ,
CreatedUnix : timeutil . TimeStampNow ( ) ,
UpdatedUnix : timeutil . TimeStampNow ( ) ,
DoerID : doer . ID ,
TeamIDs : make ( [ ] int64 , 0 , len ( teams ) ) ,
}
for k := range teams {
transfer . TeamIDs = append ( transfer . TeamIDs , teams [ k ] . ID )
}
2021-11-21 18:41:00 +03:00
if err := db . Insert ( ctx , transfer ) ; err != nil {
2021-03-01 03:47:30 +03:00
return err
}
2021-11-21 18:41:00 +03:00
return committer . Commit ( )
2021-03-01 03:47:30 +03:00
}
// TransferOwnership transfers all corresponding repository items from old user to new one.
2021-12-10 04:27:50 +03:00
func TransferOwnership ( doer * user_model . User , newOwnerName string , repo * repo_model . Repository ) ( err error ) {
2021-03-05 05:28:52 +03:00
repoRenamed := false
wikiRenamed := false
oldOwnerName := doer . Name
defer func ( ) {
if ! repoRenamed && ! wikiRenamed {
return
}
recoverErr := recover ( )
if err == nil && recoverErr == nil {
return
}
if repoRenamed {
2021-12-10 04:27:50 +03:00
if err := util . Rename ( repo_model . RepoPath ( newOwnerName , repo . Name ) , repo_model . RepoPath ( oldOwnerName , repo . Name ) ) ; err != nil {
log . Critical ( "Unable to move repository %s/%s directory from %s back to correct place %s: %v" , oldOwnerName , repo . Name ,
repo_model . RepoPath ( newOwnerName , repo . Name ) , repo_model . RepoPath ( oldOwnerName , repo . Name ) , err )
2021-03-05 05:28:52 +03:00
}
}
if wikiRenamed {
2021-12-10 04:27:50 +03:00
if err := util . Rename ( repo_model . WikiPath ( newOwnerName , repo . Name ) , repo_model . WikiPath ( oldOwnerName , repo . Name ) ) ; err != nil {
log . Critical ( "Unable to move wiki for repository %s/%s directory from %s back to correct place %s: %v" , oldOwnerName , repo . Name ,
repo_model . WikiPath ( newOwnerName , repo . Name ) , repo_model . WikiPath ( oldOwnerName , repo . Name ) , err )
2021-03-05 05:28:52 +03:00
}
}
if recoverErr != nil {
log . Error ( "Panic within TransferOwnership: %v\n%s" , recoverErr , log . Stack ( 2 ) )
panic ( recoverErr )
}
} ( )
2021-11-21 18:41:00 +03:00
ctx , committer , err := db . TxContext ( )
if err != nil {
return err
2021-03-01 03:47:30 +03:00
}
2021-11-21 18:41:00 +03:00
defer committer . Close ( )
sess := db . GetEngine ( ctx )
2021-03-01 03:47:30 +03:00
2022-05-20 17:08:52 +03:00
newOwner , err := user_model . GetUserByName ( ctx , newOwnerName )
2021-03-01 03:47:30 +03:00
if err != nil {
return fmt . Errorf ( "get new owner '%s': %v" , newOwnerName , err )
}
2021-03-05 05:28:52 +03:00
newOwnerName = newOwner . Name // ensure capitalisation matches
2021-03-01 03:47:30 +03:00
// Check if new owner has repository with same name.
2022-05-20 17:08:52 +03:00
if has , err := repo_model . IsRepositoryExist ( ctx , newOwner , repo . Name ) ; err != nil {
2021-03-01 03:47:30 +03:00
return fmt . Errorf ( "IsRepositoryExist: %v" , err )
} else if has {
2021-12-12 18:48:20 +03:00
return repo_model . ErrRepoAlreadyExist {
Uname : newOwnerName ,
Name : repo . Name ,
}
2021-03-01 03:47:30 +03:00
}
oldOwner := repo . Owner
2021-03-05 05:28:52 +03:00
oldOwnerName = oldOwner . Name
2021-03-01 03:47:30 +03:00
// Note: we have to set value here to make sure recalculate accesses is based on
// new owner.
repo . OwnerID = newOwner . ID
repo . Owner = newOwner
repo . OwnerName = newOwner . Name
// Update repository.
if _ , err := sess . ID ( repo . ID ) . Update ( repo ) ; err != nil {
return fmt . Errorf ( "update owner: %v" , err )
}
// Remove redundant collaborators.
2022-05-11 13:09:36 +03:00
collaborators , err := repo_model . GetCollaborators ( ctx , repo . ID , db . ListOptions { } )
2021-03-01 03:47:30 +03:00
if err != nil {
return fmt . Errorf ( "getCollaborators: %v" , err )
}
// Dummy object.
2022-05-11 13:09:36 +03:00
collaboration := & repo_model . Collaboration { RepoID : repo . ID }
2021-03-01 03:47:30 +03:00
for _ , c := range collaborators {
2021-09-27 21:07:19 +03:00
if c . IsGhost ( ) {
collaboration . ID = c . Collaboration . ID
if _ , err := sess . Delete ( collaboration ) ; err != nil {
return fmt . Errorf ( "remove collaborator '%d': %v" , c . ID , err )
}
collaboration . ID = 0
}
2021-03-01 03:47:30 +03:00
if c . ID != newOwner . ID {
2022-03-29 09:29:02 +03:00
isMember , err := organization . IsOrganizationMember ( ctx , newOwner . ID , c . ID )
2021-03-01 03:47:30 +03:00
if err != nil {
return fmt . Errorf ( "IsOrgMember: %v" , err )
} else if ! isMember {
continue
}
}
collaboration . UserID = c . ID
if _ , err := sess . Delete ( collaboration ) ; err != nil {
return fmt . Errorf ( "remove collaborator '%d': %v" , c . ID , err )
}
2021-09-27 21:07:19 +03:00
collaboration . UserID = 0
2021-03-01 03:47:30 +03:00
}
// Remove old team-repository relations.
if oldOwner . IsOrganization ( ) {
2022-03-29 09:29:02 +03:00
if err := organization . RemoveOrgRepo ( ctx , oldOwner . ID , repo . ID ) ; err != nil {
2021-03-01 03:47:30 +03:00
return fmt . Errorf ( "removeOrgRepo: %v" , err )
}
}
if newOwner . IsOrganization ( ) {
2022-03-29 09:29:02 +03:00
teams , err := organization . FindOrgTeams ( ctx , newOwner . ID )
2021-11-19 14:41:40 +03:00
if err != nil {
2021-08-12 15:43:08 +03:00
return fmt . Errorf ( "LoadTeams: %v" , err )
2021-03-01 03:47:30 +03:00
}
2021-11-19 14:41:40 +03:00
for _ , t := range teams {
2021-03-01 03:47:30 +03:00
if t . IncludesAllRepositories {
2022-08-25 05:31:57 +03:00
if err := AddRepository ( ctx , t , repo ) ; err != nil {
return fmt . Errorf ( "AddRepository: %v" , err )
2021-03-01 03:47:30 +03:00
}
}
}
2022-05-11 13:09:36 +03:00
} else if err := access_model . RecalculateAccesses ( ctx , repo ) ; err != nil {
2021-03-01 03:47:30 +03:00
// Organization called this in addRepository method.
return fmt . Errorf ( "recalculateAccesses: %v" , err )
}
// Update repository count.
if _ , err := sess . Exec ( "UPDATE `user` SET num_repos=num_repos+1 WHERE id=?" , newOwner . ID ) ; err != nil {
return fmt . Errorf ( "increase new owner repository count: %v" , err )
} else if _ , err := sess . Exec ( "UPDATE `user` SET num_repos=num_repos-1 WHERE id=?" , oldOwner . ID ) ; err != nil {
return fmt . Errorf ( "decrease old owner repository count: %v" , err )
}
2022-05-20 17:08:52 +03:00
if err := repo_model . WatchRepo ( ctx , doer . ID , repo . ID , true ) ; err != nil {
2021-03-01 03:47:30 +03:00
return fmt . Errorf ( "watchRepo: %v" , err )
}
// Remove watch for organization.
if oldOwner . IsOrganization ( ) {
2022-05-20 17:08:52 +03:00
if err := repo_model . WatchRepo ( ctx , oldOwner . ID , repo . ID , false ) ; err != nil {
2021-03-01 03:47:30 +03:00
return fmt . Errorf ( "watchRepo [false]: %v" , err )
}
}
2021-03-12 20:45:49 +03:00
// 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 (
SELECT il_too . id FROM (
SELECT il_too_too . id
FROM issue_label AS il_too_too
2021-03-22 21:26:38 +03:00
INNER JOIN label ON il_too_too . label_id = label . id
2021-03-12 20:45:49 +03:00
INNER JOIN issue on issue . id = il_too_too . issue_id
WHERE
2021-03-24 02:10:19 +03:00
issue . repo_id = ? AND ( ( label . org_id = 0 AND issue . repo_id != label . repo_id ) OR ( label . repo_id = 0 AND label . org_id != ? ) )
2021-03-12 20:45:49 +03:00
) AS il_too ) ` , repo . ID , newOwner . ID ) ; err != nil {
return fmt . Errorf ( "Unable to remove old org labels: %v" , err )
}
if _ , err := sess . Exec ( ` DELETE FROM comment WHERE comment . id IN (
SELECT il_too . id FROM (
SELECT com . id
FROM comment AS com
INNER JOIN label ON com . label_id = label . id
2021-03-24 02:10:19 +03:00
INNER JOIN issue ON issue . id = com . issue_id
2021-03-12 20:45:49 +03:00
WHERE
2021-03-24 02:10:19 +03:00
com . type = ? AND issue . repo_id = ? AND ( ( label . org_id = 0 AND issue . repo_id != label . repo_id ) OR ( label . repo_id = 0 AND label . org_id != ? ) )
2022-06-13 12:37:59 +03:00
) AS il_too ) ` , issues_model . CommentTypeLabel , repo . ID , newOwner . ID ) ; err != nil {
2021-03-12 20:45:49 +03:00
return fmt . Errorf ( "Unable to remove old org label comments: %v" , err )
}
}
2021-03-01 03:47:30 +03:00
// Rename remote repository to new path and delete local copy.
2021-11-24 12:49:20 +03:00
dir := user_model . UserPath ( newOwner . Name )
2021-03-01 03:47:30 +03:00
if err := os . MkdirAll ( dir , os . ModePerm ) ; err != nil {
return fmt . Errorf ( "Failed to create dir %s: %v" , dir , err )
}
2021-12-10 04:27:50 +03:00
if err := util . Rename ( repo_model . RepoPath ( oldOwner . Name , repo . Name ) , repo_model . RepoPath ( newOwner . Name , repo . Name ) ) ; err != nil {
2021-03-01 03:47:30 +03:00
return fmt . Errorf ( "rename repository directory: %v" , err )
}
2021-03-05 05:28:52 +03:00
repoRenamed = true
2021-03-01 03:47:30 +03:00
// Rename remote wiki repository to new path and delete local copy.
2021-12-10 04:27:50 +03:00
wikiPath := repo_model . WikiPath ( oldOwner . Name , repo . Name )
2021-03-01 03:47:30 +03:00
if isExist , err := util . IsExist ( wikiPath ) ; err != nil {
log . Error ( "Unable to check if %s exists. Error: %v" , wikiPath , err )
return err
} else if isExist {
2021-12-10 04:27:50 +03:00
if err := util . Rename ( wikiPath , repo_model . WikiPath ( newOwner . Name , repo . Name ) ) ; err != nil {
2021-03-01 03:47:30 +03:00
return fmt . Errorf ( "rename repository wiki: %v" , err )
}
2021-03-05 05:28:52 +03:00
wikiRenamed = true
2021-03-01 03:47:30 +03:00
}
2021-12-12 18:48:20 +03:00
if err := deleteRepositoryTransfer ( ctx , repo . ID ) ; err != nil {
2021-03-01 03:47:30 +03:00
return fmt . Errorf ( "deleteRepositoryTransfer: %v" , err )
}
2021-12-10 04:27:50 +03:00
repo . Status = repo_model . RepositoryReady
2022-05-20 17:08:52 +03:00
if err := repo_model . UpdateRepositoryCols ( ctx , repo , "status" ) ; err != nil {
2021-03-01 03:47:30 +03:00
return err
}
// If there was previously a redirect at this location, remove it.
2021-12-12 18:48:20 +03:00
if err := repo_model . DeleteRedirect ( ctx , newOwner . ID , repo . Name ) ; err != nil {
2021-03-01 03:47:30 +03:00
return fmt . Errorf ( "delete repo redirect: %v" , err )
}
2021-12-12 18:48:20 +03:00
if err := repo_model . NewRedirect ( ctx , oldOwner . ID , repo . ID , repo . Name , repo . Name ) ; err != nil {
return fmt . Errorf ( "repo_model.NewRedirect: %v" , err )
2021-03-01 03:47:30 +03:00
}
2021-11-21 18:41:00 +03:00
return committer . Commit ( )
2021-03-01 03:47:30 +03:00
}