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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  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. "code.gitea.io/gitea/models/db"
  14. "code.gitea.io/gitea/models/organization"
  15. repo_model "code.gitea.io/gitea/models/repo"
  16. unit_model "code.gitea.io/gitea/models/unit"
  17. user_model "code.gitea.io/gitea/models/user"
  18. "code.gitea.io/gitea/modules/base"
  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. "code.gitea.io/gitea/modules/setting"
  25. "code.gitea.io/gitea/modules/structs"
  26. "code.gitea.io/gitea/modules/util"
  27. "code.gitea.io/gitea/modules/validation"
  28. "code.gitea.io/gitea/modules/web"
  29. asymkey_service "code.gitea.io/gitea/services/asymkey"
  30. "code.gitea.io/gitea/services/context"
  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 || repo.IsArchived {
  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 || repo.IsArchived {
  250. ctx.NotFound("", nil)
  251. return
  252. }
  253. mirror_service.AddPullMirrorToQueue(repo.ID)
  254. ctx.Flash.Info(ctx.Tr("repo.settings.pull_mirror_sync_in_progress", repo.OriginalURL))
  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.push_mirror_sync_in_progress", m.RemoteAddress))
  268. ctx.Redirect(repo.Link() + "/settings")
  269. case "push-mirror-update":
  270. if !setting.Mirror.Enabled || repo.IsArchived {
  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 || repo.IsArchived {
  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 || repo.IsArchived {
  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 := db.Insert(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. Config: &repo_model.ProjectsConfig{
  473. ProjectsMode: repo_model.ProjectsMode(form.ProjectsMode),
  474. },
  475. })
  476. } else if !unit_model.TypeProjects.UnitGlobalDisabled() {
  477. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects)
  478. }
  479. if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() {
  480. units = append(units, repo_model.RepoUnit{
  481. RepoID: repo.ID,
  482. Type: unit_model.TypeReleases,
  483. })
  484. } else if !unit_model.TypeReleases.UnitGlobalDisabled() {
  485. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases)
  486. }
  487. if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() {
  488. units = append(units, repo_model.RepoUnit{
  489. RepoID: repo.ID,
  490. Type: unit_model.TypePackages,
  491. })
  492. } else if !unit_model.TypePackages.UnitGlobalDisabled() {
  493. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages)
  494. }
  495. if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() {
  496. units = append(units, repo_model.RepoUnit{
  497. RepoID: repo.ID,
  498. Type: unit_model.TypeActions,
  499. })
  500. } else if !unit_model.TypeActions.UnitGlobalDisabled() {
  501. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions)
  502. }
  503. if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() {
  504. units = append(units, repo_model.RepoUnit{
  505. RepoID: repo.ID,
  506. Type: unit_model.TypePullRequests,
  507. Config: &repo_model.PullRequestsConfig{
  508. IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace,
  509. AllowMerge: form.PullsAllowMerge,
  510. AllowRebase: form.PullsAllowRebase,
  511. AllowRebaseMerge: form.PullsAllowRebaseMerge,
  512. AllowSquash: form.PullsAllowSquash,
  513. AllowFastForwardOnly: form.PullsAllowFastForwardOnly,
  514. AllowManualMerge: form.PullsAllowManualMerge,
  515. AutodetectManualMerge: form.EnableAutodetectManualMerge,
  516. AllowRebaseUpdate: form.PullsAllowRebaseUpdate,
  517. DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge,
  518. DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle),
  519. DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit,
  520. },
  521. })
  522. } else if !unit_model.TypePullRequests.UnitGlobalDisabled() {
  523. deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests)
  524. }
  525. if len(units) == 0 {
  526. ctx.Flash.Error(ctx.Tr("repo.settings.update_settings_no_unit"))
  527. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  528. return
  529. }
  530. if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
  531. ctx.ServerError("UpdateRepositoryUnits", err)
  532. return
  533. }
  534. if repoChanged {
  535. if err := repo_service.UpdateRepository(ctx, repo, false); err != nil {
  536. ctx.ServerError("UpdateRepository", err)
  537. return
  538. }
  539. }
  540. log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  541. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  542. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  543. case "signing":
  544. changed := false
  545. trustModel := repo_model.ToTrustModel(form.TrustModel)
  546. if trustModel != repo.TrustModel {
  547. repo.TrustModel = trustModel
  548. changed = true
  549. }
  550. if changed {
  551. if err := repo_service.UpdateRepository(ctx, repo, false); err != nil {
  552. ctx.ServerError("UpdateRepository", err)
  553. return
  554. }
  555. }
  556. log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  557. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  558. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  559. case "admin":
  560. if !ctx.Doer.IsAdmin {
  561. ctx.Error(http.StatusForbidden)
  562. return
  563. }
  564. if repo.IsFsckEnabled != form.EnableHealthCheck {
  565. repo.IsFsckEnabled = form.EnableHealthCheck
  566. }
  567. if err := repo_service.UpdateRepository(ctx, repo, false); err != nil {
  568. ctx.ServerError("UpdateRepository", err)
  569. return
  570. }
  571. log.Trace("Repository admin settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  572. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  573. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  574. case "admin_index":
  575. if !ctx.Doer.IsAdmin {
  576. ctx.Error(http.StatusForbidden)
  577. return
  578. }
  579. switch form.RequestReindexType {
  580. case "stats":
  581. if err := stats.UpdateRepoIndexer(ctx.Repo.Repository); err != nil {
  582. ctx.ServerError("UpdateStatsRepondexer", err)
  583. return
  584. }
  585. case "code":
  586. if !setting.Indexer.RepoIndexerEnabled {
  587. ctx.Error(http.StatusForbidden)
  588. return
  589. }
  590. code.UpdateRepoIndexer(ctx.Repo.Repository)
  591. default:
  592. ctx.NotFound("", nil)
  593. return
  594. }
  595. log.Trace("Repository reindex for %s requested: %s/%s", form.RequestReindexType, ctx.Repo.Owner.Name, repo.Name)
  596. ctx.Flash.Success(ctx.Tr("repo.settings.reindex_requested"))
  597. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  598. case "convert":
  599. if !ctx.Repo.IsOwner() {
  600. ctx.Error(http.StatusNotFound)
  601. return
  602. }
  603. if repo.Name != form.RepoName {
  604. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  605. return
  606. }
  607. if !repo.IsMirror {
  608. ctx.Error(http.StatusNotFound)
  609. return
  610. }
  611. repo.IsMirror = false
  612. if _, err := repo_service.CleanUpMigrateInfo(ctx, repo); err != nil {
  613. ctx.ServerError("CleanUpMigrateInfo", err)
  614. return
  615. } else if err = repo_model.DeleteMirrorByRepoID(ctx, ctx.Repo.Repository.ID); err != nil {
  616. ctx.ServerError("DeleteMirrorByRepoID", err)
  617. return
  618. }
  619. log.Trace("Repository converted from mirror to regular: %s", repo.FullName())
  620. ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed"))
  621. ctx.Redirect(repo.Link())
  622. case "convert_fork":
  623. if !ctx.Repo.IsOwner() {
  624. ctx.Error(http.StatusNotFound)
  625. return
  626. }
  627. if err := repo.LoadOwner(ctx); err != nil {
  628. ctx.ServerError("Convert Fork", err)
  629. return
  630. }
  631. if repo.Name != form.RepoName {
  632. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  633. return
  634. }
  635. if !repo.IsFork {
  636. ctx.Error(http.StatusNotFound)
  637. return
  638. }
  639. if !ctx.Repo.Owner.CanCreateRepo() {
  640. maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit()
  641. msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
  642. ctx.Flash.Error(msg)
  643. ctx.Redirect(repo.Link() + "/settings")
  644. return
  645. }
  646. if err := repo_service.ConvertForkToNormalRepository(ctx, repo); err != nil {
  647. log.Error("Unable to convert repository %-v from fork. Error: %v", repo, err)
  648. ctx.ServerError("Convert Fork", err)
  649. return
  650. }
  651. log.Trace("Repository converted from fork to regular: %s", repo.FullName())
  652. ctx.Flash.Success(ctx.Tr("repo.settings.convert_fork_succeed"))
  653. ctx.Redirect(repo.Link())
  654. case "transfer":
  655. if !ctx.Repo.IsOwner() {
  656. ctx.Error(http.StatusNotFound)
  657. return
  658. }
  659. if repo.Name != form.RepoName {
  660. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  661. return
  662. }
  663. newOwner, err := user_model.GetUserByName(ctx, ctx.FormString("new_owner_name"))
  664. if err != nil {
  665. if user_model.IsErrUserNotExist(err) {
  666. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil)
  667. return
  668. }
  669. ctx.ServerError("IsUserExist", err)
  670. return
  671. }
  672. if newOwner.Type == user_model.UserTypeOrganization {
  673. if !ctx.Doer.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !organization.OrgFromUser(newOwner).HasMemberWithUserID(ctx, ctx.Doer.ID) {
  674. // The user shouldn't know about this organization
  675. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil)
  676. return
  677. }
  678. }
  679. // Close the GitRepo if open
  680. if ctx.Repo.GitRepo != nil {
  681. ctx.Repo.GitRepo.Close()
  682. ctx.Repo.GitRepo = nil
  683. }
  684. if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, repo, nil); err != nil {
  685. if repo_model.IsErrRepoAlreadyExist(err) {
  686. ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil)
  687. } else if models.IsErrRepoTransferInProgress(err) {
  688. ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil)
  689. } else if errors.Is(err, user_model.ErrBlockedUser) {
  690. ctx.RenderWithErr(ctx.Tr("repo.settings.transfer.blocked_user"), tplSettingsOptions, nil)
  691. } else {
  692. ctx.ServerError("TransferOwnership", err)
  693. }
  694. return
  695. }
  696. log.Trace("Repository transfer process was started: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newOwner)
  697. ctx.Flash.Success(ctx.Tr("repo.settings.transfer_started", newOwner.DisplayName()))
  698. ctx.Redirect(repo.Link() + "/settings")
  699. case "cancel_transfer":
  700. if !ctx.Repo.IsOwner() {
  701. ctx.Error(http.StatusNotFound)
  702. return
  703. }
  704. repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
  705. if err != nil {
  706. if models.IsErrNoPendingTransfer(err) {
  707. ctx.Flash.Error("repo.settings.transfer_abort_invalid")
  708. ctx.Redirect(repo.Link() + "/settings")
  709. } else {
  710. ctx.ServerError("GetPendingRepositoryTransfer", err)
  711. }
  712. return
  713. }
  714. if err := repoTransfer.LoadAttributes(ctx); err != nil {
  715. ctx.ServerError("LoadRecipient", err)
  716. return
  717. }
  718. if err := repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil {
  719. ctx.ServerError("CancelRepositoryTransfer", err)
  720. return
  721. }
  722. log.Trace("Repository transfer process was cancelled: %s/%s ", ctx.Repo.Owner.Name, repo.Name)
  723. ctx.Flash.Success(ctx.Tr("repo.settings.transfer_abort_success", repoTransfer.Recipient.Name))
  724. ctx.Redirect(repo.Link() + "/settings")
  725. case "delete":
  726. if !ctx.Repo.IsOwner() {
  727. ctx.Error(http.StatusNotFound)
  728. return
  729. }
  730. if repo.Name != form.RepoName {
  731. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  732. return
  733. }
  734. // Close the gitrepository before doing this.
  735. if ctx.Repo.GitRepo != nil {
  736. ctx.Repo.GitRepo.Close()
  737. }
  738. if err := repo_service.DeleteRepository(ctx, ctx.Doer, ctx.Repo.Repository, true); err != nil {
  739. ctx.ServerError("DeleteRepository", err)
  740. return
  741. }
  742. log.Trace("Repository deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  743. ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success"))
  744. ctx.Redirect(ctx.Repo.Owner.DashboardLink())
  745. case "delete-wiki":
  746. if !ctx.Repo.IsOwner() {
  747. ctx.Error(http.StatusNotFound)
  748. return
  749. }
  750. if repo.Name != form.RepoName {
  751. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  752. return
  753. }
  754. err := wiki_service.DeleteWiki(ctx, repo)
  755. if err != nil {
  756. log.Error("Delete Wiki: %v", err.Error())
  757. }
  758. log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  759. ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success"))
  760. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  761. case "archive":
  762. if !ctx.Repo.IsOwner() {
  763. ctx.Error(http.StatusForbidden)
  764. return
  765. }
  766. if repo.IsMirror {
  767. ctx.Flash.Error(ctx.Tr("repo.settings.archive.error_ismirror"))
  768. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  769. return
  770. }
  771. if err := repo_model.SetArchiveRepoState(ctx, repo, true); err != nil {
  772. log.Error("Tried to archive a repo: %s", err)
  773. ctx.Flash.Error(ctx.Tr("repo.settings.archive.error"))
  774. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  775. return
  776. }
  777. ctx.Flash.Success(ctx.Tr("repo.settings.archive.success"))
  778. log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  779. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  780. case "unarchive":
  781. if !ctx.Repo.IsOwner() {
  782. ctx.Error(http.StatusForbidden)
  783. return
  784. }
  785. if err := repo_model.SetArchiveRepoState(ctx, repo, false); err != nil {
  786. log.Error("Tried to unarchive a repo: %s", err)
  787. ctx.Flash.Error(ctx.Tr("repo.settings.unarchive.error"))
  788. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  789. return
  790. }
  791. ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))
  792. log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  793. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  794. default:
  795. ctx.NotFound("", nil)
  796. }
  797. }
  798. func handleSettingRemoteAddrError(ctx *context.Context, err error, form *forms.RepoSettingForm) {
  799. if models.IsErrInvalidCloneAddr(err) {
  800. addrErr := err.(*models.ErrInvalidCloneAddr)
  801. switch {
  802. case addrErr.IsProtocolInvalid:
  803. ctx.RenderWithErr(ctx.Tr("repo.mirror_address_protocol_invalid"), tplSettingsOptions, form)
  804. case addrErr.IsURLError:
  805. ctx.RenderWithErr(ctx.Tr("form.url_error", addrErr.Host), tplSettingsOptions, form)
  806. case addrErr.IsPermissionDenied:
  807. if addrErr.LocalPath {
  808. ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tplSettingsOptions, form)
  809. } else {
  810. ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied_blocked"), tplSettingsOptions, form)
  811. }
  812. case addrErr.IsInvalidPath:
  813. ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tplSettingsOptions, form)
  814. default:
  815. ctx.ServerError("Unknown error", err)
  816. }
  817. return
  818. }
  819. ctx.RenderWithErr(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, form)
  820. }
  821. func selectPushMirrorByForm(ctx *context.Context, form *forms.RepoSettingForm, repo *repo_model.Repository) (*repo_model.PushMirror, error) {
  822. id, err := strconv.ParseInt(form.PushMirrorID, 10, 64)
  823. if err != nil {
  824. return nil, err
  825. }
  826. pushMirrors, _, err := repo_model.GetPushMirrorsByRepoID(ctx, repo.ID, db.ListOptions{})
  827. if err != nil {
  828. return nil, err
  829. }
  830. for _, m := range pushMirrors {
  831. if m.ID == id {
  832. m.Repo = repo
  833. return m, nil
  834. }
  835. }
  836. return nil, fmt.Errorf("PushMirror[%v] not associated to repository %v", id, repo)
  837. }