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

setting.go 37KB

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