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

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