Files
silverwindandwxiaoguang c62db5b2a3 perf: reduce git processes and queries on pull request view (#40224)
Removes redundant work from the pull request conversation tab.

- Base and head refs resolve through the `cat-file` batch of the git
repos the callers already hold, instead of two `git rev-parse`
processes, reducing the number of git processes from 9 to 7 per page,
about 17 ms faster locally.
- Sidebar reviewers are batch-loaded and code-review comments reuse
their loaded poster, reducing the number of SQL queries from 387 to 358
on a PR with 169 timeline entries and 15 reviews.

Also fixes the commit list of merged pull requests, which resolved the
merge base as a branch name and returned 500.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-11-05 21:58:17 +00:00

116 lines
3.9 KiB
Go

// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"context"
"errors"
"fmt"
repo_model "gitea.dev/models/repo"
"gitea.dev/modules/git"
"gitea.dev/modules/util"
)
type CompareBaseHead struct {
BaseRepo *repo_model.Repository
BaseGitRepo *git.Repository
BaseRef git.RefName
HeadRepo *repo_model.Repository
HeadGitRepo *git.Repository
HeadRef git.RefName
}
// CompareInfo represents needed information for comparing references.
type CompareInfo struct {
CompareBaseHead
BaseCommitID string
HeadCommitID string
CompareSeparator string
// CompareBase is the left-side commit ID used for comparing
// for "...": it is merge base (empty for no merge base)
// for direct comparison "..": it is base commit ID
CompareBase string
Commits []*git.Commit
NumFiles int
}
func (ci *CompareInfo) IsSameRepository() bool {
return ci.BaseRepo.ID == ci.HeadRepo.ID
}
func (ci *CompareInfo) IsSameRef() bool {
return ci.IsSameRepository() && ci.BaseRef == ci.HeadRef
}
func (ci *CompareInfo) DirectComparison() bool {
// FIXME: the design of "DirectComparison" is wrong, it loses the information of `^`
// To correctly handle the comparison, developers should use `ci.CompareSeparator` directly, all "DirectComparison" related code should be rewritten.
return ci.CompareSeparator == ".."
}
// GetCompareInfo generates and returns compare information between base and head branches of repositories.
// It does its best to fill the fields as many as it can.
// MergeBase can be empty if the base and head are unrelated.
func GetCompareInfo(ctx context.Context, bh CompareBaseHead, directComparison, fileOnly bool) (ci CompareInfo, err error) {
baseCommitID, err1 := bh.BaseGitRepo.GetRefCommitID(ctx, bh.BaseRef.String())
headCommitID, err2 := bh.HeadGitRepo.GetRefCommitID(ctx, bh.HeadRef.String())
ci = CompareInfo{
CompareBaseHead: bh,
BaseCommitID: baseCommitID,
HeadCommitID: headCommitID,
CompareSeparator: util.Iif(directComparison, "..", "..."),
}
if err1 != nil || err2 != nil {
return ci, errors.Join(err1, err2)
}
// if they are not the same repository, then we need to fetch the base commit into the head repository
// because we will use the head repository in the following code
if bh.BaseRepo.ID != bh.HeadRepo.ID {
exist := bh.HeadGitRepo.IsReferenceExist(ctx, ci.BaseCommitID)
if !exist {
if err := git.FetchRemoteTempCommit(ctx, bh.HeadRepo, bh.BaseRepo, ci.BaseCommitID); err != nil {
return ci, fmt.Errorf("FetchRemoteTempCommit: %w", err)
}
}
}
if !directComparison {
ci.CompareBase, err = git.MergeBase(ctx, bh.HeadRepo, ci.BaseCommitID, ci.HeadCommitID)
if err != nil && !errors.Is(err, util.ErrNotExist) {
return ci, fmt.Errorf("MergeBase: %w", err)
}
} else {
ci.CompareBase = ci.BaseCommitID
}
if ci.CompareBase == "" {
return ci, nil
}
// We have a common base - therefore we know that ... should work
if !fileOnly {
// In git log/rev-list, the "..." syntax represents the symmetric difference between two references,
// which is different from the meaning of "..." in git diff (where it implies diffing from the merge base).
// For listing PR commits, we must use merge-base..head to include only the commits introduced by the head branch.
// Otherwise, commits newly pushed to the base branch would also be included, which is incorrect.
ci.Commits, err = bh.HeadGitRepo.ShowPrettyFormatLogToList(ctx, ci.CompareBase+".."+ci.HeadCommitID)
if err != nil {
return ci, fmt.Errorf("ShowPrettyFormatLogToList: %w", err)
}
}
// Count number of changed files.
// TODO: 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
ci.NumFiles, err = bh.HeadGitRepo.GetDiffNumChangedFiles(ctx, ci.BaseCommitID, ci.HeadCommitID, directComparison)
return ci, err
}