code.gitea.io/gitea@v1.22.3/models/repo_transfer.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package models 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 11 "code.gitea.io/gitea/models/db" 12 "code.gitea.io/gitea/models/organization" 13 repo_model "code.gitea.io/gitea/models/repo" 14 user_model "code.gitea.io/gitea/models/user" 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/timeutil" 17 18 "xorm.io/builder" 19 ) 20 21 // RepoTransfer is used to manage repository transfers 22 type RepoTransfer struct { 23 ID int64 `xorm:"pk autoincr"` 24 DoerID int64 25 Doer *user_model.User `xorm:"-"` 26 RecipientID int64 27 Recipient *user_model.User `xorm:"-"` 28 RepoID int64 29 TeamIDs []int64 30 Teams []*organization.Team `xorm:"-"` 31 32 CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"` 33 UpdatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL updated"` 34 } 35 36 func init() { 37 db.RegisterModel(new(RepoTransfer)) 38 } 39 40 // LoadAttributes fetches the transfer recipient from the database 41 func (r *RepoTransfer) LoadAttributes(ctx context.Context) error { 42 if r.Recipient == nil { 43 u, err := user_model.GetUserByID(ctx, r.RecipientID) 44 if err != nil { 45 return err 46 } 47 48 r.Recipient = u 49 } 50 51 if r.Recipient.IsOrganization() && len(r.TeamIDs) != len(r.Teams) { 52 for _, v := range r.TeamIDs { 53 team, err := organization.GetTeamByID(ctx, v) 54 if err != nil { 55 return err 56 } 57 58 if team.OrgID != r.Recipient.ID { 59 return fmt.Errorf("team %d belongs not to org %d", v, r.Recipient.ID) 60 } 61 62 r.Teams = append(r.Teams, team) 63 } 64 } 65 66 if r.Doer == nil { 67 u, err := user_model.GetUserByID(ctx, r.DoerID) 68 if err != nil { 69 return err 70 } 71 72 r.Doer = u 73 } 74 75 return nil 76 } 77 78 // CanUserAcceptTransfer checks if the user has the rights to accept/decline a repo transfer. 79 // For user, it checks if it's himself 80 // For organizations, it checks if the user is able to create repos 81 func (r *RepoTransfer) CanUserAcceptTransfer(ctx context.Context, u *user_model.User) bool { 82 if err := r.LoadAttributes(ctx); err != nil { 83 log.Error("LoadAttributes: %v", err) 84 return false 85 } 86 87 if !r.Recipient.IsOrganization() { 88 return r.RecipientID == u.ID 89 } 90 91 allowed, err := organization.CanCreateOrgRepo(ctx, r.RecipientID, u.ID) 92 if err != nil { 93 log.Error("CanCreateOrgRepo: %v", err) 94 return false 95 } 96 97 return allowed 98 } 99 100 type PendingRepositoryTransferOptions struct { 101 RepoID int64 102 SenderID int64 103 RecipientID int64 104 } 105 106 func (opts *PendingRepositoryTransferOptions) ToConds() builder.Cond { 107 cond := builder.NewCond() 108 if opts.RepoID != 0 { 109 cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) 110 } 111 if opts.SenderID != 0 { 112 cond = cond.And(builder.Eq{"doer_id": opts.SenderID}) 113 } 114 if opts.RecipientID != 0 { 115 cond = cond.And(builder.Eq{"recipient_id": opts.RecipientID}) 116 } 117 return cond 118 } 119 120 func GetPendingRepositoryTransfers(ctx context.Context, opts *PendingRepositoryTransferOptions) ([]*RepoTransfer, error) { 121 transfers := make([]*RepoTransfer, 0, 10) 122 return transfers, db.GetEngine(ctx). 123 Where(opts.ToConds()). 124 Find(&transfers) 125 } 126 127 // GetPendingRepositoryTransfer fetches the most recent and ongoing transfer 128 // process for the repository 129 func GetPendingRepositoryTransfer(ctx context.Context, repo *repo_model.Repository) (*RepoTransfer, error) { 130 transfers, err := GetPendingRepositoryTransfers(ctx, &PendingRepositoryTransferOptions{RepoID: repo.ID}) 131 if err != nil { 132 return nil, err 133 } 134 135 if len(transfers) != 1 { 136 return nil, ErrNoPendingRepoTransfer{RepoID: repo.ID} 137 } 138 139 return transfers[0], nil 140 } 141 142 func DeleteRepositoryTransfer(ctx context.Context, repoID int64) error { 143 _, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(&RepoTransfer{}) 144 return err 145 } 146 147 // TestRepositoryReadyForTransfer make sure repo is ready to transfer 148 func TestRepositoryReadyForTransfer(status repo_model.RepositoryStatus) error { 149 switch status { 150 case repo_model.RepositoryBeingMigrated: 151 return errors.New("repo is not ready, currently migrating") 152 case repo_model.RepositoryPendingTransfer: 153 return ErrRepoTransferInProgress{} 154 } 155 return nil 156 } 157 158 // CreatePendingRepositoryTransfer transfer a repo from one owner to a new one. 159 // it marks the repository transfer as "pending" 160 func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.User, repoID int64, teams []*organization.Team) error { 161 return db.WithTx(ctx, func(ctx context.Context) error { 162 repo, err := repo_model.GetRepositoryByID(ctx, repoID) 163 if err != nil { 164 return err 165 } 166 167 // Make sure repo is ready to transfer 168 if err := TestRepositoryReadyForTransfer(repo.Status); err != nil { 169 return err 170 } 171 172 repo.Status = repo_model.RepositoryPendingTransfer 173 if err := repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { 174 return err 175 } 176 177 // Check if new owner has repository with same name. 178 if has, err := repo_model.IsRepositoryModelExist(ctx, newOwner, repo.Name); err != nil { 179 return fmt.Errorf("IsRepositoryExist: %w", err) 180 } else if has { 181 return repo_model.ErrRepoAlreadyExist{ 182 Uname: newOwner.LowerName, 183 Name: repo.Name, 184 } 185 } 186 187 transfer := &RepoTransfer{ 188 RepoID: repo.ID, 189 RecipientID: newOwner.ID, 190 CreatedUnix: timeutil.TimeStampNow(), 191 UpdatedUnix: timeutil.TimeStampNow(), 192 DoerID: doer.ID, 193 TeamIDs: make([]int64, 0, len(teams)), 194 } 195 196 for k := range teams { 197 transfer.TeamIDs = append(transfer.TeamIDs, teams[k].ID) 198 } 199 200 return db.Insert(ctx, transfer) 201 }) 202 }