2021-03-01 03:47:30 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2021-03-01 03:47:30 +03:00
package models
import (
2021-12-12 18:48:20 +03:00
"context"
2024-04-21 22:44:03 +03:00
"errors"
2021-03-01 03:47:30 +03:00
"fmt"
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2022-03-29 09:29:02 +03:00
"code.gitea.io/gitea/models/organization"
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"
2024-03-04 11:16:03 +03:00
"xorm.io/builder"
2021-03-01 03:47:30 +03:00
)
// 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
2022-12-10 05:46:31 +03:00
func ( r * RepoTransfer ) LoadAttributes ( ctx context . Context ) error {
2021-03-01 03:47:30 +03:00
if r . Recipient == nil {
2022-12-10 05:46:31 +03:00
u , err := user_model . GetUserByID ( ctx , 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-12-10 05:46:31 +03:00
team , err := organization . GetTeamByID ( ctx , 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 {
2022-12-10 05:46:31 +03:00
u , err := user_model . GetUserByID ( ctx , 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
2023-09-16 17:39:12 +03:00
func ( r * RepoTransfer ) CanUserAcceptTransfer ( ctx context . Context , u * user_model . User ) bool {
if err := r . LoadAttributes ( ctx ) ; err != nil {
2021-03-01 03:47:30 +03:00
log . Error ( "LoadAttributes: %v" , err )
return false
}
if ! r . Recipient . IsOrganization ( ) {
return r . RecipientID == u . ID
}
2023-09-16 17:39:12 +03:00
allowed , err := organization . CanCreateOrgRepo ( ctx , r . RecipientID , u . ID )
2021-03-01 03:47:30 +03:00
if err != nil {
log . Error ( "CanCreateOrgRepo: %v" , err )
return false
}
return allowed
}
2024-03-04 11:16:03 +03:00
type PendingRepositoryTransferOptions struct {
RepoID int64
SenderID int64
RecipientID int64
}
func ( opts * PendingRepositoryTransferOptions ) ToConds ( ) builder . Cond {
cond := builder . NewCond ( )
if opts . RepoID != 0 {
cond = cond . And ( builder . Eq { "repo_id" : opts . RepoID } )
}
if opts . SenderID != 0 {
cond = cond . And ( builder . Eq { "doer_id" : opts . SenderID } )
}
if opts . RecipientID != 0 {
cond = cond . And ( builder . Eq { "recipient_id" : opts . RecipientID } )
}
return cond
}
func GetPendingRepositoryTransfers ( ctx context . Context , opts * PendingRepositoryTransferOptions ) ( [ ] * RepoTransfer , error ) {
transfers := make ( [ ] * RepoTransfer , 0 , 10 )
return transfers , db . GetEngine ( ctx ) .
Where ( opts . ToConds ( ) ) .
Find ( & transfers )
}
2021-03-01 03:47:30 +03:00
// GetPendingRepositoryTransfer fetches the most recent and ongoing transfer
// process for the repository
2022-12-10 05:46:31 +03:00
func GetPendingRepositoryTransfer ( ctx context . Context , repo * repo_model . Repository ) ( * RepoTransfer , error ) {
2024-03-04 11:16:03 +03:00
transfers , err := GetPendingRepositoryTransfers ( ctx , & PendingRepositoryTransferOptions { RepoID : repo . ID } )
2021-03-01 03:47:30 +03:00
if err != nil {
return nil , err
}
2024-03-04 11:16:03 +03:00
if len ( transfers ) != 1 {
2021-03-01 03:47:30 +03:00
return nil , ErrNoPendingRepoTransfer { RepoID : repo . ID }
}
2024-03-04 11:16:03 +03:00
return transfers [ 0 ] , nil
2021-03-01 03:47:30 +03:00
}
2024-02-05 09:17:23 +03:00
func DeleteRepositoryTransfer ( ctx context . Context , repoID int64 ) error {
2021-12-12 18:48:20 +03:00
_ , err := db . GetEngine ( ctx ) . Where ( "repo_id = ?" , repoID ) . Delete ( & RepoTransfer { } )
2021-03-01 03:47:30 +03:00
return err
}
// 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 :
2024-04-21 22:44:03 +03:00
return errors . New ( "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-12-10 05:46:31 +03:00
func CreatePendingRepositoryTransfer ( ctx context . Context , doer , newOwner * user_model . User , repoID int64 , teams [ ] * organization . Team ) error {
2023-01-08 04:34:58 +03:00
return db . WithTx ( ctx , func ( ctx context . Context ) error {
2022-12-10 05:46:31 +03:00
repo , err := repo_model . GetRepositoryByID ( ctx , repoID )
if err != nil {
return err
}
2021-03-01 03:47:30 +03:00
2022-12-10 05:46:31 +03:00
// Make sure repo is ready to transfer
if err := TestRepositoryReadyForTransfer ( repo . Status ) ; err != nil {
return err
}
2021-03-01 03:47:30 +03:00
2022-12-10 05:46:31 +03:00
repo . Status = repo_model . RepositoryPendingTransfer
if err := repo_model . UpdateRepositoryCols ( ctx , repo , "status" ) ; err != nil {
return err
2021-12-12 18:48:20 +03:00
}
2021-03-01 03:47:30 +03:00
2022-12-10 05:46:31 +03:00
// Check if new owner has repository with same name.
2023-04-28 21:14:26 +03:00
if has , err := repo_model . IsRepositoryModelExist ( ctx , newOwner , repo . Name ) ; err != nil {
2022-12-10 05:46:31 +03:00
return fmt . Errorf ( "IsRepositoryExist: %w" , err )
} else if has {
return repo_model . ErrRepoAlreadyExist {
Uname : newOwner . LowerName ,
Name : repo . Name ,
}
}
2021-03-01 03:47:30 +03:00
2022-12-10 05:46:31 +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 ) ) ,
}
2021-03-01 03:47:30 +03:00
2022-12-10 05:46:31 +03:00
for k := range teams {
transfer . TeamIDs = append ( transfer . TeamIDs , teams [ k ] . ID )
}
2021-03-01 03:47:30 +03:00
2022-12-10 05:46:31 +03:00
return db . Insert ( ctx , transfer )
} )
2021-03-01 03:47:30 +03:00
}