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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  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, opts private.HookOptions) {
  21. ownerName := ctx.Params(":owner")
  22. repoName := ctx.Params(":repo")
  23. repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
  24. if err != nil {
  25. log.Error("Unable to get repository: %s/%s Error: %v", ownerName, repoName, err)
  26. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  27. "err": err.Error(),
  28. })
  29. return
  30. }
  31. repo.OwnerName = ownerName
  32. for i := range opts.OldCommitIDs {
  33. oldCommitID := opts.OldCommitIDs[i]
  34. newCommitID := opts.NewCommitIDs[i]
  35. refFullName := opts.RefFullNames[i]
  36. branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
  37. protectBranch, err := models.GetProtectedBranchBy(repo.ID, branchName)
  38. if err != nil {
  39. log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err)
  40. ctx.JSON(500, map[string]interface{}{
  41. "err": err.Error(),
  42. })
  43. return
  44. }
  45. if protectBranch != nil && protectBranch.IsProtected() {
  46. // check and deletion
  47. if newCommitID == git.EmptySHA {
  48. log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo)
  49. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  50. "err": fmt.Sprintf("branch %s is protected from deletion", branchName),
  51. })
  52. return
  53. }
  54. // detect force push
  55. if git.EmptySHA != oldCommitID {
  56. env := os.Environ()
  57. if opts.GitAlternativeObjectDirectories != "" {
  58. env = append(env,
  59. private.GitAlternativeObjectDirectories+"="+opts.GitAlternativeObjectDirectories)
  60. }
  61. if opts.GitObjectDirectory != "" {
  62. env = append(env,
  63. private.GitObjectDirectory+"="+opts.GitObjectDirectory)
  64. }
  65. if opts.GitQuarantinePath != "" {
  66. env = append(env,
  67. private.GitQuarantinePath+"="+opts.GitQuarantinePath)
  68. }
  69. output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), env)
  70. if err != nil {
  71. log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err)
  72. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  73. "err": fmt.Sprintf("Fail to detect force push: %v", err),
  74. })
  75. return
  76. } else if len(output) > 0 {
  77. log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo)
  78. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  79. "err": fmt.Sprintf("branch %s is protected from force push", branchName),
  80. })
  81. return
  82. }
  83. }
  84. canPush := false
  85. if opts.IsDeployKey {
  86. canPush = protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys)
  87. } else {
  88. canPush = protectBranch.CanUserPush(opts.UserID)
  89. }
  90. if !canPush && opts.ProtectedBranchID > 0 {
  91. pr, err := models.GetPullRequestByID(opts.ProtectedBranchID)
  92. if err != nil {
  93. log.Error("Unable to get PullRequest %d Error: %v", opts.ProtectedBranchID, err)
  94. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  95. "err": fmt.Sprintf("Unable to get PullRequest %d Error: %v", opts.ProtectedBranchID, err),
  96. })
  97. return
  98. }
  99. if !protectBranch.HasEnoughApprovals(pr) {
  100. log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v and pr #%d does not have enough approvals", opts.UserID, branchName, repo, pr.Index)
  101. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  102. "err": fmt.Sprintf("protected branch %s can not be pushed to and pr #%d does not have enough approvals", branchName, opts.ProtectedBranchID),
  103. })
  104. return
  105. }
  106. if protectBranch.MergeBlockedByRejectedReview(pr) {
  107. log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v and pr #%d has requested changes", opts.UserID, branchName, repo, pr.Index)
  108. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  109. "err": fmt.Sprintf("protected branch %s can not be pushed to and pr #%d has requested changes", branchName, opts.ProtectedBranchID),
  110. })
  111. return
  112. }
  113. } else if !canPush {
  114. log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v", opts.UserID, branchName, repo)
  115. ctx.JSON(http.StatusForbidden, map[string]interface{}{
  116. "err": fmt.Sprintf("protected branch %s can not be pushed to", branchName),
  117. })
  118. return
  119. }
  120. }
  121. }
  122. ctx.PlainText(http.StatusOK, []byte("ok"))
  123. }
  124. // HookPostReceive updates services and users
  125. func HookPostReceive(ctx *macaron.Context, opts private.HookOptions) {
  126. ownerName := ctx.Params(":owner")
  127. repoName := ctx.Params(":repo")
  128. var repo *models.Repository
  129. updates := make([]*repofiles.PushUpdateOptions, 0, len(opts.OldCommitIDs))
  130. wasEmpty := false
  131. for i := range opts.OldCommitIDs {
  132. refFullName := opts.RefFullNames[i]
  133. branch := opts.RefFullNames[i]
  134. if strings.HasPrefix(branch, git.BranchPrefix) {
  135. branch = strings.TrimPrefix(branch, git.BranchPrefix)
  136. } else {
  137. branch = strings.TrimPrefix(branch, git.TagPrefix)
  138. }
  139. // Only trigger activity updates for changes to branches or
  140. // tags. Updates to other refs (eg, refs/notes, refs/changes,
  141. // or other less-standard refs spaces are ignored since there
  142. // may be a very large number of them).
  143. if strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
  144. if repo == nil {
  145. var err error
  146. repo, err = models.GetRepositoryByOwnerAndName(ownerName, repoName)
  147. if err != nil {
  148. log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
  149. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  150. Err: fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
  151. })
  152. return
  153. }
  154. if repo.OwnerName == "" {
  155. repo.OwnerName = ownerName
  156. }
  157. wasEmpty = repo.IsEmpty
  158. }
  159. option := repofiles.PushUpdateOptions{
  160. RefFullName: refFullName,
  161. OldCommitID: opts.OldCommitIDs[i],
  162. NewCommitID: opts.NewCommitIDs[i],
  163. Branch: branch,
  164. PusherID: opts.UserID,
  165. PusherName: opts.UserName,
  166. RepoUserName: ownerName,
  167. RepoName: repoName,
  168. }
  169. updates = append(updates, &option)
  170. if repo.IsEmpty && branch == "master" && strings.HasPrefix(refFullName, git.BranchPrefix) {
  171. // put the master branch first
  172. copy(updates[1:], updates)
  173. updates[0] = &option
  174. }
  175. }
  176. }
  177. if repo != nil && len(updates) > 0 {
  178. if err := repofiles.PushUpdates(repo, updates); err != nil {
  179. log.Error("Failed to Update: %s/%s Total Updates: %d", ownerName, repoName, len(updates))
  180. for i, update := range updates {
  181. log.Error("Failed to Update: %s/%s Update: %d/%d: Branch: %s", ownerName, repoName, i, len(updates), update.Branch)
  182. }
  183. log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
  184. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  185. Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err),
  186. })
  187. return
  188. }
  189. }
  190. results := make([]private.HookPostReceiveBranchResult, 0, len(opts.OldCommitIDs))
  191. // We have to reload the repo in case its state is changed above
  192. repo = nil
  193. var baseRepo *models.Repository
  194. for i := range opts.OldCommitIDs {
  195. refFullName := opts.RefFullNames[i]
  196. newCommitID := opts.NewCommitIDs[i]
  197. branch := git.RefEndName(opts.RefFullNames[i])
  198. if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) {
  199. if repo == nil {
  200. var err error
  201. repo, err = models.GetRepositoryByOwnerAndName(ownerName, repoName)
  202. if err != nil {
  203. log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
  204. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  205. Err: fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
  206. RepoWasEmpty: wasEmpty,
  207. })
  208. return
  209. }
  210. if repo.OwnerName == "" {
  211. repo.OwnerName = ownerName
  212. }
  213. if !repo.AllowsPulls() {
  214. // We can stop there's no need to go any further
  215. ctx.JSON(http.StatusOK, private.HookPostReceiveResult{
  216. RepoWasEmpty: wasEmpty,
  217. })
  218. return
  219. }
  220. baseRepo = repo
  221. if repo.IsFork {
  222. if err := repo.GetBaseRepo(); err != nil {
  223. log.Error("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err)
  224. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  225. Err: fmt.Sprintf("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err),
  226. RepoWasEmpty: wasEmpty,
  227. })
  228. return
  229. }
  230. baseRepo = repo.BaseRepo
  231. }
  232. }
  233. if !repo.IsFork && branch == baseRepo.DefaultBranch {
  234. results = append(results, private.HookPostReceiveBranchResult{})
  235. continue
  236. }
  237. pr, err := models.GetUnmergedPullRequest(repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch)
  238. if err != nil && !models.IsErrPullRequestNotExist(err) {
  239. log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err)
  240. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  241. Err: fmt.Sprintf(
  242. "Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err),
  243. RepoWasEmpty: wasEmpty,
  244. })
  245. return
  246. }
  247. if pr == nil {
  248. if repo.IsFork {
  249. branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch)
  250. }
  251. results = append(results, private.HookPostReceiveBranchResult{
  252. Message: true,
  253. Create: true,
  254. Branch: branch,
  255. URL: fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)),
  256. })
  257. } else {
  258. results = append(results, private.HookPostReceiveBranchResult{
  259. Message: true,
  260. Create: false,
  261. Branch: branch,
  262. URL: fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index),
  263. })
  264. }
  265. }
  266. }
  267. ctx.JSON(http.StatusOK, private.HookPostReceiveResult{
  268. Results: results,
  269. RepoWasEmpty: wasEmpty,
  270. })
  271. }
  272. // SetDefaultBranch updates the default branch
  273. func SetDefaultBranch(ctx *macaron.Context) {
  274. ownerName := ctx.Params(":owner")
  275. repoName := ctx.Params(":repo")
  276. branch := ctx.Params(":branch")
  277. repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
  278. if err != nil {
  279. log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
  280. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  281. "Err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
  282. })
  283. return
  284. }
  285. if repo.OwnerName == "" {
  286. repo.OwnerName = ownerName
  287. }
  288. repo.DefaultBranch = branch
  289. gitRepo, err := git.OpenRepository(repo.RepoPath())
  290. if err != nil {
  291. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  292. "Err": fmt.Sprintf("Failed to get git repository: %s/%s Error: %v", ownerName, repoName, err),
  293. })
  294. return
  295. }
  296. if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
  297. if !git.IsErrUnsupportedVersion(err) {
  298. gitRepo.Close()
  299. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  300. "Err": fmt.Sprintf("Unable to set default branch onrepository: %s/%s Error: %v", ownerName, repoName, err),
  301. })
  302. return
  303. }
  304. }
  305. gitRepo.Close()
  306. if err := repo.UpdateDefaultBranch(); err != nil {
  307. ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
  308. "Err": fmt.Sprintf("Unable to set default branch onrepository: %s/%s Error: %v", ownerName, repoName, err),
  309. })
  310. return
  311. }
  312. ctx.PlainText(200, []byte("success"))
  313. }