code.gitea.io/gitea@v1.21.7/routers/web/repo/setting/setting.go (about) 1 // Copyright 2014 The Gogs Authors. All rights reserved. 2 // Copyright 2018 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package setting 6 7 import ( 8 "fmt" 9 "net/http" 10 "strconv" 11 "strings" 12 "time" 13 14 "code.gitea.io/gitea/models" 15 "code.gitea.io/gitea/models/db" 16 "code.gitea.io/gitea/models/organization" 17 repo_model "code.gitea.io/gitea/models/repo" 18 unit_model "code.gitea.io/gitea/models/unit" 19 user_model "code.gitea.io/gitea/models/user" 20 "code.gitea.io/gitea/modules/base" 21 "code.gitea.io/gitea/modules/context" 22 "code.gitea.io/gitea/modules/git" 23 "code.gitea.io/gitea/modules/indexer/code" 24 "code.gitea.io/gitea/modules/indexer/stats" 25 "code.gitea.io/gitea/modules/lfs" 26 "code.gitea.io/gitea/modules/log" 27 repo_module "code.gitea.io/gitea/modules/repository" 28 "code.gitea.io/gitea/modules/setting" 29 "code.gitea.io/gitea/modules/structs" 30 "code.gitea.io/gitea/modules/util" 31 "code.gitea.io/gitea/modules/validation" 32 "code.gitea.io/gitea/modules/web" 33 asymkey_service "code.gitea.io/gitea/services/asymkey" 34 "code.gitea.io/gitea/services/forms" 35 "code.gitea.io/gitea/services/migrations" 36 mirror_service "code.gitea.io/gitea/services/mirror" 37 repo_service "code.gitea.io/gitea/services/repository" 38 wiki_service "code.gitea.io/gitea/services/wiki" 39 ) 40 41 const ( 42 tplSettingsOptions base.TplName = "repo/settings/options" 43 tplCollaboration base.TplName = "repo/settings/collaboration" 44 tplBranches base.TplName = "repo/settings/branches" 45 tplGithooks base.TplName = "repo/settings/githooks" 46 tplGithookEdit base.TplName = "repo/settings/githook_edit" 47 tplDeployKeys base.TplName = "repo/settings/deploy_keys" 48 ) 49 50 // SettingsCtxData is a middleware that sets all the general context data for the 51 // settings template. 52 func SettingsCtxData(ctx *context.Context) { 53 ctx.Data["Title"] = ctx.Tr("repo.settings.options") 54 ctx.Data["PageIsSettingsOptions"] = true 55 ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate 56 ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled 57 ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull 58 ctx.Data["DisableNewPushMirrors"] = setting.Mirror.DisableNewPush 59 ctx.Data["DefaultMirrorInterval"] = setting.Mirror.DefaultInterval 60 ctx.Data["MinimumMirrorInterval"] = setting.Mirror.MinInterval 61 62 signing, _ := asymkey_service.SigningKey(ctx, ctx.Repo.Repository.RepoPath()) 63 ctx.Data["SigningKeyAvailable"] = len(signing) > 0 64 ctx.Data["SigningSettings"] = setting.Repository.Signing 65 ctx.Data["CodeIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled 66 67 if ctx.Doer.IsAdmin { 68 if setting.Indexer.RepoIndexerEnabled { 69 status, err := repo_model.GetIndexerStatus(ctx, ctx.Repo.Repository, repo_model.RepoIndexerTypeCode) 70 if err != nil { 71 ctx.ServerError("repo.indexer_status", err) 72 return 73 } 74 ctx.Data["CodeIndexerStatus"] = status 75 } 76 status, err := repo_model.GetIndexerStatus(ctx, ctx.Repo.Repository, repo_model.RepoIndexerTypeStats) 77 if err != nil { 78 ctx.ServerError("repo.indexer_status", err) 79 return 80 } 81 ctx.Data["StatsIndexerStatus"] = status 82 } 83 pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, ctx.Repo.Repository.ID, db.ListOptions{}) 84 if err != nil { 85 ctx.ServerError("GetPushMirrorsByRepoID", err) 86 return 87 } 88 ctx.Data["PushMirrors"] = pushMirrors 89 } 90 91 // Settings show a repository's settings page 92 func Settings(ctx *context.Context) { 93 ctx.HTML(http.StatusOK, tplSettingsOptions) 94 } 95 96 // SettingsPost response for changes of a repository 97 func SettingsPost(ctx *context.Context) { 98 form := web.GetForm(ctx).(*forms.RepoSettingForm) 99 100 ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate 101 ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled 102 ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull 103 ctx.Data["DisableNewPushMirrors"] = setting.Mirror.DisableNewPush 104 ctx.Data["DefaultMirrorInterval"] = setting.Mirror.DefaultInterval 105 ctx.Data["MinimumMirrorInterval"] = setting.Mirror.MinInterval 106 107 signing, _ := asymkey_service.SigningKey(ctx, ctx.Repo.Repository.RepoPath()) 108 ctx.Data["SigningKeyAvailable"] = len(signing) > 0 109 ctx.Data["SigningSettings"] = setting.Repository.Signing 110 ctx.Data["CodeIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled 111 112 repo := ctx.Repo.Repository 113 114 switch ctx.FormString("action") { 115 case "update": 116 if ctx.HasError() { 117 ctx.HTML(http.StatusOK, tplSettingsOptions) 118 return 119 } 120 121 newRepoName := form.RepoName 122 // Check if repository name has been changed. 123 if repo.LowerName != strings.ToLower(newRepoName) { 124 // Close the GitRepo if open 125 if ctx.Repo.GitRepo != nil { 126 ctx.Repo.GitRepo.Close() 127 ctx.Repo.GitRepo = nil 128 } 129 if err := repo_service.ChangeRepositoryName(ctx, ctx.Doer, repo, newRepoName); err != nil { 130 ctx.Data["Err_RepoName"] = true 131 switch { 132 case repo_model.IsErrRepoAlreadyExist(err): 133 ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplSettingsOptions, &form) 134 case db.IsErrNameReserved(err): 135 ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form) 136 case repo_model.IsErrRepoFilesAlreadyExist(err): 137 ctx.Data["Err_RepoName"] = true 138 switch { 139 case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories): 140 ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tplSettingsOptions, form) 141 case setting.Repository.AllowAdoptionOfUnadoptedRepositories: 142 ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tplSettingsOptions, form) 143 case setting.Repository.AllowDeleteOfUnadoptedRepositories: 144 ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tplSettingsOptions, form) 145 default: 146 ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tplSettingsOptions, form) 147 } 148 case db.IsErrNamePatternNotAllowed(err): 149 ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form) 150 default: 151 ctx.ServerError("ChangeRepositoryName", err) 152 } 153 return 154 } 155 156 log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName) 157 } 158 // In case it's just a case change. 159 repo.Name = newRepoName 160 repo.LowerName = strings.ToLower(newRepoName) 161 repo.Description = form.Description 162 repo.Website = form.Website 163 repo.IsTemplate = form.Template 164 165 // Visibility of forked repository is forced sync with base repository. 166 if repo.IsFork { 167 form.Private = repo.BaseRepo.IsPrivate || repo.BaseRepo.Owner.Visibility == structs.VisibleTypePrivate 168 } 169 170 visibilityChanged := repo.IsPrivate != form.Private 171 // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public 172 if visibilityChanged && setting.Repository.ForcePrivate && !form.Private && !ctx.Doer.IsAdmin { 173 ctx.RenderWithErr(ctx.Tr("form.repository_force_private"), tplSettingsOptions, form) 174 return 175 } 176 177 repo.IsPrivate = form.Private 178 if err := repo_service.UpdateRepository(ctx, repo, visibilityChanged); err != nil { 179 ctx.ServerError("UpdateRepository", err) 180 return 181 } 182 log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) 183 184 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 185 ctx.Redirect(repo.Link() + "/settings") 186 187 case "mirror": 188 if !setting.Mirror.Enabled || !repo.IsMirror { 189 ctx.NotFound("", nil) 190 return 191 } 192 193 pullMirror, err := repo_model.GetMirrorByRepoID(ctx, ctx.Repo.Repository.ID) 194 if err == repo_model.ErrMirrorNotExist { 195 ctx.NotFound("", nil) 196 return 197 } 198 if err != nil { 199 ctx.ServerError("GetMirrorByRepoID", err) 200 return 201 } 202 // This section doesn't require repo_name/RepoName to be set in the form, don't show it 203 // as an error on the UI for this action 204 ctx.Data["Err_RepoName"] = nil 205 206 interval, err := time.ParseDuration(form.Interval) 207 if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { 208 ctx.Data["Err_Interval"] = true 209 ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form) 210 return 211 } 212 213 pullMirror.EnablePrune = form.EnablePrune 214 pullMirror.Interval = interval 215 pullMirror.ScheduleNextUpdate() 216 if err := repo_model.UpdateMirror(ctx, pullMirror); err != nil { 217 ctx.ServerError("UpdateMirror", err) 218 return 219 } 220 221 u, err := git.GetRemoteURL(ctx, ctx.Repo.Repository.RepoPath(), pullMirror.GetRemoteName()) 222 if err != nil { 223 ctx.Data["Err_MirrorAddress"] = true 224 handleSettingRemoteAddrError(ctx, err, form) 225 return 226 } 227 if u.User != nil && form.MirrorPassword == "" && form.MirrorUsername == u.User.Username() { 228 form.MirrorPassword, _ = u.User.Password() 229 } 230 231 address, err := forms.ParseRemoteAddr(form.MirrorAddress, form.MirrorUsername, form.MirrorPassword) 232 if err == nil { 233 err = migrations.IsMigrateURLAllowed(address, ctx.Doer) 234 } 235 if err != nil { 236 ctx.Data["Err_MirrorAddress"] = true 237 handleSettingRemoteAddrError(ctx, err, form) 238 return 239 } 240 241 if err := mirror_service.UpdateAddress(ctx, pullMirror, address); err != nil { 242 ctx.ServerError("UpdateAddress", err) 243 return 244 } 245 246 remoteAddress, err := util.SanitizeURL(form.MirrorAddress) 247 if err != nil { 248 ctx.ServerError("SanitizeURL", err) 249 return 250 } 251 pullMirror.RemoteAddress = remoteAddress 252 253 form.LFS = form.LFS && setting.LFS.StartServer 254 255 if len(form.LFSEndpoint) > 0 { 256 ep := lfs.DetermineEndpoint("", form.LFSEndpoint) 257 if ep == nil { 258 ctx.Data["Err_LFSEndpoint"] = true 259 ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_lfs_endpoint"), tplSettingsOptions, &form) 260 return 261 } 262 err = migrations.IsMigrateURLAllowed(ep.String(), ctx.Doer) 263 if err != nil { 264 ctx.Data["Err_LFSEndpoint"] = true 265 handleSettingRemoteAddrError(ctx, err, form) 266 return 267 } 268 } 269 270 pullMirror.LFS = form.LFS 271 pullMirror.LFSEndpoint = form.LFSEndpoint 272 if err := repo_model.UpdateMirror(ctx, pullMirror); err != nil { 273 ctx.ServerError("UpdateMirror", err) 274 return 275 } 276 277 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 278 ctx.Redirect(repo.Link() + "/settings") 279 280 case "mirror-sync": 281 if !setting.Mirror.Enabled || !repo.IsMirror { 282 ctx.NotFound("", nil) 283 return 284 } 285 286 mirror_service.AddPullMirrorToQueue(repo.ID) 287 288 ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress")) 289 ctx.Redirect(repo.Link() + "/settings") 290 291 case "push-mirror-sync": 292 if !setting.Mirror.Enabled { 293 ctx.NotFound("", nil) 294 return 295 } 296 297 m, err := selectPushMirrorByForm(ctx, form, repo) 298 if err != nil { 299 ctx.NotFound("", nil) 300 return 301 } 302 303 mirror_service.AddPushMirrorToQueue(m.ID) 304 305 ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress")) 306 ctx.Redirect(repo.Link() + "/settings") 307 308 case "push-mirror-update": 309 if !setting.Mirror.Enabled { 310 ctx.NotFound("", nil) 311 return 312 } 313 314 // This section doesn't require repo_name/RepoName to be set in the form, don't show it 315 // as an error on the UI for this action 316 ctx.Data["Err_RepoName"] = nil 317 318 interval, err := time.ParseDuration(form.PushMirrorInterval) 319 if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { 320 ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &forms.RepoSettingForm{}) 321 return 322 } 323 324 id, err := strconv.ParseInt(form.PushMirrorID, 10, 64) 325 if err != nil { 326 ctx.ServerError("UpdatePushMirrorIntervalPushMirrorID", err) 327 return 328 } 329 m := &repo_model.PushMirror{ 330 ID: id, 331 Interval: interval, 332 } 333 if err := repo_model.UpdatePushMirrorInterval(ctx, m); err != nil { 334 ctx.ServerError("UpdatePushMirrorInterval", err) 335 return 336 } 337 // Background why we are adding it to Queue 338 // If we observed its implementation in the context of `push-mirror-sync` where it 339 // is evident that pushing to the queue is necessary for updates. 340 // So, there are updates within the given interval, it is necessary to update the queue accordingly. 341 mirror_service.AddPushMirrorToQueue(m.ID) 342 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 343 ctx.Redirect(repo.Link() + "/settings") 344 345 case "push-mirror-remove": 346 if !setting.Mirror.Enabled { 347 ctx.NotFound("", nil) 348 return 349 } 350 351 // This section doesn't require repo_name/RepoName to be set in the form, don't show it 352 // as an error on the UI for this action 353 ctx.Data["Err_RepoName"] = nil 354 355 m, err := selectPushMirrorByForm(ctx, form, repo) 356 if err != nil { 357 ctx.NotFound("", nil) 358 return 359 } 360 361 if err = mirror_service.RemovePushMirrorRemote(ctx, m); err != nil { 362 ctx.ServerError("RemovePushMirrorRemote", err) 363 return 364 } 365 366 if err = repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: m.ID, RepoID: m.RepoID}); err != nil { 367 ctx.ServerError("DeletePushMirrorByID", err) 368 return 369 } 370 371 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 372 ctx.Redirect(repo.Link() + "/settings") 373 374 case "push-mirror-add": 375 if setting.Mirror.DisableNewPush { 376 ctx.NotFound("", nil) 377 return 378 } 379 380 // This section doesn't require repo_name/RepoName to be set in the form, don't show it 381 // as an error on the UI for this action 382 ctx.Data["Err_RepoName"] = nil 383 384 interval, err := time.ParseDuration(form.PushMirrorInterval) 385 if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { 386 ctx.Data["Err_PushMirrorInterval"] = true 387 ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form) 388 return 389 } 390 391 address, err := forms.ParseRemoteAddr(form.PushMirrorAddress, form.PushMirrorUsername, form.PushMirrorPassword) 392 if err == nil { 393 err = migrations.IsMigrateURLAllowed(address, ctx.Doer) 394 } 395 if err != nil { 396 ctx.Data["Err_PushMirrorAddress"] = true 397 handleSettingRemoteAddrError(ctx, err, form) 398 return 399 } 400 401 remoteSuffix, err := util.CryptoRandomString(10) 402 if err != nil { 403 ctx.ServerError("RandomString", err) 404 return 405 } 406 407 remoteAddress, err := util.SanitizeURL(form.PushMirrorAddress) 408 if err != nil { 409 ctx.ServerError("SanitizeURL", err) 410 return 411 } 412 413 m := &repo_model.PushMirror{ 414 RepoID: repo.ID, 415 Repo: repo, 416 RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix), 417 SyncOnCommit: form.PushMirrorSyncOnCommit, 418 Interval: interval, 419 RemoteAddress: remoteAddress, 420 } 421 if err := repo_model.InsertPushMirror(ctx, m); err != nil { 422 ctx.ServerError("InsertPushMirror", err) 423 return 424 } 425 426 if err := mirror_service.AddPushMirrorRemote(ctx, m, address); err != nil { 427 if err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: m.ID, RepoID: m.RepoID}); err != nil { 428 log.Error("DeletePushMirrors %v", err) 429 } 430 ctx.ServerError("AddPushMirrorRemote", err) 431 return 432 } 433 434 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 435 ctx.Redirect(repo.Link() + "/settings") 436 437 case "advanced": 438 var repoChanged bool 439 var units []repo_model.RepoUnit 440 var deleteUnitTypes []unit_model.Type 441 442 // This section doesn't require repo_name/RepoName to be set in the form, don't show it 443 // as an error on the UI for this action 444 ctx.Data["Err_RepoName"] = nil 445 446 if repo.CloseIssuesViaCommitInAnyBranch != form.EnableCloseIssuesViaCommitInAnyBranch { 447 repo.CloseIssuesViaCommitInAnyBranch = form.EnableCloseIssuesViaCommitInAnyBranch 448 repoChanged = true 449 } 450 451 if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() { 452 units = append(units, repo_model.RepoUnit{ 453 RepoID: repo.ID, 454 Type: unit_model.TypeCode, 455 }) 456 } else if !unit_model.TypeCode.UnitGlobalDisabled() { 457 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode) 458 } 459 460 if form.EnableWiki && form.EnableExternalWiki && !unit_model.TypeExternalWiki.UnitGlobalDisabled() { 461 if !validation.IsValidExternalURL(form.ExternalWikiURL) { 462 ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error")) 463 ctx.Redirect(repo.Link() + "/settings") 464 return 465 } 466 467 units = append(units, repo_model.RepoUnit{ 468 RepoID: repo.ID, 469 Type: unit_model.TypeExternalWiki, 470 Config: &repo_model.ExternalWikiConfig{ 471 ExternalWikiURL: form.ExternalWikiURL, 472 }, 473 }) 474 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) 475 } else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() { 476 units = append(units, repo_model.RepoUnit{ 477 RepoID: repo.ID, 478 Type: unit_model.TypeWiki, 479 Config: new(repo_model.UnitConfig), 480 }) 481 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) 482 } else { 483 if !unit_model.TypeExternalWiki.UnitGlobalDisabled() { 484 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) 485 } 486 if !unit_model.TypeWiki.UnitGlobalDisabled() { 487 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) 488 } 489 } 490 491 if form.EnableIssues && form.EnableExternalTracker && !unit_model.TypeExternalTracker.UnitGlobalDisabled() { 492 if !validation.IsValidExternalURL(form.ExternalTrackerURL) { 493 ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error")) 494 ctx.Redirect(repo.Link() + "/settings") 495 return 496 } 497 if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(form.TrackerURLFormat) { 498 ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error")) 499 ctx.Redirect(repo.Link() + "/settings") 500 return 501 } 502 units = append(units, repo_model.RepoUnit{ 503 RepoID: repo.ID, 504 Type: unit_model.TypeExternalTracker, 505 Config: &repo_model.ExternalTrackerConfig{ 506 ExternalTrackerURL: form.ExternalTrackerURL, 507 ExternalTrackerFormat: form.TrackerURLFormat, 508 ExternalTrackerStyle: form.TrackerIssueStyle, 509 ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern, 510 }, 511 }) 512 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) 513 } else if form.EnableIssues && !form.EnableExternalTracker && !unit_model.TypeIssues.UnitGlobalDisabled() { 514 units = append(units, repo_model.RepoUnit{ 515 RepoID: repo.ID, 516 Type: unit_model.TypeIssues, 517 Config: &repo_model.IssuesConfig{ 518 EnableTimetracker: form.EnableTimetracker, 519 AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, 520 EnableDependencies: form.EnableIssueDependencies, 521 }, 522 }) 523 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) 524 } else { 525 if !unit_model.TypeExternalTracker.UnitGlobalDisabled() { 526 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) 527 } 528 if !unit_model.TypeIssues.UnitGlobalDisabled() { 529 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) 530 } 531 } 532 533 if form.EnableProjects && !unit_model.TypeProjects.UnitGlobalDisabled() { 534 units = append(units, repo_model.RepoUnit{ 535 RepoID: repo.ID, 536 Type: unit_model.TypeProjects, 537 }) 538 } else if !unit_model.TypeProjects.UnitGlobalDisabled() { 539 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects) 540 } 541 542 if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() { 543 units = append(units, repo_model.RepoUnit{ 544 RepoID: repo.ID, 545 Type: unit_model.TypeReleases, 546 }) 547 } else if !unit_model.TypeReleases.UnitGlobalDisabled() { 548 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases) 549 } 550 551 if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() { 552 units = append(units, repo_model.RepoUnit{ 553 RepoID: repo.ID, 554 Type: unit_model.TypePackages, 555 }) 556 } else if !unit_model.TypePackages.UnitGlobalDisabled() { 557 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages) 558 } 559 560 if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() { 561 units = append(units, repo_model.RepoUnit{ 562 RepoID: repo.ID, 563 Type: unit_model.TypeActions, 564 }) 565 } else if !unit_model.TypeActions.UnitGlobalDisabled() { 566 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions) 567 } 568 569 if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() { 570 units = append(units, repo_model.RepoUnit{ 571 RepoID: repo.ID, 572 Type: unit_model.TypePullRequests, 573 Config: &repo_model.PullRequestsConfig{ 574 IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace, 575 AllowMerge: form.PullsAllowMerge, 576 AllowRebase: form.PullsAllowRebase, 577 AllowRebaseMerge: form.PullsAllowRebaseMerge, 578 AllowSquash: form.PullsAllowSquash, 579 AllowManualMerge: form.PullsAllowManualMerge, 580 AutodetectManualMerge: form.EnableAutodetectManualMerge, 581 AllowRebaseUpdate: form.PullsAllowRebaseUpdate, 582 DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge, 583 DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle), 584 DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit, 585 }, 586 }) 587 } else if !unit_model.TypePullRequests.UnitGlobalDisabled() { 588 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests) 589 } 590 591 if len(units) == 0 { 592 ctx.Flash.Error(ctx.Tr("repo.settings.update_settings_no_unit")) 593 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 594 return 595 } 596 597 if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil { 598 ctx.ServerError("UpdateRepositoryUnits", err) 599 return 600 } 601 if repoChanged { 602 if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { 603 ctx.ServerError("UpdateRepository", err) 604 return 605 } 606 } 607 log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) 608 609 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 610 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 611 612 case "signing": 613 changed := false 614 trustModel := repo_model.ToTrustModel(form.TrustModel) 615 if trustModel != repo.TrustModel { 616 repo.TrustModel = trustModel 617 changed = true 618 } 619 620 if changed { 621 if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { 622 ctx.ServerError("UpdateRepository", err) 623 return 624 } 625 } 626 log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) 627 628 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 629 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 630 631 case "admin": 632 if !ctx.Doer.IsAdmin { 633 ctx.Error(http.StatusForbidden) 634 return 635 } 636 637 if repo.IsFsckEnabled != form.EnableHealthCheck { 638 repo.IsFsckEnabled = form.EnableHealthCheck 639 } 640 641 if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { 642 ctx.ServerError("UpdateRepository", err) 643 return 644 } 645 646 log.Trace("Repository admin settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) 647 648 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 649 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 650 651 case "admin_index": 652 if !ctx.Doer.IsAdmin { 653 ctx.Error(http.StatusForbidden) 654 return 655 } 656 657 switch form.RequestReindexType { 658 case "stats": 659 if err := stats.UpdateRepoIndexer(ctx.Repo.Repository); err != nil { 660 ctx.ServerError("UpdateStatsRepondexer", err) 661 return 662 } 663 case "code": 664 if !setting.Indexer.RepoIndexerEnabled { 665 ctx.Error(http.StatusForbidden) 666 return 667 } 668 code.UpdateRepoIndexer(ctx.Repo.Repository) 669 default: 670 ctx.NotFound("", nil) 671 return 672 } 673 674 log.Trace("Repository reindex for %s requested: %s/%s", form.RequestReindexType, ctx.Repo.Owner.Name, repo.Name) 675 676 ctx.Flash.Success(ctx.Tr("repo.settings.reindex_requested")) 677 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 678 679 case "convert": 680 if !ctx.Repo.IsOwner() { 681 ctx.Error(http.StatusNotFound) 682 return 683 } 684 if repo.Name != form.RepoName { 685 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) 686 return 687 } 688 689 if !repo.IsMirror { 690 ctx.Error(http.StatusNotFound) 691 return 692 } 693 repo.IsMirror = false 694 695 if _, err := repo_module.CleanUpMigrateInfo(ctx, repo); err != nil { 696 ctx.ServerError("CleanUpMigrateInfo", err) 697 return 698 } else if err = repo_model.DeleteMirrorByRepoID(ctx, ctx.Repo.Repository.ID); err != nil { 699 ctx.ServerError("DeleteMirrorByRepoID", err) 700 return 701 } 702 log.Trace("Repository converted from mirror to regular: %s", repo.FullName()) 703 ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed")) 704 ctx.Redirect(repo.Link()) 705 706 case "convert_fork": 707 if !ctx.Repo.IsOwner() { 708 ctx.Error(http.StatusNotFound) 709 return 710 } 711 if err := repo.LoadOwner(ctx); err != nil { 712 ctx.ServerError("Convert Fork", err) 713 return 714 } 715 if repo.Name != form.RepoName { 716 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) 717 return 718 } 719 720 if !repo.IsFork { 721 ctx.Error(http.StatusNotFound) 722 return 723 } 724 725 if !ctx.Repo.Owner.CanCreateRepo() { 726 maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit() 727 msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) 728 ctx.Flash.Error(msg) 729 ctx.Redirect(repo.Link() + "/settings") 730 return 731 } 732 733 if err := repo_service.ConvertForkToNormalRepository(ctx, repo); err != nil { 734 log.Error("Unable to convert repository %-v from fork. Error: %v", repo, err) 735 ctx.ServerError("Convert Fork", err) 736 return 737 } 738 739 log.Trace("Repository converted from fork to regular: %s", repo.FullName()) 740 ctx.Flash.Success(ctx.Tr("repo.settings.convert_fork_succeed")) 741 ctx.Redirect(repo.Link()) 742 743 case "transfer": 744 if !ctx.Repo.IsOwner() { 745 ctx.Error(http.StatusNotFound) 746 return 747 } 748 if repo.Name != form.RepoName { 749 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) 750 return 751 } 752 753 newOwner, err := user_model.GetUserByName(ctx, ctx.FormString("new_owner_name")) 754 if err != nil { 755 if user_model.IsErrUserNotExist(err) { 756 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil) 757 return 758 } 759 ctx.ServerError("IsUserExist", err) 760 return 761 } 762 763 if newOwner.Type == user_model.UserTypeOrganization { 764 if !ctx.Doer.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !organization.OrgFromUser(newOwner).HasMemberWithUserID(ctx.Doer.ID) { 765 // The user shouldn't know about this organization 766 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil) 767 return 768 } 769 } 770 771 // Close the GitRepo if open 772 if ctx.Repo.GitRepo != nil { 773 ctx.Repo.GitRepo.Close() 774 ctx.Repo.GitRepo = nil 775 } 776 777 if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, repo, nil); err != nil { 778 if repo_model.IsErrRepoAlreadyExist(err) { 779 ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil) 780 } else if models.IsErrRepoTransferInProgress(err) { 781 ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil) 782 } else { 783 ctx.ServerError("TransferOwnership", err) 784 } 785 786 return 787 } 788 789 log.Trace("Repository transfer process was started: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newOwner) 790 ctx.Flash.Success(ctx.Tr("repo.settings.transfer_started", newOwner.DisplayName())) 791 ctx.Redirect(repo.Link() + "/settings") 792 793 case "cancel_transfer": 794 if !ctx.Repo.IsOwner() { 795 ctx.Error(http.StatusNotFound) 796 return 797 } 798 799 repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository) 800 if err != nil { 801 if models.IsErrNoPendingTransfer(err) { 802 ctx.Flash.Error("repo.settings.transfer_abort_invalid") 803 ctx.Redirect(repo.Link() + "/settings") 804 } else { 805 ctx.ServerError("GetPendingRepositoryTransfer", err) 806 } 807 808 return 809 } 810 811 if err := repoTransfer.LoadAttributes(ctx); err != nil { 812 ctx.ServerError("LoadRecipient", err) 813 return 814 } 815 816 if err := models.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil { 817 ctx.ServerError("CancelRepositoryTransfer", err) 818 return 819 } 820 821 log.Trace("Repository transfer process was cancelled: %s/%s ", ctx.Repo.Owner.Name, repo.Name) 822 ctx.Flash.Success(ctx.Tr("repo.settings.transfer_abort_success", repoTransfer.Recipient.Name)) 823 ctx.Redirect(repo.Link() + "/settings") 824 825 case "delete": 826 if !ctx.Repo.IsOwner() { 827 ctx.Error(http.StatusNotFound) 828 return 829 } 830 if repo.Name != form.RepoName { 831 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) 832 return 833 } 834 835 // Close the gitrepository before doing this. 836 if ctx.Repo.GitRepo != nil { 837 ctx.Repo.GitRepo.Close() 838 } 839 840 if err := repo_service.DeleteRepository(ctx, ctx.Doer, ctx.Repo.Repository, true); err != nil { 841 ctx.ServerError("DeleteRepository", err) 842 return 843 } 844 log.Trace("Repository deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name) 845 846 ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success")) 847 ctx.Redirect(ctx.Repo.Owner.DashboardLink()) 848 849 case "delete-wiki": 850 if !ctx.Repo.IsOwner() { 851 ctx.Error(http.StatusNotFound) 852 return 853 } 854 if repo.Name != form.RepoName { 855 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) 856 return 857 } 858 859 err := wiki_service.DeleteWiki(ctx, repo) 860 if err != nil { 861 log.Error("Delete Wiki: %v", err.Error()) 862 } 863 log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name) 864 865 ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success")) 866 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 867 868 case "archive": 869 if !ctx.Repo.IsOwner() { 870 ctx.Error(http.StatusForbidden) 871 return 872 } 873 874 if repo.IsMirror { 875 ctx.Flash.Error(ctx.Tr("repo.settings.archive.error_ismirror")) 876 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 877 return 878 } 879 880 if err := repo_model.SetArchiveRepoState(ctx, repo, true); err != nil { 881 log.Error("Tried to archive a repo: %s", err) 882 ctx.Flash.Error(ctx.Tr("repo.settings.archive.error")) 883 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 884 return 885 } 886 887 ctx.Flash.Success(ctx.Tr("repo.settings.archive.success")) 888 889 log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) 890 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 891 892 case "unarchive": 893 if !ctx.Repo.IsOwner() { 894 ctx.Error(http.StatusForbidden) 895 return 896 } 897 898 if err := repo_model.SetArchiveRepoState(ctx, repo, false); err != nil { 899 log.Error("Tried to unarchive a repo: %s", err) 900 ctx.Flash.Error(ctx.Tr("repo.settings.unarchive.error")) 901 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 902 return 903 } 904 905 ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success")) 906 907 log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) 908 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 909 910 default: 911 ctx.NotFound("", nil) 912 } 913 } 914 915 func handleSettingRemoteAddrError(ctx *context.Context, err error, form *forms.RepoSettingForm) { 916 if models.IsErrInvalidCloneAddr(err) { 917 addrErr := err.(*models.ErrInvalidCloneAddr) 918 switch { 919 case addrErr.IsProtocolInvalid: 920 ctx.RenderWithErr(ctx.Tr("repo.mirror_address_protocol_invalid"), tplSettingsOptions, form) 921 case addrErr.IsURLError: 922 ctx.RenderWithErr(ctx.Tr("form.url_error", addrErr.Host), tplSettingsOptions, form) 923 case addrErr.IsPermissionDenied: 924 if addrErr.LocalPath { 925 ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tplSettingsOptions, form) 926 } else { 927 ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied_blocked"), tplSettingsOptions, form) 928 } 929 case addrErr.IsInvalidPath: 930 ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tplSettingsOptions, form) 931 default: 932 ctx.ServerError("Unknown error", err) 933 } 934 return 935 } 936 ctx.RenderWithErr(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, form) 937 } 938 939 func selectPushMirrorByForm(ctx *context.Context, form *forms.RepoSettingForm, repo *repo_model.Repository) (*repo_model.PushMirror, error) { 940 id, err := strconv.ParseInt(form.PushMirrorID, 10, 64) 941 if err != nil { 942 return nil, err 943 } 944 945 pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{}) 946 if err != nil { 947 return nil, err 948 } 949 950 for _, m := range pushMirrors { 951 if m.ID == id { 952 m.Repo = repo 953 return m, nil 954 } 955 } 956 957 return nil, fmt.Errorf("PushMirror[%v] not associated to repository %v", id, repo) 958 }