diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go index f60696a76378d..8d26f1e9e7304 100644 --- a/modules/git/repo_compare.go +++ b/modules/git/repo_compare.go @@ -18,32 +18,6 @@ import ( "code.gitea.io/gitea/modules/git/gitcmd" ) -// GetMergeBase checks and returns merge base of two branches and the reference used as base. -func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, string, error) { - if tmpRemote == "" { - tmpRemote = "origin" - } - - if tmpRemote != "origin" { - tmpBaseName := RemotePrefix + tmpRemote + "/tmp_" + base - // Fetch commit into a temporary branch in order to be able to handle commits and tags - _, _, err := gitcmd.NewCommand("fetch", "--no-tags"). - AddDynamicArguments(tmpRemote). - AddDashesAndList(base + ":" + tmpBaseName). - WithDir(repo.Path). - RunStdString(repo.Ctx) - if err == nil { - base = tmpBaseName - } - } - - stdout, _, err := gitcmd.NewCommand("merge-base"). - AddDashesAndList(base, head). - WithDir(repo.Path). - RunStdString(repo.Ctx) - return strings.TrimSpace(stdout), base, err -} - type lineCountWriter struct { numLines int } diff --git a/modules/gitrepo/fetch.go b/modules/gitrepo/fetch.go new file mode 100644 index 0000000000000..53a4e044310c2 --- /dev/null +++ b/modules/gitrepo/fetch.go @@ -0,0 +1,19 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitrepo + +import ( + "context" + + "code.gitea.io/gitea/modules/git/gitcmd" +) + +// FetchRemoteCommit fetches a specific commit and related commits from a remote repository into the managed repository +// it will be checked in 2 weeks by default from git if the pull request created failure. +// It's enough for a temporary fetch to get the merge base. +func FetchRemoteCommit(ctx context.Context, repo, remoteRepo Repository, commitID string) error { + return RunCmd(ctx, repo, gitcmd.NewCommand("fetch", "--no-tags"). + AddDynamicArguments(repoPath(remoteRepo)). + AddDynamicArguments(commitID)) +} diff --git a/modules/gitrepo/gitrepo.go b/modules/gitrepo/gitrepo.go index 3a9b0a1c8980b..4ad08d73de886 100644 --- a/modules/gitrepo/gitrepo.go +++ b/modules/gitrepo/gitrepo.go @@ -118,3 +118,8 @@ func CreateRepoFile(ctx context.Context, repo Repository, relativeFilePath strin absoluteFilePath := filepath.Join(repoPath(repo), relativeFilePath) return os.Create(absoluteFilePath) } + +func MkRepoDir(ctx context.Context, repo Repository, relativeDirPath string) error { + absoluteDirPath := filepath.Join(repoPath(repo), relativeDirPath) + return os.MkdirAll(absoluteDirPath, os.ModePerm) +} diff --git a/modules/gitrepo/merge.go b/modules/gitrepo/merge.go new file mode 100644 index 0000000000000..2d83a1eb2ba4b --- /dev/null +++ b/modules/gitrepo/merge.go @@ -0,0 +1,34 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitrepo + +import ( + "context" + "fmt" + "strings" + + "code.gitea.io/gitea/modules/git/gitcmd" +) + +// MergeBase checks and returns merge base of two commits. +func MergeBase(ctx context.Context, repo Repository, baseCommitID, headCommitID string) (string, error) { + mergeBase, err := RunCmdString(ctx, repo, gitcmd.NewCommand("merge-base"). + AddDashesAndList(baseCommitID, headCommitID)) + if err != nil { + return "", fmt.Errorf("get merge-base of %s and %s failed: %w", baseCommitID, headCommitID, err) + } + return strings.TrimSpace(mergeBase), nil +} + +// MergeBaseFromRemote checks and returns merge base of two commits from different repositories. +func MergeBaseFromRemote(ctx context.Context, baseRepo, headRepo Repository, baseCommitID, headCommitID string) (string, error) { + // fetch head commit id into the current repository if the repositories are different + if baseRepo.RelativePath() != headRepo.RelativePath() { + if err := FetchRemoteCommit(ctx, baseRepo, headRepo, headCommitID); err != nil { + return "", fmt.Errorf("FetchRemoteCommit: %w", err) + } + } + + return MergeBase(ctx, baseRepo, baseCommitID, headCommitID) +} diff --git a/services/asymkey/sign.go b/services/asymkey/sign.go index d778ff8918be6..fb4157451aa34 100644 --- a/services/asymkey/sign.go +++ b/services/asymkey/sign.go @@ -346,7 +346,7 @@ Loop: return false, nil, nil, &ErrWontSign{commitsSigned} } // need to work out merge-base - mergeBaseCommit, _, err := gitRepo.GetMergeBase("", baseCommit, headCommit) + mergeBaseCommit, err := gitrepo.MergeBaseFromRemote(ctx, pr.BaseRepo, pr.HeadRepo, baseCommit, headCommit) if err != nil { return false, nil, nil, err } diff --git a/services/issue/pull.go b/services/issue/pull.go index 8ee14c0a4b5ae..50b40dd0df9d3 100644 --- a/services/issue/pull.go +++ b/services/issue/pull.go @@ -7,35 +7,16 @@ import ( "context" "fmt" "slices" - "time" issues_model "code.gitea.io/gitea/models/issues" org_model "code.gitea.io/gitea/models/organization" - 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/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" ) -func getMergeBase(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, pr *issues_model.PullRequest, baseBranch, headBranch string) (string, error) { - // Add a temporary remote - tmpRemote := fmt.Sprintf("mergebase-%d-%d", pr.ID, time.Now().UnixNano()) - if err := gitrepo.GitRemoteAdd(ctx, repo, tmpRemote, gitRepo.Path); err != nil { - return "", fmt.Errorf("GitRemoteAdd: %w", err) - } - defer func() { - if err := gitrepo.GitRemoteRemove(graceful.GetManager().ShutdownContext(), repo, tmpRemote); err != nil { - log.Error("getMergeBase: GitRemoteRemove: %v", err) - } - }() - - mergeBase, _, err := gitRepo.GetMergeBase(tmpRemote, baseBranch, headBranch) - return mergeBase, err -} - type ReviewRequestNotifier struct { Comment *issues_model.Comment IsAdd bool @@ -99,14 +80,16 @@ func PullRequestCodeOwnersReview(ctx context.Context, pr *issues_model.PullReque } // get the mergebase - mergeBase, err := getMergeBase(ctx, pr.BaseRepo, repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitHeadRefName()) - if err != nil { - return nil, err + if pr.MergeBase == "" { + mergeBase, err := gitrepo.MergeBaseFromRemote(ctx, pr.BaseRepo, pr.HeadRepo, git.BranchPrefix+pr.BaseBranch, pr.GetGitHeadRefName()) + if err != nil { + return nil, err + } + pr.MergeBase = mergeBase } - // https://github.com/go-gitea/gitea/issues/29763, we need to get the files changed // between the merge base and the head commit but not the base branch and the head commit - changedFiles, err := repo.GetFilesChangedBetween(mergeBase, pr.GetGitHeadRefName()) + changedFiles, err := repo.GetFilesChangedBetween(pr.MergeBase, pr.GetGitHeadRefName()) if err != nil { return nil, err } diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index 96c2655b3ab1e..b7b1631d8d363 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -8,8 +8,6 @@ import ( "context" "fmt" "io" - "os" - "path/filepath" "strconv" "strings" "time" @@ -589,12 +587,11 @@ func (g *GiteaLocalUploader) updateGitForPullRequest(ctx context.Context, pr *ba } defer ret.Close() - pullDir := filepath.Join(g.repo.RepoPath(), "pulls") - if err = os.MkdirAll(pullDir, os.ModePerm); err != nil { + if err = gitrepo.MkRepoDir(ctx, g.repo, "pulls"); err != nil { return err } - f, err := os.Create(filepath.Join(pullDir, fmt.Sprintf("%d.patch", pr.Number))) + f, err := gitrepo.CreateRepoFile(ctx, g.repo, "pulls/"+fmt.Sprintf("%d.patch", pr.Number)) if err != nil { return err } @@ -739,7 +736,7 @@ func (g *GiteaLocalUploader) newPullRequest(ctx context.Context, pr *base.PullRe if pr.Base.Ref != "" && pr.Head.SHA != "" { // A PR against a tag base does not make sense - therefore pr.Base.Ref must be a branch // TODO: should we be checking for the refs/heads/ prefix on the pr.Base.Ref? (i.e. are these actually branches or refs) - pr.Base.SHA, _, err = g.gitRepo.GetMergeBase("", git.BranchPrefix+pr.Base.Ref, pr.Head.SHA) + pr.Base.SHA, err = gitrepo.MergeBase(ctx, g.repo, git.BranchPrefix+pr.Base.Ref, pr.Head.SHA) if err != nil { log.Error("Cannot determine the merge base for PR #%d in %s/%s. Error: %v", pr.Number, g.repoOwner, g.repoName, err) } diff --git a/services/pull/compare.go b/services/pull/compare.go index fbdb17cfdd937..00a74f2972fc3 100644 --- a/services/pull/compare.go +++ b/services/pull/compare.go @@ -6,14 +6,10 @@ package pull import ( "context" "fmt" - "strconv" - "time" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" - "code.gitea.io/gitea/modules/graceful" - logger "code.gitea.io/gitea/modules/log" ) // CompareInfo represents needed information for comparing references. @@ -27,48 +23,29 @@ type CompareInfo struct { // GetCompareInfo generates and returns compare information between base and head branches of repositories. func GetCompareInfo(ctx context.Context, baseRepo, headRepo *repo_model.Repository, headGitRepo *git.Repository, baseBranch, headBranch string, directComparison, fileOnly bool) (_ *CompareInfo, err error) { - var ( - remoteBranch string - tmpRemote string - ) - - // We don't need a temporary remote for same repository. - if baseRepo.ID != headRepo.ID { - // Add a temporary remote - tmpRemote = strconv.FormatInt(time.Now().UnixNano(), 10) - if err = gitrepo.GitRemoteAdd(ctx, headRepo, tmpRemote, baseRepo.RepoPath()); err != nil { - return nil, fmt.Errorf("GitRemoteAdd: %w", err) - } - defer func() { - if err := gitrepo.GitRemoteRemove(graceful.GetManager().ShutdownContext(), headRepo, tmpRemote); err != nil { - logger.Error("GetPullRequestInfo: GitRemoteRemove: %v", err) - } - }() - } - compareInfo := new(CompareInfo) compareInfo.HeadCommitID, err = gitrepo.GetFullCommitID(ctx, headRepo, headBranch) if err != nil { compareInfo.HeadCommitID = headBranch } + compareInfo.BaseCommitID, err = gitrepo.GetFullCommitID(ctx, baseRepo, baseBranch) + if err != nil { + compareInfo.BaseCommitID = baseBranch + } - compareInfo.MergeBase, remoteBranch, err = headGitRepo.GetMergeBase(tmpRemote, baseBranch, headBranch) + compareInfo.MergeBase, err = gitrepo.MergeBaseFromRemote(ctx, baseRepo, headRepo, compareInfo.BaseCommitID, compareInfo.HeadCommitID) if err == nil { - compareInfo.BaseCommitID, err = gitrepo.GetFullCommitID(ctx, headRepo, remoteBranch) - if err != nil { - compareInfo.BaseCommitID = remoteBranch - } separator := "..." - baseCommitID := compareInfo.MergeBase + startCommitID := compareInfo.MergeBase if directComparison { separator = ".." - baseCommitID = compareInfo.BaseCommitID + startCommitID = compareInfo.BaseCommitID } // We have a common base - therefore we know that ... should work if !fileOnly { - compareInfo.Commits, err = headGitRepo.ShowPrettyFormatLogToList(ctx, baseCommitID+separator+headBranch) + compareInfo.Commits, err = headGitRepo.ShowPrettyFormatLogToList(ctx, startCommitID+separator+headBranch) if err != nil { return nil, fmt.Errorf("ShowPrettyFormatLogToList: %w", err) } @@ -77,17 +54,13 @@ func GetCompareInfo(ctx context.Context, baseRepo, headRepo *repo_model.Reposito } } else { compareInfo.Commits = []*git.Commit{} - compareInfo.MergeBase, err = gitrepo.GetFullCommitID(ctx, headRepo, remoteBranch) - if err != nil { - compareInfo.MergeBase = remoteBranch - } - compareInfo.BaseCommitID = compareInfo.MergeBase + compareInfo.MergeBase = compareInfo.BaseCommitID } // Count number of changed files. // This probably should be removed as we need to use shortstat elsewhere // Now there is git diff --shortstat but this appears to be slower than simply iterating with --nameonly - compareInfo.NumFiles, err = headGitRepo.GetDiffNumChangedFiles(remoteBranch, headBranch, directComparison) + compareInfo.NumFiles, err = headGitRepo.GetDiffNumChangedFiles(compareInfo.BaseCommitID, compareInfo.HeadCommitID, directComparison) if err != nil { return nil, err } diff --git a/services/pull/pull.go b/services/pull/pull.go index ecc0b2c7cebce..165e2750210ee 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -510,14 +510,7 @@ func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest, } defer cancel() - tmpRepo, err := git.OpenRepository(ctx, prCtx.tmpBasePath) - if err != nil { - return false, "", fmt.Errorf("OpenRepository: %w", err) - } - defer tmpRepo.Close() - - // Find the merge-base - mergeBase, _, err = tmpRepo.GetMergeBase("", "base", "tracking") + mergeBase, err = gitrepo.MergeBaseFromRemote(ctx, pr.BaseRepo, pr.HeadRepo, pr.BaseBranch, pr.HeadBranch) if err != nil { return false, "", fmt.Errorf("GetMergeBase: %w", err) } diff --git a/services/repository/generate.go b/services/repository/generate.go index b2913cd110fee..bc37bc7bfedf0 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -21,7 +21,6 @@ import ( git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/glob" "code.gitea.io/gitea/modules/log" @@ -216,19 +215,6 @@ func processGiteaTemplateFile(ctx context.Context, tmpDir string, templateRepo, } func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository, tmpDir string) error { - commitTimeStr := time.Now().Format(time.RFC3339) - authorSig := repo.Owner.NewGitSig() - - // Because this may call hooks we should pass in the environment - env := append(os.Environ(), - "GIT_AUTHOR_NAME="+authorSig.Name, - "GIT_AUTHOR_EMAIL="+authorSig.Email, - "GIT_AUTHOR_DATE="+commitTimeStr, - "GIT_COMMITTER_NAME="+authorSig.Name, - "GIT_COMMITTER_EMAIL="+authorSig.Email, - "GIT_COMMITTER_DATE="+commitTimeStr, - ) - // Clone to temporary path and do the init commit. if err := gitrepo.CloneRepoToLocal(ctx, templateRepo, tmpDir, git.CloneRepoOptions{ Depth: 1, @@ -264,15 +250,6 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r return err } - if stdout, _, err := gitcmd.NewCommand("remote", "add", "origin"). - AddDynamicArguments(repo.RepoPath()). - WithDir(tmpDir). - WithEnv(env). - RunStdString(ctx); err != nil { - log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err) - return fmt.Errorf("git remote add: %w", err) - } - if err = git.AddTemplateSubmoduleIndexes(ctx, tmpDir, submodules); err != nil { return fmt.Errorf("failed to add submodules: %v", err) } diff --git a/services/repository/init.go b/services/repository/init.go index 51cc113d63ab9..8b9081b37ecb4 100644 --- a/services/repository/init.go +++ b/services/repository/init.go @@ -11,7 +11,9 @@ import ( 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/git/gitcmd" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -71,12 +73,11 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi defaultBranch = setting.Repository.DefaultBranch } - if stdout, _, err := gitcmd.NewCommand("push", "origin"). - AddDynamicArguments("HEAD:" + defaultBranch). - WithDir(tmpPath). - WithEnv(repo_module.InternalPushingEnvironment(u, repo)). - RunStdString(ctx); err != nil { - log.Error("Failed to push back to HEAD: Stdout: %s\nError: %v", stdout, err) + if err := gitrepo.PushFromLocal(ctx, tmpPath, repo, git.PushOptions{ + Branch: 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) }