Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

setting.go 42KB

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