Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 0 additions & 26 deletions modules/git/repo_compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
19 changes: 19 additions & 0 deletions modules/gitrepo/fetch.go
Original file line number Diff line number Diff line change
@@ -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))
}
5 changes: 5 additions & 0 deletions modules/gitrepo/gitrepo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
34 changes: 34 additions & 0 deletions modules/gitrepo/merge.go
Original file line number Diff line number Diff line change
@@ -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)
}
2 changes: 1 addition & 1 deletion services/asymkey/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
31 changes: 7 additions & 24 deletions services/issue/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
9 changes: 3 additions & 6 deletions services/migrations/gitea_uploader.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}
Expand Down
47 changes: 10 additions & 37 deletions services/pull/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
}
Expand All @@ -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
}
Expand Down
9 changes: 1 addition & 8 deletions services/pull/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
23 changes: 0 additions & 23 deletions services/repository/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
Expand Down
13 changes: 7 additions & 6 deletions services/repository/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}

Expand Down
Loading