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_post_receive.go 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package private
  4. import (
  5. "fmt"
  6. "net/http"
  7. "strconv"
  8. issues_model "code.gitea.io/gitea/models/issues"
  9. repo_model "code.gitea.io/gitea/models/repo"
  10. gitea_context "code.gitea.io/gitea/modules/context"
  11. "code.gitea.io/gitea/modules/git"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/private"
  14. repo_module "code.gitea.io/gitea/modules/repository"
  15. "code.gitea.io/gitea/modules/setting"
  16. "code.gitea.io/gitea/modules/util"
  17. "code.gitea.io/gitea/modules/web"
  18. repo_service "code.gitea.io/gitea/services/repository"
  19. )
  20. // HookPostReceive updates services and users
  21. func HookPostReceive(ctx *gitea_context.PrivateContext) {
  22. opts := web.GetForm(ctx).(*private.HookOptions)
  23. // We don't rely on RepoAssignment here because:
  24. // a) we don't need the git repo in this function
  25. // b) our update function will likely change the repository in the db so we will need to refresh it
  26. // c) we don't always need the repo
  27. ownerName := ctx.Params(":owner")
  28. repoName := ctx.Params(":repo")
  29. // defer getting the repository at this point - as we should only retrieve it if we're going to call update
  30. var repo *repo_model.Repository
  31. updates := make([]*repo_module.PushUpdateOptions, 0, len(opts.OldCommitIDs))
  32. wasEmpty := false
  33. for i := range opts.OldCommitIDs {
  34. refFullName := opts.RefFullNames[i]
  35. // Only trigger activity updates for changes to branches or
  36. // tags. Updates to other refs (eg, refs/notes, refs/changes,
  37. // or other less-standard refs spaces are ignored since there
  38. // may be a very large number of them).
  39. if refFullName.IsBranch() || refFullName.IsTag() {
  40. if repo == nil {
  41. repo = loadRepository(ctx, ownerName, repoName)
  42. if ctx.Written() {
  43. // Error handled in loadRepository
  44. return
  45. }
  46. wasEmpty = repo.IsEmpty
  47. }
  48. option := &repo_module.PushUpdateOptions{
  49. RefFullName: refFullName,
  50. OldCommitID: opts.OldCommitIDs[i],
  51. NewCommitID: opts.NewCommitIDs[i],
  52. PusherID: opts.UserID,
  53. PusherName: opts.UserName,
  54. RepoUserName: ownerName,
  55. RepoName: repoName,
  56. }
  57. updates = append(updates, option)
  58. if repo.IsEmpty && (refFullName.BranchName() == "master" || refFullName.BranchName() == "main") {
  59. // put the master/main branch first
  60. copy(updates[1:], updates)
  61. updates[0] = option
  62. }
  63. }
  64. }
  65. if repo != nil && len(updates) > 0 {
  66. if err := repo_service.PushUpdates(updates); err != nil {
  67. log.Error("Failed to Update: %s/%s Total Updates: %d", ownerName, repoName, len(updates))
  68. for i, update := range updates {
  69. log.Error("Failed to Update: %s/%s Update: %d/%d: Branch: %s", ownerName, repoName, i, len(updates), update.RefFullName.BranchName())
  70. }
  71. log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
  72. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  73. Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err),
  74. })
  75. return
  76. }
  77. }
  78. // Handle Push Options
  79. if len(opts.GitPushOptions) > 0 {
  80. // load the repository
  81. if repo == nil {
  82. repo = loadRepository(ctx, ownerName, repoName)
  83. if ctx.Written() {
  84. // Error handled in loadRepository
  85. return
  86. }
  87. wasEmpty = repo.IsEmpty
  88. }
  89. repo.IsPrivate = opts.GitPushOptions.Bool(private.GitPushOptionRepoPrivate, repo.IsPrivate)
  90. repo.IsTemplate = opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate, repo.IsTemplate)
  91. if err := repo_model.UpdateRepositoryCols(ctx, repo, "is_private", "is_template"); err != nil {
  92. log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
  93. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  94. Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err),
  95. })
  96. }
  97. }
  98. results := make([]private.HookPostReceiveBranchResult, 0, len(opts.OldCommitIDs))
  99. // We have to reload the repo in case its state is changed above
  100. repo = nil
  101. var baseRepo *repo_model.Repository
  102. // Now handle the pull request notification trailers
  103. for i := range opts.OldCommitIDs {
  104. refFullName := opts.RefFullNames[i]
  105. newCommitID := opts.NewCommitIDs[i]
  106. // post update for agit pull request
  107. // FIXME: use pr.Flow to test whether it's an Agit PR or a GH PR
  108. if git.SupportProcReceive && refFullName.IsPull() {
  109. if repo == nil {
  110. repo = loadRepository(ctx, ownerName, repoName)
  111. if ctx.Written() {
  112. return
  113. }
  114. }
  115. pullIndex, _ := strconv.ParseInt(refFullName.PullName(), 10, 64)
  116. if pullIndex <= 0 {
  117. continue
  118. }
  119. pr, err := issues_model.GetPullRequestByIndex(ctx, repo.ID, pullIndex)
  120. if err != nil && !issues_model.IsErrPullRequestNotExist(err) {
  121. log.Error("Failed to get PR by index %v Error: %v", pullIndex, err)
  122. ctx.JSON(http.StatusInternalServerError, private.Response{
  123. Err: fmt.Sprintf("Failed to get PR by index %v Error: %v", pullIndex, err),
  124. })
  125. return
  126. }
  127. if pr == nil {
  128. continue
  129. }
  130. results = append(results, private.HookPostReceiveBranchResult{
  131. Message: setting.Git.PullRequestPushMessage && repo.AllowsPulls(),
  132. Create: false,
  133. Branch: "",
  134. URL: fmt.Sprintf("%s/pulls/%d", repo.HTMLURL(), pr.Index),
  135. })
  136. continue
  137. }
  138. // If we've pushed a branch (and not deleted it)
  139. if newCommitID != git.EmptySHA && refFullName.IsBranch() {
  140. // First ensure we have the repository loaded, we're allowed pulls requests and we can get the base repo
  141. if repo == nil {
  142. repo = loadRepository(ctx, ownerName, repoName)
  143. if ctx.Written() {
  144. return
  145. }
  146. baseRepo = repo
  147. if repo.IsFork {
  148. if err := repo.GetBaseRepo(ctx); err != nil {
  149. log.Error("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err)
  150. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  151. Err: fmt.Sprintf("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err),
  152. RepoWasEmpty: wasEmpty,
  153. })
  154. return
  155. }
  156. if repo.BaseRepo.AllowsPulls() {
  157. baseRepo = repo.BaseRepo
  158. }
  159. }
  160. if !baseRepo.AllowsPulls() {
  161. // We can stop there's no need to go any further
  162. ctx.JSON(http.StatusOK, private.HookPostReceiveResult{
  163. RepoWasEmpty: wasEmpty,
  164. })
  165. return
  166. }
  167. }
  168. branch := refFullName.BranchName()
  169. // If our branch is the default branch of an unforked repo - there's no PR to create or refer to
  170. if !repo.IsFork && branch == baseRepo.DefaultBranch {
  171. results = append(results, private.HookPostReceiveBranchResult{})
  172. continue
  173. }
  174. pr, err := issues_model.GetUnmergedPullRequest(ctx, repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch, issues_model.PullRequestFlowGithub)
  175. if err != nil && !issues_model.IsErrPullRequestNotExist(err) {
  176. log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err)
  177. ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
  178. Err: fmt.Sprintf(
  179. "Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err),
  180. RepoWasEmpty: wasEmpty,
  181. })
  182. return
  183. }
  184. if pr == nil {
  185. if repo.IsFork {
  186. branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch)
  187. }
  188. results = append(results, private.HookPostReceiveBranchResult{
  189. Message: setting.Git.PullRequestPushMessage && baseRepo.AllowsPulls(),
  190. Create: true,
  191. Branch: branch,
  192. URL: fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)),
  193. })
  194. } else {
  195. results = append(results, private.HookPostReceiveBranchResult{
  196. Message: setting.Git.PullRequestPushMessage && baseRepo.AllowsPulls(),
  197. Create: false,
  198. Branch: branch,
  199. URL: fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index),
  200. })
  201. }
  202. }
  203. }
  204. ctx.JSON(http.StatusOK, private.HookPostReceiveResult{
  205. Results: results,
  206. RepoWasEmpty: wasEmpty,
  207. })
  208. }