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