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.

migrate.go 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2020 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package repo
  5. import (
  6. "net/http"
  7. "net/url"
  8. "strings"
  9. "code.gitea.io/gitea/models"
  10. admin_model "code.gitea.io/gitea/models/admin"
  11. "code.gitea.io/gitea/models/db"
  12. repo_model "code.gitea.io/gitea/models/repo"
  13. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/base"
  15. "code.gitea.io/gitea/modules/context"
  16. "code.gitea.io/gitea/modules/lfs"
  17. "code.gitea.io/gitea/modules/log"
  18. "code.gitea.io/gitea/modules/setting"
  19. "code.gitea.io/gitea/modules/structs"
  20. "code.gitea.io/gitea/modules/util"
  21. "code.gitea.io/gitea/modules/web"
  22. "code.gitea.io/gitea/services/forms"
  23. "code.gitea.io/gitea/services/migrations"
  24. "code.gitea.io/gitea/services/task"
  25. )
  26. const (
  27. tplMigrate base.TplName = "repo/migrate/migrate"
  28. )
  29. // Migrate render migration of repository page
  30. func Migrate(ctx *context.Context) {
  31. if setting.Repository.DisableMigrations {
  32. ctx.Error(http.StatusForbidden, "Migrate: the site administrator has disabled migrations")
  33. return
  34. }
  35. serviceType := structs.GitServiceType(ctx.FormInt("service_type"))
  36. setMigrationContextData(ctx, serviceType)
  37. if serviceType == 0 {
  38. ctx.Data["Org"] = ctx.FormString("org")
  39. ctx.Data["Mirror"] = ctx.FormString("mirror")
  40. ctx.HTML(http.StatusOK, tplMigrate)
  41. return
  42. }
  43. ctx.Data["private"] = getRepoPrivate(ctx)
  44. ctx.Data["mirror"] = ctx.FormString("mirror") == "1"
  45. ctx.Data["lfs"] = ctx.FormString("lfs") == "1"
  46. ctx.Data["wiki"] = ctx.FormString("wiki") == "1"
  47. ctx.Data["milestones"] = ctx.FormString("milestones") == "1"
  48. ctx.Data["labels"] = ctx.FormString("labels") == "1"
  49. ctx.Data["issues"] = ctx.FormString("issues") == "1"
  50. ctx.Data["pull_requests"] = ctx.FormString("pull_requests") == "1"
  51. ctx.Data["releases"] = ctx.FormString("releases") == "1"
  52. ctxUser := checkContextUser(ctx, ctx.FormInt64("org"))
  53. if ctx.Written() {
  54. return
  55. }
  56. ctx.Data["ContextUser"] = ctxUser
  57. ctx.HTML(http.StatusOK, base.TplName("repo/migrate/"+serviceType.Name()))
  58. }
  59. func handleMigrateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl base.TplName, form *forms.MigrateRepoForm) {
  60. if setting.Repository.DisableMigrations {
  61. ctx.Error(http.StatusForbidden, "MigrateError: the site administrator has disabled migrations")
  62. return
  63. }
  64. switch {
  65. case migrations.IsRateLimitError(err):
  66. ctx.RenderWithErr(ctx.Tr("form.visit_rate_limit"), tpl, form)
  67. case migrations.IsTwoFactorAuthError(err):
  68. ctx.RenderWithErr(ctx.Tr("form.2fa_auth_required"), tpl, form)
  69. case repo_model.IsErrReachLimitOfRepo(err):
  70. maxCreationLimit := owner.MaxCreationLimit()
  71. msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
  72. ctx.RenderWithErr(msg, tpl, form)
  73. case repo_model.IsErrRepoAlreadyExist(err):
  74. ctx.Data["Err_RepoName"] = true
  75. ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form)
  76. case repo_model.IsErrRepoFilesAlreadyExist(err):
  77. ctx.Data["Err_RepoName"] = true
  78. switch {
  79. case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories):
  80. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tpl, form)
  81. case setting.Repository.AllowAdoptionOfUnadoptedRepositories:
  82. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tpl, form)
  83. case setting.Repository.AllowDeleteOfUnadoptedRepositories:
  84. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tpl, form)
  85. default:
  86. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tpl, form)
  87. }
  88. case db.IsErrNameReserved(err):
  89. ctx.Data["Err_RepoName"] = true
  90. ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tpl, form)
  91. case db.IsErrNamePatternNotAllowed(err):
  92. ctx.Data["Err_RepoName"] = true
  93. ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tpl, form)
  94. default:
  95. err = util.SanitizeErrorCredentialURLs(err)
  96. if strings.Contains(err.Error(), "Authentication failed") ||
  97. strings.Contains(err.Error(), "Bad credentials") ||
  98. strings.Contains(err.Error(), "could not read Username") {
  99. ctx.Data["Err_Auth"] = true
  100. ctx.RenderWithErr(ctx.Tr("form.auth_failed", err.Error()), tpl, form)
  101. } else if strings.Contains(err.Error(), "fatal:") {
  102. ctx.Data["Err_CloneAddr"] = true
  103. ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", err.Error()), tpl, form)
  104. } else {
  105. ctx.ServerError(name, err)
  106. }
  107. }
  108. }
  109. func handleMigrateRemoteAddrError(ctx *context.Context, err error, tpl base.TplName, form *forms.MigrateRepoForm) {
  110. if models.IsErrInvalidCloneAddr(err) {
  111. addrErr := err.(*models.ErrInvalidCloneAddr)
  112. switch {
  113. case addrErr.IsProtocolInvalid:
  114. ctx.RenderWithErr(ctx.Tr("repo.mirror_address_protocol_invalid"), tpl, form)
  115. case addrErr.IsURLError:
  116. ctx.RenderWithErr(ctx.Tr("form.url_error", addrErr.Host), tpl, form)
  117. case addrErr.IsPermissionDenied:
  118. if addrErr.LocalPath {
  119. ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tpl, form)
  120. } else {
  121. ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied_blocked"), tpl, form)
  122. }
  123. case addrErr.IsInvalidPath:
  124. ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tpl, form)
  125. default:
  126. log.Error("Error whilst updating url: %v", err)
  127. ctx.RenderWithErr(ctx.Tr("form.url_error", "unknown"), tpl, form)
  128. }
  129. } else {
  130. log.Error("Error whilst updating url: %v", err)
  131. ctx.RenderWithErr(ctx.Tr("form.url_error", "unknown"), tpl, form)
  132. }
  133. }
  134. // MigratePost response for migrating from external git repository
  135. func MigratePost(ctx *context.Context) {
  136. form := web.GetForm(ctx).(*forms.MigrateRepoForm)
  137. if setting.Repository.DisableMigrations {
  138. ctx.Error(http.StatusForbidden, "MigratePost: the site administrator has disabled migrations")
  139. return
  140. }
  141. if form.Mirror && setting.Mirror.DisableNewPull {
  142. ctx.Error(http.StatusBadRequest, "MigratePost: the site administrator has disabled creation of new mirrors")
  143. return
  144. }
  145. setMigrationContextData(ctx, form.Service)
  146. ctxUser := checkContextUser(ctx, form.UID)
  147. if ctx.Written() {
  148. return
  149. }
  150. ctx.Data["ContextUser"] = ctxUser
  151. tpl := base.TplName("repo/migrate/" + form.Service.Name())
  152. if ctx.HasError() {
  153. ctx.HTML(http.StatusOK, tpl)
  154. return
  155. }
  156. remoteAddr, err := forms.ParseRemoteAddr(form.CloneAddr, form.AuthUsername, form.AuthPassword)
  157. if err == nil {
  158. err = migrations.IsMigrateURLAllowed(remoteAddr, ctx.Doer)
  159. }
  160. if err != nil {
  161. ctx.Data["Err_CloneAddr"] = true
  162. handleMigrateRemoteAddrError(ctx, err, tpl, form)
  163. return
  164. }
  165. form.LFS = form.LFS && setting.LFS.StartServer
  166. if form.LFS && len(form.LFSEndpoint) > 0 {
  167. ep := lfs.DetermineEndpoint("", form.LFSEndpoint)
  168. if ep == nil {
  169. ctx.Data["Err_LFSEndpoint"] = true
  170. ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_lfs_endpoint"), tpl, &form)
  171. return
  172. }
  173. err = migrations.IsMigrateURLAllowed(ep.String(), ctx.Doer)
  174. if err != nil {
  175. ctx.Data["Err_LFSEndpoint"] = true
  176. handleMigrateRemoteAddrError(ctx, err, tpl, form)
  177. return
  178. }
  179. }
  180. opts := migrations.MigrateOptions{
  181. OriginalURL: form.CloneAddr,
  182. GitServiceType: form.Service,
  183. CloneAddr: remoteAddr,
  184. RepoName: form.RepoName,
  185. Description: form.Description,
  186. Private: form.Private || setting.Repository.ForcePrivate,
  187. Mirror: form.Mirror,
  188. LFS: form.LFS,
  189. LFSEndpoint: form.LFSEndpoint,
  190. AuthUsername: form.AuthUsername,
  191. AuthPassword: form.AuthPassword,
  192. AuthToken: form.AuthToken,
  193. Wiki: form.Wiki,
  194. Issues: form.Issues,
  195. Milestones: form.Milestones,
  196. Labels: form.Labels,
  197. Comments: form.Issues || form.PullRequests,
  198. PullRequests: form.PullRequests,
  199. Releases: form.Releases,
  200. }
  201. if opts.Mirror {
  202. opts.Issues = false
  203. opts.Milestones = false
  204. opts.Labels = false
  205. opts.Comments = false
  206. opts.PullRequests = false
  207. opts.Releases = false
  208. }
  209. err = repo_model.CheckCreateRepository(ctx.Doer, ctxUser, opts.RepoName, false)
  210. if err != nil {
  211. handleMigrateError(ctx, ctxUser, err, "MigratePost", tpl, form)
  212. return
  213. }
  214. err = task.MigrateRepository(ctx.Doer, ctxUser, opts)
  215. if err == nil {
  216. ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(opts.RepoName))
  217. return
  218. }
  219. handleMigrateError(ctx, ctxUser, err, "MigratePost", tpl, form)
  220. }
  221. func setMigrationContextData(ctx *context.Context, serviceType structs.GitServiceType) {
  222. ctx.Data["Title"] = ctx.Tr("new_migrate")
  223. ctx.Data["LFSActive"] = setting.LFS.StartServer
  224. ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate
  225. ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
  226. // Plain git should be first
  227. ctx.Data["Services"] = append([]structs.GitServiceType{structs.PlainGitService}, structs.SupportedFullGitService...)
  228. ctx.Data["service"] = serviceType
  229. }
  230. func MigrateCancelPost(ctx *context.Context) {
  231. migratingTask, err := admin_model.GetMigratingTask(ctx.Repo.Repository.ID)
  232. if err != nil {
  233. log.Error("GetMigratingTask: %v", err)
  234. ctx.Redirect(ctx.Repo.Repository.Link())
  235. return
  236. }
  237. if migratingTask.Status == structs.TaskStatusRunning {
  238. taskUpdate := &admin_model.Task{ID: migratingTask.ID, Status: structs.TaskStatusFailed, Message: "canceled"}
  239. if err = taskUpdate.UpdateCols("status", "message"); err != nil {
  240. ctx.ServerError("task.UpdateCols", err)
  241. return
  242. }
  243. }
  244. ctx.Redirect(ctx.Repo.Repository.Link())
  245. }