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 32KB

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