summaryrefslogtreecommitdiffstats
path: root/routers/private/hook.go
diff options
context:
space:
mode:
Diffstat (limited to 'routers/private/hook.go')
-rw-r--r--routers/private/hook.go209
1 files changed, 209 insertions, 0 deletions
diff --git a/routers/private/hook.go b/routers/private/hook.go
new file mode 100644
index 0000000000..700c8bf332
--- /dev/null
+++ b/routers/private/hook.go
@@ -0,0 +1,209 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+// 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.
+package private
+
+import (
+ "fmt"
+ "net/http"
+ "os"
+ "strings"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/private"
+ "code.gitea.io/gitea/modules/util"
+
+ macaron "gopkg.in/macaron.v1"
+)
+
+// HookPreReceive checks whether a individual commit is acceptable
+func HookPreReceive(ctx *macaron.Context) {
+ ownerName := ctx.Params(":owner")
+ repoName := ctx.Params(":repo")
+ oldCommitID := ctx.QueryTrim("old")
+ newCommitID := ctx.QueryTrim("new")
+ refFullName := ctx.QueryTrim("ref")
+ userID := ctx.QueryInt64("userID")
+ gitObjectDirectory := ctx.QueryTrim("gitObjectDirectory")
+ gitAlternativeObjectDirectories := ctx.QueryTrim("gitAlternativeObjectDirectories")
+
+ branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
+ repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
+ if err != nil {
+ log.Error("Unable to get repository: %s/%s Error: %v", ownerName, repoName, err)
+ ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
+ "err": err.Error(),
+ })
+ return
+ }
+ repo.OwnerName = ownerName
+ protectBranch, err := models.GetProtectedBranchBy(repo.ID, branchName)
+ if err != nil {
+ log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err)
+ ctx.JSON(500, map[string]interface{}{
+ "err": err.Error(),
+ })
+ return
+ }
+ if protectBranch != nil && protectBranch.IsProtected() {
+ // check and deletion
+ if newCommitID == git.EmptySHA {
+ log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo)
+ ctx.JSON(http.StatusForbidden, map[string]interface{}{
+ "err": fmt.Sprintf("branch %s is protected from deletion", branchName),
+ })
+ return
+ }
+
+ // detect force push
+ if git.EmptySHA != oldCommitID {
+ env := append(os.Environ(),
+ private.GitAlternativeObjectDirectories+"="+gitAlternativeObjectDirectories,
+ private.GitObjectDirectory+"="+gitObjectDirectory,
+ private.GitQuarantinePath+"="+gitObjectDirectory,
+ )
+
+ output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), env)
+ if err != nil {
+ log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err)
+ ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
+ "err": fmt.Sprintf("Fail to detect force push: %v", err),
+ })
+ return
+ } else if len(output) > 0 {
+ log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo)
+ ctx.JSON(http.StatusForbidden, map[string]interface{}{
+ "err": fmt.Sprintf("branch %s is protected from force push", branchName),
+ })
+ return
+
+ }
+ }
+
+ if !protectBranch.CanUserPush(userID) {
+ log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v", userID, branchName, repo)
+ ctx.JSON(http.StatusForbidden, map[string]interface{}{
+ "err": fmt.Sprintf("protected branch %s can not be pushed to", branchName),
+ })
+ return
+ }
+ }
+ ctx.PlainText(http.StatusOK, []byte("ok"))
+}
+
+// HookPostReceive updates services and users
+func HookPostReceive(ctx *macaron.Context) {
+ ownerName := ctx.Params(":owner")
+ repoName := ctx.Params(":repo")
+ oldCommitID := ctx.Query("old")
+ newCommitID := ctx.Query("new")
+ refFullName := ctx.Query("ref")
+ userID := ctx.QueryInt64("userID")
+ userName := ctx.Query("username")
+
+ branch := refFullName
+ if strings.HasPrefix(refFullName, git.BranchPrefix) {
+ branch = strings.TrimPrefix(refFullName, git.BranchPrefix)
+ } else if strings.HasPrefix(refFullName, git.TagPrefix) {
+ branch = strings.TrimPrefix(refFullName, git.TagPrefix)
+ }
+
+ // Only trigger activity updates for changes to branches or
+ // tags. Updates to other refs (eg, refs/notes, refs/changes,
+ // or other less-standard refs spaces are ignored since there
+ // may be a very large number of them).
+ if strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
+ if err := models.PushUpdate(branch, models.PushUpdateOptions{
+ RefFullName: refFullName,
+ OldCommitID: oldCommitID,
+ NewCommitID: newCommitID,
+ PusherID: userID,
+ PusherName: userName,
+ RepoUserName: ownerName,
+ RepoName: repoName,
+ }); err != nil {
+ log.Error("Failed to Update: %s/%s Branch: %s Error: %v", ownerName, repoName, branch, err)
+ ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
+ "err": fmt.Sprintf("Failed to Update: %s/%s Branch: %s Error: %v", ownerName, repoName, branch, err),
+ })
+ return
+ }
+ }
+
+ if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) {
+ repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
+ if err != nil {
+ log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
+ ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
+ "err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
+ })
+ return
+ }
+ repo.OwnerName = ownerName
+
+ pullRequestAllowed := repo.AllowsPulls()
+ if !pullRequestAllowed {
+ ctx.JSON(http.StatusOK, map[string]interface{}{
+ "message": false,
+ })
+ return
+ }
+
+ baseRepo := repo
+ if repo.IsFork {
+ if err := repo.GetBaseRepo(); err != nil {
+ log.Error("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err)
+ ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
+ "err": fmt.Sprintf("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err),
+ })
+ return
+ }
+ baseRepo = repo.BaseRepo
+ }
+
+ if !repo.IsFork && branch == baseRepo.DefaultBranch {
+ ctx.JSON(http.StatusOK, map[string]interface{}{
+ "message": false,
+ })
+ return
+ }
+
+ pr, err := models.GetUnmergedPullRequest(repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch)
+ if err != nil && !models.IsErrPullRequestNotExist(err) {
+ log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err)
+ ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
+ "err": fmt.Sprintf(
+ "Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err),
+ })
+ return
+ }
+
+ if pr == nil {
+ if repo.IsFork {
+ branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch)
+ }
+ ctx.JSON(http.StatusOK, map[string]interface{}{
+ "message": true,
+ "create": true,
+ "branch": branch,
+ "url": fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)),
+ })
+ } else {
+ ctx.JSON(http.StatusOK, map[string]interface{}{
+ "message": true,
+ "create": false,
+ "branch": branch,
+ "url": fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index),
+ })
+ }
+ return
+ }
+ ctx.JSON(http.StatusOK, map[string]interface{}{
+ "message": false,
+ })
+ return
+}