2021-03-01 01:47:30 +01:00
|
|
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
2022-11-27 19:20:29 +01:00
|
|
|
// SPDX-License-Identifier: MIT
|
2021-03-01 01:47:30 +01:00
|
|
|
|
2024-11-17 03:36:55 +01:00
|
|
|
package repo
|
2021-03-01 01:47:30 +01:00
|
|
|
|
|
|
|
import (
|
2021-12-12 16:48:20 +01:00
|
|
|
"context"
|
2024-04-21 21:44:03 +02:00
|
|
|
"errors"
|
2021-03-01 01:47:30 +01:00
|
|
|
"fmt"
|
|
|
|
|
2021-09-19 13:49:59 +02:00
|
|
|
"code.gitea.io/gitea/models/db"
|
2022-03-29 08:29:02 +02:00
|
|
|
"code.gitea.io/gitea/models/organization"
|
2021-11-24 10:49:20 +01:00
|
|
|
user_model "code.gitea.io/gitea/models/user"
|
2021-03-01 01:47:30 +01:00
|
|
|
"code.gitea.io/gitea/modules/log"
|
|
|
|
"code.gitea.io/gitea/modules/timeutil"
|
2024-11-17 03:36:55 +01:00
|
|
|
"code.gitea.io/gitea/modules/util"
|
2024-03-04 09:16:03 +01:00
|
|
|
|
|
|
|
"xorm.io/builder"
|
2021-03-01 01:47:30 +01:00
|
|
|
)
|
|
|
|
|
2024-11-17 03:36:55 +01:00
|
|
|
// ErrNoPendingRepoTransfer is an error type for repositories without a pending
|
|
|
|
// transfer request
|
|
|
|
type ErrNoPendingRepoTransfer struct {
|
|
|
|
RepoID int64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err ErrNoPendingRepoTransfer) Error() string {
|
|
|
|
return fmt.Sprintf("repository doesn't have a pending transfer [repo_id: %d]", err.RepoID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsErrNoPendingTransfer is an error type when a repository has no pending
|
|
|
|
// transfers
|
|
|
|
func IsErrNoPendingTransfer(err error) bool {
|
|
|
|
_, ok := err.(ErrNoPendingRepoTransfer)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err ErrNoPendingRepoTransfer) Unwrap() error {
|
|
|
|
return util.ErrNotExist
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrRepoTransferInProgress represents the state of a repository that has an
|
|
|
|
// ongoing transfer
|
|
|
|
type ErrRepoTransferInProgress struct {
|
|
|
|
Uname string
|
|
|
|
Name string
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsErrRepoTransferInProgress checks if an error is a ErrRepoTransferInProgress.
|
|
|
|
func IsErrRepoTransferInProgress(err error) bool {
|
|
|
|
_, ok := err.(ErrRepoTransferInProgress)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err ErrRepoTransferInProgress) Error() string {
|
|
|
|
return fmt.Sprintf("repository is already being transferred [uname: %s, name: %s]", err.Uname, err.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err ErrRepoTransferInProgress) Unwrap() error {
|
|
|
|
return util.ErrAlreadyExist
|
|
|
|
}
|
|
|
|
|
2021-03-01 01:47:30 +01:00
|
|
|
// RepoTransfer is used to manage repository transfers
|
2024-11-17 03:36:55 +01:00
|
|
|
type RepoTransfer struct { //nolint
|
2021-03-01 01:47:30 +01:00
|
|
|
ID int64 `xorm:"pk autoincr"`
|
|
|
|
DoerID int64
|
2021-11-24 10:49:20 +01:00
|
|
|
Doer *user_model.User `xorm:"-"`
|
2021-03-01 01:47:30 +01:00
|
|
|
RecipientID int64
|
2021-11-24 10:49:20 +01:00
|
|
|
Recipient *user_model.User `xorm:"-"`
|
2021-03-01 01:47:30 +01:00
|
|
|
RepoID int64
|
|
|
|
TeamIDs []int64
|
2022-03-29 08:29:02 +02:00
|
|
|
Teams []*organization.Team `xorm:"-"`
|
2021-03-01 01:47:30 +01:00
|
|
|
|
|
|
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"`
|
|
|
|
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL updated"`
|
|
|
|
}
|
|
|
|
|
2021-09-19 13:49:59 +02:00
|
|
|
func init() {
|
|
|
|
db.RegisterModel(new(RepoTransfer))
|
|
|
|
}
|
|
|
|
|
2021-03-01 01:47:30 +01:00
|
|
|
// LoadAttributes fetches the transfer recipient from the database
|
2022-12-10 03:46:31 +01:00
|
|
|
func (r *RepoTransfer) LoadAttributes(ctx context.Context) error {
|
2021-03-01 01:47:30 +01:00
|
|
|
if r.Recipient == nil {
|
2022-12-10 03:46:31 +01:00
|
|
|
u, err := user_model.GetUserByID(ctx, r.RecipientID)
|
2021-03-01 01:47:30 +01: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 03:46:31 +01:00
|
|
|
team, err := organization.GetTeamByID(ctx, v)
|
2021-03-01 01:47:30 +01: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 03:46:31 +01:00
|
|
|
u, err := user_model.GetUserByID(ctx, r.DoerID)
|
2021-03-01 01:47:30 +01: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 16:39:12 +02:00
|
|
|
func (r *RepoTransfer) CanUserAcceptTransfer(ctx context.Context, u *user_model.User) bool {
|
|
|
|
if err := r.LoadAttributes(ctx); err != nil {
|
2021-03-01 01:47:30 +01:00
|
|
|
log.Error("LoadAttributes: %v", err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if !r.Recipient.IsOrganization() {
|
|
|
|
return r.RecipientID == u.ID
|
|
|
|
}
|
|
|
|
|
2023-09-16 16:39:12 +02:00
|
|
|
allowed, err := organization.CanCreateOrgRepo(ctx, r.RecipientID, u.ID)
|
2021-03-01 01:47:30 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Error("CanCreateOrgRepo: %v", err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return allowed
|
|
|
|
}
|
|
|
|
|
2024-03-04 09:16:03 +01: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 01:47:30 +01:00
|
|
|
// GetPendingRepositoryTransfer fetches the most recent and ongoing transfer
|
|
|
|
// process for the repository
|
2024-11-17 03:36:55 +01:00
|
|
|
func GetPendingRepositoryTransfer(ctx context.Context, repo *Repository) (*RepoTransfer, error) {
|
2024-03-04 09:16:03 +01:00
|
|
|
transfers, err := GetPendingRepositoryTransfers(ctx, &PendingRepositoryTransferOptions{RepoID: repo.ID})
|
2021-03-01 01:47:30 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-03-04 09:16:03 +01:00
|
|
|
if len(transfers) != 1 {
|
2021-03-01 01:47:30 +01:00
|
|
|
return nil, ErrNoPendingRepoTransfer{RepoID: repo.ID}
|
|
|
|
}
|
|
|
|
|
2024-03-04 09:16:03 +01:00
|
|
|
return transfers[0], nil
|
2021-03-01 01:47:30 +01:00
|
|
|
}
|
|
|
|
|
2024-02-05 07:17:23 +01:00
|
|
|
func DeleteRepositoryTransfer(ctx context.Context, repoID int64) error {
|
2021-12-12 16:48:20 +01:00
|
|
|
_, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(&RepoTransfer{})
|
2021-03-01 01:47:30 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestRepositoryReadyForTransfer make sure repo is ready to transfer
|
2024-11-17 03:36:55 +01:00
|
|
|
func TestRepositoryReadyForTransfer(status RepositoryStatus) error {
|
2021-03-01 01:47:30 +01:00
|
|
|
switch status {
|
2024-11-17 03:36:55 +01:00
|
|
|
case RepositoryBeingMigrated:
|
2024-04-21 21:44:03 +02:00
|
|
|
return errors.New("repo is not ready, currently migrating")
|
2024-11-17 03:36:55 +01:00
|
|
|
case RepositoryPendingTransfer:
|
2021-03-01 01:47:30 +01: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 03:46:31 +01:00
|
|
|
func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.User, repoID int64, teams []*organization.Team) error {
|
2023-01-08 02:34:58 +01:00
|
|
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
2024-11-17 03:36:55 +01:00
|
|
|
repo, err := GetRepositoryByID(ctx, repoID)
|
2022-12-10 03:46:31 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-03-01 01:47:30 +01:00
|
|
|
|
2022-12-10 03:46:31 +01:00
|
|
|
// Make sure repo is ready to transfer
|
|
|
|
if err := TestRepositoryReadyForTransfer(repo.Status); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-03-01 01:47:30 +01:00
|
|
|
|
2024-11-17 03:36:55 +01:00
|
|
|
repo.Status = RepositoryPendingTransfer
|
|
|
|
if err := UpdateRepositoryCols(ctx, repo, "status"); err != nil {
|
2022-12-10 03:46:31 +01:00
|
|
|
return err
|
2021-12-12 16:48:20 +01:00
|
|
|
}
|
2021-03-01 01:47:30 +01:00
|
|
|
|
2022-12-10 03:46:31 +01:00
|
|
|
// Check if new owner has repository with same name.
|
2024-11-17 03:36:55 +01:00
|
|
|
if has, err := IsRepositoryModelExist(ctx, newOwner, repo.Name); err != nil {
|
2022-12-10 03:46:31 +01:00
|
|
|
return fmt.Errorf("IsRepositoryExist: %w", err)
|
|
|
|
} else if has {
|
2024-11-17 03:36:55 +01:00
|
|
|
return ErrRepoAlreadyExist{
|
2022-12-10 03:46:31 +01:00
|
|
|
Uname: newOwner.LowerName,
|
|
|
|
Name: repo.Name,
|
|
|
|
}
|
|
|
|
}
|
2021-03-01 01:47:30 +01:00
|
|
|
|
2022-12-10 03:46:31 +01: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 01:47:30 +01:00
|
|
|
|
2022-12-10 03:46:31 +01:00
|
|
|
for k := range teams {
|
|
|
|
transfer.TeamIDs = append(transfer.TeamIDs, teams[k].ID)
|
|
|
|
}
|
2021-03-01 01:47:30 +01:00
|
|
|
|
2022-12-10 03:46:31 +01:00
|
|
|
return db.Insert(ctx, transfer)
|
|
|
|
})
|
2021-03-01 01:47:30 +01:00
|
|
|
}
|