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.

hook.go 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. // Copyright 2019 The Gitea 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 private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead.
  5. package private
  6. import (
  7. "fmt"
  8. "net/http"
  9. "os"
  10. "strings"
  11. "code.gitea.io/gitea/models"
  12. "code.gitea.io/gitea/modules/git"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/private"
  15. "code.gitea.io/gitea/modules/repofiles"
  16. "code.gitea.io/gitea/modules/util"
  17. macaron "gopkg.in/macaron.v1"
  18. )
  19. // HookPreReceive checks whether a individual commit is acceptable
  20. func HookPreReceive(ctx *macaron.Context) {
  21. ownerName := ctx.Params(":owner")
  22. repoName := ctx.Params(":repo")
  23. oldCommitID := ctx.QueryTrim("old")
  24. newCommitID := ctx.QueryTrim("new")
  25. refFullName := ctx.QueryTrim("ref")
  26. userID := ctx.QueryInt64("userID")
  27. gitObjectDirectory := ctx.QueryTrim("gitObjectDirectory")
  28. gitAlternativeObjectDirectories := ctx.QueryTrim("gitAlternativeObjectDirectories")
  29. gitQuarantinePath := ctx.QueryTrim("gitQuarantinePath")
  30. prID := ctx.QueryInt64("prID")
  31. branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
  32. repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
  33. if err != nil {
  34. log.Error("Unable to get repository: %s/%s Error: %v", ownerName, repoName, err)
  35. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  36. "err": err.Error(),
  37. })
  38. return
  39. }
  40. repo.OwnerName = ownerName
  41. protectBranch, err := models.GetProtectedBranchBy(repo.ID, branchName)
  42. if err != nil {
  43. log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err)
  44. ctx.JSON(500, map[string]interface{}{
  45. "err": err.Error(),
  46. })
  47. return
  48. }
  49. if protectBranch != nil && protectBranch.IsProtected() {
  50. // check and deletion
  51. if newCommitID == git.EmptySHA {
  52. log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo)
  53. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  54. "err": fmt.Sprintf("branch %s is protected from deletion", branchName),
  55. })
  56. return
  57. }
  58. // detect force push
  59. if git.EmptySHA != oldCommitID {
  60. env := os.Environ()
  61. if gitAlternativeObjectDirectories != "" {
  62. env = append(env,
  63. private.GitAlternativeObjectDirectories+"="+gitAlternativeObjectDirectories)
  64. }
  65. if gitObjectDirectory != "" {
  66. env = append(env,
  67. private.GitObjectDirectory+"="+gitObjectDirectory)
  68. }
  69. if gitQuarantinePath != "" {
  70. env = append(env,
  71. private.GitQuarantinePath+"="+gitQuarantinePath)
  72. }
  73. output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), env)
  74. if err != nil {
  75. log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err)
  76. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  77. "err": fmt.Sprintf("Fail to detect force push: %v", err),
  78. })
  79. return
  80. } else if len(output) > 0 {
  81. log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo)
  82. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  83. "err": fmt.Sprintf("branch %s is protected from force push", branchName),
  84. })
  85. return
  86. }
  87. }
  88. canPush := protectBranch.CanUserPush(userID)
  89. if !canPush && prID > 0 {
  90. pr, err := models.GetPullRequestByID(prID)
  91. if err != nil {
  92. log.Error("Unable to get PullRequest %d Error: %v", prID, err)
  93. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  94. "err": fmt.Sprintf("Unable to get PullRequest %d Error: %v", prID, err),
  95. })
  96. return
  97. }
  98. if !protectBranch.HasEnoughApprovals(pr) {
  99. log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v and pr #%d does not have enough approvals", userID, branchName, repo, pr.Index)
  100. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  101. "err": fmt.Sprintf("protected branch %s can not be pushed to and pr #%d does not have enough approvals", branchName, prID),
  102. })
  103. return
  104. }
  105. } else if !canPush {
  106. log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v", userID, branchName, repo)
  107. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  108. "err": fmt.Sprintf("protected branch %s can not be pushed to", branchName),
  109. })
  110. return
  111. }
  112. }
  113. ctx.PlainText(http.StatusOK, []byte("ok"))
  114. }
  115. // HookPostReceive updates services and users
  116. func HookPostReceive(ctx *macaron.Context) {
  117. ownerName := ctx.Params(":owner")
  118. repoName := ctx.Params(":repo")
  119. oldCommitID := ctx.Query("old")
  120. newCommitID := ctx.Query("new")
  121. refFullName := ctx.Query("ref")
  122. userID := ctx.QueryInt64("userID")
  123. userName := ctx.Query("username")
  124. branch := refFullName
  125. if strings.HasPrefix(refFullName, git.BranchPrefix) {
  126. branch = strings.TrimPrefix(refFullName, git.BranchPrefix)
  127. } else if strings.HasPrefix(refFullName, git.TagPrefix) {
  128. branch = strings.TrimPrefix(refFullName, git.TagPrefix)
  129. }
  130. // Only trigger activity updates for changes to branches or
  131. // tags. Updates to other refs (eg, refs/notes, refs/changes,
  132. // or other less-standard refs spaces are ignored since there
  133. // may be a very large number of them).
  134. if strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
  135. repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
  136. if err != nil {
  137. log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
  138. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  139. "err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
  140. })
  141. return
  142. }
  143. if err := repofiles.PushUpdate(repo, branch, models.PushUpdateOptions{
  144. RefFullName: refFullName,
  145. OldCommitID: oldCommitID,
  146. NewCommitID: newCommitID,
  147. PusherID: userID,
  148. PusherName: userName,
  149. RepoUserName: ownerName,
  150. RepoName: repoName,
  151. }); err != nil {
  152. log.Error("Failed to Update: %s/%s Branch: %s Error: %v", ownerName, repoName, branch, err)
  153. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  154. "err": fmt.Sprintf("Failed to Update: %s/%s Branch: %s Error: %v", ownerName, repoName, branch, err),
  155. })
  156. return
  157. }
  158. }
  159. if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) {
  160. repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
  161. if err != nil {
  162. log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
  163. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  164. "err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
  165. })
  166. return
  167. }
  168. repo.OwnerName = ownerName
  169. pullRequestAllowed := repo.AllowsPulls()
  170. if !pullRequestAllowed {
  171. ctx.JSON(http.StatusOK, map[string]interface{}{
  172. "message": false,
  173. })
  174. return
  175. }
  176. baseRepo := repo
  177. if repo.IsFork {
  178. if err := repo.GetBaseRepo(); err != nil {
  179. log.Error("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err)
  180. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  181. "err": fmt.Sprintf("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err),
  182. })
  183. return
  184. }
  185. baseRepo = repo.BaseRepo
  186. }
  187. if !repo.IsFork && branch == baseRepo.DefaultBranch {
  188. ctx.JSON(http.StatusOK, map[string]interface{}{
  189. "message": false,
  190. })
  191. return
  192. }
  193. pr, err := models.GetUnmergedPullRequest(repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch)
  194. if err != nil && !models.IsErrPullRequestNotExist(err) {
  195. log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err)
  196. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  197. "err": fmt.Sprintf(
  198. "Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err),
  199. })
  200. return
  201. }
  202. if pr == nil {
  203. if repo.IsFork {
  204. branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch)
  205. }
  206. ctx.JSON(http.StatusOK, map[string]interface{}{
  207. "message": true,
  208. "create": true,
  209. "branch": branch,
  210. "url": fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)),
  211. })
  212. } else {
  213. ctx.JSON(http.StatusOK, map[string]interface{}{
  214. "message": true,
  215. "create": false,
  216. "branch": branch,
  217. "url": fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index),
  218. })
  219. }
  220. return
  221. }
  222. ctx.JSON(http.StatusOK, map[string]interface{}{
  223. "message": false,
  224. })
  225. }