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 7.7 kB

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