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

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