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.

591 lines
17 KiB

9 years ago
Feature: Timetracking (#2211) * Added comment's hashtag to url for mail notifications. * Added explanation to return statement + documentation. * Replacing in-line link generation with HTMLURL. (+gofmt) * Replaced action-based model with nil-based model. (+gofmt) * Replaced mailIssueActionToParticipants with mailIssueCommentToParticipants. * Updating comment for mailIssueCommentToParticipants * Added link to comment in "Dashboard" * Deleting feed entry if a comment is going to be deleted * Added migration * Added improved migration to add a CommentID column to action. * Added improved links to comments in feed entries. * Fixes #1956 by filtering for deleted comments that are referenced in actions. * Introducing "IsDeleted" column to action. * Adding design draft (not functional) * Adding database models for stopwatches and trackedtimes * See go-gitea/gitea#967 * Adding design draft (not functional) * Adding translations and improving design * Implementing stopwatch (for timetracking) * Make UI functional * Add hints in timeline for time tracking events * Implementing timetracking feature * Adding "Add time manual" option * Improved stopwatch * Created report of total spent time by user * Only showing total time spent if theire is something to show. * Adding license headers. * Improved error handling for "Add Time Manual" * Adding @sapks 's changes, refactoring * Adding API for feature tracking * Adding unit test * Adding DISABLE/ENABLE option to Repository settings page * Improving translations * Applying @sapk 's changes * Removing repo_unit and using IssuesSetting for disabling/enabling timetracker * Adding DEFAULT_ENABLE_TIMETRACKER to config, installation and admin menu * Improving documentation * Fixing vendor/ folder * Changing timtracking routes by adding subgroups /times and /times/stopwatch (Proposed by @lafriks ) * Restricting write access to timetracking based on the repo settings (Proposed by @lafriks ) * Fixed minor permissions bug. * Adding CanUseTimetracker and IsTimetrackerEnabled in ctx.Repo * Allow assignees and authors to track there time too. * Fixed some build-time-errors + logical errors. * Removing unused Get...ByID functions * Moving IsTimetrackerEnabled from context.Repository to models.Repository * Adding a seperate file for issue related repo functions * Adding license headers * Fixed GetUserByParams return 404 * Moving /users/:username/times to /repos/:username/:reponame/times/:username for security reasons * Adding /repos/:username/times to get all tracked times of the repo * Updating sdk-dependency * Updating swagger.v1.json * Adding warning if user has already a running stopwatch (auto-timetracker) * Replacing GetTrackedTimesBy... with GetTrackedTimes(options FindTrackedTimesOptions) * Changing code.gitea.io/sdk back to code.gitea.io/sdk * Correcting spelling mistake * Updating vendor.json * Changing GET stopwatch/toggle to POST stopwatch/toggle * Changing GET stopwatch/cancel to POST stopwatch/cancel * Added migration for stopwatches/timetracking * Fixed some access bugs for read-only users * Added default allow only contributors to track time value to config * Fixed migration by chaging x.Iterate to x.Find * Resorted imports * Moved Add Time Manually form to repo_form.go * Removed "Seconds" field from Add Time Manually * Resorted imports * Improved permission checking * Fixed some bugs * Added integration test * gofmt * Adding integration test by @lafriks * Added created_unix to comment fixtures * Using last event instead of a fixed event * Adding another integration test by @lafriks * Fixing bug Timetracker enabled causing error 500 at sidebar.tpl * Fixed a refactoring bug that resulted in hiding "HasUserStopwatch" warning. * Returning TrackedTime instead of AddTimeOption at AddTime. * Updating SDK from go-gitea/go-sdk#69 * Resetting Go-SDK back to default repository * Fixing test-vendor by changing ini back to original repository * Adding "tags" to swagger spec * govendor sync * Removed duplicate * Formatting templates * Adding IsTimetrackingEnabled checks to API * Improving translations / english texts * Improving documentation * Updating swagger spec * Fixing integration test caused be translation-changes * Removed encoding issues in local_en-US.ini. * "Added" copyright line * Moved unit.IssuesConfig().EnableTimetracker into a != nil check * Removed some other encoding issues in local_en-US.ini * Improved javascript by checking if data-context exists * Replaced manual comment creation with CreateComment * Removed unnecessary code * Improved error checking * Small cosmetic changes * Replaced int>string>duration parsing with int>duration parsing * Fixed encoding issues * Removed unused imports Signed-off-by: Jonas Franz <info@jonasfranz.software>
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package repo
  5. import (
  6. "strings"
  7. "time"
  8. "code.gitea.io/git"
  9. "code.gitea.io/gitea/models"
  10. "code.gitea.io/gitea/modules/auth"
  11. "code.gitea.io/gitea/modules/base"
  12. "code.gitea.io/gitea/modules/context"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/setting"
  15. )
  16. const (
  17. tplSettingsOptions base.TplName = "repo/settings/options"
  18. tplCollaboration base.TplName = "repo/settings/collaboration"
  19. tplBranches base.TplName = "repo/settings/branches"
  20. tplGithooks base.TplName = "repo/settings/githooks"
  21. tplGithookEdit base.TplName = "repo/settings/githook_edit"
  22. tplDeployKeys base.TplName = "repo/settings/deploy_keys"
  23. tplProtectedBranch base.TplName = "repo/settings/protected_branch"
  24. )
  25. // Settings show a repository's settings page
  26. func Settings(ctx *context.Context) {
  27. ctx.Data["Title"] = ctx.Tr("repo.settings")
  28. ctx.Data["PageIsSettingsOptions"] = true
  29. ctx.HTML(200, tplSettingsOptions)
  30. }
  31. // SettingsPost response for changes of a repository
  32. func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
  33. ctx.Data["Title"] = ctx.Tr("repo.settings")
  34. ctx.Data["PageIsSettingsOptions"] = true
  35. repo := ctx.Repo.Repository
  36. switch ctx.Query("action") {
  37. case "update":
  38. if ctx.HasError() {
  39. ctx.HTML(200, tplSettingsOptions)
  40. return
  41. }
  42. isNameChanged := false
  43. oldRepoName := repo.Name
  44. newRepoName := form.RepoName
  45. // Check if repository name has been changed.
  46. if repo.LowerName != strings.ToLower(newRepoName) {
  47. isNameChanged = true
  48. if err := models.ChangeRepositoryName(ctx.Repo.Owner, repo.Name, newRepoName); err != nil {
  49. ctx.Data["Err_RepoName"] = true
  50. switch {
  51. case models.IsErrRepoAlreadyExist(err):
  52. ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplSettingsOptions, &form)
  53. case models.IsErrNameReserved(err):
  54. ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tplSettingsOptions, &form)
  55. case models.IsErrNamePatternNotAllowed(err):
  56. ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
  57. default:
  58. ctx.Handle(500, "ChangeRepositoryName", err)
  59. }
  60. return
  61. }
  62. err := models.NewRepoRedirect(ctx.Repo.Owner.ID, repo.ID, repo.Name, newRepoName)
  63. if err != nil {
  64. ctx.Handle(500, "NewRepoRedirect", err)
  65. return
  66. }
  67. log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName)
  68. }
  69. // In case it's just a case change.
  70. repo.Name = newRepoName
  71. repo.LowerName = strings.ToLower(newRepoName)
  72. repo.Description = form.Description
  73. repo.Website = form.Website
  74. // Visibility of forked repository is forced sync with base repository.
  75. if repo.IsFork {
  76. form.Private = repo.BaseRepo.IsPrivate
  77. }
  78. visibilityChanged := repo.IsPrivate != form.Private
  79. repo.IsPrivate = form.Private
  80. if err := models.UpdateRepository(repo, visibilityChanged); err != nil {
  81. ctx.Handle(500, "UpdateRepository", err)
  82. return
  83. }
  84. log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  85. if isNameChanged {
  86. if err := models.RenameRepoAction(ctx.User, oldRepoName, repo); err != nil {
  87. log.Error(4, "RenameRepoAction: %v", err)
  88. }
  89. }
  90. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  91. ctx.Redirect(repo.Link() + "/settings")
  92. case "mirror":
  93. if !repo.IsMirror {
  94. ctx.Handle(404, "", nil)
  95. return
  96. }
  97. interval, err := time.ParseDuration(form.Interval)
  98. if err != nil || interval < setting.Mirror.MinInterval {
  99. ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
  100. } else {
  101. ctx.Repo.Mirror.EnablePrune = form.EnablePrune
  102. ctx.Repo.Mirror.Interval = interval
  103. ctx.Repo.Mirror.NextUpdate = time.Now().Add(interval)
  104. if err := models.UpdateMirror(ctx.Repo.Mirror); err != nil {
  105. ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
  106. return
  107. }
  108. }
  109. if err := ctx.Repo.Mirror.SaveAddress(form.MirrorAddress); err != nil {
  110. ctx.Handle(500, "SaveAddress", err)
  111. return
  112. }
  113. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  114. ctx.Redirect(repo.Link() + "/settings")
  115. case "mirror-sync":
  116. if !repo.IsMirror {
  117. ctx.Handle(404, "", nil)
  118. return
  119. }
  120. go models.MirrorQueue.Add(repo.ID)
  121. ctx.Flash.Info(ctx.Tr("repo.settings.mirror_sync_in_progress"))
  122. ctx.Redirect(repo.Link() + "/settings")
  123. case "advanced":
  124. var units []models.RepoUnit
  125. for _, tp := range models.MustRepoUnits {
  126. units = append(units, models.RepoUnit{
  127. RepoID: repo.ID,
  128. Type: tp,
  129. Config: new(models.UnitConfig),
  130. })
  131. }
  132. if form.EnableWiki {
  133. if form.EnableExternalWiki {
  134. if !strings.HasPrefix(form.ExternalWikiURL, "http://") && !strings.HasPrefix(form.ExternalWikiURL, "https://") {
  135. ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error"))
  136. ctx.Redirect(repo.Link() + "/settings")
  137. return
  138. }
  139. units = append(units, models.RepoUnit{
  140. RepoID: repo.ID,
  141. Type: models.UnitTypeExternalWiki,
  142. Config: &models.ExternalWikiConfig{
  143. ExternalWikiURL: form.ExternalWikiURL,
  144. },
  145. })
  146. } else {
  147. units = append(units, models.RepoUnit{
  148. RepoID: repo.ID,
  149. Type: models.UnitTypeWiki,
  150. Config: new(models.UnitConfig),
  151. })
  152. }
  153. }
  154. if form.EnableIssues {
  155. if form.EnableExternalTracker {
  156. if !strings.HasPrefix(form.ExternalTrackerURL, "http://") && !strings.HasPrefix(form.ExternalTrackerURL, "https://") {
  157. ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error"))
  158. ctx.Redirect(repo.Link() + "/settings")
  159. return
  160. }
  161. units = append(units, models.RepoUnit{
  162. RepoID: repo.ID,
  163. Type: models.UnitTypeExternalTracker,
  164. Config: &models.ExternalTrackerConfig{
  165. ExternalTrackerURL: form.ExternalTrackerURL,
  166. ExternalTrackerFormat: form.TrackerURLFormat,
  167. ExternalTrackerStyle: form.TrackerIssueStyle,
  168. },
  169. })
  170. } else {
  171. units = append(units, models.RepoUnit{
  172. RepoID: repo.ID,
  173. Type: models.UnitTypeIssues,
  174. Config: &models.IssuesConfig{
  175. EnableTimetracker: form.EnableTimetracker,
  176. AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
  177. },
  178. })
  179. }
  180. }
  181. if form.EnablePulls {
  182. units = append(units, models.RepoUnit{
  183. RepoID: repo.ID,
  184. Type: models.UnitTypePullRequests,
  185. Config: new(models.UnitConfig),
  186. })
  187. }
  188. if err := models.UpdateRepositoryUnits(repo, units); err != nil {
  189. ctx.Handle(500, "UpdateRepositoryUnits", err)
  190. return
  191. }
  192. log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  193. ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
  194. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  195. case "convert":
  196. if !ctx.Repo.IsOwner() {
  197. ctx.Error(404)
  198. return
  199. }
  200. if repo.Name != form.RepoName {
  201. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  202. return
  203. }
  204. if ctx.Repo.Owner.IsOrganization() {
  205. if !ctx.Repo.Owner.IsOwnedBy(ctx.User.ID) {
  206. ctx.Error(404)
  207. return
  208. }
  209. }
  210. if !repo.IsMirror {
  211. ctx.Error(404)
  212. return
  213. }
  214. repo.IsMirror = false
  215. if _, err := models.CleanUpMigrateInfo(repo); err != nil {
  216. ctx.Handle(500, "CleanUpMigrateInfo", err)
  217. return
  218. } else if err = models.DeleteMirrorByRepoID(ctx.Repo.Repository.ID); err != nil {
  219. ctx.Handle(500, "DeleteMirrorByRepoID", err)
  220. return
  221. }
  222. log.Trace("Repository converted from mirror to regular: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  223. ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed"))
  224. ctx.Redirect(setting.AppSubURL + "/" + ctx.Repo.Owner.Name + "/" + repo.Name)
  225. case "transfer":
  226. if !ctx.Repo.IsOwner() {
  227. ctx.Error(404)
  228. return
  229. }
  230. if repo.Name != form.RepoName {
  231. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  232. return
  233. }
  234. if ctx.Repo.Owner.IsOrganization() {
  235. if !ctx.Repo.Owner.IsOwnedBy(ctx.User.ID) {
  236. ctx.Error(404)
  237. return
  238. }
  239. }
  240. newOwner := ctx.Query("new_owner_name")
  241. isExist, err := models.IsUserExist(0, newOwner)
  242. if err != nil {
  243. ctx.Handle(500, "IsUserExist", err)
  244. return
  245. } else if !isExist {
  246. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil)
  247. return
  248. }
  249. if err = models.TransferOwnership(ctx.User, newOwner, repo); err != nil {
  250. if models.IsErrRepoAlreadyExist(err) {
  251. ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil)
  252. } else {
  253. ctx.Handle(500, "TransferOwnership", err)
  254. }
  255. return
  256. }
  257. log.Trace("Repository transferred: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newOwner)
  258. ctx.Flash.Success(ctx.Tr("repo.settings.transfer_succeed"))
  259. ctx.Redirect(setting.AppSubURL + "/" + newOwner + "/" + repo.Name)
  260. case "delete":
  261. if !ctx.Repo.IsOwner() {
  262. ctx.Error(404)
  263. return
  264. }
  265. if repo.Name != form.RepoName {
  266. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  267. return
  268. }
  269. if ctx.Repo.Owner.IsOrganization() {
  270. if !ctx.Repo.Owner.IsOwnedBy(ctx.User.ID) {
  271. ctx.Error(404)
  272. return
  273. }
  274. }
  275. if err := models.DeleteRepository(ctx.User, ctx.Repo.Owner.ID, repo.ID); err != nil {
  276. ctx.Handle(500, "DeleteRepository", err)
  277. return
  278. }
  279. log.Trace("Repository deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  280. ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success"))
  281. ctx.Redirect(ctx.Repo.Owner.DashboardLink())
  282. case "delete-wiki":
  283. if !ctx.Repo.IsOwner() {
  284. ctx.Error(404)
  285. return
  286. }
  287. if repo.Name != form.RepoName {
  288. ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
  289. return
  290. }
  291. if ctx.Repo.Owner.IsOrganization() {
  292. if !ctx.Repo.Owner.IsOwnedBy(ctx.User.ID) {
  293. ctx.Error(404)
  294. return
  295. }
  296. }
  297. repo.DeleteWiki()
  298. log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name)
  299. ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success"))
  300. ctx.Redirect(ctx.Repo.RepoLink + "/settings")
  301. default:
  302. ctx.Handle(404, "", nil)
  303. }
  304. }
  305. // Collaboration render a repository's collaboration page
  306. func Collaboration(ctx *context.Context) {
  307. ctx.Data["Title"] = ctx.Tr("repo.settings")
  308. ctx.Data["PageIsSettingsCollaboration"] = true
  309. users, err := ctx.Repo.Repository.GetCollaborators()
  310. if err != nil {
  311. ctx.Handle(500, "GetCollaborators", err)
  312. return
  313. }
  314. ctx.Data["Collaborators"] = users
  315. ctx.HTML(200, tplCollaboration)
  316. }
  317. // CollaborationPost response for actions for a collaboration of a repository
  318. func CollaborationPost(ctx *context.Context) {
  319. name := strings.ToLower(ctx.Query("collaborator"))
  320. if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
  321. ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path)
  322. return
  323. }
  324. u, err := models.GetUserByName(name)
  325. if err != nil {
  326. if models.IsErrUserNotExist(err) {
  327. ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
  328. ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path)
  329. } else {
  330. ctx.Handle(500, "GetUserByName", err)
  331. }
  332. return
  333. }
  334. // Organization is not allowed to be added as a collaborator.
  335. if u.IsOrganization() {
  336. ctx.Flash.Error(ctx.Tr("repo.settings.org_not_allowed_to_be_collaborator"))
  337. ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path)
  338. return
  339. }
  340. // Check if user is organization member.
  341. if ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOrgMember(u.ID) {
  342. ctx.Flash.Info(ctx.Tr("repo.settings.user_is_org_member"))
  343. ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
  344. return
  345. }
  346. if err = ctx.Repo.Repository.AddCollaborator(u); err != nil {
  347. ctx.Handle(500, "AddCollaborator", err)
  348. return
  349. }
  350. if setting.Service.EnableNotifyMail {
  351. models.SendCollaboratorMail(u, ctx.User, ctx.Repo.Repository)
  352. }
  353. ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success"))
  354. ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path)
  355. }
  356. // ChangeCollaborationAccessMode response for changing access of a collaboration
  357. func ChangeCollaborationAccessMode(ctx *context.Context) {
  358. if err := ctx.Repo.Repository.ChangeCollaborationAccessMode(
  359. ctx.QueryInt64("uid"),
  360. models.AccessMode(ctx.QueryInt("mode"))); err != nil {
  361. log.Error(4, "ChangeCollaborationAccessMode: %v", err)
  362. }
  363. }
  364. // DeleteCollaboration delete a collaboration for a repository
  365. func DeleteCollaboration(ctx *context.Context) {
  366. if err := ctx.Repo.Repository.DeleteCollaboration(ctx.QueryInt64("id")); err != nil {
  367. ctx.Flash.Error("DeleteCollaboration: " + err.Error())
  368. } else {
  369. ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success"))
  370. }
  371. ctx.JSON(200, map[string]interface{}{
  372. "redirect": ctx.Repo.RepoLink + "/settings/collaboration",
  373. })
  374. }
  375. // parseOwnerAndRepo get repos by owner
  376. func parseOwnerAndRepo(ctx *context.Context) (*models.User, *models.Repository) {
  377. owner, err := models.GetUserByName(ctx.Params(":username"))
  378. if err != nil {
  379. if models.IsErrUserNotExist(err) {
  380. ctx.Handle(404, "GetUserByName", err)
  381. } else {
  382. ctx.Handle(500, "GetUserByName", err)
  383. }
  384. return nil, nil
  385. }
  386. repo, err := models.GetRepositoryByName(owner.ID, ctx.Params(":reponame"))
  387. if err != nil {
  388. if models.IsErrRepoNotExist(err) {
  389. ctx.Handle(404, "GetRepositoryByName", err)
  390. } else {
  391. ctx.Handle(500, "GetRepositoryByName", err)
  392. }
  393. return nil, nil
  394. }
  395. return owner, repo
  396. }
  397. // GitHooks hooks of a repository
  398. func GitHooks(ctx *context.Context) {
  399. ctx.Data["Title"] = ctx.Tr("repo.settings.githooks")
  400. ctx.Data["PageIsSettingsGitHooks"] = true
  401. hooks, err := ctx.Repo.GitRepo.Hooks()
  402. if err != nil {
  403. ctx.Handle(500, "Hooks", err)
  404. return
  405. }
  406. ctx.Data["Hooks"] = hooks
  407. ctx.HTML(200, tplGithooks)
  408. }
  409. // GitHooksEdit render for editing a hook of repository page
  410. func GitHooksEdit(ctx *context.Context) {
  411. ctx.Data["Title"] = ctx.Tr("repo.settings.githooks")
  412. ctx.Data["PageIsSettingsGitHooks"] = true
  413. name := ctx.Params(":name")
  414. hook, err := ctx.Repo.GitRepo.GetHook(name)
  415. if err != nil {
  416. if err == git.ErrNotValidHook {
  417. ctx.Handle(404, "GetHook", err)
  418. } else {
  419. ctx.Handle(500, "GetHook", err)
  420. }
  421. return
  422. }
  423. ctx.Data["Hook"] = hook
  424. ctx.HTML(200, tplGithookEdit)
  425. }
  426. // GitHooksEditPost response for editing a git hook of a repository
  427. func GitHooksEditPost(ctx *context.Context) {
  428. name := ctx.Params(":name")
  429. hook, err := ctx.Repo.GitRepo.GetHook(name)
  430. if err != nil {
  431. if err == git.ErrNotValidHook {
  432. ctx.Handle(404, "GetHook", err)
  433. } else {
  434. ctx.Handle(500, "GetHook", err)
  435. }
  436. return
  437. }
  438. hook.Content = ctx.Query("content")
  439. if err = hook.Update(); err != nil {
  440. ctx.Handle(500, "hook.Update", err)
  441. return
  442. }
  443. ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks/git")
  444. }
  445. // DeployKeys render the deploy keys list of a repository page
  446. func DeployKeys(ctx *context.Context) {
  447. ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys")
  448. ctx.Data["PageIsSettingsKeys"] = true
  449. keys, err := models.ListDeployKeys(ctx.Repo.Repository.ID)
  450. if err != nil {
  451. ctx.Handle(500, "ListDeployKeys", err)
  452. return
  453. }
  454. ctx.Data["Deploykeys"] = keys
  455. ctx.HTML(200, tplDeployKeys)
  456. }
  457. // DeployKeysPost response for adding a deploy key of a repository
  458. func DeployKeysPost(ctx *context.Context, form auth.AddKeyForm) {
  459. ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys")
  460. ctx.Data["PageIsSettingsKeys"] = true
  461. keys, err := models.ListDeployKeys(ctx.Repo.Repository.ID)
  462. if err != nil {
  463. ctx.Handle(500, "ListDeployKeys", err)
  464. return
  465. }
  466. ctx.Data["Deploykeys"] = keys
  467. if ctx.HasError() {
  468. ctx.HTML(200, tplDeployKeys)
  469. return
  470. }
  471. content, err := models.CheckPublicKeyString(form.Content)
  472. if err != nil {
  473. if models.IsErrKeyUnableVerify(err) {
  474. ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key"))
  475. } else {
  476. ctx.Data["HasError"] = true
  477. ctx.Data["Err_Content"] = true
  478. ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error()))
  479. ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys")
  480. return
  481. }
  482. }
  483. key, err := models.AddDeployKey(ctx.Repo.Repository.ID, form.Title, content)
  484. if err != nil {
  485. ctx.Data["HasError"] = true
  486. switch {
  487. case models.IsErrKeyAlreadyExist(err):
  488. ctx.Data["Err_Content"] = true
  489. ctx.RenderWithErr(ctx.Tr("repo.settings.key_been_used"), tplDeployKeys, &form)
  490. case models.IsErrKeyNameAlreadyUsed(err):
  491. ctx.Data["Err_Title"] = true
  492. ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form)
  493. default:
  494. ctx.Handle(500, "AddDeployKey", err)
  495. }
  496. return
  497. }
  498. log.Trace("Deploy key added: %d", ctx.Repo.Repository.ID)
  499. ctx.Flash.Success(ctx.Tr("repo.settings.add_key_success", key.Name))
  500. ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys")
  501. }
  502. // DeleteDeployKey response for deleting a deploy key
  503. func DeleteDeployKey(ctx *context.Context) {
  504. if err := models.DeleteDeployKey(ctx.User, ctx.QueryInt64("id")); err != nil {
  505. ctx.Flash.Error("DeleteDeployKey: " + err.Error())
  506. } else {
  507. ctx.Flash.Success(ctx.Tr("repo.settings.deploy_key_deletion_success"))
  508. }
  509. ctx.JSON(200, map[string]interface{}{
  510. "redirect": ctx.Repo.RepoLink + "/settings/keys",
  511. })
  512. }