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

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