1// Copyright 2014 The Gogs Authors. All rights reserved. 2// Copyright 2018 The Gitea Authors. All rights reserved. 3// Use of this source code is governed by a MIT-style 4// license that can be found in the LICENSE file. 5 6package repo 7 8import ( 9 "errors" 10 "fmt" 11 "io" 12 "net/http" 13 "strconv" 14 "strings" 15 "time" 16 17 "code.gitea.io/gitea/models" 18 asymkey_model "code.gitea.io/gitea/models/asymkey" 19 "code.gitea.io/gitea/models/db" 20 "code.gitea.io/gitea/models/perm" 21 repo_model "code.gitea.io/gitea/models/repo" 22 unit_model "code.gitea.io/gitea/models/unit" 23 user_model "code.gitea.io/gitea/models/user" 24 "code.gitea.io/gitea/modules/base" 25 "code.gitea.io/gitea/modules/context" 26 "code.gitea.io/gitea/modules/git" 27 "code.gitea.io/gitea/modules/indexer/code" 28 "code.gitea.io/gitea/modules/indexer/stats" 29 "code.gitea.io/gitea/modules/lfs" 30 "code.gitea.io/gitea/modules/log" 31 "code.gitea.io/gitea/modules/repository" 32 "code.gitea.io/gitea/modules/setting" 33 "code.gitea.io/gitea/modules/structs" 34 "code.gitea.io/gitea/modules/timeutil" 35 "code.gitea.io/gitea/modules/typesniffer" 36 "code.gitea.io/gitea/modules/util" 37 "code.gitea.io/gitea/modules/validation" 38 "code.gitea.io/gitea/modules/web" 39 "code.gitea.io/gitea/routers/utils" 40 asymkey_service "code.gitea.io/gitea/services/asymkey" 41 "code.gitea.io/gitea/services/forms" 42 "code.gitea.io/gitea/services/mailer" 43 "code.gitea.io/gitea/services/migrations" 44 mirror_service "code.gitea.io/gitea/services/mirror" 45 repo_service "code.gitea.io/gitea/services/repository" 46 wiki_service "code.gitea.io/gitea/services/wiki" 47) 48 49const ( 50 tplSettingsOptions base.TplName = "repo/settings/options" 51 tplCollaboration base.TplName = "repo/settings/collaboration" 52 tplBranches base.TplName = "repo/settings/branches" 53 tplTags base.TplName = "repo/settings/tags" 54 tplGithooks base.TplName = "repo/settings/githooks" 55 tplGithookEdit base.TplName = "repo/settings/githook_edit" 56 tplDeployKeys base.TplName = "repo/settings/deploy_keys" 57 tplProtectedBranch base.TplName = "repo/settings/protected_branch" 58) 59 60// Settings show a repository's settings page 61func Settings(ctx *context.Context) { 62 ctx.Data["Title"] = ctx.Tr("repo.settings") 63 ctx.Data["PageIsSettingsOptions"] = true 64 ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate 65 ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled 66 ctx.Data["DisableNewPushMirrors"] = setting.Mirror.DisableNewPush 67 ctx.Data["DefaultMirrorInterval"] = setting.Mirror.DefaultInterval 68 69 signing, _ := asymkey_service.SigningKey(ctx.Repo.Repository.RepoPath()) 70 ctx.Data["SigningKeyAvailable"] = len(signing) > 0 71 ctx.Data["SigningSettings"] = setting.Repository.Signing 72 ctx.Data["CodeIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled 73 if ctx.User.IsAdmin { 74 if setting.Indexer.RepoIndexerEnabled { 75 status, err := repo_model.GetIndexerStatus(ctx.Repo.Repository, repo_model.RepoIndexerTypeCode) 76 if err != nil { 77 ctx.ServerError("repo.indexer_status", err) 78 return 79 } 80 ctx.Data["CodeIndexerStatus"] = status 81 } 82 status, err := repo_model.GetIndexerStatus(ctx.Repo.Repository, repo_model.RepoIndexerTypeStats) 83 if err != nil { 84 ctx.ServerError("repo.indexer_status", err) 85 return 86 } 87 ctx.Data["StatsIndexerStatus"] = status 88 } 89 pushMirrors, err := repo_model.GetPushMirrorsByRepoID(ctx.Repo.Repository.ID) 90 if err != nil { 91 ctx.ServerError("GetPushMirrorsByRepoID", err) 92 return 93 } 94 ctx.Data["PushMirrors"] = pushMirrors 95 96 ctx.HTML(http.StatusOK, tplSettingsOptions) 97} 98 99// SettingsPost response for changes of a repository 100func SettingsPost(ctx *context.Context) { 101 form := web.GetForm(ctx).(*forms.RepoSettingForm) 102 ctx.Data["Title"] = ctx.Tr("repo.settings") 103 ctx.Data["PageIsSettingsOptions"] = true 104 105 repo := ctx.Repo.Repository 106 107 switch ctx.FormString("action") { 108 case "update": 109 if ctx.HasError() { 110 ctx.HTML(http.StatusOK, tplSettingsOptions) 111 return 112 } 113 114 newRepoName := form.RepoName 115 // Check if repository name has been changed. 116 if repo.LowerName != strings.ToLower(newRepoName) { 117 // Close the GitRepo if open 118 if ctx.Repo.GitRepo != nil { 119 ctx.Repo.GitRepo.Close() 120 ctx.Repo.GitRepo = nil 121 } 122 if err := repo_service.ChangeRepositoryName(ctx.User, repo, newRepoName); err != nil { 123 ctx.Data["Err_RepoName"] = true 124 switch { 125 case repo_model.IsErrRepoAlreadyExist(err): 126 ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplSettingsOptions, &form) 127 case db.IsErrNameReserved(err): 128 ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form) 129 case repo_model.IsErrRepoFilesAlreadyExist(err): 130 ctx.Data["Err_RepoName"] = true 131 switch { 132 case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories): 133 ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tplSettingsOptions, form) 134 case setting.Repository.AllowAdoptionOfUnadoptedRepositories: 135 ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tplSettingsOptions, form) 136 case setting.Repository.AllowDeleteOfUnadoptedRepositories: 137 ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tplSettingsOptions, form) 138 default: 139 ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tplSettingsOptions, form) 140 } 141 case db.IsErrNamePatternNotAllowed(err): 142 ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form) 143 default: 144 ctx.ServerError("ChangeRepositoryName", err) 145 } 146 return 147 } 148 149 log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName) 150 } 151 // In case it's just a case change. 152 repo.Name = newRepoName 153 repo.LowerName = strings.ToLower(newRepoName) 154 repo.Description = form.Description 155 repo.Website = form.Website 156 repo.IsTemplate = form.Template 157 158 // Visibility of forked repository is forced sync with base repository. 159 if repo.IsFork { 160 form.Private = repo.BaseRepo.IsPrivate || repo.BaseRepo.Owner.Visibility == structs.VisibleTypePrivate 161 } 162 163 visibilityChanged := repo.IsPrivate != form.Private 164 // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public 165 if visibilityChanged && setting.Repository.ForcePrivate && !form.Private && !ctx.User.IsAdmin { 166 ctx.RenderWithErr(ctx.Tr("form.repository_force_private"), tplSettingsOptions, form) 167 return 168 } 169 170 repo.IsPrivate = form.Private 171 if err := models.UpdateRepository(repo, visibilityChanged); err != nil { 172 ctx.ServerError("UpdateRepository", err) 173 return 174 } 175 log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) 176 177 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 178 ctx.Redirect(repo.Link() + "/settings") 179 180 case "mirror": 181 if !setting.Mirror.Enabled || !repo.IsMirror { 182 ctx.NotFound("", nil) 183 return 184 } 185 186 // This section doesn't require repo_name/RepoName to be set in the form, don't show it 187 // as an error on the UI for this action 188 ctx.Data["Err_RepoName"] = nil 189 190 interval, err := time.ParseDuration(form.Interval) 191 if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { 192 ctx.Data["Err_Interval"] = true 193 ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form) 194 } else { 195 ctx.Repo.Mirror.EnablePrune = form.EnablePrune 196 ctx.Repo.Mirror.Interval = interval 197 if interval != 0 { 198 ctx.Repo.Mirror.NextUpdateUnix = timeutil.TimeStampNow().AddDuration(interval) 199 } else { 200 ctx.Repo.Mirror.NextUpdateUnix = 0 201 } 202 if err := repo_model.UpdateMirror(ctx.Repo.Mirror); err != nil { 203 ctx.Data["Err_Interval"] = true 204 ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form) 205 return 206 } 207 } 208 209 u, _ := git.GetRemoteAddress(ctx, ctx.Repo.Repository.RepoPath(), ctx.Repo.Mirror.GetRemoteName()) 210 if u.User != nil && form.MirrorPassword == "" && form.MirrorUsername == u.User.Username() { 211 form.MirrorPassword, _ = u.User.Password() 212 } 213 214 address, err := forms.ParseRemoteAddr(form.MirrorAddress, form.MirrorUsername, form.MirrorPassword) 215 if err == nil { 216 err = migrations.IsMigrateURLAllowed(address, ctx.User) 217 } 218 if err != nil { 219 ctx.Data["Err_MirrorAddress"] = true 220 handleSettingRemoteAddrError(ctx, err, form) 221 return 222 } 223 224 if err := mirror_service.UpdateAddress(ctx.Repo.Mirror, address); err != nil { 225 ctx.ServerError("UpdateAddress", err) 226 return 227 } 228 229 form.LFS = form.LFS && setting.LFS.StartServer 230 231 if len(form.LFSEndpoint) > 0 { 232 ep := lfs.DetermineEndpoint("", form.LFSEndpoint) 233 if ep == nil { 234 ctx.Data["Err_LFSEndpoint"] = true 235 ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_lfs_endpoint"), tplSettingsOptions, &form) 236 return 237 } 238 err = migrations.IsMigrateURLAllowed(ep.String(), ctx.User) 239 if err != nil { 240 ctx.Data["Err_LFSEndpoint"] = true 241 handleSettingRemoteAddrError(ctx, err, form) 242 return 243 } 244 } 245 246 ctx.Repo.Mirror.LFS = form.LFS 247 ctx.Repo.Mirror.LFSEndpoint = form.LFSEndpoint 248 if err := repo_model.UpdateMirror(ctx.Repo.Mirror); err != nil { 249 ctx.ServerError("UpdateMirror", err) 250 return 251 } 252 253 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 254 ctx.Redirect(repo.Link() + "/settings") 255 256 case "mirror-sync": 257 if !setting.Mirror.Enabled || !repo.IsMirror { 258 ctx.NotFound("", nil) 259 return 260 } 261 262 mirror_service.StartToMirror(repo.ID) 263 264 ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress")) 265 ctx.Redirect(repo.Link() + "/settings") 266 267 case "push-mirror-sync": 268 if !setting.Mirror.Enabled { 269 ctx.NotFound("", nil) 270 return 271 } 272 273 m, err := selectPushMirrorByForm(form, repo) 274 if err != nil { 275 ctx.NotFound("", nil) 276 return 277 } 278 279 mirror_service.AddPushMirrorToQueue(m.ID) 280 281 ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress")) 282 ctx.Redirect(repo.Link() + "/settings") 283 284 case "push-mirror-remove": 285 if !setting.Mirror.Enabled { 286 ctx.NotFound("", nil) 287 return 288 } 289 290 // This section doesn't require repo_name/RepoName to be set in the form, don't show it 291 // as an error on the UI for this action 292 ctx.Data["Err_RepoName"] = nil 293 294 m, err := selectPushMirrorByForm(form, repo) 295 if err != nil { 296 ctx.NotFound("", nil) 297 return 298 } 299 300 if err = mirror_service.RemovePushMirrorRemote(m); err != nil { 301 ctx.ServerError("RemovePushMirrorRemote", err) 302 return 303 } 304 305 if err = repo_model.DeletePushMirrorByID(m.ID); err != nil { 306 ctx.ServerError("DeletePushMirrorByID", err) 307 return 308 } 309 310 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 311 ctx.Redirect(repo.Link() + "/settings") 312 313 case "push-mirror-add": 314 if setting.Mirror.DisableNewPush { 315 ctx.NotFound("", nil) 316 return 317 } 318 319 // This section doesn't require repo_name/RepoName to be set in the form, don't show it 320 // as an error on the UI for this action 321 ctx.Data["Err_RepoName"] = nil 322 323 interval, err := time.ParseDuration(form.PushMirrorInterval) 324 if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { 325 ctx.Data["Err_PushMirrorInterval"] = true 326 ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form) 327 return 328 } 329 330 address, err := forms.ParseRemoteAddr(form.PushMirrorAddress, form.PushMirrorUsername, form.PushMirrorPassword) 331 if err == nil { 332 err = migrations.IsMigrateURLAllowed(address, ctx.User) 333 } 334 if err != nil { 335 ctx.Data["Err_PushMirrorAddress"] = true 336 handleSettingRemoteAddrError(ctx, err, form) 337 return 338 } 339 340 remoteSuffix, err := util.RandomString(10) 341 if err != nil { 342 ctx.ServerError("RandomString", err) 343 return 344 } 345 346 m := &repo_model.PushMirror{ 347 RepoID: repo.ID, 348 Repo: repo, 349 RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix), 350 Interval: interval, 351 } 352 if err := repo_model.InsertPushMirror(m); err != nil { 353 ctx.ServerError("InsertPushMirror", err) 354 return 355 } 356 357 if err := mirror_service.AddPushMirrorRemote(m, address); err != nil { 358 if err := repo_model.DeletePushMirrorByID(m.ID); err != nil { 359 log.Error("DeletePushMirrorByID %v", err) 360 } 361 ctx.ServerError("AddPushMirrorRemote", err) 362 return 363 } 364 365 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 366 ctx.Redirect(repo.Link() + "/settings") 367 368 case "advanced": 369 var repoChanged bool 370 var units []repo_model.RepoUnit 371 var deleteUnitTypes []unit_model.Type 372 373 // This section doesn't require repo_name/RepoName to be set in the form, don't show it 374 // as an error on the UI for this action 375 ctx.Data["Err_RepoName"] = nil 376 377 if repo.CloseIssuesViaCommitInAnyBranch != form.EnableCloseIssuesViaCommitInAnyBranch { 378 repo.CloseIssuesViaCommitInAnyBranch = form.EnableCloseIssuesViaCommitInAnyBranch 379 repoChanged = true 380 } 381 382 if form.EnableWiki && form.EnableExternalWiki && !unit_model.TypeExternalWiki.UnitGlobalDisabled() { 383 if !validation.IsValidExternalURL(form.ExternalWikiURL) { 384 ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error")) 385 ctx.Redirect(repo.Link() + "/settings") 386 return 387 } 388 389 units = append(units, repo_model.RepoUnit{ 390 RepoID: repo.ID, 391 Type: unit_model.TypeExternalWiki, 392 Config: &repo_model.ExternalWikiConfig{ 393 ExternalWikiURL: form.ExternalWikiURL, 394 }, 395 }) 396 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) 397 } else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() { 398 units = append(units, repo_model.RepoUnit{ 399 RepoID: repo.ID, 400 Type: unit_model.TypeWiki, 401 Config: new(repo_model.UnitConfig), 402 }) 403 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) 404 } else { 405 if !unit_model.TypeExternalWiki.UnitGlobalDisabled() { 406 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) 407 } 408 if !unit_model.TypeWiki.UnitGlobalDisabled() { 409 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) 410 } 411 } 412 413 if form.EnableIssues && form.EnableExternalTracker && !unit_model.TypeExternalTracker.UnitGlobalDisabled() { 414 if !validation.IsValidExternalURL(form.ExternalTrackerURL) { 415 ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error")) 416 ctx.Redirect(repo.Link() + "/settings") 417 return 418 } 419 if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(form.TrackerURLFormat) { 420 ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error")) 421 ctx.Redirect(repo.Link() + "/settings") 422 return 423 } 424 units = append(units, repo_model.RepoUnit{ 425 RepoID: repo.ID, 426 Type: unit_model.TypeExternalTracker, 427 Config: &repo_model.ExternalTrackerConfig{ 428 ExternalTrackerURL: form.ExternalTrackerURL, 429 ExternalTrackerFormat: form.TrackerURLFormat, 430 ExternalTrackerStyle: form.TrackerIssueStyle, 431 }, 432 }) 433 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) 434 } else if form.EnableIssues && !form.EnableExternalTracker && !unit_model.TypeIssues.UnitGlobalDisabled() { 435 units = append(units, repo_model.RepoUnit{ 436 RepoID: repo.ID, 437 Type: unit_model.TypeIssues, 438 Config: &repo_model.IssuesConfig{ 439 EnableTimetracker: form.EnableTimetracker, 440 AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, 441 EnableDependencies: form.EnableIssueDependencies, 442 }, 443 }) 444 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) 445 } else { 446 if !unit_model.TypeExternalTracker.UnitGlobalDisabled() { 447 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) 448 } 449 if !unit_model.TypeIssues.UnitGlobalDisabled() { 450 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) 451 } 452 } 453 454 if form.EnableProjects && !unit_model.TypeProjects.UnitGlobalDisabled() { 455 units = append(units, repo_model.RepoUnit{ 456 RepoID: repo.ID, 457 Type: unit_model.TypeProjects, 458 }) 459 } else if !unit_model.TypeProjects.UnitGlobalDisabled() { 460 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects) 461 } 462 463 if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() { 464 units = append(units, repo_model.RepoUnit{ 465 RepoID: repo.ID, 466 Type: unit_model.TypePullRequests, 467 Config: &repo_model.PullRequestsConfig{ 468 IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace, 469 AllowMerge: form.PullsAllowMerge, 470 AllowRebase: form.PullsAllowRebase, 471 AllowRebaseMerge: form.PullsAllowRebaseMerge, 472 AllowSquash: form.PullsAllowSquash, 473 AllowManualMerge: form.PullsAllowManualMerge, 474 AutodetectManualMerge: form.EnableAutodetectManualMerge, 475 DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge, 476 DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle), 477 }, 478 }) 479 } else if !unit_model.TypePullRequests.UnitGlobalDisabled() { 480 deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests) 481 } 482 483 if err := repo_model.UpdateRepositoryUnits(repo, units, deleteUnitTypes); err != nil { 484 ctx.ServerError("UpdateRepositoryUnits", err) 485 return 486 } 487 if repoChanged { 488 if err := models.UpdateRepository(repo, false); err != nil { 489 ctx.ServerError("UpdateRepository", err) 490 return 491 } 492 } 493 log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) 494 495 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 496 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 497 498 case "signing": 499 changed := false 500 trustModel := repo_model.ToTrustModel(form.TrustModel) 501 if trustModel != repo.TrustModel { 502 repo.TrustModel = trustModel 503 changed = true 504 } 505 506 if changed { 507 if err := models.UpdateRepository(repo, false); err != nil { 508 ctx.ServerError("UpdateRepository", err) 509 return 510 } 511 } 512 log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) 513 514 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 515 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 516 517 case "admin": 518 if !ctx.User.IsAdmin { 519 ctx.Error(http.StatusForbidden) 520 return 521 } 522 523 if repo.IsFsckEnabled != form.EnableHealthCheck { 524 repo.IsFsckEnabled = form.EnableHealthCheck 525 } 526 527 if err := models.UpdateRepository(repo, false); err != nil { 528 ctx.ServerError("UpdateRepository", err) 529 return 530 } 531 532 log.Trace("Repository admin settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) 533 534 ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 535 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 536 537 case "admin_index": 538 if !ctx.User.IsAdmin { 539 ctx.Error(http.StatusForbidden) 540 return 541 } 542 543 switch form.RequestReindexType { 544 case "stats": 545 if err := stats.UpdateRepoIndexer(ctx.Repo.Repository); err != nil { 546 ctx.ServerError("UpdateStatsRepondexer", err) 547 return 548 } 549 case "code": 550 if !setting.Indexer.RepoIndexerEnabled { 551 ctx.Error(http.StatusForbidden) 552 return 553 } 554 code.UpdateRepoIndexer(ctx.Repo.Repository) 555 default: 556 ctx.NotFound("", nil) 557 return 558 } 559 560 log.Trace("Repository reindex for %s requested: %s/%s", form.RequestReindexType, ctx.Repo.Owner.Name, repo.Name) 561 562 ctx.Flash.Success(ctx.Tr("repo.settings.reindex_requested")) 563 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 564 565 case "convert": 566 if !ctx.Repo.IsOwner() { 567 ctx.Error(http.StatusNotFound) 568 return 569 } 570 if repo.Name != form.RepoName { 571 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) 572 return 573 } 574 575 if !repo.IsMirror { 576 ctx.Error(http.StatusNotFound) 577 return 578 } 579 repo.IsMirror = false 580 581 if _, err := repository.CleanUpMigrateInfo(repo); err != nil { 582 ctx.ServerError("CleanUpMigrateInfo", err) 583 return 584 } else if err = repo_model.DeleteMirrorByRepoID(ctx.Repo.Repository.ID); err != nil { 585 ctx.ServerError("DeleteMirrorByRepoID", err) 586 return 587 } 588 log.Trace("Repository converted from mirror to regular: %s", repo.FullName()) 589 ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed")) 590 ctx.Redirect(repo.Link()) 591 592 case "convert_fork": 593 if !ctx.Repo.IsOwner() { 594 ctx.Error(http.StatusNotFound) 595 return 596 } 597 if err := repo.GetOwner(db.DefaultContext); err != nil { 598 ctx.ServerError("Convert Fork", err) 599 return 600 } 601 if repo.Name != form.RepoName { 602 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) 603 return 604 } 605 606 if !repo.IsFork { 607 ctx.Error(http.StatusNotFound) 608 return 609 } 610 611 if !ctx.Repo.Owner.CanCreateRepo() { 612 maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit() 613 msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) 614 ctx.Flash.Error(msg) 615 ctx.Redirect(repo.Link() + "/settings") 616 return 617 } 618 619 if err := repo_service.ConvertForkToNormalRepository(repo); err != nil { 620 log.Error("Unable to convert repository %-v from fork. Error: %v", repo, err) 621 ctx.ServerError("Convert Fork", err) 622 return 623 } 624 625 log.Trace("Repository converted from fork to regular: %s", repo.FullName()) 626 ctx.Flash.Success(ctx.Tr("repo.settings.convert_fork_succeed")) 627 ctx.Redirect(repo.Link()) 628 629 case "transfer": 630 if !ctx.Repo.IsOwner() { 631 ctx.Error(http.StatusNotFound) 632 return 633 } 634 if repo.Name != form.RepoName { 635 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) 636 return 637 } 638 639 newOwner, err := user_model.GetUserByName(ctx.FormString("new_owner_name")) 640 if err != nil { 641 if user_model.IsErrUserNotExist(err) { 642 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil) 643 return 644 } 645 ctx.ServerError("IsUserExist", err) 646 return 647 } 648 649 if newOwner.Type == user_model.UserTypeOrganization { 650 if !ctx.User.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !models.OrgFromUser(newOwner).HasMemberWithUserID(ctx.User.ID) { 651 // The user shouldn't know about this organization 652 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil) 653 return 654 } 655 } 656 657 // Close the GitRepo if open 658 if ctx.Repo.GitRepo != nil { 659 ctx.Repo.GitRepo.Close() 660 ctx.Repo.GitRepo = nil 661 } 662 663 if err := repo_service.StartRepositoryTransfer(ctx.User, newOwner, repo, nil); err != nil { 664 if repo_model.IsErrRepoAlreadyExist(err) { 665 ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil) 666 } else if models.IsErrRepoTransferInProgress(err) { 667 ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil) 668 } else { 669 ctx.ServerError("TransferOwnership", err) 670 } 671 672 return 673 } 674 675 log.Trace("Repository transfer process was started: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newOwner) 676 ctx.Flash.Success(ctx.Tr("repo.settings.transfer_started", newOwner.DisplayName())) 677 ctx.Redirect(repo.Link() + "/settings") 678 679 case "cancel_transfer": 680 if !ctx.Repo.IsOwner() { 681 ctx.Error(http.StatusNotFound) 682 return 683 } 684 685 repoTransfer, err := models.GetPendingRepositoryTransfer(ctx.Repo.Repository) 686 if err != nil { 687 if models.IsErrNoPendingTransfer(err) { 688 ctx.Flash.Error("repo.settings.transfer_abort_invalid") 689 ctx.Redirect(repo.Link() + "/settings") 690 } else { 691 ctx.ServerError("GetPendingRepositoryTransfer", err) 692 } 693 694 return 695 } 696 697 if err := repoTransfer.LoadAttributes(); err != nil { 698 ctx.ServerError("LoadRecipient", err) 699 return 700 } 701 702 if err := models.CancelRepositoryTransfer(ctx.Repo.Repository); err != nil { 703 ctx.ServerError("CancelRepositoryTransfer", err) 704 return 705 } 706 707 log.Trace("Repository transfer process was cancelled: %s/%s ", ctx.Repo.Owner.Name, repo.Name) 708 ctx.Flash.Success(ctx.Tr("repo.settings.transfer_abort_success", repoTransfer.Recipient.Name)) 709 ctx.Redirect(repo.Link() + "/settings") 710 711 case "delete": 712 if !ctx.Repo.IsOwner() { 713 ctx.Error(http.StatusNotFound) 714 return 715 } 716 if repo.Name != form.RepoName { 717 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) 718 return 719 } 720 721 // Close the gitrepository before doing this. 722 if ctx.Repo.GitRepo != nil { 723 ctx.Repo.GitRepo.Close() 724 } 725 726 if err := repo_service.DeleteRepository(ctx.User, ctx.Repo.Repository, true); err != nil { 727 ctx.ServerError("DeleteRepository", err) 728 return 729 } 730 log.Trace("Repository deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name) 731 732 ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success")) 733 ctx.Redirect(ctx.Repo.Owner.DashboardLink()) 734 735 case "delete-wiki": 736 if !ctx.Repo.IsOwner() { 737 ctx.Error(http.StatusNotFound) 738 return 739 } 740 if repo.Name != form.RepoName { 741 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) 742 return 743 } 744 745 err := wiki_service.DeleteWiki(repo) 746 if err != nil { 747 log.Error("Delete Wiki: %v", err.Error()) 748 } 749 log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name) 750 751 ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success")) 752 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 753 754 case "archive": 755 if !ctx.Repo.IsOwner() { 756 ctx.Error(http.StatusForbidden) 757 return 758 } 759 760 if repo.IsMirror { 761 ctx.Flash.Error(ctx.Tr("repo.settings.archive.error_ismirror")) 762 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 763 return 764 } 765 766 if err := repo_model.SetArchiveRepoState(repo, true); err != nil { 767 log.Error("Tried to archive a repo: %s", err) 768 ctx.Flash.Error(ctx.Tr("repo.settings.archive.error")) 769 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 770 return 771 } 772 773 ctx.Flash.Success(ctx.Tr("repo.settings.archive.success")) 774 775 log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) 776 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 777 778 case "unarchive": 779 if !ctx.Repo.IsOwner() { 780 ctx.Error(http.StatusForbidden) 781 return 782 } 783 784 if err := repo_model.SetArchiveRepoState(repo, false); err != nil { 785 log.Error("Tried to unarchive a repo: %s", err) 786 ctx.Flash.Error(ctx.Tr("repo.settings.unarchive.error")) 787 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 788 return 789 } 790 791 ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success")) 792 793 log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) 794 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 795 796 default: 797 ctx.NotFound("", nil) 798 } 799} 800 801func handleSettingRemoteAddrError(ctx *context.Context, err error, form *forms.RepoSettingForm) { 802 if models.IsErrInvalidCloneAddr(err) { 803 addrErr := err.(*models.ErrInvalidCloneAddr) 804 switch { 805 case addrErr.IsProtocolInvalid: 806 ctx.RenderWithErr(ctx.Tr("repo.mirror_address_protocol_invalid"), tplSettingsOptions, form) 807 case addrErr.IsURLError: 808 ctx.RenderWithErr(ctx.Tr("form.url_error"), tplSettingsOptions, form) 809 case addrErr.IsPermissionDenied: 810 if addrErr.LocalPath { 811 ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tplSettingsOptions, form) 812 } else { 813 ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied_blocked"), tplSettingsOptions, form) 814 } 815 case addrErr.IsInvalidPath: 816 ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tplSettingsOptions, form) 817 default: 818 ctx.ServerError("Unknown error", err) 819 } 820 return 821 } 822 ctx.RenderWithErr(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, form) 823} 824 825// Collaboration render a repository's collaboration page 826func Collaboration(ctx *context.Context) { 827 ctx.Data["Title"] = ctx.Tr("repo.settings") 828 ctx.Data["PageIsSettingsCollaboration"] = true 829 830 users, err := models.GetCollaborators(ctx.Repo.Repository.ID, db.ListOptions{}) 831 if err != nil { 832 ctx.ServerError("GetCollaborators", err) 833 return 834 } 835 ctx.Data["Collaborators"] = users 836 837 teams, err := models.GetRepoTeams(ctx.Repo.Repository) 838 if err != nil { 839 ctx.ServerError("GetRepoTeams", err) 840 return 841 } 842 ctx.Data["Teams"] = teams 843 ctx.Data["Repo"] = ctx.Repo.Repository 844 ctx.Data["OrgID"] = ctx.Repo.Repository.OwnerID 845 ctx.Data["OrgName"] = ctx.Repo.Repository.OwnerName 846 ctx.Data["Org"] = ctx.Repo.Repository.Owner 847 ctx.Data["Units"] = unit_model.Units 848 849 ctx.HTML(http.StatusOK, tplCollaboration) 850} 851 852// CollaborationPost response for actions for a collaboration of a repository 853func CollaborationPost(ctx *context.Context) { 854 name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("collaborator"))) 855 if len(name) == 0 || ctx.Repo.Owner.LowerName == name { 856 ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) 857 return 858 } 859 860 u, err := user_model.GetUserByName(name) 861 if err != nil { 862 if user_model.IsErrUserNotExist(err) { 863 ctx.Flash.Error(ctx.Tr("form.user_not_exist")) 864 ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) 865 } else { 866 ctx.ServerError("GetUserByName", err) 867 } 868 return 869 } 870 871 if !u.IsActive { 872 ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_inactive_user")) 873 ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) 874 return 875 } 876 877 // Organization is not allowed to be added as a collaborator. 878 if u.IsOrganization() { 879 ctx.Flash.Error(ctx.Tr("repo.settings.org_not_allowed_to_be_collaborator")) 880 ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) 881 return 882 } 883 884 if got, err := models.IsCollaborator(ctx.Repo.Repository.ID, u.ID); err == nil && got { 885 ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_duplicate")) 886 ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") 887 return 888 } 889 890 if err = models.AddCollaborator(ctx.Repo.Repository, u); err != nil { 891 ctx.ServerError("AddCollaborator", err) 892 return 893 } 894 895 if setting.Service.EnableNotifyMail { 896 mailer.SendCollaboratorMail(u, ctx.User, ctx.Repo.Repository) 897 } 898 899 ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success")) 900 ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) 901} 902 903// ChangeCollaborationAccessMode response for changing access of a collaboration 904func ChangeCollaborationAccessMode(ctx *context.Context) { 905 if err := models.ChangeCollaborationAccessMode( 906 ctx.Repo.Repository, 907 ctx.FormInt64("uid"), 908 perm.AccessMode(ctx.FormInt("mode"))); err != nil { 909 log.Error("ChangeCollaborationAccessMode: %v", err) 910 } 911} 912 913// DeleteCollaboration delete a collaboration for a repository 914func DeleteCollaboration(ctx *context.Context) { 915 if err := models.DeleteCollaboration(ctx.Repo.Repository, ctx.FormInt64("id")); err != nil { 916 ctx.Flash.Error("DeleteCollaboration: " + err.Error()) 917 } else { 918 ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) 919 } 920 921 ctx.JSON(http.StatusOK, map[string]interface{}{ 922 "redirect": ctx.Repo.RepoLink + "/settings/collaboration", 923 }) 924} 925 926// AddTeamPost response for adding a team to a repository 927func AddTeamPost(ctx *context.Context) { 928 if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() { 929 ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed")) 930 ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") 931 return 932 } 933 934 name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("team"))) 935 if len(name) == 0 { 936 ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") 937 return 938 } 939 940 team, err := models.OrgFromUser(ctx.Repo.Owner).GetTeam(name) 941 if err != nil { 942 if models.IsErrTeamNotExist(err) { 943 ctx.Flash.Error(ctx.Tr("form.team_not_exist")) 944 ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") 945 } else { 946 ctx.ServerError("GetTeam", err) 947 } 948 return 949 } 950 951 if team.OrgID != ctx.Repo.Repository.OwnerID { 952 ctx.Flash.Error(ctx.Tr("repo.settings.team_not_in_organization")) 953 ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") 954 return 955 } 956 957 if models.HasTeamRepo(ctx.Repo.Repository.OwnerID, team.ID, ctx.Repo.Repository.ID) { 958 ctx.Flash.Error(ctx.Tr("repo.settings.add_team_duplicate")) 959 ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") 960 return 961 } 962 963 if err = team.AddRepository(ctx.Repo.Repository); err != nil { 964 ctx.ServerError("team.AddRepository", err) 965 return 966 } 967 968 ctx.Flash.Success(ctx.Tr("repo.settings.add_team_success")) 969 ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") 970} 971 972// DeleteTeam response for deleting a team from a repository 973func DeleteTeam(ctx *context.Context) { 974 if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() { 975 ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed")) 976 ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") 977 return 978 } 979 980 team, err := models.GetTeamByID(ctx.FormInt64("id")) 981 if err != nil { 982 ctx.ServerError("GetTeamByID", err) 983 return 984 } 985 986 if err = team.RemoveRepository(ctx.Repo.Repository.ID); err != nil { 987 ctx.ServerError("team.RemoveRepositorys", err) 988 return 989 } 990 991 ctx.Flash.Success(ctx.Tr("repo.settings.remove_team_success")) 992 ctx.JSON(http.StatusOK, map[string]interface{}{ 993 "redirect": ctx.Repo.RepoLink + "/settings/collaboration", 994 }) 995} 996 997// GitHooks hooks of a repository 998func GitHooks(ctx *context.Context) { 999 ctx.Data["Title"] = ctx.Tr("repo.settings.githooks") 1000 ctx.Data["PageIsSettingsGitHooks"] = true 1001 1002 hooks, err := ctx.Repo.GitRepo.Hooks() 1003 if err != nil { 1004 ctx.ServerError("Hooks", err) 1005 return 1006 } 1007 ctx.Data["Hooks"] = hooks 1008 1009 ctx.HTML(http.StatusOK, tplGithooks) 1010} 1011 1012// GitHooksEdit render for editing a hook of repository page 1013func GitHooksEdit(ctx *context.Context) { 1014 ctx.Data["Title"] = ctx.Tr("repo.settings.githooks") 1015 ctx.Data["PageIsSettingsGitHooks"] = true 1016 1017 name := ctx.Params(":name") 1018 hook, err := ctx.Repo.GitRepo.GetHook(name) 1019 if err != nil { 1020 if err == git.ErrNotValidHook { 1021 ctx.NotFound("GetHook", err) 1022 } else { 1023 ctx.ServerError("GetHook", err) 1024 } 1025 return 1026 } 1027 ctx.Data["Hook"] = hook 1028 ctx.HTML(http.StatusOK, tplGithookEdit) 1029} 1030 1031// GitHooksEditPost response for editing a git hook of a repository 1032func GitHooksEditPost(ctx *context.Context) { 1033 name := ctx.Params(":name") 1034 hook, err := ctx.Repo.GitRepo.GetHook(name) 1035 if err != nil { 1036 if err == git.ErrNotValidHook { 1037 ctx.NotFound("GetHook", err) 1038 } else { 1039 ctx.ServerError("GetHook", err) 1040 } 1041 return 1042 } 1043 hook.Content = ctx.FormString("content") 1044 if err = hook.Update(); err != nil { 1045 ctx.ServerError("hook.Update", err) 1046 return 1047 } 1048 ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks/git") 1049} 1050 1051// DeployKeys render the deploy keys list of a repository page 1052func DeployKeys(ctx *context.Context) { 1053 ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") 1054 ctx.Data["PageIsSettingsKeys"] = true 1055 ctx.Data["DisableSSH"] = setting.SSH.Disabled 1056 1057 keys, err := asymkey_model.ListDeployKeys(db.DefaultContext, &asymkey_model.ListDeployKeysOptions{RepoID: ctx.Repo.Repository.ID}) 1058 if err != nil { 1059 ctx.ServerError("ListDeployKeys", err) 1060 return 1061 } 1062 ctx.Data["Deploykeys"] = keys 1063 1064 ctx.HTML(http.StatusOK, tplDeployKeys) 1065} 1066 1067// DeployKeysPost response for adding a deploy key of a repository 1068func DeployKeysPost(ctx *context.Context) { 1069 form := web.GetForm(ctx).(*forms.AddKeyForm) 1070 ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") 1071 ctx.Data["PageIsSettingsKeys"] = true 1072 1073 keys, err := asymkey_model.ListDeployKeys(db.DefaultContext, &asymkey_model.ListDeployKeysOptions{RepoID: ctx.Repo.Repository.ID}) 1074 if err != nil { 1075 ctx.ServerError("ListDeployKeys", err) 1076 return 1077 } 1078 ctx.Data["Deploykeys"] = keys 1079 1080 if ctx.HasError() { 1081 ctx.HTML(http.StatusOK, tplDeployKeys) 1082 return 1083 } 1084 1085 content, err := asymkey_model.CheckPublicKeyString(form.Content) 1086 if err != nil { 1087 if db.IsErrSSHDisabled(err) { 1088 ctx.Flash.Info(ctx.Tr("settings.ssh_disabled")) 1089 } else if asymkey_model.IsErrKeyUnableVerify(err) { 1090 ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key")) 1091 } else { 1092 ctx.Data["HasError"] = true 1093 ctx.Data["Err_Content"] = true 1094 ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error())) 1095 } 1096 ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") 1097 return 1098 } 1099 1100 key, err := asymkey_model.AddDeployKey(ctx.Repo.Repository.ID, form.Title, content, !form.IsWritable) 1101 if err != nil { 1102 ctx.Data["HasError"] = true 1103 switch { 1104 case asymkey_model.IsErrDeployKeyAlreadyExist(err): 1105 ctx.Data["Err_Content"] = true 1106 ctx.RenderWithErr(ctx.Tr("repo.settings.key_been_used"), tplDeployKeys, &form) 1107 case asymkey_model.IsErrKeyAlreadyExist(err): 1108 ctx.Data["Err_Content"] = true 1109 ctx.RenderWithErr(ctx.Tr("settings.ssh_key_been_used"), tplDeployKeys, &form) 1110 case asymkey_model.IsErrKeyNameAlreadyUsed(err): 1111 ctx.Data["Err_Title"] = true 1112 ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form) 1113 case asymkey_model.IsErrDeployKeyNameAlreadyUsed(err): 1114 ctx.Data["Err_Title"] = true 1115 ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form) 1116 default: 1117 ctx.ServerError("AddDeployKey", err) 1118 } 1119 return 1120 } 1121 1122 log.Trace("Deploy key added: %d", ctx.Repo.Repository.ID) 1123 ctx.Flash.Success(ctx.Tr("repo.settings.add_key_success", key.Name)) 1124 ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") 1125} 1126 1127// DeleteDeployKey response for deleting a deploy key 1128func DeleteDeployKey(ctx *context.Context) { 1129 if err := asymkey_service.DeleteDeployKey(ctx.User, ctx.FormInt64("id")); err != nil { 1130 ctx.Flash.Error("DeleteDeployKey: " + err.Error()) 1131 } else { 1132 ctx.Flash.Success(ctx.Tr("repo.settings.deploy_key_deletion_success")) 1133 } 1134 1135 ctx.JSON(http.StatusOK, map[string]interface{}{ 1136 "redirect": ctx.Repo.RepoLink + "/settings/keys", 1137 }) 1138} 1139 1140// UpdateAvatarSetting update repo's avatar 1141func UpdateAvatarSetting(ctx *context.Context, form forms.AvatarForm) error { 1142 ctxRepo := ctx.Repo.Repository 1143 1144 if form.Avatar == nil { 1145 // No avatar is uploaded and we not removing it here. 1146 // No random avatar generated here. 1147 // Just exit, no action. 1148 if ctxRepo.CustomAvatarRelativePath() == "" { 1149 log.Trace("No avatar was uploaded for repo: %d. Default icon will appear instead.", ctxRepo.ID) 1150 } 1151 return nil 1152 } 1153 1154 r, err := form.Avatar.Open() 1155 if err != nil { 1156 return fmt.Errorf("Avatar.Open: %v", err) 1157 } 1158 defer r.Close() 1159 1160 if form.Avatar.Size > setting.Avatar.MaxFileSize { 1161 return errors.New(ctx.Tr("settings.uploaded_avatar_is_too_big")) 1162 } 1163 1164 data, err := io.ReadAll(r) 1165 if err != nil { 1166 return fmt.Errorf("io.ReadAll: %v", err) 1167 } 1168 st := typesniffer.DetectContentType(data) 1169 if !(st.IsImage() && !st.IsSvgImage()) { 1170 return errors.New(ctx.Tr("settings.uploaded_avatar_not_a_image")) 1171 } 1172 if err = repo_service.UploadAvatar(ctxRepo, data); err != nil { 1173 return fmt.Errorf("UploadAvatar: %v", err) 1174 } 1175 return nil 1176} 1177 1178// SettingsAvatar save new POSTed repository avatar 1179func SettingsAvatar(ctx *context.Context) { 1180 form := web.GetForm(ctx).(*forms.AvatarForm) 1181 form.Source = forms.AvatarLocal 1182 if err := UpdateAvatarSetting(ctx, *form); err != nil { 1183 ctx.Flash.Error(err.Error()) 1184 } else { 1185 ctx.Flash.Success(ctx.Tr("repo.settings.update_avatar_success")) 1186 } 1187 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 1188} 1189 1190// SettingsDeleteAvatar delete repository avatar 1191func SettingsDeleteAvatar(ctx *context.Context) { 1192 if err := repo_service.DeleteAvatar(ctx.Repo.Repository); err != nil { 1193 ctx.Flash.Error(fmt.Sprintf("DeleteAvatar: %v", err)) 1194 } 1195 ctx.Redirect(ctx.Repo.RepoLink + "/settings") 1196} 1197 1198func selectPushMirrorByForm(form *forms.RepoSettingForm, repo *repo_model.Repository) (*repo_model.PushMirror, error) { 1199 id, err := strconv.ParseInt(form.PushMirrorID, 10, 64) 1200 if err != nil { 1201 return nil, err 1202 } 1203 1204 pushMirrors, err := repo_model.GetPushMirrorsByRepoID(repo.ID) 1205 if err != nil { 1206 return nil, err 1207 } 1208 1209 for _, m := range pushMirrors { 1210 if m.ID == id { 1211 return m, nil 1212 } 1213 } 1214 1215 return nil, fmt.Errorf("PushMirror[%v] not associated to repository %v", id, repo) 1216} 1217