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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package setting
  5. import (
  6. "fmt"
  7. "net/http"
  8. "strconv"
  9. "strings"
  10. "time"
  11. "code.gitea.io/gitea/models"
  12. "code.gitea.io/gitea/models/db"
  13. "code.gitea.io/gitea/models/organization"
  14. repo_model "code.gitea.io/gitea/models/repo"
  15. unit_model "code.gitea.io/gitea/models/unit"
  16. user_model "code.gitea.io/gitea/models/user"
  17. "code.gitea.io/gitea/modules/base"
  18. "code.gitea.io/gitea/modules/context"
  19. "code.gitea.io/gitea/modules/git"
  20. "code.gitea.io/gitea/modules/indexer/code"
  21. "code.gitea.io/gitea/modules/indexer/stats"
  22. "code.gitea.io/gitea/modules/lfs"
  23. "code.gitea.io/gitea/modules/log"
  24. repo_module "code.gitea.io/gitea/modules/repository"
  25. "code.gitea.io/gitea/modules/setting"
  26. "code.gitea.io/gitea/modules/structs"
  27. "code.gitea.io/gitea/modules/util"
  28. "code.gitea.io/gitea/modules/validation"
  29. "code.gitea.io/gitea/modules/web"
  30. asymkey_service "code.gitea.io/gitea/services/asymkey"
  31. "code.gitea.io/gitea/services/forms"
  32. "code.gitea.io/gitea/services/migrations"
  33. mirror_service "code.gitea.io/gitea/services/mirror"
  34. repo_service "code.gitea.io/gitea/services/repository"
  35. wiki_service "code.gitea.io/gitea/services/wiki"
  36. )
  37. const (
  38. tplSettingsOptions base.TplName = "repo/settings/options"
  39. tplCollaboration base.TplName = "repo/settings/collaboration"
  40. tplBranches base.TplName = "repo/settings/branches"
  41. tplGithooks base.TplName = "repo/settings/githooks"
  42. tplGithookEdit base.TplName = "repo/settings/githook_edit"
  43. tplDeployKeys base.TplName = "repo/settings/deploy_keys"
  44. )
  45. // SettingsCtxData is a middleware that sets all the general context data for the
  46. // settings template.
  47. func SettingsCtxData(ctx *context.Context) {
  48. ctx.Data["Title"] = ctx.Tr("repo.settings.options")
  49. ctx.Data["PageIsSettingsOptions"] = true
  50. ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate
  51. ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled
  52. ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
  53. ctx.Data["DisableNewPushMirrors"] = setting.Mirror.DisableNewPush
  54. ctx.Data["DefaultMirrorInterval"] = setting.Mirror.DefaultInterval
  55. ctx.Data["MinimumMirrorInterval"] = setting.Mirror.MinInterval
  56. signing, _ := asymkey_service.SigningKey(ctx, ctx.Repo.Repository.RepoPath())
  57. ctx.Data["SigningKeyAvailable"] = len(signing) > 0
  58. ctx.Data["SigningSettings"] = setting.Repository.Signing
  59. ctx.Data["CodeIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
  60. if ctx.Doer.IsAdmin {
  61. if setting.Indexer.RepoIndexerEnabled {
  62. status, err := repo_model.GetIndexerStatus(ctx, ctx.Repo.Repository, repo_model.RepoIndexerTypeCode)
  63. if err != nil {
  64. ctx.ServerError("repo.indexer_status", err)
  65. return
  66. }
  67. ctx.Data["CodeIndexerStatus"] = status
  68. }
  69. status, err := repo_model.GetIndexerStatus(ctx, ctx.Repo.Repository, repo_model.RepoIndexerTypeStats)
  70. if err != nil {
  71. ctx.ServerError("repo.indexer_status", err)
  72. return
  73. }
  74. ctx.Data["StatsIndexerStatus"] = status
  75. }
  76. pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, ctx.Repo.Repository.ID, db.ListOptions{})
  77. if err != nil {
  78. ctx.ServerError("GetPushMirrorsByRepoID", err)
  79. return
  80. }
  81. ctx.Data["PushMirrors"] = pushMirrors
  82. }
  83. // Settings show a repository's settings page
  84. func Settings(ctx *context.Context) {
  85. ctx.HTML(http.StatusOK, tplSettingsOptions)
  86. }
  87. // SettingsPost response for changes of a repository
  88. func SettingsPost(ctx *context.Context) {
  89. form := web.GetForm(ctx).(*forms.RepoSettingForm)
  90. ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate
  91. ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled
  92. ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
  93. ctx.Data["DisableNewPushMirrors"] = setting.Mirror.DisableNewPush
  94. ctx.Data["DefaultMirrorInterval"] = setting.Mirror.DefaultInterval
  95. ctx.Data["MinimumMirrorInterval"] = setting.Mirror.MinInterval
  96. signing, _ := asymkey_service.SigningKey(ctx, ctx.Repo.Repository.RepoPath())
  97. ctx.Data["SigningKeyAvailable"] = len(signing) > 0
  98. ctx.Data["SigningSettings"] = setting.Repository.Signing
  99. ctx.Data["CodeIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
  100. repo := ctx.Repo.Repository
  101. switch ctx.FormString("action") {
  102. case "update":
  103. if ctx.HasError() {
  104. ctx.HTML(http.StatusOK, tplSettingsOptions)
  105. return
  106. }
  107. newRepoName := form.RepoName
  108. // Check if repository name has been changed.
  109. if repo.LowerName != strings.ToLower(newRepoName) {
  110. // Close the GitRepo if open
  111. if ctx.Repo.GitRepo != nil {
  112. ctx.Repo.GitRepo.Close()
  113. ctx.Repo.GitRepo = nil
  114. }
  115. if err := repo_service.ChangeRepositoryName(ctx, ctx.Doer, repo, newRepoName); err != nil {
  116. ctx.Data["Err_RepoName"] = true
  117. switch {
  118. case repo_model.IsErrRepoAlreadyExist(err):
  119. ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplSettingsOptions, &form)
  120. case db.IsErrNameReserved(err):
  121. ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form)
  122. case repo_model.IsErrRepoFilesAlreadyExist(err):
  123. ctx.Data["Err_RepoName"] = true
  124. switch {
  125. case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories):
  126. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tplSettingsOptions, form)
  127. case setting.Repository.AllowAdoptionOfUnadoptedRepositories:
  128. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tplSettingsOptions, form)
  129. case setting.Repository.AllowDeleteOfUnadoptedRepositories:
  130. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tplSettingsOptions, form)
  131. default:
  132. ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tplSettingsOptions, form)
  133. }
  134. case db.IsErrNamePatternNotAllowed(err):
  135. ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
  136. default:
  137. ctx.ServerError("ChangeRepositoryName", err)
  138. }
  139. return
  140. }
  141. log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName)
  142. }
  143. // In case it's just a case change.
  144. repo.Name = newRepoName
  145. repo.LowerName = strings.ToLower(newRepoName)
  146. repo.Description = form.Description
  147. repo.Website = form.Website
  148. repo.IsTemplate = form.Template
  149. // Visibility of forked repository is forced sync with base repository.
  150. if repo.IsFork {
  151. form.Private = repo.BaseRepo.IsPrivate || repo.BaseRepo.Owner.Visibility == structs.VisibleTypePrivate
  152. }
  153. visibilityChanged := repo.IsPrivate != form.Private
  154. // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public
  155. if visibilityChanged && setting.Repository.ForcePrivate && !form.Private && !ctx.Doer.IsAdmin {
  156. ctx.RenderWithErr(ctx.Tr("form.repository_force_private"), tplSettingsOptions, form)
  157. return
  158. }
  159. repo.IsPrivate = form.Private
  160. if err := repo_service.UpdateRepository(ctx, repo, visibilityChanged); err != nil {
  161. ctx.ServerError("UpdateRepository", err)
  162. return
  163. }
  164. log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  165. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  166. ctx.Redirect(repo.Link() + "/settings")
  167. case "mirror":
  168. if !setting.Mirror.Enabled || !repo.IsMirror {
  169. ctx.NotFound("", nil)
  170. return
  171. }
  172. pullMirror, err := repo_model.GetMirrorByRepoID(ctx, ctx.Repo.Repository.ID)
  173. if err == repo_model.ErrMirrorNotExist {
  174. ctx.NotFound("", nil)
  175. return
  176. }
  177. if err != nil {
  178. ctx.ServerError("GetMirrorByRepoID", err)
  179. return
  180. }
  181. // This section doesn't require repo_name/RepoName to be set in the form, don't show it
  182. // as an error on the UI for this action
  183. ctx.Data["Err_RepoName"] = nil
  184. interval, err := time.ParseDuration(form.Interval)
  185. if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) {
  186. ctx.Data["Err_Interval"] = true
  187. ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
  188. return
  189. }
  190. pullMirror.EnablePrune = form.EnablePrune
  191. pullMirror.Interval = interval
  192. pullMirror.ScheduleNextUpdate()
  193. if err := repo_model.UpdateMirror(ctx, pullMirror); err != nil {
  194. ctx.ServerError("UpdateMirror", err)
  195. return
  196. }
  197. u, err := git.GetRemoteURL(ctx, ctx.Repo.Repository.RepoPath(), pullMirror.GetRemoteName())
  198. if err != nil {
  199. ctx.Data["Err_MirrorAddress"] = true
  200. handleSettingRemoteAddrError(ctx, err, form)
  201. return
  202. }
  203. if u.User != nil && form.MirrorPassword == "" && form.MirrorUsername == u.User.Username() {
  204. form.MirrorPassword, _ = u.User.Password()
  205. }
  206. address, err := forms.ParseRemoteAddr(form.MirrorAddress, form.MirrorUsername, form.MirrorPassword)
  207. if err == nil {
  208. err = migrations.IsMigrateURLAllowed(address, ctx.Doer)
  209. }
  210. if err != nil {
  211. ctx.Data["Err_MirrorAddress"] = true
  212. handleSettingRemoteAddrError(ctx, err, form)
  213. return
  214. }
  215. if err := mirror_service.UpdateAddress(ctx, pullMirror, address); err != nil {
  216. ctx.ServerError("UpdateAddress", err)
  217. return
  218. }
  219. remoteAddress, err := util.SanitizeURL(form.MirrorAddress)
  220. if err != nil {
  221. ctx.ServerError("SanitizeURL", err)
  222. return
  223. }
  224. pullMirror.RemoteAddress = remoteAddress
  225. form.LFS = form.LFS && setting.LFS.StartServer
  226. if len(form.LFSEndpoint) > 0 {
  227. ep := lfs.DetermineEndpoint("", form.LFSEndpoint)
  228. if ep == nil {
  229. ctx.Data["Err_LFSEndpoint"] = true
  230. ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_lfs_endpoint"), tplSettingsOptions, &form)
  231. return
  232. }
  233. err = migrations.IsMigrateURLAllowed(ep.String(), ctx.Doer)
  234. if err != nil {
  235. ctx.Data["Err_LFSEndpoint"] = true
  236. handleSettingRemoteAddrError(ctx, err, form)
  237. return
  238. }
  239. }
  240. pullMirror.LFS = form.LFS
  241. pullMirror.LFSEndpoint = form.LFSEndpoint
  242. if err := repo_model.UpdateMirror(ctx, pullMirror); err != nil {
  243. ctx.ServerError("UpdateMirror", err)
  244. return
  245. }
  246. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  247. ctx.Redirect(repo.Link() + "/settings")
  248. case "mirror-sync":
  249. if !setting.Mirror.Enabled || !repo.IsMirror {
  250. ctx.NotFound("", nil)
  251. return
  252. }
  253. mirror_service.AddPullMirrorToQueue(repo.ID)
  254. ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress"))
  255. ctx.Redirect(repo.Link() + "/settings")
  256. case "push-mirror-sync":
  257. if !setting.Mirror.Enabled {
  258. ctx.NotFound("", nil)
  259. return
  260. }
  261. m, err := selectPushMirrorByForm(ctx, form, repo)
  262. if err != nil {
  263. ctx.NotFound("", nil)
  264. return
  265. }
  266. mirror_service.AddPushMirrorToQueue(m.ID)
  267. ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress"))
  268. ctx.Redirect(repo.Link() + "/settings")
  269. case "push-mirror-update":
  270. if !setting.Mirror.Enabled {
  271. ctx.NotFound("", nil)
  272. return
  273. }
  274. // This section doesn't require repo_name/RepoName to be set in the form, don't show it
  275. // as an error on the UI for this action
  276. ctx.Data["Err_RepoName"] = nil
  277. interval, err := time.ParseDuration(form.PushMirrorInterval)
  278. if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) {
  279. ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &forms.RepoSettingForm{})
  280. return
  281. }
  282. id, err := strconv.ParseInt(form.PushMirrorID, 10, 64)
  283. if err != nil {
  284. ctx.ServerError("UpdatePushMirrorIntervalPushMirrorID", err)
  285. return
  286. }
  287. m := &repo_model.PushMirror{
  288. ID: id,
  289. Interval: interval,
  290. }
  291. if err := repo_model.UpdatePushMirrorInterval(ctx, m); err != nil {
  292. ctx.ServerError("UpdatePushMirrorInterval", err)
  293. return
  294. }
  295. // Background why we are adding it to Queue
  296. // If we observed its implementation in the context of `push-mirror-sync` where it
  297. // is evident that pushing to the queue is necessary for updates.
  298. // So, there are updates within the given interval, it is necessary to update the queue accordingly.
  299. mirror_service.AddPushMirrorToQueue(m.ID)
  300. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  301. ctx.Redirect(repo.Link() + "/settings")
  302. case "push-mirror-remove":
  303. if !setting.Mirror.Enabled {
  304. ctx.NotFound("", nil)
  305. return
  306. }
  307. // This section doesn't require repo_name/RepoName to be set in the form, don't show it
  308. // as an error on the UI for this action
  309. ctx.Data["Err_RepoName"] = nil
  310. m, err := selectPushMirrorByForm(ctx, form, repo)
  311. if err != nil {
  312. ctx.NotFound("", nil)
  313. return
  314. }
  315. if err = mirror_service.RemovePushMirrorRemote(ctx, m); err != nil {
  316. ctx.ServerError("RemovePushMirrorRemote", err)
  317. return
  318. }
  319. if err = repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: m.ID, RepoID: m.RepoID}); err != nil {
  320. ctx.ServerError("DeletePushMirrorByID", err)
  321. return
  322. }
  323. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  324. ctx.Redirect(repo.Link() + "/settings")
  325. case "push-mirror-add":
  326. if setting.Mirror.DisableNewPush {
  327. ctx.NotFound("", nil)
  328. return
  329. }
  330. // This section doesn't require repo_name/RepoName to be set in the form, don't show it
  331. // as an error on the UI for this action
  332. ctx.Data["Err_RepoName"] = nil
  333. interval, err := time.ParseDuration(form.PushMirrorInterval)
  334. if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) {
  335. ctx.Data["Err_PushMirrorInterval"] = true
  336. ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
  337. return
  338. }
  339. address, err := forms.ParseRemoteAddr(form.PushMirrorAddress, form.PushMirrorUsername, form.PushMirrorPassword)
  340. if err == nil {
  341. err = migrations.IsMigrateURLAllowed(address, ctx.Doer)
  342. }
  343. if err != nil {
  344. ctx.Data["Err_PushMirrorAddress"] = true
  345. handleSettingRemoteAddrError(ctx, err, form)
  346. return
  347. }
  348. remoteSuffix, err := util.CryptoRandomString(10)
  349. if err != nil {
  350. ctx.ServerError("RandomString", err)
  351. return
  352. }
  353. remoteAddress, err := util.SanitizeURL(form.PushMirrorAddress)
  354. if err != nil {
  355. ctx.ServerError("SanitizeURL", err)
  356. return
  357. }
  358. m := &repo_model.PushMirror{
  359. RepoID: repo.ID,
  360. Repo: repo,
  361. RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix),
  362. SyncOnCommit: form.PushMirrorSyncOnCommit,
  363. Interval: interval,
  364. RemoteAddress: remoteAddress,
  365. }
  366. if err := repo_model.InsertPushMirror(ctx, m); err != nil {
  367. ctx.ServerError("InsertPushMirror", err)
  368. return
  369. }
  370. if err := mirror_service.AddPushMirrorRemote(ctx, m, address); err != nil {
  371. if err := repo_model.DeletePushMirrors(ctx, repo_model.PushMirrorOptions{ID: m.ID, RepoID: m.RepoID}); err != nil {
  372. log.Error("DeletePushMirrors %v", err)
  373. }
  374. ctx.ServerError("AddPushMirrorRemote", err)
  375. return
  376. }
  377. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  378. ctx.Redirect(repo.Link() + "/settings")
  379. case "advanced":
  380. var repoChanged bool
  381. var units []repo_model.RepoUnit
  382. var deleteUnitTypes []unit_model.Type
  383. // This section doesn't require repo_name/RepoName to be set in the form, don't show it
  384. // as an error on the UI for this action
  385. ctx.Data["Err_RepoName"] = nil
  386. if repo.CloseIssuesViaCommitInAnyBranch != form.EnableCloseIssuesViaCommitInAnyBranch {
  387. repo.CloseIssuesViaCommitInAnyBranch = form.EnableCloseIssuesViaCommitInAnyBranch
  388. repoChanged = true
  389. }
  390. if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() {
  391. units = append(units, repo_model.RepoUnit{
  392. RepoID: repo.ID,
  393. Type: unit_model.TypeCode,
  394. })
  395. } else if !unit_model.TypeCode.UnitGlobalDisabled() {
  396. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode)
  397. }
  398. if form.EnableWiki && form.EnableExternalWiki && !unit_model.TypeExternalWiki.UnitGlobalDisabled() {
  399. if !validation.IsValidExternalURL(form.ExternalWikiURL) {
  400. ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error"))
  401. ctx.Redirect(repo.Link() + "/settings")
  402. return
  403. }
  404. units = append(units, repo_model.RepoUnit{
  405. RepoID: repo.ID,
  406. Type: unit_model.TypeExternalWiki,
  407. Config: &repo_model.ExternalWikiConfig{
  408. ExternalWikiURL: form.ExternalWikiURL,
  409. },
  410. })
  411. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki)
  412. } else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() {
  413. units = append(units, repo_model.RepoUnit{
  414. RepoID: repo.ID,
  415. Type: unit_model.TypeWiki,
  416. Config: new(repo_model.UnitConfig),
  417. })
  418. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
  419. } else {
  420. if !unit_model.TypeExternalWiki.UnitGlobalDisabled() {
  421. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
  422. }
  423. if !unit_model.TypeWiki.UnitGlobalDisabled() {
  424. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki)
  425. }
  426. }
  427. if form.EnableIssues && form.EnableExternalTracker && !unit_model.TypeExternalTracker.UnitGlobalDisabled() {
  428. if !validation.IsValidExternalURL(form.ExternalTrackerURL) {
  429. ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error"))
  430. ctx.Redirect(repo.Link() + "/settings")
  431. return
  432. }
  433. if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(form.TrackerURLFormat) {
  434. ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error"))
  435. ctx.Redirect(repo.Link() + "/settings")
  436. return
  437. }
  438. units = append(units, repo_model.RepoUnit{
  439. RepoID: repo.ID,
  440. Type: unit_model.TypeExternalTracker,
  441. Config: &repo_model.ExternalTrackerConfig{
  442. ExternalTrackerURL: form.ExternalTrackerURL,
  443. ExternalTrackerFormat: form.TrackerURLFormat,
  444. ExternalTrackerStyle: form.TrackerIssueStyle,
  445. ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern,
  446. },
  447. })
  448. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues)
  449. } else if form.EnableIssues && !form.EnableExternalTracker && !unit_model.TypeIssues.UnitGlobalDisabled() {
  450. units = append(units, repo_model.RepoUnit{
  451. RepoID: repo.ID,
  452. Type: unit_model.TypeIssues,
  453. Config: &repo_model.IssuesConfig{
  454. EnableTimetracker: form.EnableTimetracker,
  455. AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
  456. EnableDependencies: form.EnableIssueDependencies,
  457. },
  458. })
  459. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker)
  460. } else {
  461. if !unit_model.TypeExternalTracker.UnitGlobalDisabled() {
  462. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker)
  463. }
  464. if !unit_model.TypeIssues.UnitGlobalDisabled() {
  465. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues)
  466. }
  467. }
  468. if form.EnableProjects && !unit_model.TypeProjects.UnitGlobalDisabled() {
  469. units = append(units, repo_model.RepoUnit{
  470. RepoID: repo.ID,
  471. Type: unit_model.TypeProjects,
  472. })
  473. } else if !unit_model.TypeProjects.UnitGlobalDisabled() {
  474. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects)
  475. }
  476. if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() {
  477. units = append(units, repo_model.RepoUnit{
  478. RepoID: repo.ID,
  479. Type: unit_model.TypeReleases,
  480. })
  481. } else if !unit_model.TypeReleases.UnitGlobalDisabled() {
  482. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases)
  483. }
  484. if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() {
  485. units = append(units, repo_model.RepoUnit{
  486. RepoID: repo.ID,
  487. Type: unit_model.TypePackages,
  488. })
  489. } else if !unit_model.TypePackages.UnitGlobalDisabled() {
  490. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages)
  491. }
  492. if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() {
  493. units = append(units, repo_model.RepoUnit{
  494. RepoID: repo.ID,
  495. Type: unit_model.TypeActions,
  496. })
  497. } else if !unit_model.TypeActions.UnitGlobalDisabled() {
  498. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions)
  499. }
  500. if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() {
  501. units = append(units, repo_model.RepoUnit{
  502. RepoID: repo.ID,
  503. Type: unit_model.TypePullRequests,
  504. Config: &repo_model.PullRequestsConfig{
  505. IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace,
  506. AllowMerge: form.PullsAllowMerge,
  507. AllowRebase: form.PullsAllowRebase,
  508. AllowRebaseMerge: form.PullsAllowRebaseMerge,
  509. AllowSquash: form.PullsAllowSquash,
  510. AllowManualMerge: form.PullsAllowManualMerge,
  511. AutodetectManualMerge: form.EnableAutodetectManualMerge,
  512. AllowRebaseUpdate: form.PullsAllowRebaseUpdate,
  513. DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge,
  514. DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle),
  515. DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit,
  516. },
  517. })
  518. } else if !unit_model.TypePullRequests.UnitGlobalDisabled() {
  519. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests)
  520. }
  521. if len(units) == 0 {
  522. ctx.Flash.Error(ctx.Tr("repo.settings.update_settings_no_unit"))
  523. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  524. return
  525. }
  526. if err := repo_model.UpdateRepositoryUnits(repo, units, deleteUnitTypes); err != nil {
  527. ctx.ServerError("UpdateRepositoryUnits", err)
  528. return
  529. }
  530. if repoChanged {
  531. if err := repo_service.UpdateRepository(ctx, repo, false); err != nil {
  532. ctx.ServerError("UpdateRepository", err)
  533. return
  534. }
  535. }
  536. log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  537. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  538. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  539. case "signing":
  540. changed := false
  541. trustModel := repo_model.ToTrustModel(form.TrustModel)
  542. if trustModel != repo.TrustModel {
  543. repo.TrustModel = trustModel
  544. changed = true
  545. }
  546. if changed {
  547. if err := repo_service.UpdateRepository(ctx, repo, false); err != nil {
  548. ctx.ServerError("UpdateRepository", err)
  549. return
  550. }
  551. }
  552. log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  553. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  554. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  555. case "admin":
  556. if !ctx.Doer.IsAdmin {
  557. ctx.Error(http.StatusForbidden)
  558. return
  559. }
  560. if repo.IsFsckEnabled != form.EnableHealthCheck {
  561. repo.IsFsckEnabled = form.EnableHealthCheck
  562. }
  563. if err := repo_service.UpdateRepository(ctx, repo, false); err != nil {
  564. ctx.ServerError("UpdateRepository", err)
  565. return
  566. }
  567. log.Trace("Repository admin settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  568. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  569. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  570. case "admin_index":
  571. if !ctx.Doer.IsAdmin {
  572. ctx.Error(http.StatusForbidden)
  573. return
  574. }
  575. switch form.RequestReindexType {
  576. case "stats":
  577. if err := stats.UpdateRepoIndexer(ctx.Repo.Repository); err != nil {
  578. ctx.ServerError("UpdateStatsRepondexer", err)
  579. return
  580. }
  581. case "code":
  582. if !setting.Indexer.RepoIndexerEnabled {
  583. ctx.Error(http.StatusForbidden)
  584. return
  585. }
  586. code.UpdateRepoIndexer(ctx.Repo.Repository)
  587. default:
  588. ctx.NotFound("", nil)
  589. return
  590. }
  591. log.Trace("Repository reindex for %s requested: %s/%s", form.RequestReindexType, ctx.Repo.Owner.Name, repo.Name)
  592. ctx.Flash.Success(ctx.Tr("repo.settings.reindex_requested"))
  593. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  594. case "convert":
  595. if !ctx.Repo.IsOwner() {
  596. ctx.Error(http.StatusNotFound)
  597. return
  598. }
  599. if repo.Name != form.RepoName {
  600. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  601. return
  602. }
  603. if !repo.IsMirror {
  604. ctx.Error(http.StatusNotFound)
  605. return
  606. }
  607. repo.IsMirror = false
  608. if _, err := repo_module.CleanUpMigrateInfo(ctx, repo); err != nil {
  609. ctx.ServerError("CleanUpMigrateInfo", err)
  610. return
  611. } else if err = repo_model.DeleteMirrorByRepoID(ctx, ctx.Repo.Repository.ID); err != nil {
  612. ctx.ServerError("DeleteMirrorByRepoID", err)
  613. return
  614. }
  615. log.Trace("Repository converted from mirror to regular: %s", repo.FullName())
  616. ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed"))
  617. ctx.Redirect(repo.Link())
  618. case "convert_fork":
  619. if !ctx.Repo.IsOwner() {
  620. ctx.Error(http.StatusNotFound)
  621. return
  622. }
  623. if err := repo.LoadOwner(ctx); err != nil {
  624. ctx.ServerError("Convert Fork", err)
  625. return
  626. }
  627. if repo.Name != form.RepoName {
  628. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  629. return
  630. }
  631. if !repo.IsFork {
  632. ctx.Error(http.StatusNotFound)
  633. return
  634. }
  635. if !ctx.Repo.Owner.CanCreateRepo() {
  636. maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit()
  637. msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
  638. ctx.Flash.Error(msg)
  639. ctx.Redirect(repo.Link() + "/settings")
  640. return
  641. }
  642. if err := repo_service.ConvertForkToNormalRepository(ctx, repo); err != nil {
  643. log.Error("Unable to convert repository %-v from fork. Error: %v", repo, err)
  644. ctx.ServerError("Convert Fork", err)
  645. return
  646. }
  647. log.Trace("Repository converted from fork to regular: %s", repo.FullName())
  648. ctx.Flash.Success(ctx.Tr("repo.settings.convert_fork_succeed"))
  649. ctx.Redirect(repo.Link())
  650. case "transfer":
  651. if !ctx.Repo.IsOwner() {
  652. ctx.Error(http.StatusNotFound)
  653. return
  654. }
  655. if repo.Name != form.RepoName {
  656. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  657. return
  658. }
  659. newOwner, err := user_model.GetUserByName(ctx, ctx.FormString("new_owner_name"))
  660. if err != nil {
  661. if user_model.IsErrUserNotExist(err) {
  662. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil)
  663. return
  664. }
  665. ctx.ServerError("IsUserExist", err)
  666. return
  667. }
  668. if newOwner.Type == user_model.UserTypeOrganization {
  669. if !ctx.Doer.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !organization.OrgFromUser(newOwner).HasMemberWithUserID(ctx.Doer.ID) {
  670. // The user shouldn't know about this organization
  671. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil)
  672. return
  673. }
  674. }
  675. // Close the GitRepo if open
  676. if ctx.Repo.GitRepo != nil {
  677. ctx.Repo.GitRepo.Close()
  678. ctx.Repo.GitRepo = nil
  679. }
  680. if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, repo, nil); err != nil {
  681. if repo_model.IsErrRepoAlreadyExist(err) {
  682. ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil)
  683. } else if models.IsErrRepoTransferInProgress(err) {
  684. ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil)
  685. } else {
  686. ctx.ServerError("TransferOwnership", err)
  687. }
  688. return
  689. }
  690. log.Trace("Repository transfer process was started: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newOwner)
  691. ctx.Flash.Success(ctx.Tr("repo.settings.transfer_started", newOwner.DisplayName()))
  692. ctx.Redirect(repo.Link() + "/settings")
  693. case "cancel_transfer":
  694. if !ctx.Repo.IsOwner() {
  695. ctx.Error(http.StatusNotFound)
  696. return
  697. }
  698. repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
  699. if err != nil {
  700. if models.IsErrNoPendingTransfer(err) {
  701. ctx.Flash.Error("repo.settings.transfer_abort_invalid")
  702. ctx.Redirect(repo.Link() + "/settings")
  703. } else {
  704. ctx.ServerError("GetPendingRepositoryTransfer", err)
  705. }
  706. return
  707. }
  708. if err := repoTransfer.LoadAttributes(ctx); err != nil {
  709. ctx.ServerError("LoadRecipient", err)
  710. return
  711. }
  712. if err := models.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil {
  713. ctx.ServerError("CancelRepositoryTransfer", err)
  714. return
  715. }
  716. log.Trace("Repository transfer process was cancelled: %s/%s ", ctx.Repo.Owner.Name, repo.Name)
  717. ctx.Flash.Success(ctx.Tr("repo.settings.transfer_abort_success", repoTransfer.Recipient.Name))
  718. ctx.Redirect(repo.Link() + "/settings")
  719. case "delete":
  720. if !ctx.Repo.IsOwner() {
  721. ctx.Error(http.StatusNotFound)
  722. return
  723. }
  724. if repo.Name != form.RepoName {
  725. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  726. return
  727. }
  728. // Close the gitrepository before doing this.
  729. if ctx.Repo.GitRepo != nil {
  730. ctx.Repo.GitRepo.Close()
  731. }
  732. if err := repo_service.DeleteRepository(ctx, ctx.Doer, ctx.Repo.Repository, true); err != nil {
  733. ctx.ServerError("DeleteRepository", err)
  734. return
  735. }
  736. log.Trace("Repository deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  737. ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success"))
  738. ctx.Redirect(ctx.Repo.Owner.DashboardLink())
  739. case "delete-wiki":
  740. if !ctx.Repo.IsOwner() {
  741. ctx.Error(http.StatusNotFound)
  742. return
  743. }
  744. if repo.Name != form.RepoName {
  745. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  746. return
  747. }
  748. err := wiki_service.DeleteWiki(ctx, repo)
  749. if err != nil {
  750. log.Error("Delete Wiki: %v", err.Error())
  751. }
  752. log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  753. ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success"))
  754. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  755. case "archive":
  756. if !ctx.Repo.IsOwner() {
  757. ctx.Error(http.StatusForbidden)
  758. return
  759. }
  760. if repo.IsMirror {
  761. ctx.Flash.Error(ctx.Tr("repo.settings.archive.error_ismirror"))
  762. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  763. return
  764. }
  765. if err := repo_model.SetArchiveRepoState(ctx, repo, true); err != nil {
  766. log.Error("Tried to archive a repo: %s", err)
  767. ctx.Flash.Error(ctx.Tr("repo.settings.archive.error"))
  768. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  769. return
  770. }
  771. ctx.Flash.Success(ctx.Tr("repo.settings.archive.success"))
  772. log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  773. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  774. case "unarchive":
  775. if !ctx.Repo.IsOwner() {
  776. ctx.Error(http.StatusForbidden)
  777. return
  778. }
  779. if err := repo_model.SetArchiveRepoState(ctx, repo, false); err != nil {
  780. log.Error("Tried to unarchive a repo: %s", err)
  781. ctx.Flash.Error(ctx.Tr("repo.settings.unarchive.error"))
  782. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  783. return
  784. }
  785. ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))
  786. log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  787. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  788. default:
  789. ctx.NotFound("", nil)
  790. }
  791. }
  792. func handleSettingRemoteAddrError(ctx *context.Context, err error, form *forms.RepoSettingForm) {
  793. if models.IsErrInvalidCloneAddr(err) {
  794. addrErr := err.(*models.ErrInvalidCloneAddr)
  795. switch {
  796. case addrErr.IsProtocolInvalid:
  797. ctx.RenderWithErr(ctx.Tr("repo.mirror_address_protocol_invalid"), tplSettingsOptions, form)
  798. case addrErr.IsURLError:
  799. ctx.RenderWithErr(ctx.Tr("form.url_error", addrErr.Host), tplSettingsOptions, form)
  800. case addrErr.IsPermissionDenied:
  801. if addrErr.LocalPath {
  802. ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tplSettingsOptions, form)
  803. } else {
  804. ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied_blocked"), tplSettingsOptions, form)
  805. }
  806. case addrErr.IsInvalidPath:
  807. ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tplSettingsOptions, form)
  808. default:
  809. ctx.ServerError("Unknown error", err)
  810. }
  811. return
  812. }
  813. ctx.RenderWithErr(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, form)
  814. }
  815. func selectPushMirrorByForm(ctx *context.Context, form *forms.RepoSettingForm, repo *repo_model.Repository) (*repo_model.PushMirror, error) {
  816. id, err := strconv.ParseInt(form.PushMirrorID, 10, 64)
  817. if err != nil {
  818. return nil, err
  819. }
  820. pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{})
  821. if err != nil {
  822. return nil, err
  823. }
  824. for _, m := range pushMirrors {
  825. if m.ID == id {
  826. m.Repo = repo
  827. return m, nil
  828. }
  829. }
  830. return nil, fmt.Errorf("PushMirror[%v] not associated to repository %v", id, repo)
  831. }