Skip to content
Draft
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
61 changes: 16 additions & 45 deletions services/git/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,10 @@ package git
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 Down Expand Up @@ -41,56 +37,29 @@ func (ci *CompareInfo) IsSameRef() bool {

// 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, baseRef, headRef git.RefName, 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 := &CompareInfo{
BaseRepo: baseRepo,
BaseRef: baseRef,
HeadRepo: headRepo,
HeadGitRepo: headGitRepo,
HeadRef: headRef,
DirectComparison: directComparison,
}
compareInfo := new(CompareInfo)

compareInfo.HeadCommitID, err = gitrepo.GetFullCommitID(ctx, headRepo, headRef.String())
if err != nil {
compareInfo.HeadCommitID = headRef.String()
}
compareInfo.BaseCommitID, err = gitrepo.GetFullCommitID(ctx, baseRepo, baseRef.String())
if err != nil {
compareInfo.BaseCommitID = baseRef.String()
}

// FIXME: It seems we don't need mergebase if it's a direct comparison?
compareInfo.MergeBase, remoteBranch, err = headGitRepo.GetMergeBase(tmpRemote, baseRef.String(), headRef.String())
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+headRef.String())
compareInfo.Commits, err = headGitRepo.ShowPrettyFormatLogToList(ctx, startCommitID+separator+headRef.String())
if err != nil {
return nil, fmt.Errorf("ShowPrettyFormatLogToList: %w", err)
}
Expand All @@ -99,17 +68,19 @@ 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.MergeBase = compareInfo.BaseCommitID
}

if baseRepo.ID != headRepo.ID {
if err := gitrepo.FetchRemoteCommit(ctx, headRepo, baseRepo, compareInfo.BaseCommitID); err != nil {
return nil, fmt.Errorf("FetchRemoteCommit: %w", err)
}
compareInfo.BaseCommitID = compareInfo.MergeBase
}

// 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, headRef.String(), directComparison)
compareInfo.NumFiles, err = headGitRepo.GetDiffNumChangedFiles(compareInfo.BaseCommitID, compareInfo.HeadCommitID, directComparison)
if err != nil {
return 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
9 changes: 1 addition & 8 deletions services/pull/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,14 +511,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
Loading