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