You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

hook.go 8.5 kB

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