mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Move push update to post-receive and protected branch check to pre-receive (#1030)
* move all push update to git hook post-receive and protected branch check to git hook pre-receive * add SSH_ORIGINAL_COMMAND check back * remove all unused codes * fix the import
This commit is contained in:
		
							
								
								
									
										135
									
								
								cmd/hook.go
									
									
									
									
									
								
							
							
						
						
									
										135
									
								
								cmd/hook.go
									
									
									
									
									
								
							@@ -5,11 +5,22 @@
 | 
			
		||||
package cmd
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/git"
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
	"code.gitea.io/gitea/modules/httplib"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
 | 
			
		||||
	"github.com/Unknwon/com"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -57,10 +68,59 @@ func runHookPreReceive(c *cli.Context) error {
 | 
			
		||||
	if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := setup("hooks/pre-receive.log"); err != nil {
 | 
			
		||||
		fail("Hook pre-receive init failed", fmt.Sprintf("setup: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// the environment setted on serv command
 | 
			
		||||
	repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64)
 | 
			
		||||
	isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
 | 
			
		||||
 | 
			
		||||
	buf := bytes.NewBuffer(nil)
 | 
			
		||||
	scanner := bufio.NewScanner(os.Stdin)
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		buf.Write(scanner.Bytes())
 | 
			
		||||
		buf.WriteByte('\n')
 | 
			
		||||
 | 
			
		||||
		// TODO: support news feeds for wiki
 | 
			
		||||
		if isWiki {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fields := bytes.Fields(scanner.Bytes())
 | 
			
		||||
		if len(fields) != 3 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		oldCommitID := string(fields[0])
 | 
			
		||||
		newCommitID := string(fields[1])
 | 
			
		||||
		refFullName := string(fields[2])
 | 
			
		||||
 | 
			
		||||
		branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
 | 
			
		||||
		protectBranch, err := models.GetProtectedBranchBy(repoID, branchName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.GitLogger.Fatal(2, "retrieve protected branches information failed")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if protectBranch != nil {
 | 
			
		||||
			fail(fmt.Sprintf("protected branch %s can not be pushed to", branchName), "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// check and deletion
 | 
			
		||||
		if newCommitID == git.EmptySHA {
 | 
			
		||||
			fail(fmt.Sprintf("Branch '%s' is protected from deletion", branchName), "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Check force push
 | 
			
		||||
		output, err := git.NewCommand("rev-list", oldCommitID, "^"+newCommitID).Run()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fail("Internal error", "Fail to detect force push: %v", err)
 | 
			
		||||
		} else if len(output) > 0 {
 | 
			
		||||
			fail(fmt.Sprintf("Branch '%s' is protected from force push", branchName), "")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -73,23 +133,6 @@ func runHookUpdate(c *cli.Context) error {
 | 
			
		||||
		fail("Hook update init failed", fmt.Sprintf("setup: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	args := c.Args()
 | 
			
		||||
	if len(args) != 3 {
 | 
			
		||||
		fail("Arguments received are not equal to three", "Arguments received are not equal to three")
 | 
			
		||||
	} else if len(args[0]) == 0 {
 | 
			
		||||
		fail("First argument 'refName' is empty", "First argument 'refName' is empty")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uuid := os.Getenv(envUpdateTaskUUID)
 | 
			
		||||
	if err := models.AddUpdateTask(&models.UpdateTask{
 | 
			
		||||
		UUID:        uuid,
 | 
			
		||||
		RefName:     args[0],
 | 
			
		||||
		OldCommitID: args[1],
 | 
			
		||||
		NewCommitID: args[2],
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		fail("Internal error", "Fail to add update task '%s': %v", uuid, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -102,5 +145,63 @@ func runHookPostReceive(c *cli.Context) error {
 | 
			
		||||
		fail("Hook post-receive init failed", fmt.Sprintf("setup: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// the environment setted on serv command
 | 
			
		||||
	repoUser := os.Getenv(models.EnvRepoUsername)
 | 
			
		||||
	repoUserSalt := os.Getenv(models.EnvRepoUserSalt)
 | 
			
		||||
	isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
 | 
			
		||||
	repoName := os.Getenv(models.EnvRepoName)
 | 
			
		||||
	pusherID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
 | 
			
		||||
	pusherName := os.Getenv(models.EnvPusherName)
 | 
			
		||||
 | 
			
		||||
	buf := bytes.NewBuffer(nil)
 | 
			
		||||
	scanner := bufio.NewScanner(os.Stdin)
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		buf.Write(scanner.Bytes())
 | 
			
		||||
		buf.WriteByte('\n')
 | 
			
		||||
 | 
			
		||||
		// TODO: support news feeds for wiki
 | 
			
		||||
		if isWiki {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fields := bytes.Fields(scanner.Bytes())
 | 
			
		||||
		if len(fields) != 3 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		oldCommitID := string(fields[0])
 | 
			
		||||
		newCommitID := string(fields[1])
 | 
			
		||||
		refFullName := string(fields[2])
 | 
			
		||||
 | 
			
		||||
		if err := models.PushUpdate(models.PushUpdateOptions{
 | 
			
		||||
			RefFullName:  refFullName,
 | 
			
		||||
			OldCommitID:  oldCommitID,
 | 
			
		||||
			NewCommitID:  newCommitID,
 | 
			
		||||
			PusherID:     pusherID,
 | 
			
		||||
			PusherName:   pusherName,
 | 
			
		||||
			RepoUserName: repoUser,
 | 
			
		||||
			RepoName:     repoName,
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			log.GitLogger.Error(2, "Update: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Ask for running deliver hook and test pull request tasks.
 | 
			
		||||
		reqURL := setting.LocalURL + repoUser + "/" + repoName + "/tasks/trigger?branch=" +
 | 
			
		||||
			strings.TrimPrefix(refFullName, git.BranchPrefix) + "&secret=" + base.EncodeMD5(repoUserSalt) + "&pusher=" + com.ToStr(pusherID)
 | 
			
		||||
		log.GitLogger.Trace("Trigger task: %s", reqURL)
 | 
			
		||||
 | 
			
		||||
		resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{
 | 
			
		||||
			InsecureSkipVerify: true,
 | 
			
		||||
		}).Response()
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			resp.Body.Close()
 | 
			
		||||
			if resp.StatusCode/100 != 2 {
 | 
			
		||||
				log.GitLogger.Error(2, "Failed to trigger task: not 2xx response code")
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			log.GitLogger.Error(2, "Failed to trigger task: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										76
									
								
								cmd/serv.go
									
									
									
									
									
								
							
							
						
						
									
										76
									
								
								cmd/serv.go
									
									
									
									
									
								
							@@ -6,7 +6,6 @@
 | 
			
		||||
package cmd
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
@@ -15,22 +14,17 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/git"
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
	"code.gitea.io/gitea/modules/httplib"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"github.com/Unknwon/com"
 | 
			
		||||
	"github.com/dgrijalva/jwt-go"
 | 
			
		||||
	gouuid "github.com/satori/go.uuid"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	accessDenied        = "Repository does not exist or you do not have access"
 | 
			
		||||
	lfsAuthenticateVerb = "git-lfs-authenticate"
 | 
			
		||||
	envUpdateTaskUUID   = "GITEA_UUID"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CmdServ represents the available serv sub-command.
 | 
			
		||||
@@ -96,52 +90,6 @@ func fail(userMessage, logMessage string, args ...interface{}) {
 | 
			
		||||
	os.Exit(1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleUpdateTask(uuid string, user, repoUser *models.User, reponame string, isWiki bool) {
 | 
			
		||||
	task, err := models.GetUpdateTaskByUUID(uuid)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrUpdateTaskNotExist(err) {
 | 
			
		||||
			log.GitLogger.Trace("No update task is presented: %s", uuid)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		log.GitLogger.Fatal(2, "GetUpdateTaskByUUID: %v", err)
 | 
			
		||||
	} else if err = models.DeleteUpdateTaskByUUID(uuid); err != nil {
 | 
			
		||||
		log.GitLogger.Fatal(2, "DeleteUpdateTaskByUUID: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if isWiki {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = models.PushUpdate(models.PushUpdateOptions{
 | 
			
		||||
		RefFullName:  task.RefName,
 | 
			
		||||
		OldCommitID:  task.OldCommitID,
 | 
			
		||||
		NewCommitID:  task.NewCommitID,
 | 
			
		||||
		PusherID:     user.ID,
 | 
			
		||||
		PusherName:   user.Name,
 | 
			
		||||
		RepoUserName: repoUser.Name,
 | 
			
		||||
		RepoName:     reponame,
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		log.GitLogger.Error(2, "Update: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Ask for running deliver hook and test pull request tasks.
 | 
			
		||||
	reqURL := setting.LocalURL + repoUser.Name + "/" + reponame + "/tasks/trigger?branch=" +
 | 
			
		||||
		strings.TrimPrefix(task.RefName, git.BranchPrefix) + "&secret=" + base.EncodeMD5(repoUser.Salt) + "&pusher=" + com.ToStr(user.ID)
 | 
			
		||||
	log.GitLogger.Trace("Trigger task: %s", reqURL)
 | 
			
		||||
 | 
			
		||||
	resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{
 | 
			
		||||
		InsecureSkipVerify: true,
 | 
			
		||||
	}).Response()
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		resp.Body.Close()
 | 
			
		||||
		if resp.StatusCode/100 != 2 {
 | 
			
		||||
			log.GitLogger.Error(2, "Failed to trigger task: not 2xx response code")
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		log.GitLogger.Error(2, "Failed to trigger task: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runServ(c *cli.Context) error {
 | 
			
		||||
	if c.IsSet("config") {
 | 
			
		||||
		setting.CustomConf = c.String("config")
 | 
			
		||||
@@ -187,6 +135,7 @@ func runServ(c *cli.Context) error {
 | 
			
		||||
	if len(rr) != 2 {
 | 
			
		||||
		fail("Invalid repository path", "Invalid repository path: %v", args)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	username := strings.ToLower(rr[0])
 | 
			
		||||
	reponame := strings.ToLower(strings.TrimSuffix(rr[1], ".git"))
 | 
			
		||||
 | 
			
		||||
@@ -196,6 +145,14 @@ func runServ(c *cli.Context) error {
 | 
			
		||||
		reponame = reponame[:len(reponame)-5]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	os.Setenv(models.EnvRepoUsername, username)
 | 
			
		||||
	if isWiki {
 | 
			
		||||
		os.Setenv(models.EnvRepoIsWiki, "true")
 | 
			
		||||
	} else {
 | 
			
		||||
		os.Setenv(models.EnvRepoIsWiki, "false")
 | 
			
		||||
	}
 | 
			
		||||
	os.Setenv(models.EnvRepoName, reponame)
 | 
			
		||||
 | 
			
		||||
	repoUser, err := models.GetUserByName(username)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrUserNotExist(err) {
 | 
			
		||||
@@ -204,6 +161,8 @@ func runServ(c *cli.Context) error {
 | 
			
		||||
		fail("Internal error", "Failed to get repository owner (%s): %v", username, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	os.Setenv(models.EnvRepoUserSalt, repoUser.Salt)
 | 
			
		||||
 | 
			
		||||
	repo, err := models.GetRepositoryByName(repoUser.ID, reponame)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrRepoNotExist(err) {
 | 
			
		||||
@@ -286,7 +245,8 @@ func runServ(c *cli.Context) error {
 | 
			
		||||
					user.Name, requestedMode, repoPath)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			os.Setenv("GITEA_PUSHER_NAME", user.Name)
 | 
			
		||||
			os.Setenv(models.EnvPusherName, user.Name)
 | 
			
		||||
			os.Setenv(models.EnvPusherID, fmt.Sprintf("%d", user.ID))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -323,11 +283,6 @@ func runServ(c *cli.Context) error {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uuid := gouuid.NewV4().String()
 | 
			
		||||
	os.Setenv(envUpdateTaskUUID, uuid)
 | 
			
		||||
	// Keep the old env variable name for backward compability
 | 
			
		||||
	os.Setenv("uuid", uuid)
 | 
			
		||||
 | 
			
		||||
	// Special handle for Windows.
 | 
			
		||||
	if setting.IsWindows {
 | 
			
		||||
		verb = strings.Replace(verb, "-", " ", 1)
 | 
			
		||||
@@ -341,7 +296,6 @@ func runServ(c *cli.Context) error {
 | 
			
		||||
		gitcmd = exec.Command(verb, repoPath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	os.Setenv(models.ProtectedBranchAccessMode, requestedMode.String())
 | 
			
		||||
	os.Setenv(models.ProtectedBranchRepoID, fmt.Sprintf("%d", repo.ID))
 | 
			
		||||
 | 
			
		||||
	gitcmd.Dir = setting.RepoRootPath
 | 
			
		||||
@@ -352,10 +306,6 @@ func runServ(c *cli.Context) error {
 | 
			
		||||
		fail("Internal error", "Failed to execute git command: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if requestedMode == models.AccessModeWrite {
 | 
			
		||||
		handleUpdateTask(uuid, user, repoUser, reponame, isWiki)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Update user key activity.
 | 
			
		||||
	if keyID > 0 {
 | 
			
		||||
		key, err := models.GetPublicKeyByID(keyID)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,83 +0,0 @@
 | 
			
		||||
// Copyright 2014 The Gogs 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 cmd
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/git"
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CmdUpdate represents the available update sub-command.
 | 
			
		||||
var CmdUpdate = cli.Command{
 | 
			
		||||
	Name:        "update",
 | 
			
		||||
	Usage:       "This command should only be called by Git hook",
 | 
			
		||||
	Description: `Update get pushed info and insert into database`,
 | 
			
		||||
	Action:      runUpdate,
 | 
			
		||||
	Flags: []cli.Flag{
 | 
			
		||||
		cli.StringFlag{
 | 
			
		||||
			Name:  "config, c",
 | 
			
		||||
			Value: "custom/conf/app.ini",
 | 
			
		||||
			Usage: "Custom configuration file path",
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runUpdate(c *cli.Context) error {
 | 
			
		||||
	if c.IsSet("config") {
 | 
			
		||||
		setting.CustomConf = c.String("config")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setup("update.log")
 | 
			
		||||
 | 
			
		||||
	if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
 | 
			
		||||
		log.GitLogger.Trace("SSH_ORIGINAL_COMMAND is empty")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	args := c.Args()
 | 
			
		||||
	if len(args) != 3 {
 | 
			
		||||
		log.GitLogger.Fatal(2, "Arguments received are not equal to three")
 | 
			
		||||
	} else if len(args[0]) == 0 {
 | 
			
		||||
		log.GitLogger.Fatal(2, "First argument 'refName' is empty, shouldn't use")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// protected branch check
 | 
			
		||||
	branchName := strings.TrimPrefix(args[0], git.BranchPrefix)
 | 
			
		||||
	repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64)
 | 
			
		||||
	log.GitLogger.Trace("pushing to %d %v", repoID, branchName)
 | 
			
		||||
	accessMode := models.ParseAccessMode(os.Getenv(models.ProtectedBranchAccessMode))
 | 
			
		||||
	// skip admin or owner AccessMode
 | 
			
		||||
	if accessMode == models.AccessModeWrite {
 | 
			
		||||
		protectBranch, err := models.GetProtectedBranchBy(repoID, branchName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.GitLogger.Fatal(2, "retrieve protected branches information failed")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if protectBranch != nil {
 | 
			
		||||
			log.GitLogger.Fatal(2, "protected branches can not be pushed to")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	task := models.UpdateTask{
 | 
			
		||||
		UUID:        os.Getenv("GITEA_UUID"),
 | 
			
		||||
		RefName:     args[0],
 | 
			
		||||
		OldCommitID: args[1],
 | 
			
		||||
		NewCommitID: args[2],
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := models.AddUpdateTask(&task); err != nil {
 | 
			
		||||
		log.GitLogger.Fatal(2, "AddUpdateTask: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								main.go
									
									
									
									
									
								
							@@ -40,5 +40,4 @@ func main() {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(4, "Failed to run app with %s: %v", os.Args, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +0,0 @@
 | 
			
		||||
-
 | 
			
		||||
  id: 1
 | 
			
		||||
  uuid: uuid1
 | 
			
		||||
  ref_name: refName1
 | 
			
		||||
  old_commit_id: oldCommitId1
 | 
			
		||||
  new_commit_id: newCommitId1
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 2
 | 
			
		||||
  uuid: uuid2
 | 
			
		||||
  ref_name: refName2
 | 
			
		||||
  old_commit_id: oldCommitId2
 | 
			
		||||
  new_commit_id: newCommitId2
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 3
 | 
			
		||||
  uuid: uuid3
 | 
			
		||||
  ref_name: refName3
 | 
			
		||||
  old_commit_id: oldCommitId3
 | 
			
		||||
  new_commit_id: newCommitId3
 | 
			
		||||
@@ -100,7 +100,6 @@ func init() {
 | 
			
		||||
		new(Release),
 | 
			
		||||
		new(LoginSource),
 | 
			
		||||
		new(Webhook),
 | 
			
		||||
		new(UpdateTask),
 | 
			
		||||
		new(HookTask),
 | 
			
		||||
		new(Team),
 | 
			
		||||
		new(OrgUser),
 | 
			
		||||
@@ -316,7 +315,6 @@ func GetStatistic() (stats Statistic) {
 | 
			
		||||
	stats.Counter.Label, _ = x.Count(new(Label))
 | 
			
		||||
	stats.Counter.HookTask, _ = x.Count(new(HookTask))
 | 
			
		||||
	stats.Counter.Team, _ = x.Count(new(Team))
 | 
			
		||||
	stats.Counter.UpdateTask, _ = x.Count(new(UpdateTask))
 | 
			
		||||
	stats.Counter.Attachment, _ = x.Count(new(Attachment))
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,40 +15,15 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UpdateTask defines an UpdateTask
 | 
			
		||||
type UpdateTask struct {
 | 
			
		||||
	ID          int64  `xorm:"pk autoincr"`
 | 
			
		||||
	UUID        string `xorm:"index"`
 | 
			
		||||
	RefName     string
 | 
			
		||||
	OldCommitID string
 | 
			
		||||
	NewCommitID string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddUpdateTask adds an UpdateTask
 | 
			
		||||
func AddUpdateTask(task *UpdateTask) error {
 | 
			
		||||
	_, err := x.Insert(task)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUpdateTaskByUUID returns update task by given UUID.
 | 
			
		||||
func GetUpdateTaskByUUID(uuid string) (*UpdateTask, error) {
 | 
			
		||||
	task := &UpdateTask{
 | 
			
		||||
		UUID: uuid,
 | 
			
		||||
	}
 | 
			
		||||
	has, err := x.Get(task)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if !has {
 | 
			
		||||
		return nil, ErrUpdateTaskNotExist{uuid}
 | 
			
		||||
	}
 | 
			
		||||
	return task, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteUpdateTaskByUUID deletes an UpdateTask from the database
 | 
			
		||||
func DeleteUpdateTaskByUUID(uuid string) error {
 | 
			
		||||
	_, err := x.Delete(&UpdateTask{UUID: uuid})
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
// env keys for git hooks need
 | 
			
		||||
const (
 | 
			
		||||
	EnvRepoName     = "GITEA_REPO_NAME"
 | 
			
		||||
	EnvRepoUsername = "GITEA_REPO_USER_NAME"
 | 
			
		||||
	EnvRepoUserSalt = "GITEA_REPO_USER_SALT"
 | 
			
		||||
	EnvRepoIsWiki   = "GITEA_REPO_IS_WIKI"
 | 
			
		||||
	EnvPusherName   = "GITEA_PUSHER_NAME"
 | 
			
		||||
	EnvPusherID     = "GITEA_PUSHER_ID"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CommitToPushCommit transforms a git.Commit to PushCommit type.
 | 
			
		||||
func CommitToPushCommit(commit *git.Commit) *PushCommit {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,40 +14,6 @@ import (
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestAddUpdateTask(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
	task := &UpdateTask{
 | 
			
		||||
		UUID:        "uuid4",
 | 
			
		||||
		RefName:     "refName4",
 | 
			
		||||
		OldCommitID: "oldCommitId4",
 | 
			
		||||
		NewCommitID: "newCommitId4",
 | 
			
		||||
	}
 | 
			
		||||
	assert.NoError(t, AddUpdateTask(task))
 | 
			
		||||
	AssertExistsAndLoadBean(t, task)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetUpdateTaskByUUID(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
	task, err := GetUpdateTaskByUUID("uuid1")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "uuid1", task.UUID)
 | 
			
		||||
	assert.Equal(t, "refName1", task.RefName)
 | 
			
		||||
	assert.Equal(t, "oldCommitId1", task.OldCommitID)
 | 
			
		||||
	assert.Equal(t, "newCommitId1", task.NewCommitID)
 | 
			
		||||
 | 
			
		||||
	_, err = GetUpdateTaskByUUID("invalid")
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.True(t, IsErrUpdateTaskNotExist(err))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteUpdateTaskByUUID(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
	assert.NoError(t, DeleteUpdateTaskByUUID("uuid1"))
 | 
			
		||||
	AssertNotExistsBean(t, &UpdateTask{UUID: "uuid1"})
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, DeleteUpdateTaskByUUID("invalid"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCommitToPushCommit(t *testing.T) {
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	sig := &git.Signature{
 | 
			
		||||
 
 | 
			
		||||
@@ -8,20 +8,15 @@ import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"compress/gzip"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/git"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
@@ -89,6 +84,7 @@ func HTTP(ctx *context.Context) {
 | 
			
		||||
		authUser     *models.User
 | 
			
		||||
		authUsername string
 | 
			
		||||
		authPasswd   string
 | 
			
		||||
		environ      []string
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	// check access
 | 
			
		||||
@@ -182,86 +178,33 @@ func HTTP(ctx *context.Context) {
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	callback := func(rpc string, input []byte) {
 | 
			
		||||
		if rpc != "receive-pack" || isWiki {
 | 
			
		||||
			return
 | 
			
		||||
		environ = []string{
 | 
			
		||||
			models.EnvRepoUsername + "=" + username,
 | 
			
		||||
			models.EnvRepoName + "=" + reponame,
 | 
			
		||||
			models.EnvRepoUserSalt + "=" + repoUser.Salt,
 | 
			
		||||
			models.EnvPusherName + "=" + authUser.Name,
 | 
			
		||||
			models.EnvPusherID + fmt.Sprintf("=%d", authUser.ID),
 | 
			
		||||
			models.ProtectedBranchRepoID + fmt.Sprintf("=%d", repo.ID),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var lastLine int64
 | 
			
		||||
		for {
 | 
			
		||||
			head := input[lastLine: lastLine + 2]
 | 
			
		||||
			if head[0] == '0' && head[1] == '0' {
 | 
			
		||||
				size, err := strconv.ParseInt(string(input[lastLine + 2:lastLine + 4]), 16, 32)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Error(4, "%v", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if size == 0 {
 | 
			
		||||
					//fmt.Println(string(input[lastLine:]))
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				line := input[lastLine: lastLine + size]
 | 
			
		||||
				idx := bytes.IndexRune(line, '\000')
 | 
			
		||||
				if idx > -1 {
 | 
			
		||||
					line = line[:idx]
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				fields := strings.Fields(string(line))
 | 
			
		||||
				if len(fields) >= 3 {
 | 
			
		||||
					oldCommitID := fields[0][4:]
 | 
			
		||||
					newCommitID := fields[1]
 | 
			
		||||
					refFullName := fields[2]
 | 
			
		||||
 | 
			
		||||
					// FIXME: handle error.
 | 
			
		||||
					if err = models.PushUpdate(models.PushUpdateOptions{
 | 
			
		||||
						RefFullName:  refFullName,
 | 
			
		||||
						OldCommitID:  oldCommitID,
 | 
			
		||||
						NewCommitID:  newCommitID,
 | 
			
		||||
						PusherID:     authUser.ID,
 | 
			
		||||
						PusherName:   authUser.Name,
 | 
			
		||||
						RepoUserName: username,
 | 
			
		||||
						RepoName:     reponame,
 | 
			
		||||
					}); err == nil {
 | 
			
		||||
						go models.AddTestPullRequestTask(authUser, repo.ID, strings.TrimPrefix(refFullName, git.BranchPrefix), true)
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
				lastLine = lastLine + size
 | 
			
		||||
		if isWiki {
 | 
			
		||||
			environ = append(environ, models.EnvRepoIsWiki+"=true")
 | 
			
		||||
		} else {
 | 
			
		||||
				break
 | 
			
		||||
			environ = append(environ, models.EnvRepoIsWiki+"=false")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	params := make(map[string]string)
 | 
			
		||||
 | 
			
		||||
	if askAuth {
 | 
			
		||||
		params[models.ProtectedBranchUserID] = fmt.Sprintf("%d", authUser.ID)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			params[models.ProtectedBranchAccessMode] = accessMode.String()
 | 
			
		||||
		}
 | 
			
		||||
		params[models.ProtectedBranchRepoID] = fmt.Sprintf("%d", repo.ID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	HTTPBackend(ctx, &serviceConfig{
 | 
			
		||||
		UploadPack:  true,
 | 
			
		||||
		ReceivePack: true,
 | 
			
		||||
		Params:      params,
 | 
			
		||||
		OnSucceed:   callback,
 | 
			
		||||
		Env:         environ,
 | 
			
		||||
	})(ctx.Resp, ctx.Req.Request)
 | 
			
		||||
 | 
			
		||||
	runtime.GC()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type serviceConfig struct {
 | 
			
		||||
	UploadPack  bool
 | 
			
		||||
	ReceivePack bool
 | 
			
		||||
	Params      map[string]string
 | 
			
		||||
	OnSucceed   func(rpc string, input []byte)
 | 
			
		||||
	Env         []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type serviceHandler struct {
 | 
			
		||||
@@ -270,6 +213,7 @@ type serviceHandler struct {
 | 
			
		||||
	r       *http.Request
 | 
			
		||||
	dir     string
 | 
			
		||||
	file    string
 | 
			
		||||
	environ []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *serviceHandler) setHeaderNoCache() {
 | 
			
		||||
@@ -278,42 +222,6 @@ func (h *serviceHandler) setHeaderNoCache() {
 | 
			
		||||
	h.w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *serviceHandler) getBranch(input []byte) string {
 | 
			
		||||
	var lastLine int64
 | 
			
		||||
	var branchName string
 | 
			
		||||
	for {
 | 
			
		||||
		head := input[lastLine : lastLine+2]
 | 
			
		||||
		if head[0] == '0' && head[1] == '0' {
 | 
			
		||||
			size, err := strconv.ParseInt(string(input[lastLine+2:lastLine+4]), 16, 32)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error(4, "%v", err)
 | 
			
		||||
				return branchName
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if size == 0 {
 | 
			
		||||
				//fmt.Println(string(input[lastLine:]))
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			line := input[lastLine : lastLine+size]
 | 
			
		||||
			idx := bytes.IndexRune(line, '\000')
 | 
			
		||||
			if idx > -1 {
 | 
			
		||||
				line = line[:idx]
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			fields := strings.Fields(string(line))
 | 
			
		||||
			if len(fields) >= 3 {
 | 
			
		||||
				refFullName := fields[2]
 | 
			
		||||
				branchName = strings.TrimPrefix(refFullName, git.BranchPrefix)
 | 
			
		||||
			}
 | 
			
		||||
			lastLine = lastLine + size
 | 
			
		||||
		} else {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return branchName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *serviceHandler) setHeaderCacheForever() {
 | 
			
		||||
	now := time.Now().Unix()
 | 
			
		||||
	expires := now + 31536000
 | 
			
		||||
@@ -414,13 +322,8 @@ func serviceRPC(h serviceHandler, service string) {
 | 
			
		||||
 | 
			
		||||
	h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		reqBody    = h.r.Body
 | 
			
		||||
		input      []byte
 | 
			
		||||
		br         io.Reader
 | 
			
		||||
		err        error
 | 
			
		||||
		branchName string
 | 
			
		||||
	)
 | 
			
		||||
	var err error
 | 
			
		||||
	var reqBody = h.r.Body
 | 
			
		||||
 | 
			
		||||
	// Handle GZIP.
 | 
			
		||||
	if h.r.Header.Get("Content-Encoding") == "gzip" {
 | 
			
		||||
@@ -432,52 +335,23 @@ func serviceRPC(h serviceHandler, service string) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if h.cfg.OnSucceed != nil {
 | 
			
		||||
		input, err = ioutil.ReadAll(reqBody)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.GitLogger.Error(2, "fail to read request body: %v", err)
 | 
			
		||||
			h.w.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		branchName = h.getBranch(input)
 | 
			
		||||
		br = bytes.NewReader(input)
 | 
			
		||||
	} else {
 | 
			
		||||
		br = reqBody
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check protected branch
 | 
			
		||||
	repoID, _ := strconv.ParseInt(h.cfg.Params[models.ProtectedBranchRepoID], 10, 64)
 | 
			
		||||
	accessMode := models.ParseAccessMode(h.cfg.Params[models.ProtectedBranchAccessMode])
 | 
			
		||||
	// skip admin or owner AccessMode
 | 
			
		||||
	if accessMode == models.AccessModeWrite {
 | 
			
		||||
		protectBranch, err := models.GetProtectedBranchBy(repoID, branchName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.GitLogger.Error(2, "fail to get protected branch information: %v", err)
 | 
			
		||||
			h.w.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if protectBranch != nil {
 | 
			
		||||
			log.GitLogger.Error(2, "protected branches can not be pushed to")
 | 
			
		||||
			h.w.WriteHeader(http.StatusForbidden)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// set this for allow pre-receive and post-receive execute
 | 
			
		||||
	h.environ = append(h.environ, "SSH_ORIGINAL_COMMAND="+service)
 | 
			
		||||
 | 
			
		||||
	var stderr bytes.Buffer
 | 
			
		||||
	cmd := exec.Command("git", service, "--stateless-rpc", h.dir)
 | 
			
		||||
	cmd.Dir = h.dir
 | 
			
		||||
	if service == "receive-pack" {
 | 
			
		||||
		cmd.Env = append(os.Environ(), h.environ...)
 | 
			
		||||
	}
 | 
			
		||||
	cmd.Stdout = h.w
 | 
			
		||||
	cmd.Stdin = br
 | 
			
		||||
	cmd.Stdin = reqBody
 | 
			
		||||
	cmd.Stderr = &stderr
 | 
			
		||||
	if err := cmd.Run(); err != nil {
 | 
			
		||||
		log.GitLogger.Error(2, "fail to serve RPC(%s): %v", service, err)
 | 
			
		||||
		log.GitLogger.Error(2, "fail to serve RPC(%s): %v - %v", service, err, stderr)
 | 
			
		||||
		h.w.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if h.cfg.OnSucceed != nil {
 | 
			
		||||
		h.cfg.OnSucceed(service, input)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func serviceUploadPack(h serviceHandler) {
 | 
			
		||||
@@ -593,7 +467,7 @@ func HTTPBackend(ctx *context.Context, cfg *serviceConfig) http.HandlerFunc {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				route.handler(serviceHandler{cfg, w, r, dir, file})
 | 
			
		||||
				route.handler(serviceHandler{cfg, w, r, dir, file, cfg.Env})
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user