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

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