gitea源码

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