mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Storage buckets, indexer names, the bleve analyzer, the lock prefix, SSH host keys, the markdown front-matter key, the container data paths, the RPM repo id and the API security scheme all read as this product now, and app.example.ini matches the code again. The upstream maintainer list and the changelog generator config went with the release history they belonged to, and the citation comments state the fact rather than link the issue. Three inert rules turn out to have been inert because they named a module path this tree does not use: gci grouped no first-party imports under prefix(), and depguard let models/migrations import models and modules/structs. They now name github.com/hanzoai/git. The config example also documented .gitea/workflows while the code has read .hanzo/workflows for some time. The authorized_keys marker follows the delegate-hook precedent: the new name is written and the old one is still recognised, because a marker we do not know is copied through as a hand-added key and leaves a revoked key authorized. The stored user_setting key keeps its value; only a migration may change that. Co-authored-by: Hanzo Dev <[email protected]>
197 lines
6.5 KiB
Go
197 lines
6.5 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
||
// SPDX-License-Identifier: MIT
|
||
|
||
package git
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"runtime"
|
||
"strings"
|
||
|
||
"github.com/hanzoai/git/modules/git/gitcmd"
|
||
"github.com/hanzoai/git/modules/log"
|
||
"github.com/hanzoai/git/modules/setting"
|
||
"github.com/hanzoai/git/modules/tempdir"
|
||
"github.com/hanzoai/git/modules/testlogger"
|
||
|
||
"github.com/hashicorp/go-version"
|
||
)
|
||
|
||
const RequiredVersion = "2.13.0" // the minimum Git version required
|
||
|
||
type Features struct {
|
||
gitVersion *version.Version
|
||
|
||
UsingGogit bool
|
||
SupportProcReceive bool // >= 2.29
|
||
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
|
||
SupportedObjectFormats []ObjectFormat // sha1, sha256
|
||
SupportCheckAttrOnBare bool // >= 2.40
|
||
SupportCatFileBatchCommand bool // >= 2.36, support `git cat-file --batch-command`
|
||
SupportGitMergeTree bool // >= 2.40 // we also need "--merge-base"
|
||
}
|
||
|
||
var defaultFeatures *Features
|
||
|
||
func (f *Features) CheckVersionAtLeast(atLeast string) bool {
|
||
return f.gitVersion.Compare(version.Must(version.NewVersion(atLeast))) >= 0
|
||
}
|
||
|
||
// VersionInfo returns git version information
|
||
func (f *Features) VersionInfo() string {
|
||
return f.gitVersion.Original()
|
||
}
|
||
|
||
func DefaultFeatures() *Features {
|
||
if defaultFeatures == nil {
|
||
if !setting.IsProd || setting.IsInTesting {
|
||
log.Warn("git.DefaultFeatures is called before git.InitXxx, initializing with default values")
|
||
}
|
||
if err := InitSimple(); err != nil {
|
||
log.Fatal("git.InitSimple failed: %v", err)
|
||
}
|
||
}
|
||
return defaultFeatures
|
||
}
|
||
|
||
func loadGitVersionFeatures() (*Features, error) {
|
||
stdout, _, runErr := gitcmd.NewCommand("version").RunStdString(context.Background())
|
||
if runErr != nil {
|
||
return nil, runErr
|
||
}
|
||
|
||
ver, err := parseGitVersionLine(strings.TrimSpace(stdout))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
features := &Features{gitVersion: ver, UsingGogit: isGogit}
|
||
features.SupportProcReceive = features.CheckVersionAtLeast("2.29")
|
||
features.SupportHashSha256 = features.CheckVersionAtLeast("2.42") && !isGogit
|
||
features.SupportedObjectFormats = []ObjectFormat{Sha1ObjectFormat}
|
||
if features.SupportHashSha256 {
|
||
features.SupportedObjectFormats = append(features.SupportedObjectFormats, Sha256ObjectFormat)
|
||
}
|
||
features.SupportCheckAttrOnBare = features.CheckVersionAtLeast("2.40")
|
||
features.SupportCatFileBatchCommand = features.CheckVersionAtLeast("2.36")
|
||
features.SupportGitMergeTree = features.CheckVersionAtLeast("2.40") // we also need "--merge-base"
|
||
return features, nil
|
||
}
|
||
|
||
func parseGitVersionLine(s string) (*version.Version, error) {
|
||
fields := strings.Fields(s)
|
||
if len(fields) < 3 {
|
||
return nil, fmt.Errorf("invalid git version: %q", s)
|
||
}
|
||
|
||
// version output is like: "git version {versionString}"
|
||
// versionString can be:
|
||
// * "2.5.3"
|
||
// * "2.29.3.windows.1"
|
||
// * "2.28.0.618.gf4bc123cb7": a build from a git checkout
|
||
versionString := fields[2]
|
||
versionFields := strings.Split(versionString, ".")
|
||
if len(versionFields) > 3 {
|
||
versionFields = versionFields[:3]
|
||
}
|
||
return version.NewVersion(strings.Join(versionFields, "."))
|
||
}
|
||
|
||
func checkGitVersionCompatibility(gitVer *version.Version) error {
|
||
badVersions := []struct {
|
||
Version *version.Version
|
||
Reason string
|
||
}{
|
||
{version.Must(version.NewVersion("2.43.1")), "regression bug of GIT_FLUSH"},
|
||
}
|
||
for _, bad := range badVersions {
|
||
if gitVer.Equal(bad.Version) {
|
||
return errors.New(bad.Reason)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func ensureGitVersion() error {
|
||
if !DefaultFeatures().CheckVersionAtLeast(RequiredVersion) {
|
||
moreHint := "get git: https://git-scm.com/downloads"
|
||
if runtime.GOOS == "linux" {
|
||
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
|
||
if _, err := os.Stat("/etc/redhat-release"); err == nil {
|
||
// ius.io is the recommended official(git-scm.com) method to install git
|
||
moreHint = "get git: https://git-scm.com/downloads/linux and https://ius.io"
|
||
}
|
||
}
|
||
return fmt.Errorf("installed git version %q is not supported, Hanzo Git requires git version >= %q, %s", DefaultFeatures().gitVersion.Original(), RequiredVersion, moreHint)
|
||
}
|
||
|
||
if err := checkGitVersionCompatibility(DefaultFeatures().gitVersion); err != nil {
|
||
return fmt.Errorf("installed git version %s has a known compatibility issue with Hanzo Git: %w, please upgrade (or downgrade) git", DefaultFeatures().gitVersion.String(), err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
|
||
// This method doesn't change anything to filesystem. At the moment, it is only used by some Hanzo Git sub-commands.
|
||
func InitSimple() error {
|
||
if setting.Git.HomePath == "" {
|
||
return errors.New("unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
|
||
}
|
||
|
||
if defaultFeatures != nil && (!setting.IsProd || setting.IsInTesting) {
|
||
log.Warn("git module has been initialized already, duplicate init may work but it's better to fix it")
|
||
}
|
||
|
||
if err := gitcmd.SetExecutablePath(setting.Git.Path); err != nil {
|
||
return err
|
||
}
|
||
|
||
var err error
|
||
defaultFeatures, err = loadGitVersionFeatures()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err = ensureGitVersion(); err != nil {
|
||
return err
|
||
}
|
||
|
||
// when git works with gnupg (commit signing), there should be a stable home for gnupg commands
|
||
if _, ok := os.LookupEnv("GNUPGHOME"); !ok {
|
||
_ = os.Setenv("GNUPGHOME", filepath.Join(gitcmd.HomeDir(), ".gnupg"))
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// InitFull initializes git module with version check and change global variables, sync gitconfig.
|
||
// It should only be called once at the beginning of the program initialization (TestMain/GlobalInitInstalled) as this code makes unsynchronized changes to variables.
|
||
func InitFull() (err error) {
|
||
if err = InitSimple(); err != nil {
|
||
return err
|
||
}
|
||
return syncGitConfig(context.Background())
|
||
}
|
||
|
||
// RunGitTests helps to init the git module and run tests.
|
||
// FIXME: GIT-PACKAGE-DEPENDENCY: the dependency is not right, setting.Git.HomePath is initialized in this package but used in gitcmd package
|
||
func RunGitTests(m interface{ Run() int }) {
|
||
os.Exit(runGitTests(m))
|
||
}
|
||
|
||
func runGitTests(m interface{ Run() int }) int {
|
||
gitHomePath, cleanup, err := tempdir.OsTempDir("git-test").MkdirTempRandom("git-home")
|
||
if err != nil {
|
||
return testlogger.MainErrorf("unable to create temp dir: %v", err)
|
||
}
|
||
defer cleanup()
|
||
|
||
setting.Git.HomePath = gitHomePath
|
||
if err = InitFull(); err != nil {
|
||
return testlogger.MainErrorf("failed to call Init: %v", err)
|
||
}
|
||
return m.Run()
|
||
}
|