mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 09:44:21 +00:00 
			
		
		
		
	Performance optimization for git push (#30104)
Agit returned result should be from `ProcReceive` hook but not `PostReceive` hook. Then for all non-agit pull requests, it will not check the pull requests for every pushing `refs/pull/%d/head`.
This commit is contained in:
		
							
								
								
									
										37
									
								
								cmd/hook.go
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								cmd/hook.go
									
									
									
									
									
								
							@@ -448,23 +448,26 @@ Gitea or set your environment appropriately.`, "")
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
		hookPrintResult(res.Message, res.Create, res.Branch, res.URL)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func hookPrintResult(output, isCreate bool, branch, url string) {
 | 
			
		||||
	if !output {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Fprintln(os.Stderr, "")
 | 
			
		||||
	if isCreate {
 | 
			
		||||
		fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", branch)
 | 
			
		||||
		fmt.Fprintf(os.Stderr, "  %s\n", url)
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
 | 
			
		||||
		fmt.Fprintf(os.Stderr, "  %s\n", 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 {
 | 
			
		||||
@@ -691,6 +694,12 @@ Gitea or set your environment appropriately.`, "")
 | 
			
		||||
	}
 | 
			
		||||
	err = writeFlushPktLine(ctx, os.Stdout)
 | 
			
		||||
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		for _, res := range resp.Results {
 | 
			
		||||
			hookPrintResult(res.ShouldShowMessage, res.IsCreatePR, res.HeadBranch, res.URL)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/optional"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -32,13 +33,13 @@ const (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Bool checks for a key in the map and parses as a boolean
 | 
			
		||||
func (g GitPushOptions) Bool(key string, def bool) bool {
 | 
			
		||||
func (g GitPushOptions) Bool(key string) optional.Option[bool] {
 | 
			
		||||
	if val, ok := g[key]; ok {
 | 
			
		||||
		if b, err := strconv.ParseBool(val); err == nil {
 | 
			
		||||
			return b
 | 
			
		||||
			return optional.Some(b)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return def
 | 
			
		||||
	return optional.None[bool]()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HookOptions represents the options for the Hook calls
 | 
			
		||||
@@ -87,13 +88,17 @@ type HookProcReceiveResult struct {
 | 
			
		||||
 | 
			
		||||
// HookProcReceiveRefResult represents an individual result from ProcReceive
 | 
			
		||||
type HookProcReceiveRefResult struct {
 | 
			
		||||
	OldOID       string
 | 
			
		||||
	NewOID       string
 | 
			
		||||
	Ref          string
 | 
			
		||||
	OriginalRef  git.RefName
 | 
			
		||||
	IsForcePush  bool
 | 
			
		||||
	IsNotMatched bool
 | 
			
		||||
	Err          string
 | 
			
		||||
	OldOID            string
 | 
			
		||||
	NewOID            string
 | 
			
		||||
	Ref               string
 | 
			
		||||
	OriginalRef       git.RefName
 | 
			
		||||
	IsForcePush       bool
 | 
			
		||||
	IsNotMatched      bool
 | 
			
		||||
	Err               string
 | 
			
		||||
	IsCreatePR        bool
 | 
			
		||||
	URL               string
 | 
			
		||||
	ShouldShowMessage bool
 | 
			
		||||
	HeadBranch        string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HookPreReceive check whether the provided commits are allowed
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,12 @@ package private
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	git_model "code.gitea.io/gitea/models/git"
 | 
			
		||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
			
		||||
	access_model "code.gitea.io/gitea/models/perm/access"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/gitrepo"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
@@ -159,8 +160,10 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isPrivate := opts.GitPushOptions.Bool(private.GitPushOptionRepoPrivate)
 | 
			
		||||
	isTemplate := opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate)
 | 
			
		||||
	// Handle Push Options
 | 
			
		||||
	if len(opts.GitPushOptions) > 0 {
 | 
			
		||||
	if isPrivate.Has() || isTemplate.Has() {
 | 
			
		||||
		// load the repository
 | 
			
		||||
		if repo == nil {
 | 
			
		||||
			repo = loadRepository(ctx, ownerName, repoName)
 | 
			
		||||
@@ -171,13 +174,49 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
 | 
			
		||||
			wasEmpty = repo.IsEmpty
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		repo.IsPrivate = opts.GitPushOptions.Bool(private.GitPushOptionRepoPrivate, repo.IsPrivate)
 | 
			
		||||
		repo.IsTemplate = opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate, repo.IsTemplate)
 | 
			
		||||
		if err := repo_model.UpdateRepositoryCols(ctx, repo, "is_private", "is_template"); err != nil {
 | 
			
		||||
		pusher, err := user_model.GetUserByID(ctx, opts.UserID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
 | 
			
		||||
			ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
 | 
			
		||||
				Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err),
 | 
			
		||||
			})
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		perm, err := access_model.GetUserRepoPermission(ctx, repo, pusher)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
 | 
			
		||||
			ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
 | 
			
		||||
				Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err),
 | 
			
		||||
			})
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if !perm.IsOwner() && !perm.IsAdmin() {
 | 
			
		||||
			ctx.JSON(http.StatusNotFound, private.HookPostReceiveResult{
 | 
			
		||||
				Err: "Permissions denied",
 | 
			
		||||
			})
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cols := make([]string, 0, len(opts.GitPushOptions))
 | 
			
		||||
 | 
			
		||||
		if isPrivate.Has() {
 | 
			
		||||
			repo.IsPrivate = isPrivate.Value()
 | 
			
		||||
			cols = append(cols, "is_private")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if isTemplate.Has() {
 | 
			
		||||
			repo.IsTemplate = isTemplate.Value()
 | 
			
		||||
			cols = append(cols, "is_template")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(cols) > 0 {
 | 
			
		||||
			if err := repo_model.UpdateRepositoryCols(ctx, repo, cols...); err != nil {
 | 
			
		||||
				log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
 | 
			
		||||
				ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
 | 
			
		||||
					Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err),
 | 
			
		||||
				})
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -192,42 +231,6 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
 | 
			
		||||
		refFullName := opts.RefFullNames[i]
 | 
			
		||||
		newCommitID := opts.NewCommitIDs[i]
 | 
			
		||||
 | 
			
		||||
		// post update for agit pull request
 | 
			
		||||
		// FIXME: use pr.Flow to test whether it's an Agit PR or a GH PR
 | 
			
		||||
		if git.DefaultFeatures.SupportProcReceive && refFullName.IsPull() {
 | 
			
		||||
			if repo == nil {
 | 
			
		||||
				repo = loadRepository(ctx, ownerName, repoName)
 | 
			
		||||
				if ctx.Written() {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			pullIndex, _ := strconv.ParseInt(refFullName.PullName(), 10, 64)
 | 
			
		||||
			if pullIndex <= 0 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			pr, err := issues_model.GetPullRequestByIndex(ctx, repo.ID, pullIndex)
 | 
			
		||||
			if err != nil && !issues_model.IsErrPullRequestNotExist(err) {
 | 
			
		||||
				log.Error("Failed to get PR by index %v Error: %v", pullIndex, err)
 | 
			
		||||
				ctx.JSON(http.StatusInternalServerError, private.Response{
 | 
			
		||||
					Err: fmt.Sprintf("Failed to get PR by index %v Error: %v", pullIndex, err),
 | 
			
		||||
				})
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if pr == nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			results = append(results, private.HookPostReceiveBranchResult{
 | 
			
		||||
				Message: setting.Git.PullRequestPushMessage && repo.AllowsPulls(ctx),
 | 
			
		||||
				Create:  false,
 | 
			
		||||
				Branch:  "",
 | 
			
		||||
				URL:     fmt.Sprintf("%s/pulls/%d", repo.HTMLURL(), pr.Index),
 | 
			
		||||
			})
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// If we've pushed a branch (and not deleted it)
 | 
			
		||||
		if !git.IsEmptyCommitID(newCommitID) && refFullName.IsBranch() {
 | 
			
		||||
			// First ensure we have the repository loaded, we're allowed pulls requests and we can get the base repo
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/private"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	notify_service "code.gitea.io/gitea/services/notify"
 | 
			
		||||
	pull_service "code.gitea.io/gitea/services/pull"
 | 
			
		||||
)
 | 
			
		||||
@@ -145,10 +146,14 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
 | 
			
		||||
			log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
 | 
			
		||||
 | 
			
		||||
			results = append(results, private.HookProcReceiveRefResult{
 | 
			
		||||
				Ref:         pr.GetGitRefName(),
 | 
			
		||||
				OriginalRef: opts.RefFullNames[i],
 | 
			
		||||
				OldOID:      objectFormat.EmptyObjectID().String(),
 | 
			
		||||
				NewOID:      opts.NewCommitIDs[i],
 | 
			
		||||
				Ref:               pr.GetGitRefName(),
 | 
			
		||||
				OriginalRef:       opts.RefFullNames[i],
 | 
			
		||||
				OldOID:            objectFormat.EmptyObjectID().String(),
 | 
			
		||||
				NewOID:            opts.NewCommitIDs[i],
 | 
			
		||||
				IsCreatePR:        true,
 | 
			
		||||
				URL:               fmt.Sprintf("%s/pulls/%d", repo.HTMLURL(), pr.Index),
 | 
			
		||||
				ShouldShowMessage: setting.Git.PullRequestPushMessage && repo.AllowsPulls(ctx),
 | 
			
		||||
				HeadBranch:        headBranch,
 | 
			
		||||
			})
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
@@ -208,11 +213,14 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
 | 
			
		||||
		isForcePush := comment != nil && comment.IsForcePush
 | 
			
		||||
 | 
			
		||||
		results = append(results, private.HookProcReceiveRefResult{
 | 
			
		||||
			OldOID:      oldCommitID,
 | 
			
		||||
			NewOID:      opts.NewCommitIDs[i],
 | 
			
		||||
			Ref:         pr.GetGitRefName(),
 | 
			
		||||
			OriginalRef: opts.RefFullNames[i],
 | 
			
		||||
			IsForcePush: isForcePush,
 | 
			
		||||
			OldOID:            oldCommitID,
 | 
			
		||||
			NewOID:            opts.NewCommitIDs[i],
 | 
			
		||||
			Ref:               pr.GetGitRefName(),
 | 
			
		||||
			OriginalRef:       opts.RefFullNames[i],
 | 
			
		||||
			IsForcePush:       isForcePush,
 | 
			
		||||
			IsCreatePR:        false,
 | 
			
		||||
			URL:               fmt.Sprintf("%s/pulls/%d", repo.HTMLURL(), pr.Index),
 | 
			
		||||
			ShouldShowMessage: setting.Git.PullRequestPushMessage && repo.AllowsPulls(ctx),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,17 @@ func testGitPush(t *testing.T, u *url.URL) {
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("Push branch with options", func(t *testing.T) {
 | 
			
		||||
		runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) {
 | 
			
		||||
			branchName := "branch-with-options"
 | 
			
		||||
			doGitCreateBranch(gitPath, branchName)(t)
 | 
			
		||||
			doGitPushTestRepository(gitPath, "origin", branchName, "-o", "repo.private=true", "-o", "repo.template=true")(t)
 | 
			
		||||
			pushed = append(pushed, branchName)
 | 
			
		||||
 | 
			
		||||
			return pushed, deleted
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("Delete branches", func(t *testing.T) {
 | 
			
		||||
		runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) {
 | 
			
		||||
			doGitPushTestRepository(gitPath, "origin", "master")(t) // make sure master is the default branch instead of a branch we are going to delete
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user