本站源代码
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.

887 lines
26KB

  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package repo
  6. import (
  7. "errors"
  8. "fmt"
  9. "io/ioutil"
  10. "net/url"
  11. "regexp"
  12. "strings"
  13. "time"
  14. "code.gitea.io/gitea/models"
  15. "code.gitea.io/gitea/modules/auth"
  16. "code.gitea.io/gitea/modules/base"
  17. "code.gitea.io/gitea/modules/context"
  18. "code.gitea.io/gitea/modules/git"
  19. "code.gitea.io/gitea/modules/log"
  20. "code.gitea.io/gitea/modules/setting"
  21. "code.gitea.io/gitea/modules/timeutil"
  22. "code.gitea.io/gitea/modules/validation"
  23. "code.gitea.io/gitea/routers/utils"
  24. "code.gitea.io/gitea/services/mailer"
  25. mirror_service "code.gitea.io/gitea/services/mirror"
  26. repo_service "code.gitea.io/gitea/services/repository"
  27. "github.com/unknwon/com"
  28. "mvdan.cc/xurls/v2"
  29. )
  30. const (
  31. tplSettingsOptions base.TplName = "repo/settings/options"
  32. tplCollaboration base.TplName = "repo/settings/collaboration"
  33. tplBranches base.TplName = "repo/settings/branches"
  34. tplGithooks base.TplName = "repo/settings/githooks"
  35. tplGithookEdit base.TplName = "repo/settings/githook_edit"
  36. tplDeployKeys base.TplName = "repo/settings/deploy_keys"
  37. tplProtectedBranch base.TplName = "repo/settings/protected_branch"
  38. )
  39. var validFormAddress *regexp.Regexp
  40. // Settings show a repository's settings page
  41. func Settings(ctx *context.Context) {
  42. ctx.Data["Title"] = ctx.Tr("repo.settings")
  43. ctx.Data["PageIsSettingsOptions"] = true
  44. ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate
  45. ctx.HTML(200, tplSettingsOptions)
  46. }
  47. // SettingsPost response for changes of a repository
  48. func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
  49. ctx.Data["Title"] = ctx.Tr("repo.settings")
  50. ctx.Data["PageIsSettingsOptions"] = true
  51. repo := ctx.Repo.Repository
  52. switch ctx.Query("action") {
  53. case "update":
  54. if ctx.HasError() {
  55. ctx.HTML(200, tplSettingsOptions)
  56. return
  57. }
  58. isNameChanged := false
  59. oldRepoName := repo.Name
  60. newRepoName := form.RepoName
  61. // Check if repository name has been changed.
  62. if repo.LowerName != strings.ToLower(newRepoName) {
  63. isNameChanged = true
  64. if err := models.ChangeRepositoryName(ctx.Repo.Owner, repo.Name, newRepoName); err != nil {
  65. ctx.Data["Err_RepoName"] = true
  66. switch {
  67. case models.IsErrRepoAlreadyExist(err):
  68. ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplSettingsOptions, &form)
  69. case models.IsErrNameReserved(err):
  70. ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tplSettingsOptions, &form)
  71. case models.IsErrNamePatternNotAllowed(err):
  72. ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
  73. default:
  74. ctx.ServerError("ChangeRepositoryName", err)
  75. }
  76. return
  77. }
  78. err := models.NewRepoRedirect(ctx.Repo.Owner.ID, repo.ID, repo.Name, newRepoName)
  79. if err != nil {
  80. ctx.ServerError("NewRepoRedirect", err)
  81. return
  82. }
  83. log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName)
  84. }
  85. // In case it's just a case change.
  86. repo.Name = newRepoName
  87. repo.LowerName = strings.ToLower(newRepoName)
  88. repo.Description = form.Description
  89. repo.Website = form.Website
  90. repo.Percent = form.Percent
  91. repo.FundingToCollaboration = form.FundingToCollaboration
  92. repo.Point = form.Point
  93. repo.NextPoint = int64(float64(repo.Point) * float64(repo.Percent) * 0.01)
  94. // Visibility of forked repository is forced sync with base repository.
  95. if repo.IsFork {
  96. form.Private = repo.BaseRepo.IsPrivate
  97. }
  98. visibilityChanged := repo.IsPrivate != form.Private
  99. // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public
  100. if visibilityChanged && setting.Repository.ForcePrivate && !form.Private && !ctx.User.IsAdmin {
  101. ctx.ServerError("Force Private enabled", errors.New("cannot change private repository to public"))
  102. return
  103. }
  104. repo.IsPrivate = form.Private
  105. if err := models.UpdateRepository(repo, visibilityChanged); err != nil {
  106. ctx.ServerError("UpdateRepository", err)
  107. return
  108. }
  109. log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  110. if isNameChanged {
  111. if err := models.RenameRepoAction(ctx.User, oldRepoName, repo); err != nil {
  112. log.Error("RenameRepoAction: %v", err)
  113. }
  114. }
  115. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  116. ctx.Redirect(repo.Link() + "/settings")
  117. case "mirror":
  118. if !repo.IsMirror {
  119. ctx.NotFound("", nil)
  120. return
  121. }
  122. // This section doesn't require repo_name/RepoName to be set in the form, don't show it
  123. // as an error on the UI for this action
  124. ctx.Data["Err_RepoName"] = nil
  125. interval, err := time.ParseDuration(form.Interval)
  126. if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) {
  127. ctx.Data["Err_Interval"] = true
  128. ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
  129. } else {
  130. ctx.Repo.Mirror.EnablePrune = form.EnablePrune
  131. ctx.Repo.Mirror.Interval = interval
  132. if interval != 0 {
  133. ctx.Repo.Mirror.NextUpdateUnix = timeutil.TimeStampNow().AddDuration(interval)
  134. } else {
  135. ctx.Repo.Mirror.NextUpdateUnix = 0
  136. }
  137. if err := models.UpdateMirror(ctx.Repo.Mirror); err != nil {
  138. ctx.Data["Err_Interval"] = true
  139. ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
  140. return
  141. }
  142. }
  143. // Validate the form.MirrorAddress
  144. u, err := url.Parse(form.MirrorAddress)
  145. if err != nil {
  146. ctx.Data["Err_MirrorAddress"] = true
  147. ctx.RenderWithErr(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, &form)
  148. return
  149. }
  150. if u.Opaque != "" || !(u.Scheme == "http" || u.Scheme == "https" || u.Scheme == "git") {
  151. ctx.Data["Err_MirrorAddress"] = true
  152. ctx.RenderWithErr(ctx.Tr("repo.mirror_address_protocol_invalid"), tplSettingsOptions, &form)
  153. return
  154. }
  155. if form.MirrorUsername != "" || form.MirrorPassword != "" {
  156. u.User = url.UserPassword(form.MirrorUsername, form.MirrorPassword)
  157. }
  158. // Now use xurls
  159. address := validFormAddress.FindString(form.MirrorAddress)
  160. if address != form.MirrorAddress && form.MirrorAddress != "" {
  161. ctx.Data["Err_MirrorAddress"] = true
  162. ctx.RenderWithErr(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, &form)
  163. return
  164. }
  165. if u.EscapedPath() == "" || u.Host == "" || !u.IsAbs() {
  166. ctx.Data["Err_MirrorAddress"] = true
  167. ctx.RenderWithErr(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, &form)
  168. return
  169. }
  170. address = u.String()
  171. if err := mirror_service.SaveAddress(ctx.Repo.Mirror, address); err != nil {
  172. ctx.ServerError("SaveAddress", err)
  173. return
  174. }
  175. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  176. ctx.Redirect(repo.Link() + "/settings")
  177. case "mirror-sync":
  178. if !repo.IsMirror {
  179. ctx.NotFound("", nil)
  180. return
  181. }
  182. mirror_service.StartToMirror(repo.ID)
  183. ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress"))
  184. ctx.Redirect(repo.Link() + "/settings")
  185. case "advanced":
  186. var units []models.RepoUnit
  187. // This section doesn't require repo_name/RepoName to be set in the form, don't show it
  188. // as an error on the UI for this action
  189. ctx.Data["Err_RepoName"] = nil
  190. for _, tp := range models.MustRepoUnits {
  191. units = append(units, models.RepoUnit{
  192. RepoID: repo.ID,
  193. Type: tp,
  194. Config: new(models.UnitConfig),
  195. })
  196. }
  197. if form.EnableWiki {
  198. if form.EnableExternalWiki {
  199. if !validation.IsValidExternalURL(form.ExternalWikiURL) {
  200. ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error"))
  201. ctx.Redirect(repo.Link() + "/settings")
  202. return
  203. }
  204. units = append(units, models.RepoUnit{
  205. RepoID: repo.ID,
  206. Type: models.UnitTypeExternalWiki,
  207. Config: &models.ExternalWikiConfig{
  208. ExternalWikiURL: form.ExternalWikiURL,
  209. },
  210. })
  211. } else {
  212. units = append(units, models.RepoUnit{
  213. RepoID: repo.ID,
  214. Type: models.UnitTypeWiki,
  215. Config: new(models.UnitConfig),
  216. })
  217. }
  218. }
  219. if form.EnableIssues {
  220. if form.EnableExternalTracker {
  221. if !validation.IsValidExternalURL(form.ExternalTrackerURL) {
  222. ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error"))
  223. ctx.Redirect(repo.Link() + "/settings")
  224. return
  225. }
  226. if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(form.TrackerURLFormat) {
  227. ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error"))
  228. ctx.Redirect(repo.Link() + "/settings")
  229. return
  230. }
  231. units = append(units, models.RepoUnit{
  232. RepoID: repo.ID,
  233. Type: models.UnitTypeExternalTracker,
  234. Config: &models.ExternalTrackerConfig{
  235. ExternalTrackerURL: form.ExternalTrackerURL,
  236. ExternalTrackerFormat: form.TrackerURLFormat,
  237. ExternalTrackerStyle: form.TrackerIssueStyle,
  238. },
  239. })
  240. } else {
  241. units = append(units, models.RepoUnit{
  242. RepoID: repo.ID,
  243. Type: models.UnitTypeIssues,
  244. Config: &models.IssuesConfig{
  245. EnableTimetracker: form.EnableTimetracker,
  246. AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
  247. EnableDependencies: form.EnableIssueDependencies,
  248. },
  249. })
  250. }
  251. }
  252. if form.EnablePulls {
  253. units = append(units, models.RepoUnit{
  254. RepoID: repo.ID,
  255. Type: models.UnitTypePullRequests,
  256. Config: &models.PullRequestsConfig{
  257. IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace,
  258. AllowMerge: form.PullsAllowMerge,
  259. AllowRebase: form.PullsAllowRebase,
  260. AllowRebaseMerge: form.PullsAllowRebaseMerge,
  261. AllowSquash: form.PullsAllowSquash,
  262. },
  263. })
  264. }
  265. if err := models.UpdateRepositoryUnits(repo, units); err != nil {
  266. ctx.ServerError("UpdateRepositoryUnits", err)
  267. return
  268. }
  269. log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  270. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  271. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  272. case "admin":
  273. if !ctx.User.IsAdmin {
  274. ctx.Error(403)
  275. return
  276. }
  277. if repo.IsFsckEnabled != form.EnableHealthCheck {
  278. repo.IsFsckEnabled = form.EnableHealthCheck
  279. }
  280. if repo.CloseIssuesViaCommitInAnyBranch != form.EnableCloseIssuesViaCommitInAnyBranch {
  281. repo.CloseIssuesViaCommitInAnyBranch = form.EnableCloseIssuesViaCommitInAnyBranch
  282. }
  283. if err := models.UpdateRepository(repo, false); err != nil {
  284. ctx.ServerError("UpdateRepository", err)
  285. return
  286. }
  287. log.Trace("Repository admin settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  288. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  289. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  290. case "convert":
  291. if !ctx.Repo.IsOwner() {
  292. ctx.Error(404)
  293. return
  294. }
  295. if repo.Name != form.RepoName {
  296. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  297. return
  298. }
  299. if !repo.IsMirror {
  300. ctx.Error(404)
  301. return
  302. }
  303. repo.IsMirror = false
  304. if _, err := models.CleanUpMigrateInfo(repo); err != nil {
  305. ctx.ServerError("CleanUpMigrateInfo", err)
  306. return
  307. } else if err = models.DeleteMirrorByRepoID(ctx.Repo.Repository.ID); err != nil {
  308. ctx.ServerError("DeleteMirrorByRepoID", err)
  309. return
  310. }
  311. log.Trace("Repository converted from mirror to regular: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  312. ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed"))
  313. ctx.Redirect(setting.AppSubURL + "/" + ctx.Repo.Owner.Name + "/" + repo.Name)
  314. case "transfer":
  315. if !ctx.Repo.IsOwner() {
  316. ctx.Error(404)
  317. return
  318. }
  319. if repo.Name != form.RepoName {
  320. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  321. return
  322. }
  323. newOwner := ctx.Query("new_owner_name")
  324. isExist, err := models.IsUserExist(0, newOwner)
  325. if err != nil {
  326. ctx.ServerError("IsUserExist", err)
  327. return
  328. } else if !isExist {
  329. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil)
  330. return
  331. }
  332. oldOwnerID := ctx.Repo.Owner.ID
  333. if err = models.TransferOwnership(ctx.User, newOwner, repo); err != nil {
  334. if models.IsErrRepoAlreadyExist(err) {
  335. ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil)
  336. } else {
  337. ctx.ServerError("TransferOwnership", err)
  338. }
  339. return
  340. }
  341. err = models.NewRepoRedirect(oldOwnerID, repo.ID, repo.Name, repo.Name)
  342. if err != nil {
  343. ctx.ServerError("NewRepoRedirect", err)
  344. return
  345. }
  346. log.Trace("Repository transferred: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newOwner)
  347. ctx.Flash.Success(ctx.Tr("repo.settings.transfer_succeed"))
  348. ctx.Redirect(setting.AppSubURL + "/" + newOwner + "/" + repo.Name)
  349. case "delete":
  350. if !ctx.Repo.IsOwner() {
  351. ctx.Error(404)
  352. return
  353. }
  354. if repo.Name != form.RepoName {
  355. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  356. return
  357. }
  358. if err := repo_service.DeleteRepository(ctx.User, ctx.Repo.Repository); err != nil {
  359. ctx.ServerError("DeleteRepository", err)
  360. return
  361. }
  362. log.Trace("Repository deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  363. ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success"))
  364. ctx.Redirect(ctx.Repo.Owner.DashboardLink())
  365. case "delete-wiki":
  366. if !ctx.Repo.IsOwner() {
  367. ctx.Error(404)
  368. return
  369. }
  370. if repo.Name != form.RepoName {
  371. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  372. return
  373. }
  374. err := repo.DeleteWiki()
  375. if err != nil {
  376. log.Error("Delete Wiki: %v", err.Error())
  377. }
  378. log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  379. ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success"))
  380. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  381. case "archive":
  382. if !ctx.Repo.IsOwner() {
  383. ctx.Error(403)
  384. return
  385. }
  386. if repo.IsMirror {
  387. ctx.Flash.Error(ctx.Tr("repo.settings.archive.error_ismirror"))
  388. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  389. return
  390. }
  391. if err := repo.SetArchiveRepoState(true); err != nil {
  392. log.Error("Tried to archive a repo: %s", err)
  393. ctx.Flash.Error(ctx.Tr("repo.settings.archive.error"))
  394. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  395. return
  396. }
  397. ctx.Flash.Success(ctx.Tr("repo.settings.archive.success"))
  398. log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  399. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  400. case "unarchive":
  401. if !ctx.Repo.IsOwner() {
  402. ctx.Error(403)
  403. return
  404. }
  405. if err := repo.SetArchiveRepoState(false); err != nil {
  406. log.Error("Tried to unarchive a repo: %s", err)
  407. ctx.Flash.Error(ctx.Tr("repo.settings.unarchive.error"))
  408. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  409. return
  410. }
  411. ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))
  412. log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  413. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  414. default:
  415. ctx.NotFound("", nil)
  416. }
  417. }
  418. // Collaboration render a repository's collaboration page
  419. func Collaboration(ctx *context.Context) {
  420. ctx.Data["Title"] = ctx.Tr("repo.settings")
  421. ctx.Data["PageIsSettingsCollaboration"] = true
  422. users, err := ctx.Repo.Repository.GetCollaborators()
  423. if err != nil {
  424. ctx.ServerError("GetCollaborators", err)
  425. return
  426. }
  427. ctx.Data["Collaborators"] = users
  428. teams, err := ctx.Repo.Repository.GetRepoTeams()
  429. if err != nil {
  430. ctx.ServerError("GetRepoTeams", err)
  431. return
  432. }
  433. ctx.Data["Teams"] = teams
  434. ctx.Data["Repo"] = ctx.Repo.Repository
  435. ctx.Data["OrgID"] = ctx.Repo.Repository.OwnerID
  436. ctx.Data["OrgName"] = ctx.Repo.Repository.OwnerName
  437. ctx.Data["Org"] = ctx.Repo.Repository.Owner
  438. ctx.Data["Units"] = models.Units
  439. ctx.HTML(200, tplCollaboration)
  440. }
  441. // CollaborationPost response for actions for a collaboration of a repository
  442. func CollaborationPost(ctx *context.Context) {
  443. name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.Query("collaborator")))
  444. if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
  445. ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path)
  446. return
  447. }
  448. u, err := models.GetUserByName(name)
  449. if err != nil {
  450. if models.IsErrUserNotExist(err) {
  451. ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
  452. ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path)
  453. } else {
  454. ctx.ServerError("GetUserByName", err)
  455. }
  456. return
  457. }
  458. if !u.IsActive {
  459. ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_inactive_user"))
  460. ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path)
  461. return
  462. }
  463. // Organization is not allowed to be added as a collaborator.
  464. if u.IsOrganization() {
  465. ctx.Flash.Error(ctx.Tr("repo.settings.org_not_allowed_to_be_collaborator"))
  466. ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path)
  467. return
  468. }
  469. if got, err := ctx.Repo.Repository.IsCollaborator(u.ID); err == nil && got {
  470. ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_duplicate"))
  471. ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
  472. return
  473. }
  474. if err = ctx.Repo.Repository.AddCollaborator(u); err != nil {
  475. ctx.ServerError("AddCollaborator", err)
  476. return
  477. }
  478. if setting.Service.EnableNotifyMail {
  479. mailer.SendCollaboratorMail(u, ctx.User, ctx.Repo.Repository)
  480. }
  481. ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success"))
  482. ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path)
  483. }
  484. // ChangeCollaborationAccessMode response for changing access of a collaboration
  485. func ChangeCollaborationAccessMode(ctx *context.Context) {
  486. if err := ctx.Repo.Repository.ChangeCollaborationAccessMode(
  487. ctx.QueryInt64("uid"),
  488. models.AccessMode(ctx.QueryInt("mode"))); err != nil {
  489. log.Error("ChangeCollaborationAccessMode: %v", err)
  490. }
  491. }
  492. // DeleteCollaboration delete a collaboration for a repository
  493. func DeleteCollaboration(ctx *context.Context) {
  494. if err := ctx.Repo.Repository.DeleteCollaboration(ctx.QueryInt64("id")); err != nil {
  495. ctx.Flash.Error("DeleteCollaboration: " + err.Error())
  496. } else {
  497. ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success"))
  498. }
  499. ctx.JSON(200, map[string]interface{}{
  500. "redirect": ctx.Repo.RepoLink + "/settings/collaboration",
  501. })
  502. }
  503. // AddTeamPost response for adding a team to a repository
  504. func AddTeamPost(ctx *context.Context) {
  505. if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
  506. ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed"))
  507. ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
  508. return
  509. }
  510. name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.Query("team")))
  511. if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
  512. ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
  513. return
  514. }
  515. team, err := ctx.Repo.Owner.GetTeam(name)
  516. if err != nil {
  517. if models.IsErrTeamNotExist(err) {
  518. ctx.Flash.Error(ctx.Tr("form.team_not_exist"))
  519. ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
  520. } else {
  521. ctx.ServerError("GetTeam", err)
  522. }
  523. return
  524. }
  525. if team.OrgID != ctx.Repo.Repository.OwnerID {
  526. ctx.Flash.Error(ctx.Tr("repo.settings.team_not_in_organization"))
  527. ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
  528. return
  529. }
  530. if models.HasTeamRepo(ctx.Repo.Repository.OwnerID, team.ID, ctx.Repo.Repository.ID) {
  531. ctx.Flash.Error(ctx.Tr("repo.settings.add_team_duplicate"))
  532. ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
  533. return
  534. }
  535. if err = team.AddRepository(ctx.Repo.Repository); err != nil {
  536. ctx.ServerError("team.AddRepository", err)
  537. return
  538. }
  539. ctx.Flash.Success(ctx.Tr("repo.settings.add_team_success"))
  540. ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
  541. }
  542. // DeleteTeam response for deleting a team from a repository
  543. func DeleteTeam(ctx *context.Context) {
  544. if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
  545. ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed"))
  546. ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
  547. return
  548. }
  549. team, err := models.GetTeamByID(ctx.QueryInt64("id"))
  550. if err != nil {
  551. ctx.ServerError("GetTeamByID", err)
  552. return
  553. }
  554. if err = team.RemoveRepository(ctx.Repo.Repository.ID); err != nil {
  555. ctx.ServerError("team.RemoveRepositorys", err)
  556. return
  557. }
  558. ctx.Flash.Success(ctx.Tr("repo.settings.remove_team_success"))
  559. ctx.JSON(200, map[string]interface{}{
  560. "redirect": ctx.Repo.RepoLink + "/settings/collaboration",
  561. })
  562. }
  563. // parseOwnerAndRepo get repos by owner
  564. func parseOwnerAndRepo(ctx *context.Context) (*models.User, *models.Repository) {
  565. owner, err := models.GetUserByName(ctx.Params(":username"))
  566. if err != nil {
  567. if models.IsErrUserNotExist(err) {
  568. ctx.NotFound("GetUserByName", err)
  569. } else {
  570. ctx.ServerError("GetUserByName", err)
  571. }
  572. return nil, nil
  573. }
  574. repo, err := models.GetRepositoryByName(owner.ID, ctx.Params(":reponame"))
  575. if err != nil {
  576. if models.IsErrRepoNotExist(err) {
  577. ctx.NotFound("GetRepositoryByName", err)
  578. } else {
  579. ctx.ServerError("GetRepositoryByName", err)
  580. }
  581. return nil, nil
  582. }
  583. return owner, repo
  584. }
  585. // GitHooks hooks of a repository
  586. func GitHooks(ctx *context.Context) {
  587. ctx.Data["Title"] = ctx.Tr("repo.settings.githooks")
  588. ctx.Data["PageIsSettingsGitHooks"] = true
  589. hooks, err := ctx.Repo.GitRepo.Hooks()
  590. if err != nil {
  591. ctx.ServerError("Hooks", err)
  592. return
  593. }
  594. ctx.Data["Hooks"] = hooks
  595. ctx.HTML(200, tplGithooks)
  596. }
  597. // GitHooksEdit render for editing a hook of repository page
  598. func GitHooksEdit(ctx *context.Context) {
  599. ctx.Data["Title"] = ctx.Tr("repo.settings.githooks")
  600. ctx.Data["PageIsSettingsGitHooks"] = true
  601. name := ctx.Params(":name")
  602. hook, err := ctx.Repo.GitRepo.GetHook(name)
  603. if err != nil {
  604. if err == git.ErrNotValidHook {
  605. ctx.NotFound("GetHook", err)
  606. } else {
  607. ctx.ServerError("GetHook", err)
  608. }
  609. return
  610. }
  611. ctx.Data["Hook"] = hook
  612. ctx.HTML(200, tplGithookEdit)
  613. }
  614. // GitHooksEditPost response for editing a git hook of a repository
  615. func GitHooksEditPost(ctx *context.Context) {
  616. name := ctx.Params(":name")
  617. hook, err := ctx.Repo.GitRepo.GetHook(name)
  618. if err != nil {
  619. if err == git.ErrNotValidHook {
  620. ctx.NotFound("GetHook", err)
  621. } else {
  622. ctx.ServerError("GetHook", err)
  623. }
  624. return
  625. }
  626. hook.Content = ctx.Query("content")
  627. if err = hook.Update(); err != nil {
  628. ctx.ServerError("hook.Update", err)
  629. return
  630. }
  631. ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks/git")
  632. }
  633. // DeployKeys render the deploy keys list of a repository page
  634. func DeployKeys(ctx *context.Context) {
  635. ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys")
  636. ctx.Data["PageIsSettingsKeys"] = true
  637. ctx.Data["DisableSSH"] = setting.SSH.Disabled
  638. keys, err := models.ListDeployKeys(ctx.Repo.Repository.ID)
  639. if err != nil {
  640. ctx.ServerError("ListDeployKeys", err)
  641. return
  642. }
  643. ctx.Data["Deploykeys"] = keys
  644. ctx.HTML(200, tplDeployKeys)
  645. }
  646. // DeployKeysPost response for adding a deploy key of a repository
  647. func DeployKeysPost(ctx *context.Context, form auth.AddKeyForm) {
  648. ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys")
  649. ctx.Data["PageIsSettingsKeys"] = true
  650. keys, err := models.ListDeployKeys(ctx.Repo.Repository.ID)
  651. if err != nil {
  652. ctx.ServerError("ListDeployKeys", err)
  653. return
  654. }
  655. ctx.Data["Deploykeys"] = keys
  656. if ctx.HasError() {
  657. ctx.HTML(200, tplDeployKeys)
  658. return
  659. }
  660. content, err := models.CheckPublicKeyString(form.Content)
  661. if err != nil {
  662. if models.IsErrSSHDisabled(err) {
  663. ctx.Flash.Info(ctx.Tr("settings.ssh_disabled"))
  664. } else if models.IsErrKeyUnableVerify(err) {
  665. ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key"))
  666. } else {
  667. ctx.Data["HasError"] = true
  668. ctx.Data["Err_Content"] = true
  669. ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error()))
  670. }
  671. ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys")
  672. return
  673. }
  674. key, err := models.AddDeployKey(ctx.Repo.Repository.ID, form.Title, content, !form.IsWritable)
  675. if err != nil {
  676. ctx.Data["HasError"] = true
  677. switch {
  678. case models.IsErrDeployKeyAlreadyExist(err):
  679. ctx.Data["Err_Content"] = true
  680. ctx.RenderWithErr(ctx.Tr("repo.settings.key_been_used"), tplDeployKeys, &form)
  681. case models.IsErrKeyAlreadyExist(err):
  682. ctx.Data["Err_Content"] = true
  683. ctx.RenderWithErr(ctx.Tr("settings.ssh_key_been_used"), tplDeployKeys, &form)
  684. case models.IsErrKeyNameAlreadyUsed(err):
  685. ctx.Data["Err_Title"] = true
  686. ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form)
  687. default:
  688. ctx.ServerError("AddDeployKey", err)
  689. }
  690. return
  691. }
  692. log.Trace("Deploy key added: %d", ctx.Repo.Repository.ID)
  693. ctx.Flash.Success(ctx.Tr("repo.settings.add_key_success", key.Name))
  694. ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys")
  695. }
  696. // DeleteDeployKey response for deleting a deploy key
  697. func DeleteDeployKey(ctx *context.Context) {
  698. if err := models.DeleteDeployKey(ctx.User, ctx.QueryInt64("id")); err != nil {
  699. ctx.Flash.Error("DeleteDeployKey: " + err.Error())
  700. } else {
  701. ctx.Flash.Success(ctx.Tr("repo.settings.deploy_key_deletion_success"))
  702. }
  703. ctx.JSON(200, map[string]interface{}{
  704. "redirect": ctx.Repo.RepoLink + "/settings/keys",
  705. })
  706. }
  707. func init() {
  708. var err error
  709. validFormAddress, err = xurls.StrictMatchingScheme(`(https?)|(git)://`)
  710. if err != nil {
  711. panic(err)
  712. }
  713. }
  714. // UpdateAvatarSetting update repo's avatar
  715. func UpdateAvatarSetting(ctx *context.Context, form auth.AvatarForm) error {
  716. ctxRepo := ctx.Repo.Repository
  717. if form.Avatar == nil {
  718. // No avatar is uploaded and we not removing it here.
  719. // No random avatar generated here.
  720. // Just exit, no action.
  721. if !com.IsFile(ctxRepo.CustomAvatarPath()) {
  722. log.Trace("No avatar was uploaded for repo: %d. Default icon will appear instead.", ctxRepo.ID)
  723. }
  724. return nil
  725. }
  726. r, err := form.Avatar.Open()
  727. if err != nil {
  728. return fmt.Errorf("Avatar.Open: %v", err)
  729. }
  730. defer r.Close()
  731. if form.Avatar.Size > setting.AvatarMaxFileSize {
  732. return errors.New(ctx.Tr("settings.uploaded_avatar_is_too_big"))
  733. }
  734. data, err := ioutil.ReadAll(r)
  735. if err != nil {
  736. return fmt.Errorf("ioutil.ReadAll: %v", err)
  737. }
  738. if !base.IsImageFile(data) {
  739. return errors.New(ctx.Tr("settings.uploaded_avatar_not_a_image"))
  740. }
  741. if err = ctxRepo.UploadAvatar(data); err != nil {
  742. return fmt.Errorf("UploadAvatar: %v", err)
  743. }
  744. return nil
  745. }
  746. // SettingsAvatar save new POSTed repository avatar
  747. func SettingsAvatar(ctx *context.Context, form auth.AvatarForm) {
  748. form.Source = auth.AvatarLocal
  749. if err := UpdateAvatarSetting(ctx, form); err != nil {
  750. ctx.Flash.Error(err.Error())
  751. } else {
  752. ctx.Flash.Success(ctx.Tr("repo.settings.update_avatar_success"))
  753. }
  754. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  755. }
  756. // SettingsDeleteAvatar delete repository avatar
  757. func SettingsDeleteAvatar(ctx *context.Context) {
  758. if err := ctx.Repo.Repository.DeleteAvatar(); err != nil {
  759. ctx.Flash.Error(fmt.Sprintf("DeleteAvatar: %v", err))
  760. }
  761. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  762. }
上海开阖软件有限公司 沪ICP备12045867号-1