mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 00:20:25 +08:00 
			
		
		
		
	Propagate context and ensure git commands run in request context (#17868)
This PR continues the work in #17125 by progressively ensuring that git commands run within the request context. This now means that the if there is a git repo already open in the context it will be used instead of reopening it. Signed-off-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
		@@ -303,6 +303,7 @@ func APIContexter() func(http.Handler) http.Handler {
 | 
			
		||||
			ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
 | 
			
		||||
 | 
			
		||||
			ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken())
 | 
			
		||||
			ctx.Data["Context"] = &ctx
 | 
			
		||||
 | 
			
		||||
			next.ServeHTTP(ctx.Resp, ctx.Req)
 | 
			
		||||
 | 
			
		||||
@@ -321,35 +322,32 @@ func APIContexter() func(http.Handler) http.Handler {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReferencesGitRepo injects the GitRepo into the Context
 | 
			
		||||
func ReferencesGitRepo(allowEmpty bool) func(http.Handler) http.Handler {
 | 
			
		||||
	return func(next http.Handler) http.Handler {
 | 
			
		||||
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			ctx := GetAPIContext(req)
 | 
			
		||||
			// Empty repository does not have reference information.
 | 
			
		||||
			if !allowEmpty && ctx.Repo.Repository.IsEmpty {
 | 
			
		||||
func ReferencesGitRepo(allowEmpty bool) func(ctx *APIContext) (cancel context.CancelFunc) {
 | 
			
		||||
	return func(ctx *APIContext) (cancel context.CancelFunc) {
 | 
			
		||||
		// Empty repository does not have reference information.
 | 
			
		||||
		if !allowEmpty && ctx.Repo.Repository.IsEmpty {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// For API calls.
 | 
			
		||||
		if ctx.Repo.GitRepo == nil {
 | 
			
		||||
			repoPath := repo_model.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
 | 
			
		||||
			gitRepo, err := git.OpenRepositoryCtx(ctx, repoPath)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.Error(http.StatusInternalServerError, "RepoRef Invalid repo "+repoPath, err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// For API calls.
 | 
			
		||||
			if ctx.Repo.GitRepo == nil {
 | 
			
		||||
				repoPath := repo_model.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
 | 
			
		||||
				gitRepo, err := git.OpenRepository(repoPath)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					ctx.Error(http.StatusInternalServerError, "RepoRef Invalid repo "+repoPath, err)
 | 
			
		||||
					return
 | 
			
		||||
			ctx.Repo.GitRepo = gitRepo
 | 
			
		||||
			// We opened it, we should close it
 | 
			
		||||
			return func() {
 | 
			
		||||
				// If it's been set to nil then assume someone else has closed it.
 | 
			
		||||
				if ctx.Repo.GitRepo != nil {
 | 
			
		||||
					ctx.Repo.GitRepo.Close()
 | 
			
		||||
				}
 | 
			
		||||
				ctx.Repo.GitRepo = gitRepo
 | 
			
		||||
				// We opened it, we should close it
 | 
			
		||||
				defer func() {
 | 
			
		||||
					// If it's been set to nil then assume someone else has closed it.
 | 
			
		||||
					if ctx.Repo.GitRepo != nil {
 | 
			
		||||
						ctx.Repo.GitRepo.Close()
 | 
			
		||||
					}
 | 
			
		||||
				}()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
			next.ServeHTTP(w, req)
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -391,7 +389,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
 | 
			
		||||
 | 
			
		||||
		if ctx.Repo.GitRepo == nil {
 | 
			
		||||
			repoPath := repo_model.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
 | 
			
		||||
			ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
 | 
			
		||||
			ctx.Repo.GitRepo, err = git.OpenRepositoryCtx(ctx, repoPath)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.InternalServerError(err)
 | 
			
		||||
				return
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ import (
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
	mc "code.gitea.io/gitea/modules/cache"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/json"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
@@ -529,6 +530,10 @@ func (ctx *Context) Err() error {
 | 
			
		||||
 | 
			
		||||
// Value is part of the interface for context.Context and we pass this to the request context
 | 
			
		||||
func (ctx *Context) Value(key interface{}) interface{} {
 | 
			
		||||
	if key == git.RepositoryContextKey && ctx.Repo != nil {
 | 
			
		||||
		return ctx.Repo.GitRepo
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ctx.Req.Context().Value(key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -631,6 +636,7 @@ func Contexter() func(next http.Handler) http.Handler {
 | 
			
		||||
			// PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
 | 
			
		||||
			ctx.PageData = map[string]interface{}{}
 | 
			
		||||
			ctx.Data["PageData"] = ctx.PageData
 | 
			
		||||
			ctx.Data["Context"] = &ctx
 | 
			
		||||
 | 
			
		||||
			ctx.Req = WithContext(req, &ctx)
 | 
			
		||||
			ctx.csrf = Csrfer(csrfOpts, &ctx)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,42 @@ package context
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/graceful"
 | 
			
		||||
	"code.gitea.io/gitea/modules/process"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// PrivateContext represents a context for private routes
 | 
			
		||||
type PrivateContext struct {
 | 
			
		||||
	*Context
 | 
			
		||||
	Override context.Context
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Deadline is part of the interface for context.Context and we pass this to the request context
 | 
			
		||||
func (ctx *PrivateContext) Deadline() (deadline time.Time, ok bool) {
 | 
			
		||||
	if ctx.Override != nil {
 | 
			
		||||
		return ctx.Override.Deadline()
 | 
			
		||||
	}
 | 
			
		||||
	return ctx.Req.Context().Deadline()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Done is part of the interface for context.Context and we pass this to the request context
 | 
			
		||||
func (ctx *PrivateContext) Done() <-chan struct{} {
 | 
			
		||||
	if ctx.Override != nil {
 | 
			
		||||
		return ctx.Override.Done()
 | 
			
		||||
	}
 | 
			
		||||
	return ctx.Req.Context().Done()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Err is part of the interface for context.Context and we pass this to the request context
 | 
			
		||||
func (ctx *PrivateContext) Err() error {
 | 
			
		||||
	if ctx.Override != nil {
 | 
			
		||||
		return ctx.Override.Err()
 | 
			
		||||
	}
 | 
			
		||||
	return ctx.Req.Context().Err()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
@@ -39,7 +69,18 @@ func PrivateContexter() func(http.Handler) http.Handler {
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Req = WithPrivateContext(req, ctx)
 | 
			
		||||
			ctx.Data["Context"] = ctx
 | 
			
		||||
			next.ServeHTTP(ctx.Resp, ctx.Req)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OverrideContext overrides the underlying request context for Done() etc.
 | 
			
		||||
// This function should be used when there is a need for work to continue even if the request has been cancelled.
 | 
			
		||||
// Primarily this affects hook/post-receive and hook/proc-receive both of which need to continue working even if
 | 
			
		||||
// the underlying request has timed out from the ssh/http push
 | 
			
		||||
func OverrideContext(ctx *PrivateContext) (cancel context.CancelFunc) {
 | 
			
		||||
	// We now need to override the request context as the base for our work because even if the request is cancelled we have to continue this work
 | 
			
		||||
	ctx.Override, _, cancel = process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PrivateContext: %s", ctx.Req.RequestURI))
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -109,7 +109,7 @@ type CanCommitToBranchResults struct {
 | 
			
		||||
 | 
			
		||||
// CanCommitToBranch returns true if repository is editable and user has proper access level
 | 
			
		||||
//   and branch is not protected for push
 | 
			
		||||
func (r *Repository) CanCommitToBranch(doer *user_model.User) (CanCommitToBranchResults, error) {
 | 
			
		||||
func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.User) (CanCommitToBranchResults, error) {
 | 
			
		||||
	protectedBranch, err := models.GetProtectedBranchBy(r.Repository.ID, r.BranchName)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -122,7 +122,7 @@ func (r *Repository) CanCommitToBranch(doer *user_model.User) (CanCommitToBranch
 | 
			
		||||
		requireSigned = protectedBranch.RequireSignedCommits
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sign, keyID, _, err := asymkey_service.SignCRUDAction(r.Repository.RepoPath(), doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName)
 | 
			
		||||
	sign, keyID, _, err := asymkey_service.SignCRUDAction(ctx, r.Repository.RepoPath(), doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName)
 | 
			
		||||
 | 
			
		||||
	canCommit := r.CanEnableEditor() && userCanPush
 | 
			
		||||
	if requireSigned {
 | 
			
		||||
@@ -180,14 +180,14 @@ func (r *Repository) GetCommitsCount() (int64, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetCommitGraphsCount returns cached commit count for current view
 | 
			
		||||
func (r *Repository) GetCommitGraphsCount(hidePRRefs bool, branches, files []string) (int64, error) {
 | 
			
		||||
func (r *Repository) GetCommitGraphsCount(ctx context.Context, hidePRRefs bool, branches, files []string) (int64, error) {
 | 
			
		||||
	cacheKey := fmt.Sprintf("commits-count-%d-graph-%t-%s-%s", r.Repository.ID, hidePRRefs, branches, files)
 | 
			
		||||
 | 
			
		||||
	return cache.GetInt64(cacheKey, func() (int64, error) {
 | 
			
		||||
		if len(branches) == 0 {
 | 
			
		||||
			return git.AllCommitsCount(r.Repository.RepoPath(), hidePRRefs, files...)
 | 
			
		||||
			return git.AllCommitsCount(ctx, r.Repository.RepoPath(), hidePRRefs, files...)
 | 
			
		||||
		}
 | 
			
		||||
		return git.CommitsCountFiles(r.Repository.RepoPath(), branches, files)
 | 
			
		||||
		return git.CommitsCountFiles(ctx, r.Repository.RepoPath(), branches, files)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user