You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

109 lines
3.1 KiB

  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package repo
  5. import (
  6. "fmt"
  7. "net/http"
  8. "code.gitea.io/gitea/models"
  9. "code.gitea.io/gitea/modules/context"
  10. "code.gitea.io/gitea/modules/convert"
  11. "code.gitea.io/gitea/modules/log"
  12. "code.gitea.io/gitea/modules/structs"
  13. api "code.gitea.io/gitea/modules/structs"
  14. repo_service "code.gitea.io/gitea/services/repository"
  15. )
  16. // Transfer transfers the ownership of a repository
  17. func Transfer(ctx *context.APIContext, opts api.TransferRepoOption) {
  18. // swagger:operation POST /repos/{owner}/{repo}/transfer repository repoTransfer
  19. // ---
  20. // summary: Transfer a repo ownership
  21. // produces:
  22. // - application/json
  23. // parameters:
  24. // - name: owner
  25. // in: path
  26. // description: owner of the repo to transfer
  27. // type: string
  28. // required: true
  29. // - name: repo
  30. // in: path
  31. // description: name of the repo to transfer
  32. // type: string
  33. // required: true
  34. // - name: body
  35. // in: body
  36. // description: "Transfer Options"
  37. // required: true
  38. // schema:
  39. // "$ref": "#/definitions/TransferRepoOption"
  40. // responses:
  41. // "202":
  42. // "$ref": "#/responses/Repository"
  43. // "403":
  44. // "$ref": "#/responses/forbidden"
  45. // "404":
  46. // "$ref": "#/responses/notFound"
  47. // "422":
  48. // "$ref": "#/responses/validationError"
  49. newOwner, err := models.GetUserByName(opts.NewOwner)
  50. if err != nil {
  51. if models.IsErrUserNotExist(err) {
  52. ctx.Error(http.StatusNotFound, "", "The new owner does not exist or cannot be found")
  53. return
  54. }
  55. ctx.InternalServerError(err)
  56. return
  57. }
  58. if newOwner.Type == models.UserTypeOrganization {
  59. if !ctx.User.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !newOwner.HasMemberWithUserID(ctx.User.ID) {
  60. // The user shouldn't know about this organization
  61. ctx.Error(http.StatusNotFound, "", "The new owner does not exist or cannot be found")
  62. return
  63. }
  64. }
  65. var teams []*models.Team
  66. if opts.TeamIDs != nil {
  67. if !newOwner.IsOrganization() {
  68. ctx.Error(http.StatusUnprocessableEntity, "repoTransfer", "Teams can only be added to organization-owned repositories")
  69. return
  70. }
  71. org := convert.ToOrganization(newOwner)
  72. for _, tID := range *opts.TeamIDs {
  73. team, err := models.GetTeamByID(tID)
  74. if err != nil {
  75. ctx.Error(http.StatusUnprocessableEntity, "team", fmt.Errorf("team %d not found", tID))
  76. return
  77. }
  78. if team.OrgID != org.ID {
  79. ctx.Error(http.StatusForbidden, "team", fmt.Errorf("team %d belongs not to org %d", tID, org.ID))
  80. return
  81. }
  82. teams = append(teams, team)
  83. }
  84. }
  85. if err = repo_service.TransferOwnership(ctx.User, newOwner, ctx.Repo.Repository, teams); err != nil {
  86. ctx.InternalServerError(err)
  87. return
  88. }
  89. newRepo, err := models.GetRepositoryByName(newOwner.ID, ctx.Repo.Repository.Name)
  90. if err != nil {
  91. ctx.InternalServerError(err)
  92. return
  93. }
  94. log.Trace("Repository transferred: %s -> %s", ctx.Repo.Repository.FullName(), newOwner.Name)
  95. ctx.JSON(http.StatusAccepted, newRepo.APIFormat(models.AccessModeAdmin))
  96. }