mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 05:25:15 +01:00 
			
		
		
		
	Found using
`find . -type f -name '*.go' -print -exec vim {} -c
':%s/fmt\.Errorf(\(.*\)%v\(.*\)err/fmt.Errorf(\1%w\2err/g' -c ':wq' \;`
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
		
	
			
		
			
				
	
	
		
			809 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			809 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2017 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 cmd
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"net/http"
 | 
						|
	"os"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"code.gitea.io/gitea/modules/git"
 | 
						|
	"code.gitea.io/gitea/modules/private"
 | 
						|
	repo_module "code.gitea.io/gitea/modules/repository"
 | 
						|
	"code.gitea.io/gitea/modules/setting"
 | 
						|
	"code.gitea.io/gitea/modules/util"
 | 
						|
 | 
						|
	"github.com/urfave/cli"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	hookBatchSize = 30
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	// CmdHook represents the available hooks sub-command.
 | 
						|
	CmdHook = cli.Command{
 | 
						|
		Name:        "hook",
 | 
						|
		Usage:       "Delegate commands to corresponding Git hooks",
 | 
						|
		Description: "This should only be called by Git",
 | 
						|
		Subcommands: []cli.Command{
 | 
						|
			subcmdHookPreReceive,
 | 
						|
			subcmdHookUpdate,
 | 
						|
			subcmdHookPostReceive,
 | 
						|
			subcmdHookProcReceive,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	subcmdHookPreReceive = cli.Command{
 | 
						|
		Name:        "pre-receive",
 | 
						|
		Usage:       "Delegate pre-receive Git hook",
 | 
						|
		Description: "This command should only be called by Git",
 | 
						|
		Action:      runHookPreReceive,
 | 
						|
		Flags: []cli.Flag{
 | 
						|
			cli.BoolFlag{
 | 
						|
				Name: "debug",
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	subcmdHookUpdate = cli.Command{
 | 
						|
		Name:        "update",
 | 
						|
		Usage:       "Delegate update Git hook",
 | 
						|
		Description: "This command should only be called by Git",
 | 
						|
		Action:      runHookUpdate,
 | 
						|
		Flags: []cli.Flag{
 | 
						|
			cli.BoolFlag{
 | 
						|
				Name: "debug",
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	subcmdHookPostReceive = cli.Command{
 | 
						|
		Name:        "post-receive",
 | 
						|
		Usage:       "Delegate post-receive Git hook",
 | 
						|
		Description: "This command should only be called by Git",
 | 
						|
		Action:      runHookPostReceive,
 | 
						|
		Flags: []cli.Flag{
 | 
						|
			cli.BoolFlag{
 | 
						|
				Name: "debug",
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	// Note: new hook since git 2.29
 | 
						|
	subcmdHookProcReceive = cli.Command{
 | 
						|
		Name:        "proc-receive",
 | 
						|
		Usage:       "Delegate proc-receive Git hook",
 | 
						|
		Description: "This command should only be called by Git",
 | 
						|
		Action:      runHookProcReceive,
 | 
						|
		Flags: []cli.Flag{
 | 
						|
			cli.BoolFlag{
 | 
						|
				Name: "debug",
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
)
 | 
						|
 | 
						|
type delayWriter struct {
 | 
						|
	internal io.Writer
 | 
						|
	buf      *bytes.Buffer
 | 
						|
	timer    *time.Timer
 | 
						|
}
 | 
						|
 | 
						|
func newDelayWriter(internal io.Writer, delay time.Duration) *delayWriter {
 | 
						|
	timer := time.NewTimer(delay)
 | 
						|
	return &delayWriter{
 | 
						|
		internal: internal,
 | 
						|
		buf:      &bytes.Buffer{},
 | 
						|
		timer:    timer,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (d *delayWriter) Write(p []byte) (n int, err error) {
 | 
						|
	if d.buf != nil {
 | 
						|
		select {
 | 
						|
		case <-d.timer.C:
 | 
						|
			_, err := d.internal.Write(d.buf.Bytes())
 | 
						|
			if err != nil {
 | 
						|
				return 0, err
 | 
						|
			}
 | 
						|
			d.buf = nil
 | 
						|
			return d.internal.Write(p)
 | 
						|
		default:
 | 
						|
			return d.buf.Write(p)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return d.internal.Write(p)
 | 
						|
}
 | 
						|
 | 
						|
func (d *delayWriter) WriteString(s string) (n int, err error) {
 | 
						|
	if d.buf != nil {
 | 
						|
		select {
 | 
						|
		case <-d.timer.C:
 | 
						|
			_, err := d.internal.Write(d.buf.Bytes())
 | 
						|
			if err != nil {
 | 
						|
				return 0, err
 | 
						|
			}
 | 
						|
			d.buf = nil
 | 
						|
			return d.internal.Write([]byte(s))
 | 
						|
		default:
 | 
						|
			return d.buf.WriteString(s)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return d.internal.Write([]byte(s))
 | 
						|
}
 | 
						|
 | 
						|
func (d *delayWriter) Close() error {
 | 
						|
	if d == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	stopped := util.StopTimer(d.timer)
 | 
						|
	if stopped || d.buf == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	_, err := d.internal.Write(d.buf.Bytes())
 | 
						|
	d.buf = nil
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
type nilWriter struct{}
 | 
						|
 | 
						|
func (n *nilWriter) Write(p []byte) (int, error) {
 | 
						|
	return len(p), nil
 | 
						|
}
 | 
						|
 | 
						|
func (n *nilWriter) WriteString(s string) (int, error) {
 | 
						|
	return len(s), nil
 | 
						|
}
 | 
						|
 | 
						|
func runHookPreReceive(c *cli.Context) error {
 | 
						|
	if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	ctx, cancel := installSignals()
 | 
						|
	defer cancel()
 | 
						|
 | 
						|
	setup("hooks/pre-receive.log", c.Bool("debug"))
 | 
						|
 | 
						|
	if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
 | 
						|
		if setting.OnlyAllowPushIfGiteaEnvironmentSet {
 | 
						|
			return fail(`Rejecting changes as Gitea environment not set.
 | 
						|
If you are pushing over SSH you must push with a key managed by
 | 
						|
Gitea or set your environment appropriately.`, "")
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// the environment is set by serv command
 | 
						|
	isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
 | 
						|
	username := os.Getenv(repo_module.EnvRepoUsername)
 | 
						|
	reponame := os.Getenv(repo_module.EnvRepoName)
 | 
						|
	userID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
 | 
						|
	prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
 | 
						|
	deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64)
 | 
						|
 | 
						|
	hookOptions := private.HookOptions{
 | 
						|
		UserID:                          userID,
 | 
						|
		GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories),
 | 
						|
		GitObjectDirectory:              os.Getenv(private.GitObjectDirectory),
 | 
						|
		GitQuarantinePath:               os.Getenv(private.GitQuarantinePath),
 | 
						|
		GitPushOptions:                  pushOptions(),
 | 
						|
		PullRequestID:                   prID,
 | 
						|
		DeployKeyID:                     deployKeyID,
 | 
						|
	}
 | 
						|
 | 
						|
	scanner := bufio.NewScanner(os.Stdin)
 | 
						|
 | 
						|
	oldCommitIDs := make([]string, hookBatchSize)
 | 
						|
	newCommitIDs := make([]string, hookBatchSize)
 | 
						|
	refFullNames := make([]string, hookBatchSize)
 | 
						|
	count := 0
 | 
						|
	total := 0
 | 
						|
	lastline := 0
 | 
						|
 | 
						|
	var out io.Writer
 | 
						|
	out = &nilWriter{}
 | 
						|
	if setting.Git.VerbosePush {
 | 
						|
		if setting.Git.VerbosePushDelay > 0 {
 | 
						|
			dWriter := newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
 | 
						|
			defer dWriter.Close()
 | 
						|
			out = dWriter
 | 
						|
		} else {
 | 
						|
			out = os.Stdout
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	supportProcRecive := false
 | 
						|
	if git.CheckGitVersionAtLeast("2.29") == nil {
 | 
						|
		supportProcRecive = true
 | 
						|
	}
 | 
						|
 | 
						|
	for scanner.Scan() {
 | 
						|
		// 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])
 | 
						|
		total++
 | 
						|
		lastline++
 | 
						|
 | 
						|
		// If the ref is a branch or tag, check if it's protected
 | 
						|
		// if supportProcRecive all ref should be checked because
 | 
						|
		// permission check was delayed
 | 
						|
		if supportProcRecive || strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
 | 
						|
			oldCommitIDs[count] = oldCommitID
 | 
						|
			newCommitIDs[count] = newCommitID
 | 
						|
			refFullNames[count] = refFullName
 | 
						|
			count++
 | 
						|
			fmt.Fprintf(out, "*")
 | 
						|
 | 
						|
			if count >= hookBatchSize {
 | 
						|
				fmt.Fprintf(out, " Checking %d references\n", count)
 | 
						|
 | 
						|
				hookOptions.OldCommitIDs = oldCommitIDs
 | 
						|
				hookOptions.NewCommitIDs = newCommitIDs
 | 
						|
				hookOptions.RefFullNames = refFullNames
 | 
						|
				statusCode, msg := private.HookPreReceive(ctx, username, reponame, hookOptions)
 | 
						|
				switch statusCode {
 | 
						|
				case http.StatusOK:
 | 
						|
					// no-op
 | 
						|
				case http.StatusInternalServerError:
 | 
						|
					return fail("Internal Server Error", msg)
 | 
						|
				default:
 | 
						|
					return fail(msg, "")
 | 
						|
				}
 | 
						|
				count = 0
 | 
						|
				lastline = 0
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			fmt.Fprintf(out, ".")
 | 
						|
		}
 | 
						|
		if lastline >= hookBatchSize {
 | 
						|
			fmt.Fprintf(out, "\n")
 | 
						|
			lastline = 0
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if count > 0 {
 | 
						|
		hookOptions.OldCommitIDs = oldCommitIDs[:count]
 | 
						|
		hookOptions.NewCommitIDs = newCommitIDs[:count]
 | 
						|
		hookOptions.RefFullNames = refFullNames[:count]
 | 
						|
 | 
						|
		fmt.Fprintf(out, " Checking %d references\n", count)
 | 
						|
 | 
						|
		statusCode, msg := private.HookPreReceive(ctx, username, reponame, hookOptions)
 | 
						|
		switch statusCode {
 | 
						|
		case http.StatusInternalServerError:
 | 
						|
			return fail("Internal Server Error", msg)
 | 
						|
		case http.StatusForbidden:
 | 
						|
			return fail(msg, "")
 | 
						|
		}
 | 
						|
	} else if lastline > 0 {
 | 
						|
		fmt.Fprintf(out, "\n")
 | 
						|
	}
 | 
						|
 | 
						|
	fmt.Fprintf(out, "Checked %d references in total\n", total)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func runHookUpdate(c *cli.Context) error {
 | 
						|
	// Update is empty and is kept only for backwards compatibility
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func runHookPostReceive(c *cli.Context) error {
 | 
						|
	ctx, cancel := installSignals()
 | 
						|
	defer cancel()
 | 
						|
 | 
						|
	setup("hooks/post-receive.log", c.Bool("debug"))
 | 
						|
 | 
						|
	// First of all run update-server-info no matter what
 | 
						|
	if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil {
 | 
						|
		return fmt.Errorf("Failed to call 'git update-server-info': %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Now if we're an internal don't do anything else
 | 
						|
	if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
 | 
						|
		if setting.OnlyAllowPushIfGiteaEnvironmentSet {
 | 
						|
			return fail(`Rejecting changes as Gitea environment not set.
 | 
						|
If you are pushing over SSH you must push with a key managed by
 | 
						|
Gitea or set your environment appropriately.`, "")
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	var out io.Writer
 | 
						|
	var dWriter *delayWriter
 | 
						|
	out = &nilWriter{}
 | 
						|
	if setting.Git.VerbosePush {
 | 
						|
		if setting.Git.VerbosePushDelay > 0 {
 | 
						|
			dWriter = newDelayWriter(os.Stdout, setting.Git.VerbosePushDelay)
 | 
						|
			defer dWriter.Close()
 | 
						|
			out = dWriter
 | 
						|
		} else {
 | 
						|
			out = os.Stdout
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// the environment is set by serv command
 | 
						|
	repoUser := os.Getenv(repo_module.EnvRepoUsername)
 | 
						|
	isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
 | 
						|
	repoName := os.Getenv(repo_module.EnvRepoName)
 | 
						|
	pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
 | 
						|
	pusherName := os.Getenv(repo_module.EnvPusherName)
 | 
						|
 | 
						|
	hookOptions := private.HookOptions{
 | 
						|
		UserName:                        pusherName,
 | 
						|
		UserID:                          pusherID,
 | 
						|
		GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories),
 | 
						|
		GitObjectDirectory:              os.Getenv(private.GitObjectDirectory),
 | 
						|
		GitQuarantinePath:               os.Getenv(private.GitQuarantinePath),
 | 
						|
		GitPushOptions:                  pushOptions(),
 | 
						|
	}
 | 
						|
	oldCommitIDs := make([]string, hookBatchSize)
 | 
						|
	newCommitIDs := make([]string, hookBatchSize)
 | 
						|
	refFullNames := make([]string, hookBatchSize)
 | 
						|
	count := 0
 | 
						|
	total := 0
 | 
						|
	wasEmpty := false
 | 
						|
	masterPushed := false
 | 
						|
	results := make([]private.HookPostReceiveBranchResult, 0)
 | 
						|
 | 
						|
	scanner := bufio.NewScanner(os.Stdin)
 | 
						|
	for scanner.Scan() {
 | 
						|
		// TODO: support news feeds for wiki
 | 
						|
		if isWiki {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		fields := bytes.Fields(scanner.Bytes())
 | 
						|
		if len(fields) != 3 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		fmt.Fprintf(out, ".")
 | 
						|
		oldCommitIDs[count] = string(fields[0])
 | 
						|
		newCommitIDs[count] = string(fields[1])
 | 
						|
		refFullNames[count] = string(fields[2])
 | 
						|
		if refFullNames[count] == git.BranchPrefix+"master" && newCommitIDs[count] != git.EmptySHA && count == total {
 | 
						|
			masterPushed = true
 | 
						|
		}
 | 
						|
		count++
 | 
						|
		total++
 | 
						|
 | 
						|
		if count >= hookBatchSize {
 | 
						|
			fmt.Fprintf(out, " Processing %d references\n", count)
 | 
						|
			hookOptions.OldCommitIDs = oldCommitIDs
 | 
						|
			hookOptions.NewCommitIDs = newCommitIDs
 | 
						|
			hookOptions.RefFullNames = refFullNames
 | 
						|
			resp, err := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
 | 
						|
			if resp == nil {
 | 
						|
				_ = dWriter.Close()
 | 
						|
				hookPrintResults(results)
 | 
						|
				return fail("Internal Server Error", err)
 | 
						|
			}
 | 
						|
			wasEmpty = wasEmpty || resp.RepoWasEmpty
 | 
						|
			results = append(results, resp.Results...)
 | 
						|
			count = 0
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if count == 0 {
 | 
						|
		if wasEmpty && masterPushed {
 | 
						|
			// We need to tell the repo to reset the default branch to master
 | 
						|
			err := private.SetDefaultBranch(ctx, repoUser, repoName, "master")
 | 
						|
			if err != nil {
 | 
						|
				return fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		fmt.Fprintf(out, "Processed %d references in total\n", total)
 | 
						|
 | 
						|
		_ = dWriter.Close()
 | 
						|
		hookPrintResults(results)
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	hookOptions.OldCommitIDs = oldCommitIDs[:count]
 | 
						|
	hookOptions.NewCommitIDs = newCommitIDs[:count]
 | 
						|
	hookOptions.RefFullNames = refFullNames[:count]
 | 
						|
 | 
						|
	fmt.Fprintf(out, " Processing %d references\n", count)
 | 
						|
 | 
						|
	resp, err := private.HookPostReceive(ctx, repoUser, repoName, hookOptions)
 | 
						|
	if resp == nil {
 | 
						|
		_ = dWriter.Close()
 | 
						|
		hookPrintResults(results)
 | 
						|
		return fail("Internal Server Error", err)
 | 
						|
	}
 | 
						|
	wasEmpty = wasEmpty || resp.RepoWasEmpty
 | 
						|
	results = append(results, resp.Results...)
 | 
						|
 | 
						|
	fmt.Fprintf(out, "Processed %d references in total\n", total)
 | 
						|
 | 
						|
	if wasEmpty && masterPushed {
 | 
						|
		// We need to tell the repo to reset the default branch to master
 | 
						|
		err := private.SetDefaultBranch(ctx, repoUser, repoName, "master")
 | 
						|
		if err != nil {
 | 
						|
			return fail("Internal Server Error", "SetDefaultBranch failed with Error: %v", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	_ = dWriter.Close()
 | 
						|
	hookPrintResults(results)
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func hookPrintResults(results []private.HookPostReceiveBranchResult) {
 | 
						|
	for _, res := range results {
 | 
						|
		if !res.Message {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		fmt.Fprintln(os.Stderr, "")
 | 
						|
		if res.Create {
 | 
						|
			fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", res.Branch)
 | 
						|
			fmt.Fprintf(os.Stderr, "  %s\n", res.URL)
 | 
						|
		} else {
 | 
						|
			fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
 | 
						|
			fmt.Fprintf(os.Stderr, "  %s\n", res.URL)
 | 
						|
		}
 | 
						|
		fmt.Fprintln(os.Stderr, "")
 | 
						|
		os.Stderr.Sync()
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func pushOptions() map[string]string {
 | 
						|
	opts := make(map[string]string)
 | 
						|
	if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil {
 | 
						|
		for idx := 0; idx < pushCount; idx++ {
 | 
						|
			opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", idx))
 | 
						|
			kv := strings.SplitN(opt, "=", 2)
 | 
						|
			if len(kv) == 2 {
 | 
						|
				opts[kv[0]] = kv[1]
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return opts
 | 
						|
}
 | 
						|
 | 
						|
func runHookProcReceive(c *cli.Context) error {
 | 
						|
	setup("hooks/proc-receive.log", c.Bool("debug"))
 | 
						|
 | 
						|
	if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
 | 
						|
		if setting.OnlyAllowPushIfGiteaEnvironmentSet {
 | 
						|
			return fail(`Rejecting changes as Gitea environment not set.
 | 
						|
If you are pushing over SSH you must push with a key managed by
 | 
						|
Gitea or set your environment appropriately.`, "")
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	ctx, cancel := installSignals()
 | 
						|
	defer cancel()
 | 
						|
 | 
						|
	if git.CheckGitVersionAtLeast("2.29") != nil {
 | 
						|
		return fail("Internal Server Error", "git not support proc-receive.")
 | 
						|
	}
 | 
						|
 | 
						|
	reader := bufio.NewReader(os.Stdin)
 | 
						|
	repoUser := os.Getenv(repo_module.EnvRepoUsername)
 | 
						|
	repoName := os.Getenv(repo_module.EnvRepoName)
 | 
						|
	pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
 | 
						|
	pusherName := os.Getenv(repo_module.EnvPusherName)
 | 
						|
 | 
						|
	// 1. Version and features negotiation.
 | 
						|
	// S: PKT-LINE(version=1\0push-options atomic...) / PKT-LINE(version=1\n)
 | 
						|
	// S: flush-pkt
 | 
						|
	// H: PKT-LINE(version=1\0push-options...)
 | 
						|
	// H: flush-pkt
 | 
						|
 | 
						|
	rs, err := readPktLine(reader, pktLineTypeData)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	const VersionHead string = "version=1"
 | 
						|
 | 
						|
	var (
 | 
						|
		hasPushOptions bool
 | 
						|
		response       = []byte(VersionHead)
 | 
						|
		requestOptions []string
 | 
						|
	)
 | 
						|
 | 
						|
	index := bytes.IndexByte(rs.Data, byte(0))
 | 
						|
	if index >= len(rs.Data) {
 | 
						|
		return fail("Internal Server Error", "pkt-line: format error "+fmt.Sprint(rs.Data))
 | 
						|
	}
 | 
						|
 | 
						|
	if index < 0 {
 | 
						|
		if len(rs.Data) == 10 && rs.Data[9] == '\n' {
 | 
						|
			index = 9
 | 
						|
		} else {
 | 
						|
			return fail("Internal Server Error", "pkt-line: format error "+fmt.Sprint(rs.Data))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if string(rs.Data[0:index]) != VersionHead {
 | 
						|
		return fail("Internal Server Error", "Received unsupported version: %s", string(rs.Data[0:index]))
 | 
						|
	}
 | 
						|
	requestOptions = strings.Split(string(rs.Data[index+1:]), " ")
 | 
						|
 | 
						|
	for _, option := range requestOptions {
 | 
						|
		if strings.HasPrefix(option, "push-options") {
 | 
						|
			response = append(response, byte(0))
 | 
						|
			response = append(response, []byte("push-options")...)
 | 
						|
			hasPushOptions = true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	response = append(response, '\n')
 | 
						|
 | 
						|
	_, err = readPktLine(reader, pktLineTypeFlush)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	err = writeDataPktLine(os.Stdout, response)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	err = writeFlushPktLine(os.Stdout)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// 2. receive commands from server.
 | 
						|
	// S: PKT-LINE(<old-oid> <new-oid> <ref>)
 | 
						|
	// S: ... ...
 | 
						|
	// S: flush-pkt
 | 
						|
	// # [receive push-options]
 | 
						|
	// S: PKT-LINE(push-option)
 | 
						|
	// S: ... ...
 | 
						|
	// S: flush-pkt
 | 
						|
	hookOptions := private.HookOptions{
 | 
						|
		UserName: pusherName,
 | 
						|
		UserID:   pusherID,
 | 
						|
	}
 | 
						|
	hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize)
 | 
						|
	hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize)
 | 
						|
	hookOptions.RefFullNames = make([]string, 0, hookBatchSize)
 | 
						|
 | 
						|
	for {
 | 
						|
		// note: pktLineTypeUnknow means pktLineTypeFlush and pktLineTypeData all allowed
 | 
						|
		rs, err = readPktLine(reader, pktLineTypeUnknow)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		if rs.Type == pktLineTypeFlush {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		t := strings.SplitN(string(rs.Data), " ", 3)
 | 
						|
		if len(t) != 3 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		hookOptions.OldCommitIDs = append(hookOptions.OldCommitIDs, t[0])
 | 
						|
		hookOptions.NewCommitIDs = append(hookOptions.NewCommitIDs, t[1])
 | 
						|
		hookOptions.RefFullNames = append(hookOptions.RefFullNames, t[2])
 | 
						|
	}
 | 
						|
 | 
						|
	hookOptions.GitPushOptions = make(map[string]string)
 | 
						|
 | 
						|
	if hasPushOptions {
 | 
						|
		for {
 | 
						|
			rs, err = readPktLine(reader, pktLineTypeUnknow)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			if rs.Type == pktLineTypeFlush {
 | 
						|
				break
 | 
						|
			}
 | 
						|
 | 
						|
			kv := strings.SplitN(string(rs.Data), "=", 2)
 | 
						|
			if len(kv) == 2 {
 | 
						|
				hookOptions.GitPushOptions[kv[0]] = kv[1]
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 3. run hook
 | 
						|
	resp, err := private.HookProcReceive(ctx, repoUser, repoName, hookOptions)
 | 
						|
	if err != nil {
 | 
						|
		return fail("Internal Server Error", "run proc-receive hook failed :%v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// 4. response result to service
 | 
						|
	// # a. OK, but has an alternate reference.  The alternate reference name
 | 
						|
	// # and other status can be given in option directives.
 | 
						|
	// H: PKT-LINE(ok <ref>)
 | 
						|
	// H: PKT-LINE(option refname <refname>)
 | 
						|
	// H: PKT-LINE(option old-oid <old-oid>)
 | 
						|
	// H: PKT-LINE(option new-oid <new-oid>)
 | 
						|
	// H: PKT-LINE(option forced-update)
 | 
						|
	// H: ... ...
 | 
						|
	// H: flush-pkt
 | 
						|
	// # b. NO, I reject it.
 | 
						|
	// H: PKT-LINE(ng <ref> <reason>)
 | 
						|
	// # c. Fall through, let 'receive-pack' to execute it.
 | 
						|
	// H: PKT-LINE(ok <ref>)
 | 
						|
	// H: PKT-LINE(option fall-through)
 | 
						|
 | 
						|
	for _, rs := range resp.Results {
 | 
						|
		if len(rs.Err) > 0 {
 | 
						|
			err = writeDataPktLine(os.Stdout, []byte("ng "+rs.OriginalRef+" "+rs.Err))
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if rs.IsNotMatched {
 | 
						|
			err = writeDataPktLine(os.Stdout, []byte("ok "+rs.OriginalRef))
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			err = writeDataPktLine(os.Stdout, []byte("option fall-through"))
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		err = writeDataPktLine(os.Stdout, []byte("ok "+rs.OriginalRef))
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		err = writeDataPktLine(os.Stdout, []byte("option refname "+rs.Ref))
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if rs.OldOID != git.EmptySHA {
 | 
						|
			err = writeDataPktLine(os.Stdout, []byte("option old-oid "+rs.OldOID))
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
		err = writeDataPktLine(os.Stdout, []byte("option new-oid "+rs.NewOID))
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if rs.IsForcePush {
 | 
						|
			err = writeDataPktLine(os.Stdout, []byte("option forced-update"))
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	err = writeFlushPktLine(os.Stdout)
 | 
						|
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// git PKT-Line api
 | 
						|
// pktLineType message type of pkt-line
 | 
						|
type pktLineType int64
 | 
						|
 | 
						|
const (
 | 
						|
	// UnKnow type
 | 
						|
	pktLineTypeUnknow pktLineType = 0
 | 
						|
	// flush-pkt "0000"
 | 
						|
	pktLineTypeFlush pktLineType = iota
 | 
						|
	// data line
 | 
						|
	pktLineTypeData
 | 
						|
)
 | 
						|
 | 
						|
// gitPktLine pkt-line api
 | 
						|
type gitPktLine struct {
 | 
						|
	Type   pktLineType
 | 
						|
	Length uint64
 | 
						|
	Data   []byte
 | 
						|
}
 | 
						|
 | 
						|
func readPktLine(in *bufio.Reader, requestType pktLineType) (*gitPktLine, error) {
 | 
						|
	var (
 | 
						|
		err error
 | 
						|
		r   *gitPktLine
 | 
						|
	)
 | 
						|
 | 
						|
	// read prefix
 | 
						|
	lengthBytes := make([]byte, 4)
 | 
						|
	for i := 0; i < 4; i++ {
 | 
						|
		lengthBytes[i], err = in.ReadByte()
 | 
						|
		if err != nil {
 | 
						|
			return nil, fail("Internal Server Error", "Pkt-Line: read stdin failed : %v", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	r = new(gitPktLine)
 | 
						|
	r.Length, err = strconv.ParseUint(string(lengthBytes), 16, 32)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fail("Internal Server Error", "Pkt-Line format is wrong :%v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if r.Length == 0 {
 | 
						|
		if requestType == pktLineTypeData {
 | 
						|
			return nil, fail("Internal Server Error", "Pkt-Line format is wrong")
 | 
						|
		}
 | 
						|
		r.Type = pktLineTypeFlush
 | 
						|
		return r, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if r.Length <= 4 || r.Length > 65520 || requestType == pktLineTypeFlush {
 | 
						|
		return nil, fail("Internal Server Error", "Pkt-Line format is wrong")
 | 
						|
	}
 | 
						|
 | 
						|
	r.Data = make([]byte, r.Length-4)
 | 
						|
	for i := range r.Data {
 | 
						|
		r.Data[i], err = in.ReadByte()
 | 
						|
		if err != nil {
 | 
						|
			return nil, fail("Internal Server Error", "Pkt-Line: read stdin failed : %v", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	r.Type = pktLineTypeData
 | 
						|
 | 
						|
	return r, nil
 | 
						|
}
 | 
						|
 | 
						|
func writeFlushPktLine(out io.Writer) error {
 | 
						|
	l, err := out.Write([]byte("0000"))
 | 
						|
	if err != nil {
 | 
						|
		return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
 | 
						|
	}
 | 
						|
	if l != 4 {
 | 
						|
		return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func writeDataPktLine(out io.Writer, data []byte) error {
 | 
						|
	hexchar := []byte("0123456789abcdef")
 | 
						|
	hex := func(n uint64) byte {
 | 
						|
		return hexchar[(n)&15]
 | 
						|
	}
 | 
						|
 | 
						|
	length := uint64(len(data) + 4)
 | 
						|
	tmp := make([]byte, 4)
 | 
						|
	tmp[0] = hex(length >> 12)
 | 
						|
	tmp[1] = hex(length >> 8)
 | 
						|
	tmp[2] = hex(length >> 4)
 | 
						|
	tmp[3] = hex(length)
 | 
						|
 | 
						|
	lr, err := out.Write(tmp)
 | 
						|
	if err != nil {
 | 
						|
		return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
 | 
						|
	}
 | 
						|
	if lr != 4 {
 | 
						|
		return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	lr, err = out.Write(data)
 | 
						|
	if err != nil {
 | 
						|
		return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
 | 
						|
	}
 | 
						|
	if int(length-4) != lr {
 | 
						|
		return fail("Internal Server Error", "Pkt-Line response failed: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 |