You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

setting.go 37KB

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