aboutsummaryrefslogtreecommitdiffstats
path: root/services/repository/transfer.go
diff options
context:
space:
mode:
Diffstat (limited to 'services/repository/transfer.go')
-rw-r--r--services/repository/transfer.go272
1 files changed, 184 insertions, 88 deletions
diff --git a/services/repository/transfer.go b/services/repository/transfer.go
index 9ef28ddeb9..5ad63cca67 100644
--- a/services/repository/transfer.go
+++ b/services/repository/transfer.go
@@ -17,29 +17,31 @@ import (
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/globallock"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"
)
-func getRepoWorkingLockKey(repoID int64) string {
- return fmt.Sprintf("repo_working_%d", repoID)
+type LimitReachedError struct{ Limit int }
+
+func (LimitReachedError) Error() string {
+ return "Repository limit has been reached"
}
-// TransferOwnership 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 {
- 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)
- }
- }
+func IsRepositoryLimitReached(err error) bool {
+ _, ok := err.(LimitReachedError)
+ return ok
+}
- oldOwner := repo.Owner
+func getRepoWorkingLockKey(repoID int64) string {
+ return fmt.Sprintf("repo_working_%d", repoID)
+}
+// AcceptTransferOwnership transfers all corresponding setting from old user to new one.
+func AcceptTransferOwnership(ctx context.Context, repo *repo_model.Repository, doer *user_model.User) error {
releaser, err := globallock.Lock(ctx, getRepoWorkingLockKey(repo.ID))
if err != nil {
log.Error("lock.Lock(): %v", err)
@@ -47,29 +49,49 @@ func TransferOwnership(ctx context.Context, doer, newOwner *user_model.User, rep
}
defer releaser()
- if err := transferOwnership(ctx, doer, newOwner.Name, repo); err != nil {
- return err
- }
- releaser()
-
- newRepo, err := repo_model.GetRepositoryByID(ctx, repo.ID)
+ repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, repo)
if err != nil {
return err
}
- for _, team := range teams {
- if err := addRepositoryToTeam(ctx, team, newRepo); err != nil {
+ oldOwnerName := repo.OwnerName
+
+ if err := db.WithTx(ctx, func(ctx context.Context) error {
+ if err := repoTransfer.LoadAttributes(ctx); err != nil {
return err
}
+
+ if !doer.CanCreateRepoIn(repoTransfer.Recipient) {
+ limit := util.Iif(repoTransfer.Recipient.MaxRepoCreation >= 0, repoTransfer.Recipient.MaxRepoCreation, setting.Repository.MaxCreationLimit)
+ return LimitReachedError{Limit: limit}
+ }
+
+ if !repoTransfer.CanUserAcceptOrRejectTransfer(ctx, doer) {
+ return util.ErrPermissionDenied
+ }
+
+ if err := repo.LoadOwner(ctx); err != nil {
+ 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)
+ }
+ }
+
+ return transferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient.Name, repo, repoTransfer.Teams)
+ }); err != nil {
+ return err
}
+ releaser()
- notify_service.TransferRepository(ctx, doer, repo, oldOwner.Name)
+ notify_service.TransferRepository(ctx, doer, repo, oldOwnerName)
return nil
}
// 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
wikiRenamed := false
oldOwnerName := doer.Name
@@ -138,7 +160,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
repo.OwnerName = newOwner.Name
// Update repository.
- if _, err := sess.ID(repo.ID).Update(repo); err != nil {
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "owner_id", "owner_name"); err != nil {
return fmt.Errorf("update owner: %w", err)
}
@@ -174,15 +196,13 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
collaboration.UserID = 0
}
- // Remove old team-repository relations.
if oldOwner.IsOrganization() {
+ // Remove old team-repository relations.
if err := organization.RemoveOrgRepo(ctx, oldOwner.ID, repo.ID); err != nil {
return fmt.Errorf("removeOrgRepo: %w", err)
}
- }
- // Remove project's issues that belong to old organization's projects
- if oldOwner.IsOrganization() {
+ // Remove project's issues that belong to old organization's projects
projects, err := project_model.GetAllProjectsIDsByOwnerIDAndType(ctx, oldOwner.ID, project_model.TypeOrganization)
if err != nil {
return fmt.Errorf("Unable to find old org projects: %w", err)
@@ -225,15 +245,13 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
return fmt.Errorf("watchRepo: %w", err)
}
- // Remove watch for organization.
if oldOwner.IsOrganization() {
+ // Remove watch for organization.
if err := repo_model.WatchRepo(ctx, oldOwner, repo, false); err != nil {
return fmt.Errorf("watchRepo [false]: %w", err)
}
- }
- // Delete labels that belong to the old organization and comments that added these labels
- if oldOwner.IsOrganization() {
+ // Delete labels that belong to the old organization and comments that added these labels
if _, err := sess.Exec(`DELETE FROM issue_label WHERE issue_label.id IN (
SELECT il_too.id FROM (
SELECT il_too_too.id
@@ -261,7 +279,6 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
// Rename remote repository to new path and delete local copy.
dir := user_model.UserPath(newOwner.Name)
-
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return fmt.Errorf("Failed to create dir %s: %w", dir, err)
}
@@ -273,7 +290,6 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
// Rename remote wiki repository to new path and delete local copy.
wikiPath := repo_model.WikiPath(oldOwner.Name, repo.Name)
-
if isExist, err := util.IsExist(wikiPath); err != nil {
log.Error("Unable to check if %s exists. Error: %v", wikiPath, err)
return err
@@ -288,7 +304,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
return fmt.Errorf("deleteRepositoryTransfer: %w", err)
}
repo.Status = repo_model.RepositoryReady
- if err := repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "status"); err != nil {
return err
}
@@ -301,6 +317,17 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
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()
}
@@ -321,13 +348,13 @@ func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newR
return fmt.Errorf("IsRepositoryExist: %w", err)
} else if has {
return repo_model.ErrRepoAlreadyExist{
- Uname: repo.Owner.Name,
+ Uname: repo.OwnerName,
Name: newRepoName,
}
}
- newRepoPath := repo_model.RepoPath(repo.Owner.Name, newRepoName)
- if err = util.Rename(repo.RepoPath(), newRepoPath); err != nil {
+ if err = gitrepo.RenameRepository(ctx, repo,
+ repo_model.StorageRepo(repo_model.RelativePath(repo.OwnerName, newRepoName))); err != nil {
return fmt.Errorf("rename repository directory: %w", err)
}
@@ -343,17 +370,9 @@ func changeRepositoryName(ctx context.Context, repo *repo_model.Repository, newR
}
}
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- 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()
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ return repo_model.NewRedirect(ctx, repo.Owner.ID, repo.ID, oldRepoName, newRepoName)
+ })
}
// ChangeRepositoryName changes all corresponding setting from old repository name to new one.
@@ -387,70 +406,147 @@ func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo
// 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.
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 {
return err
}
- // Admin is always allowed to transfer || user transfer repo back to his account
- if doer.IsAdmin || doer.ID == newOwner.ID {
- return TransferOwnership(ctx, doer, newOwner, repo, teams)
+ if !doer.CanForkRepoIn(newOwner) {
+ limit := util.Iif(newOwner.MaxRepoCreation >= 0, newOwner.MaxRepoCreation, setting.Repository.MaxCreationLimit)
+ return LimitReachedError{Limit: limit}
}
- if user_model.IsUserBlockedBy(ctx, doer, newOwner.ID) {
- return user_model.ErrBlockedUser
- }
+ var isDirectTransfer bool
+ oldOwnerName := repo.OwnerName
- // If new owner is an org and user can create repos he can transfer directly too
- if newOwner.IsOrganization() {
- allowed, err := organization.CanCreateOrgRepo(ctx, newOwner.ID, doer.ID)
- if err != nil {
- return err
+ 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 {
+ isDirectTransfer = true
+ return transferOwnership(ctx, doer, newOwner.Name, repo, teams)
}
- if allowed {
- return TransferOwnership(ctx, doer, newOwner, repo, teams)
+
+ if user_model.IsUserBlockedBy(ctx, doer, newOwner.ID) {
+ return user_model.ErrBlockedUser
}
- }
- // In case the new owner would not have sufficient access to the repo, give access rights for read
- hasAccess, err := access_model.HasAnyUnitAccess(ctx, newOwner.ID, repo)
- if err != nil {
- return err
- }
- if !hasAccess {
- if err := AddOrUpdateCollaborator(ctx, repo, newOwner, perm.AccessModeRead); err != nil {
+ // If new owner is an org and user can create repos he can transfer directly too
+ if newOwner.IsOrganization() {
+ allowed, err := organization.CanCreateOrgRepo(ctx, newOwner.ID, doer.ID)
+ if err != nil {
+ return err
+ }
+ if allowed {
+ isDirectTransfer = true
+ return transferOwnership(ctx, doer, newOwner.Name, repo, teams)
+ }
+ }
+
+ // In case the new owner would not have sufficient access to the repo, give access rights for read
+ hasAccess, err := access_model.HasAnyUnitAccess(ctx, newOwner.ID, repo)
+ if err != nil {
return err
}
- }
+ if !hasAccess {
+ if err := AddOrUpdateCollaborator(ctx, repo, newOwner, perm.AccessModeRead); err != nil {
+ return err
+ }
+ }
- // Make repo as pending for transfer
- repo.Status = repo_model.RepositoryPendingTransfer
- if err := repo_model.CreatePendingRepositoryTransfer(ctx, doer, newOwner, repo.ID, teams); err != nil {
+ // Make repo as pending for transfer
+ repo.Status = repo_model.RepositoryPendingTransfer
+ return repo_model.CreatePendingRepositoryTransfer(ctx, doer, newOwner, repo.ID, teams)
+ }); err != nil {
return err
}
- // notify users who are able to accept / reject transfer
- notify_service.RepoPendingTransfer(ctx, doer, newOwner, repo)
+ if isDirectTransfer {
+ notify_service.TransferRepository(ctx, doer, repo, oldOwnerName)
+ } else {
+ // notify users who are able to accept / reject transfer
+ notify_service.RepoPendingTransfer(ctx, doer, newOwner, repo)
+ }
return nil
}
-// CancelRepositoryTransfer marks the repository as ready and remove pending transfer entry,
+// RejectRepositoryTransfer marks the repository as ready and remove pending transfer entry,
// thus cancel the transfer process.
-func CancelRepositoryTransfer(ctx context.Context, repo *repo_model.Repository) error {
- ctx, committer, err := db.TxContext(ctx)
- if err != nil {
- return err
+// The accepter can reject the transfer.
+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 {
+ return err
+ }
+
+ if err := repoTransfer.LoadAttributes(ctx); err != nil {
+ return err
+ }
+
+ if !repoTransfer.CanUserAcceptOrRejectTransfer(ctx, doer) {
+ return util.ErrPermissionDenied
+ }
+
+ repo.Status = repo_model.RepositoryReady
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "status"); err != nil {
+ return err
+ }
+
+ 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
}
- defer committer.Close()
- repo.Status = repo_model.RepositoryReady
- if err := repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
- return err
+ if err := r.LoadAttributes(ctx); err != nil {
+ log.Error("LoadAttributes: %v", err)
+ return false
}
- if err := repo_model.DeleteRepositoryTransfer(ctx, repo.ID); err != nil {
- return err
+ if err := r.Repo.LoadOwner(ctx); err != nil {
+ log.Error("LoadOwner: %v", err)
+ return false
}
- return committer.Commit()
+ 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
+ }
+
+ if !canUserCancelTransfer(ctx, repoTransfer, doer) {
+ return util.ErrPermissionDenied
+ }
+
+ repoTransfer.Repo.Status = repo_model.RepositoryReady
+ if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repoTransfer.Repo, "status"); err != nil {
+ return err
+ }
+
+ return repo_model.DeleteRepositoryTransfer(ctx, repoTransfer.RepoID)
+ })
}