From 20ea3e204d4de98c6cee8611fdb59b05b3862150 Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 6 Oct 2026 16:39:36 +0200 Subject: [PATCH] perf(repo): fetch the initial commit instead of pushing it (#39628) Creating a repository with an initial commit, via `auto_init` or a template with git content, pushed that commit through receive-pack and three server-side hooks that do nothing for this internal push except refresh server info. Fetching it into the bare repository instead makes such repository creation about 3.7x faster locally (median 790ms to 214ms). E2E tests that create initialized repositories get about 0.2-0.3s faster each. 1. Run `update-server-info` after template content generation, which the push's `post-receive` hook did before. 2. Remove the now unused `InternalPushingEnvironment` and `PushOptions.LocalRefName`. --- cmd/hook.go | 13 ------------- modules/git/fetch.go | 15 +++++++++++---- modules/git/repo.go | 9 +-------- modules/repository/env.go | 17 +++-------------- services/repository/init.go | 13 ++++--------- services/repository/template.go | 12 ++++++------ 6 files changed, 25 insertions(+), 54 deletions(-) diff --git a/cmd/hook.go b/cmd/hook.go index dee0d868a9b..2280d66a018 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -169,10 +169,6 @@ func parseGitHookCommitRefLine(line string) (oldCommitID, newCommitID string, re } func runHookPreReceive(ctx context.Context, c *cli.Command) error { - if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { - return nil - } - setup(ctx, c.Bool("debug")) if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { @@ -280,10 +276,6 @@ Gitea or set your environment appropriately.`, "") // runHookUpdate avoid to do heavy operations on update hook because it will be // invoked for every ref update which does not like pre-receive and post-receive func runHookUpdate(_ context.Context, c *cli.Command) error { - if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { - return nil - } - // Update is empty and is kept only for backwards compatibility if len(os.Args) < 3 { return nil @@ -304,11 +296,6 @@ func runHookPostReceive(ctx context.Context, c *cli.Command) error { 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(ctx, `Rejecting changes as Gitea environment not set. diff --git a/modules/git/fetch.go b/modules/git/fetch.go index 45208da49ea..db4648279f6 100644 --- a/modules/git/fetch.go +++ b/modules/git/fetch.go @@ -18,8 +18,15 @@ import ( // Ref: https://www.kernel.org/pub/software/scm/git/docs/git-gc.html func FetchRemoteTempCommit(ctx context.Context, repo, remoteRepo RepositoryFacade, commitID string) error { // since it is for temporary use, we don't need to update any ref or lock anything or maintain anything - return gitcmd.NewCommand("fetch", "--no-tags", "--no-write-commit-graph", "--no-write-fetch-head", "--no-auto-maintenance"). - AddDynamicArguments(gitrepo.RepoLocalPath(remoteRepo)). - AddDynamicArguments(commitID). - WithRepo(repo).Run(ctx) + return fetchRemoteCommand(remoteRepo, commitID).WithRepo(repo).Run(ctx) +} + +// FetchRemoteCommitUpdateRef fetches a commit from a remote repository and points refName at it +func FetchRemoteCommitUpdateRef(ctx context.Context, repo, remoteRepo RepositoryFacade, commitish, refName string) error { + return fetchRemoteCommand(remoteRepo, "+"+commitish+":"+refName).WithRepo(repo).RunWithStderr(ctx) +} + +func fetchRemoteCommand(remoteRepo RepositoryFacade, refspec string) *gitcmd.Command { + return gitcmd.NewCommand("fetch", "--no-tags", "--no-write-commit-graph", "--no-write-fetch-head", "--no-auto-maintenance"). + AddDynamicArguments(gitrepo.RepoLocalPath(remoteRepo), refspec) } diff --git a/modules/git/repo.go b/modules/git/repo.go index a340364c93a..2c59e902ecf 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -226,7 +226,6 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error { // PushOptions options when push to remote type PushOptions struct { Remote string - LocalRefName string Branch string Force bool ForceWithLease string @@ -248,13 +247,7 @@ func Push(ctx context.Context, localRepoPath string, opts PushOptions) error { } remoteBranchArgs := []string{opts.Remote} if len(opts.Branch) > 0 { - var refspec string - if opts.LocalRefName != "" { - refspec = fmt.Sprintf("%s:%s", opts.LocalRefName, opts.Branch) - } else { - refspec = opts.Branch - } - remoteBranchArgs = append(remoteBranchArgs, refspec) + remoteBranchArgs = append(remoteBranchArgs, opts.Branch) } cmd.AddDashesAndList(remoteBranchArgs...) diff --git a/modules/repository/env.go b/modules/repository/env.go index 1cd442a6b43..cec22cccbae 100644 --- a/modules/repository/env.go +++ b/modules/repository/env.go @@ -28,22 +28,11 @@ const ( EnvPusherID = "GITEA_PUSHER_ID" EnvPusherExtDoerData = "GITEA_PUSHER_EXT_DOER_DATA" - EnvPRID = "GITEA_PR_ID" - EnvPRIndex = "GITEA_PR_INDEX" // not used by Gitea at the moment, it is for custom git hooks - EnvIsInternal = "GITEA_INTERNAL_PUSH" - EnvAppURL = "GITEA_ROOT_URL" + EnvPRID = "GITEA_PR_ID" + EnvPRIndex = "GITEA_PR_INDEX" // not used by Gitea at the moment, it is for custom git hooks + EnvAppURL = "GITEA_ROOT_URL" ) -// InternalPushingEnvironment returns an os environment to switch off hooks on push -// It is recommended to avoid using this unless you are pushing within a transaction -// or if you absolutely are sure that post-receive and pre-receive will do nothing -// We provide the full pushing-environment for other hook providers -func InternalPushingEnvironment(doer *user_model.User, repo *repo_model.Repository) []string { - return append(PushingEnvironment(doer, repo), - EnvIsInternal+"=true", - ) -} - // PushingEnvironment returns an os environment to allow hooks to work on push func PushingEnvironment(doer *user_model.User, repo *repo_model.Repository) []string { return FullPushingEnvironment(doer, doer, repo, repo.Name, 0, 0) diff --git a/services/repository/init.go b/services/repository/init.go index 8db359f0547..e54d3c8fc36 100644 --- a/services/repository/init.go +++ b/services/repository/init.go @@ -13,8 +13,8 @@ import ( user_model "gitea.dev/models/user" "gitea.dev/modules/git" "gitea.dev/modules/git/gitcmd" + "gitea.dev/modules/git/gitrepo" "gitea.dev/modules/log" - repo_module "gitea.dev/modules/repository" asymkey_service "gitea.dev/services/asymkey" ) @@ -67,14 +67,9 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi return fmt.Errorf("git commit: %w", err) } - if err := git.PushFromLocal(ctx, tmpPath, repo, git.PushOptions{ - LocalRefName: "HEAD", - Branch: repo.DefaultBranch, - Env: repo_module.InternalPushingEnvironment(u, repo), - }); err != nil { - log.Error("Failed to push back to HEAD Error: %v", err) - return fmt.Errorf("git push: %w", err) + if err := git.FetchRemoteCommitUpdateRef(ctx, repo, gitrepo.RepositoryUnmanaged(tmpPath), "HEAD", git.BranchPrefix+repo.DefaultBranch); err != nil { + log.Error("Failed to fetch initial commit: %v", err) + return fmt.Errorf("git fetch: %w", err) } - return nil } diff --git a/services/repository/template.go b/services/repository/template.go index 6aad16b51b2..e0f3a814de4 100644 --- a/services/repository/template.go +++ b/services/repository/template.go @@ -128,12 +128,7 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ return nil, fmt.Errorf("createDelegateHooks: %w", err) } - // 4 - Update the git repository - if err = updateGitRepoAfterCreate(ctx, generateRepo); err != nil { - return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) - } - - // 5 - generate the repository contents according to the template + // 4 - generate the repository contents according to the template // Git Content if opts.GitContent && !templateRepo.IsEmpty { if err = GenerateGitContent(ctx, templateRepo, generateRepo); err != nil { @@ -182,6 +177,11 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ } } + // 5 - Update the git repository + if err = updateGitRepoAfterCreate(ctx, generateRepo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) + } + // 6 - update repository status to be ready generateRepo.Status = repo_model.RepositoryReady if err = repo_model.UpdateRepositoryColsWithAutoTime(ctx, generateRepo, "status"); err != nil {