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