mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
114 lines
4.2 KiB
Go
114 lines
4.2 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package git
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
repo_model "github.com/hanzoai/git/models/repo"
|
|
"github.com/hanzoai/git/modules/git"
|
|
"github.com/hanzoai/git/modules/gitrepo"
|
|
"github.com/hanzoai/git/modules/util"
|
|
)
|
|
|
|
// CompareInfo represents needed information for comparing references.
|
|
type CompareInfo struct {
|
|
BaseRepo *repo_model.Repository
|
|
BaseRef git.RefName
|
|
BaseCommitID string
|
|
HeadRepo *repo_model.Repository
|
|
HeadGitRepo *git.Repository
|
|
HeadRef git.RefName
|
|
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, baseRepo, headRepo *repo_model.Repository, headGitRepo *git.Repository, baseRef, headRef git.RefName, directComparison, fileOnly bool) (compareInfo CompareInfo, err error) {
|
|
baseCommitID, err1 := gitrepo.GetFullCommitID(ctx, baseRepo, baseRef.String())
|
|
headCommitID, err2 := gitrepo.GetFullCommitID(ctx, headRepo, headRef.String())
|
|
compareInfo = CompareInfo{
|
|
BaseRepo: baseRepo,
|
|
BaseRef: baseRef,
|
|
BaseCommitID: baseCommitID,
|
|
HeadRepo: headRepo,
|
|
HeadGitRepo: headGitRepo,
|
|
HeadRef: headRef,
|
|
HeadCommitID: headCommitID,
|
|
CompareSeparator: util.Iif(directComparison, "..", "..."),
|
|
}
|
|
if err1 != nil || err2 != nil {
|
|
return compareInfo, 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 headGitRepo in the following code
|
|
if baseRepo.ID != headRepo.ID {
|
|
exist := headGitRepo.IsReferenceExist(compareInfo.BaseCommitID)
|
|
if !exist {
|
|
if err := gitrepo.FetchRemoteCommit(ctx, headRepo, baseRepo, compareInfo.BaseCommitID); err != nil {
|
|
return compareInfo, fmt.Errorf("FetchRemoteCommit: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if !directComparison {
|
|
compareInfo.CompareBase, err = gitrepo.MergeBase(ctx, headRepo, compareInfo.BaseCommitID, compareInfo.HeadCommitID)
|
|
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
|
return compareInfo, fmt.Errorf("MergeBase: %w", err)
|
|
}
|
|
} else {
|
|
compareInfo.CompareBase = compareInfo.BaseCommitID
|
|
}
|
|
|
|
if compareInfo.CompareBase == "" {
|
|
return compareInfo, 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.
|
|
compareInfo.Commits, err = headGitRepo.ShowPrettyFormatLogToList(ctx, compareInfo.CompareBase+".."+compareInfo.HeadCommitID)
|
|
if err != nil {
|
|
return compareInfo, 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
|
|
compareInfo.NumFiles, err = headGitRepo.GetDiffNumChangedFiles(compareInfo.BaseCommitID, compareInfo.HeadCommitID, directComparison)
|
|
return compareInfo, err
|
|
}
|