mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-04 05:18:25 +00:00 
			
		
		
		
	Refactor repository transfer (#33211)
- Both have `RejectTransfer` and `CancelTransfer` because the permission checks are not the same. `CancelTransfer` can be done by the doer or those who have admin permission to access this repository. `RejectTransfer` can be done by the receiver user if it's an individual or those who can create repositories if it's an organization. - Some tests are wrong, this PR corrects them. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		@@ -68,6 +68,7 @@ type RepoTransfer struct { //nolint
 | 
			
		||||
	RecipientID int64
 | 
			
		||||
	Recipient   *user_model.User `xorm:"-"`
 | 
			
		||||
	RepoID      int64
 | 
			
		||||
	Repo        *Repository `xorm:"-"`
 | 
			
		||||
	TeamIDs     []int64
 | 
			
		||||
	Teams       []*organization.Team `xorm:"-"`
 | 
			
		||||
 | 
			
		||||
@@ -79,48 +80,65 @@ func init() {
 | 
			
		||||
	db.RegisterModel(new(RepoTransfer))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadAttributes fetches the transfer recipient from the database
 | 
			
		||||
func (r *RepoTransfer) LoadAttributes(ctx context.Context) error {
 | 
			
		||||
func (r *RepoTransfer) LoadRecipient(ctx context.Context) error {
 | 
			
		||||
	if r.Recipient == nil {
 | 
			
		||||
		u, err := user_model.GetUserByID(ctx, r.RecipientID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		r.Recipient = u
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r.Recipient.IsOrganization() && len(r.TeamIDs) != len(r.Teams) {
 | 
			
		||||
		for _, v := range r.TeamIDs {
 | 
			
		||||
			team, err := organization.GetTeamByID(ctx, v)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *RepoTransfer) LoadRepo(ctx context.Context) error {
 | 
			
		||||
	if r.Repo == nil {
 | 
			
		||||
		repo, err := GetRepositoryByID(ctx, r.RepoID)
 | 
			
		||||
		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.Repo = repo
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadAttributes fetches the transfer recipient from the database
 | 
			
		||||
func (r *RepoTransfer) LoadAttributes(ctx context.Context) error {
 | 
			
		||||
	if err := r.LoadRecipient(ctx); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r.Recipient.IsOrganization() && r.Teams == nil {
 | 
			
		||||
		teamsMap, err := organization.GetTeamsByIDs(ctx, r.TeamIDs)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for _, team := range teamsMap {
 | 
			
		||||
			r.Teams = append(r.Teams, team)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := r.LoadRepo(ctx); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r.Doer == nil {
 | 
			
		||||
		u, err := user_model.GetUserByID(ctx, r.DoerID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		r.Doer = u
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CanUserAcceptTransfer checks if the user has the rights to accept/decline a repo transfer.
 | 
			
		||||
// CanUserAcceptOrRejectTransfer 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
 | 
			
		||||
func (r *RepoTransfer) CanUserAcceptTransfer(ctx context.Context, u *user_model.User) bool {
 | 
			
		||||
func (r *RepoTransfer) CanUserAcceptOrRejectTransfer(ctx context.Context, u *user_model.User) bool {
 | 
			
		||||
	if err := r.LoadAttributes(ctx); err != nil {
 | 
			
		||||
		log.Error("LoadAttributes: %v", err)
 | 
			
		||||
		return false
 | 
			
		||||
@@ -166,6 +184,10 @@ func GetPendingRepositoryTransfers(ctx context.Context, opts *PendingRepositoryT
 | 
			
		||||
		Find(&transfers)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsRepositoryTransferExist(ctx context.Context, repoID int64) (bool, error) {
 | 
			
		||||
	return db.GetEngine(ctx).Where("repo_id = ?", repoID).Exist(new(RepoTransfer))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPendingRepositoryTransfer fetches the most recent and ongoing transfer
 | 
			
		||||
// process for the repository
 | 
			
		||||
func GetPendingRepositoryTransfer(ctx context.Context, repo *Repository) (*RepoTransfer, error) {
 | 
			
		||||
@@ -206,11 +228,26 @@ func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_m
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, err := user_model.GetUserByID(ctx, newOwner.ID); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Make sure repo is ready to transfer
 | 
			
		||||
		if err := TestRepositoryReadyForTransfer(repo.Status); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		exist, err := IsRepositoryTransferExist(ctx, repo.ID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if exist {
 | 
			
		||||
			return ErrRepoTransferInProgress{
 | 
			
		||||
				Uname: repo.Owner.LowerName,
 | 
			
		||||
				Name:  repo.Name,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		repo.Status = RepositoryPendingTransfer
 | 
			
		||||
		if err := UpdateRepositoryCols(ctx, repo, "status"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ import (
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/services/context"
 | 
			
		||||
	"code.gitea.io/gitea/services/convert"
 | 
			
		||||
@@ -161,12 +162,16 @@ func AcceptTransfer(ctx *context.APIContext) {
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
 | 
			
		||||
	err := acceptOrRejectRepoTransfer(ctx, true)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err := repo_service.AcceptTransferOwnership(ctx, ctx.Repo.Repository, ctx.Doer)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "acceptOrRejectRepoTransfer", err)
 | 
			
		||||
		switch {
 | 
			
		||||
		case repo_model.IsErrNoPendingTransfer(err):
 | 
			
		||||
			ctx.Error(http.StatusNotFound, "AcceptTransferOwnership", err)
 | 
			
		||||
		case errors.Is(err, util.ErrPermissionDenied):
 | 
			
		||||
			ctx.Error(http.StatusForbidden, "AcceptTransferOwnership", err)
 | 
			
		||||
		default:
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "AcceptTransferOwnership", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -199,40 +204,18 @@ func RejectTransfer(ctx *context.APIContext) {
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
 | 
			
		||||
	err := acceptOrRejectRepoTransfer(ctx, false)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err := repo_service.RejectRepositoryTransfer(ctx, ctx.Repo.Repository, ctx.Doer)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "acceptOrRejectRepoTransfer", err)
 | 
			
		||||
		switch {
 | 
			
		||||
		case repo_model.IsErrNoPendingTransfer(err):
 | 
			
		||||
			ctx.Error(http.StatusNotFound, "RejectRepositoryTransfer", err)
 | 
			
		||||
		case errors.Is(err, util.ErrPermissionDenied):
 | 
			
		||||
			ctx.Error(http.StatusForbidden, "RejectRepositoryTransfer", err)
 | 
			
		||||
		default:
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "RejectRepositoryTransfer", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToRepo(ctx, ctx.Repo.Repository, ctx.Repo.Permission))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error {
 | 
			
		||||
	repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if repo_model.IsErrNoPendingTransfer(err) {
 | 
			
		||||
			ctx.NotFound()
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := repoTransfer.LoadAttributes(ctx); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer) {
 | 
			
		||||
		ctx.Error(http.StatusForbidden, "CanUserAcceptTransfer", nil)
 | 
			
		||||
		return fmt.Errorf("user does not have permissions to do this")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if accept {
 | 
			
		||||
		return repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -309,6 +309,36 @@ const (
 | 
			
		||||
	tplStarUnstar   templates.TplName = "repo/star_unstar"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func acceptTransfer(ctx *context.Context) {
 | 
			
		||||
	err := repo_service.AcceptTransferOwnership(ctx, ctx.Repo.Repository, ctx.Doer)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		ctx.Flash.Success(ctx.Tr("repo.settings.transfer.success"))
 | 
			
		||||
		ctx.Redirect(ctx.Repo.Repository.Link())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	handleActionError(ctx, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func rejectTransfer(ctx *context.Context) {
 | 
			
		||||
	err := repo_service.RejectRepositoryTransfer(ctx, ctx.Repo.Repository, ctx.Doer)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected"))
 | 
			
		||||
		ctx.Redirect(ctx.Repo.Repository.Link())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	handleActionError(ctx, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleActionError(ctx *context.Context, err error) {
 | 
			
		||||
	if errors.Is(err, user_model.ErrBlockedUser) {
 | 
			
		||||
		ctx.Flash.Error(ctx.Tr("repo.action.blocked_user"))
 | 
			
		||||
	} else if errors.Is(err, util.ErrPermissionDenied) {
 | 
			
		||||
		ctx.Error(http.StatusNotFound)
 | 
			
		||||
	} else {
 | 
			
		||||
		ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Action response for actions to a repository
 | 
			
		||||
func Action(ctx *context.Context) {
 | 
			
		||||
	var err error
 | 
			
		||||
@@ -322,9 +352,11 @@ func Action(ctx *context.Context) {
 | 
			
		||||
	case "unstar":
 | 
			
		||||
		err = repo_model.StarRepo(ctx, ctx.Doer, ctx.Repo.Repository, false)
 | 
			
		||||
	case "accept_transfer":
 | 
			
		||||
		err = acceptOrRejectRepoTransfer(ctx, true)
 | 
			
		||||
		acceptTransfer(ctx)
 | 
			
		||||
		return
 | 
			
		||||
	case "reject_transfer":
 | 
			
		||||
		err = acceptOrRejectRepoTransfer(ctx, false)
 | 
			
		||||
		rejectTransfer(ctx)
 | 
			
		||||
		return
 | 
			
		||||
	case "desc": // FIXME: this is not used
 | 
			
		||||
		if !ctx.Repo.IsOwner() {
 | 
			
		||||
			ctx.Error(http.StatusNotFound)
 | 
			
		||||
@@ -337,13 +369,9 @@ func Action(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if errors.Is(err, user_model.ErrBlockedUser) {
 | 
			
		||||
			ctx.Flash.Error(ctx.Tr("repo.action.blocked_user"))
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err)
 | 
			
		||||
		handleActionError(ctx, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch ctx.PathParam("action") {
 | 
			
		||||
	case "watch", "unwatch":
 | 
			
		||||
@@ -377,41 +405,6 @@ func Action(ctx *context.Context) {
 | 
			
		||||
	ctx.RedirectToCurrentSite(ctx.FormString("redirect_to"), ctx.Repo.RepoLink)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error {
 | 
			
		||||
	repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := repoTransfer.LoadAttributes(ctx); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer) {
 | 
			
		||||
		return errors.New("user does not have enough permissions")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if accept {
 | 
			
		||||
		if ctx.Repo.GitRepo != nil {
 | 
			
		||||
			ctx.Repo.GitRepo.Close()
 | 
			
		||||
			ctx.Repo.GitRepo = nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Flash.Success(ctx.Tr("repo.settings.transfer.success"))
 | 
			
		||||
	} else {
 | 
			
		||||
		if err := repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Redirect(ctx.Repo.Repository.Link())
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RedirectDownload return a file based on the following infos:
 | 
			
		||||
func RedirectDownload(ctx *context.Context) {
 | 
			
		||||
	var (
 | 
			
		||||
 
 | 
			
		||||
@@ -832,16 +832,10 @@ func SettingsPost(ctx *context.Context) {
 | 
			
		||||
			} else {
 | 
			
		||||
				ctx.ServerError("GetPendingRepositoryTransfer", err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := repoTransfer.LoadAttributes(ctx); err != nil {
 | 
			
		||||
			ctx.ServerError("LoadRecipient", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil {
 | 
			
		||||
		if err := repo_service.CancelRepositoryTransfer(ctx, repoTransfer, ctx.Doer); err != nil {
 | 
			
		||||
			ctx.ServerError("CancelRepositoryTransfer", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -653,7 +653,7 @@ func RepoAssignment(ctx *Context) {
 | 
			
		||||
 | 
			
		||||
		ctx.Data["RepoTransfer"] = repoTransfer
 | 
			
		||||
		if ctx.Doer != nil {
 | 
			
		||||
			ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer)
 | 
			
		||||
			ctx.Data["CanUserAcceptOrRejectTransfer"] = repoTransfer.CanUserAcceptOrRejectTransfer(ctx, ctx.Doer)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -272,9 +272,9 @@ func MigrateRepository(ctx context.Context, doer, u *user_model.User, repo *repo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TransferRepository notifies create repository to notifiers
 | 
			
		||||
func TransferRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, newOwnerName string) {
 | 
			
		||||
func TransferRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldOwnerName string) {
 | 
			
		||||
	for _, notifier := range notifiers {
 | 
			
		||||
		notifier.TransferRepository(ctx, doer, repo, newOwnerName)
 | 
			
		||||
		notifier.TransferRepository(ctx, doer, repo, oldOwnerName)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -27,19 +27,8 @@ func getRepoWorkingLockKey(repoID int64) string {
 | 
			
		||||
	return fmt.Sprintf("repo_working_%d", repoID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	oldOwner := repo.Owner
 | 
			
		||||
 | 
			
		||||
// 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 +36,44 @@ 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 !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)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	notify_service.TransferRepository(ctx, doer, repo, oldOwner.Name)
 | 
			
		||||
		return transferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient.Name, repo, repoTransfer.Teams)
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	releaser()
 | 
			
		||||
 | 
			
		||||
	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 +142,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.UpdateRepositoryCols(ctx, repo, "owner_id", "owner_name"); err != nil {
 | 
			
		||||
		return fmt.Errorf("update owner: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -174,15 +178,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() {
 | 
			
		||||
		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 +227,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() {
 | 
			
		||||
		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 +261,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 +272,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
 | 
			
		||||
@@ -301,6 +299,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()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -343,17 +352,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,13 +388,25 @@ 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
 | 
			
		||||
	var isDirectTransfer bool
 | 
			
		||||
	oldOwnerName := repo.OwnerName
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
		return TransferOwnership(ctx, doer, newOwner, repo, teams)
 | 
			
		||||
			isDirectTransfer = true
 | 
			
		||||
			return transferOwnership(ctx, doer, newOwner.Name, repo, teams)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if user_model.IsUserBlockedBy(ctx, doer, newOwner.ID) {
 | 
			
		||||
@@ -407,7 +420,8 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			if allowed {
 | 
			
		||||
			return TransferOwnership(ctx, doer, newOwner, repo, teams)
 | 
			
		||||
				isDirectTransfer = true
 | 
			
		||||
				return transferOwnership(ctx, doer, newOwner.Name, repo, teams)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -424,33 +438,92 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use
 | 
			
		||||
 | 
			
		||||
		// Make repo as pending for transfer
 | 
			
		||||
		repo.Status = repo_model.RepositoryPendingTransfer
 | 
			
		||||
	if err := repo_model.CreatePendingRepositoryTransfer(ctx, doer, newOwner, repo.ID, teams); err != nil {
 | 
			
		||||
		return repo_model.CreatePendingRepositoryTransfer(ctx, doer, newOwner, repo.ID, teams)
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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)
 | 
			
		||||
// 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
 | 
			
		||||
		}
 | 
			
		||||
	defer committer.Close()
 | 
			
		||||
 | 
			
		||||
		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.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	if err := repo_model.DeleteRepositoryTransfer(ctx, repo.ID); err != nil {
 | 
			
		||||
		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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := r.LoadAttributes(ctx); err != nil {
 | 
			
		||||
		log.Error("LoadAttributes: %v", err)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := r.Repo.LoadOwner(ctx); err != nil {
 | 
			
		||||
		log.Error("LoadOwner: %v", err)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	return committer.Commit()
 | 
			
		||||
		if !canUserCancelTransfer(ctx, repoTransfer, doer) {
 | 
			
		||||
			return util.ErrPermissionDenied
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		repoTransfer.Repo.Status = repo_model.RepositoryReady
 | 
			
		||||
		if err := repo_model.UpdateRepositoryCols(ctx, repoTransfer.Repo, "status"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return repo_model.DeleteRepositoryTransfer(ctx, repoTransfer.RepoID)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,23 +34,26 @@ func TestTransferOwnership(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 | 
			
		||||
	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
 | 
			
		||||
	repo.Owner = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 | 
			
		||||
	assert.NoError(t, TransferOwnership(db.DefaultContext, doer, doer, repo, nil))
 | 
			
		||||
	assert.NoError(t, repo.LoadOwner(db.DefaultContext))
 | 
			
		||||
	repoTransfer := unittest.AssertExistsAndLoadBean(t, &repo_model.RepoTransfer{ID: 1})
 | 
			
		||||
	assert.NoError(t, repoTransfer.LoadAttributes(db.DefaultContext))
 | 
			
		||||
	assert.NoError(t, AcceptTransferOwnership(db.DefaultContext, repo, doer))
 | 
			
		||||
 | 
			
		||||
	transferredRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
 | 
			
		||||
	assert.EqualValues(t, 2, transferredRepo.OwnerID)
 | 
			
		||||
	assert.EqualValues(t, 1, transferredRepo.OwnerID) // repo_transfer.yml id=1
 | 
			
		||||
	unittest.AssertNotExistsBean(t, &repo_model.RepoTransfer{ID: 1})
 | 
			
		||||
 | 
			
		||||
	exist, err := util.IsExist(repo_model.RepoPath("org3", "repo3"))
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.False(t, exist)
 | 
			
		||||
	exist, err = util.IsExist(repo_model.RepoPath("user2", "repo3"))
 | 
			
		||||
	exist, err = util.IsExist(repo_model.RepoPath("user1", "repo3"))
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.True(t, exist)
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
 | 
			
		||||
		OpType:    activities_model.ActionTransferRepo,
 | 
			
		||||
		ActUserID: 2,
 | 
			
		||||
		ActUserID: 1,
 | 
			
		||||
		RepoID:    3,
 | 
			
		||||
		Content:   "org3/repo3",
 | 
			
		||||
	})
 | 
			
		||||
@@ -61,10 +64,10 @@ func TestTransferOwnership(t *testing.T) {
 | 
			
		||||
func TestStartRepositoryTransferSetPermission(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
 | 
			
		||||
	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 | 
			
		||||
	recipient := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
 | 
			
		||||
	repo.Owner = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
 | 
			
		||||
	assert.NoError(t, repo.LoadOwner(db.DefaultContext))
 | 
			
		||||
 | 
			
		||||
	hasAccess, err := access_model.HasAnyUnitAccess(db.DefaultContext, recipient.ID, repo)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
@@ -82,7 +85,7 @@ func TestStartRepositoryTransferSetPermission(t *testing.T) {
 | 
			
		||||
func TestRepositoryTransfer(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
 | 
			
		||||
	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
 | 
			
		||||
 | 
			
		||||
	transfer, err := repo_model.GetPendingRepositoryTransfer(db.DefaultContext, repo)
 | 
			
		||||
@@ -90,7 +93,7 @@ func TestRepositoryTransfer(t *testing.T) {
 | 
			
		||||
	assert.NotNil(t, transfer)
 | 
			
		||||
 | 
			
		||||
	// Cancel transfer
 | 
			
		||||
	assert.NoError(t, CancelRepositoryTransfer(db.DefaultContext, repo))
 | 
			
		||||
	assert.NoError(t, CancelRepositoryTransfer(db.DefaultContext, transfer, doer))
 | 
			
		||||
 | 
			
		||||
	transfer, err = repo_model.GetPendingRepositoryTransfer(db.DefaultContext, repo)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
@@ -113,10 +116,12 @@ func TestRepositoryTransfer(t *testing.T) {
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.True(t, repo_model.IsErrRepoTransferInProgress(err))
 | 
			
		||||
 | 
			
		||||
	// Unknown user
 | 
			
		||||
	err = repo_model.CreatePendingRepositoryTransfer(db.DefaultContext, doer, &user_model.User{ID: 1000, LowerName: "user1000"}, repo.ID, nil)
 | 
			
		||||
	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
 | 
			
		||||
	// Unknown user, transfer non-existent transfer repo id = 2
 | 
			
		||||
	err = repo_model.CreatePendingRepositoryTransfer(db.DefaultContext, doer, &user_model.User{ID: 1000, LowerName: "user1000"}, repo2.ID, nil)
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
 | 
			
		||||
	// Cancel transfer
 | 
			
		||||
	assert.NoError(t, CancelRepositoryTransfer(db.DefaultContext, repo))
 | 
			
		||||
	// Reject transfer
 | 
			
		||||
	err = RejectRepositoryTransfer(db.DefaultContext, repo2, doer)
 | 
			
		||||
	assert.True(t, repo_model.IsErrNoPendingTransfer(err))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -117,10 +117,10 @@ func BlockUser(ctx context.Context, doer, blocker, blockee *user_model.User, not
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// cancel each other repository transfers
 | 
			
		||||
		if err := cancelRepositoryTransfers(ctx, blocker, blockee); err != nil {
 | 
			
		||||
		if err := cancelRepositoryTransfers(ctx, doer, blocker, blockee); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := cancelRepositoryTransfers(ctx, blockee, blocker); err != nil {
 | 
			
		||||
		if err := cancelRepositoryTransfers(ctx, doer, blockee, blocker); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -192,7 +192,7 @@ func unwatchRepos(ctx context.Context, watcher, repoOwner *user_model.User) erro
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cancelRepositoryTransfers(ctx context.Context, sender, recipient *user_model.User) error {
 | 
			
		||||
func cancelRepositoryTransfers(ctx context.Context, doer, sender, recipient *user_model.User) error {
 | 
			
		||||
	transfers, err := repo_model.GetPendingRepositoryTransfers(ctx, &repo_model.PendingRepositoryTransferOptions{
 | 
			
		||||
		SenderID:    sender.ID,
 | 
			
		||||
		RecipientID: recipient.ID,
 | 
			
		||||
@@ -202,12 +202,7 @@ func cancelRepositoryTransfers(ctx context.Context, sender, recipient *user_mode
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, transfer := range transfers {
 | 
			
		||||
		repo, err := repo_model.GetRepositoryByID(ctx, transfer.RepoID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := repo_service.CancelRepositoryTransfer(ctx, repo); err != nil {
 | 
			
		||||
		if err := repo_service.CancelRepositoryTransfer(ctx, transfer, doer); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -39,16 +39,16 @@
 | 
			
		||||
					{{if $.RepoTransfer}}
 | 
			
		||||
						<form method="post" action="{{$.RepoLink}}/action/accept_transfer?redirect_to={{$.RepoLink}}">
 | 
			
		||||
							{{$.CsrfTokenHtml}}
 | 
			
		||||
							<div data-tooltip-content="{{if $.CanUserAcceptTransfer}}{{ctx.Locale.Tr "repo.transfer.accept_desc" $.RepoTransfer.Recipient.DisplayName}}{{else}}{{ctx.Locale.Tr "repo.transfer.no_permission_to_accept"}}{{end}}">
 | 
			
		||||
								<button type="submit" class="ui basic button {{if $.CanUserAcceptTransfer}}primary {{end}} ok small"{{if not $.CanUserAcceptTransfer}} disabled{{end}}>
 | 
			
		||||
							<div data-tooltip-content="{{if $.CanUserAcceptOrRejectTransfer}}{{ctx.Locale.Tr "repo.transfer.accept_desc" $.RepoTransfer.Recipient.DisplayName}}{{else}}{{ctx.Locale.Tr "repo.transfer.no_permission_to_accept"}}{{end}}">
 | 
			
		||||
								<button type="submit" class="ui basic button {{if $.CanUserAcceptOrRejectTransfer}}primary {{end}} ok small"{{if not $.CanUserAcceptOrRejectTransfer}} disabled{{end}}>
 | 
			
		||||
									{{ctx.Locale.Tr "repo.transfer.accept"}}
 | 
			
		||||
								</button>
 | 
			
		||||
							</div>
 | 
			
		||||
						</form>
 | 
			
		||||
						<form method="post" action="{{$.RepoLink}}/action/reject_transfer?redirect_to={{$.RepoLink}}">
 | 
			
		||||
							{{$.CsrfTokenHtml}}
 | 
			
		||||
							<div data-tooltip-content="{{if $.CanUserAcceptTransfer}}{{ctx.Locale.Tr "repo.transfer.reject_desc" $.RepoTransfer.Recipient.DisplayName}}{{else}}{{ctx.Locale.Tr "repo.transfer.no_permission_to_reject"}}{{end}}">
 | 
			
		||||
								<button type="submit" class="ui basic button {{if $.CanUserAcceptTransfer}}red {{end}}ok small"{{if not $.CanUserAcceptTransfer}} disabled{{end}}>
 | 
			
		||||
							<div data-tooltip-content="{{if $.CanUserAcceptOrRejectTransfer}}{{ctx.Locale.Tr "repo.transfer.reject_desc" $.RepoTransfer.Recipient.DisplayName}}{{else}}{{ctx.Locale.Tr "repo.transfer.no_permission_to_reject"}}{{end}}">
 | 
			
		||||
								<button type="submit" class="ui basic button {{if $.CanUserAcceptOrRejectTransfer}}red {{end}}ok small"{{if not $.CanUserAcceptOrRejectTransfer}} disabled{{end}}>
 | 
			
		||||
									{{ctx.Locale.Tr "repo.transfer.reject"}}
 | 
			
		||||
								</button>
 | 
			
		||||
							</div>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user