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.

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