2016-11-03 16:16:01 -06:00
// Copyright 2015 The Gogs Authors. All rights reserved.
2017-04-28 08:20:58 -06:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2022-11-27 11:20:29 -07:00
// SPDX-License-Identifier: MIT
2016-11-03 16:16:01 -06:00
package git
import (
2019-11-30 07:40:22 -07:00
"context"
2022-06-16 09:47:44 -06:00
"errors"
2016-11-03 16:16:01 -06:00
"fmt"
2022-05-02 06:30:24 -06:00
"os"
2019-04-17 05:11:37 -06:00
"os/exec"
2022-07-08 02:09:07 -06:00
"path/filepath"
2019-11-01 23:40:49 -06:00
"runtime"
2016-11-03 16:16:01 -06:00
"strings"
"time"
2017-04-28 08:20:58 -06:00
2022-06-09 19:57:49 -06:00
"code.gitea.io/gitea/modules/log"
2021-06-26 05:28:55 -06:00
"code.gitea.io/gitea/modules/setting"
2022-07-15 07:01:32 -06:00
2020-09-05 10:42:58 -06:00
"github.com/hashicorp/go-version"
2016-11-03 16:16:01 -06:00
)
2024-05-06 10:34:16 -06:00
const RequiredVersion = "2.0.0" // the minimum Git version required
2019-04-17 05:11:37 -06:00
2024-05-06 10:34:16 -06:00
type Features struct {
gitVersion * version . Version
2019-11-30 07:40:22 -07:00
2024-05-06 10:34:16 -06:00
UsingGogit bool
SupportProcReceive bool // >= 2.29
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘ experimental curiosity’
SupportedObjectFormats [ ] ObjectFormat // sha1, sha256
}
2016-11-03 16:16:01 -06:00
2024-05-06 10:34:16 -06:00
var (
GitExecutable = "git" // the command name of git, will be updated to an absolute path during initialization
DefaultContext context . Context // the default context to run git commands in, must be initialized by git.InitXxx
defaultFeatures * Features
2022-06-09 19:57:49 -06:00
)
2020-09-05 10:42:58 -06:00
2024-05-06 10:34:16 -06:00
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 ( context . Background ( ) ) ; err != nil {
log . Fatal ( "git.InitSimple failed: %v" , err )
}
2016-11-03 16:16:01 -06:00
}
2024-05-06 10:34:16 -06:00
return defaultFeatures
}
2016-11-03 16:16:01 -06:00
2024-05-06 10:34:16 -06:00
func loadGitVersionFeatures ( ) ( * Features , error ) {
2022-06-09 19:57:49 -06:00
stdout , _ , runErr := NewCommand ( DefaultContext , "version" ) . RunStdString ( nil )
2022-03-31 20:55:30 -06:00
if runErr != nil {
2024-05-06 10:34:16 -06:00
return nil , runErr
2016-11-03 16:16:01 -06:00
}
2024-02-14 10:18:30 -07:00
ver , err := parseGitVersionLine ( strings . TrimSpace ( stdout ) )
2024-05-06 10:34:16 -06:00
if err != nil {
return nil , err
2016-11-03 16:16:01 -06:00
}
2024-05-06 10:34:16 -06:00
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 )
}
return features , nil
2024-02-14 10:18:30 -07:00
}
2016-11-03 16:16:01 -06:00
2024-02-14 10:18:30 -07:00
func parseGitVersionLine ( s string ) ( * version . Version , error ) {
fields := strings . Fields ( s )
if len ( fields ) < 3 {
return nil , fmt . Errorf ( "invalid git version: %q" , s )
2016-11-03 16:16:01 -06:00
}
2024-02-14 10:18:30 -07:00
// version string is like: "git version 2.29.3" or "git version 2.29.3.windows.1"
versionString := fields [ 2 ]
if pos := strings . Index ( versionString , "windows" ) ; pos >= 1 {
versionString = versionString [ : pos - 1 ]
}
return version . NewVersion ( versionString )
2016-11-03 16:16:01 -06:00
}
2024-11-20 12:26:12 -07:00
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" } ,
2019-07-07 01:26:56 -06:00
}
2024-11-20 12:26:12 -07:00
for _ , bad := range badVersions {
if gitVer . Equal ( bad . Version ) {
return errors . New ( bad . Reason )
}
2019-04-17 05:11:37 -06:00
}
2024-05-06 10:34:16 -06:00
return nil
}
2019-04-17 05:11:37 -06:00
2024-05-06 10:34:16 -06:00
func ensureGitVersion ( ) error {
if ! DefaultFeatures ( ) . CheckVersionAtLeast ( RequiredVersion ) {
2024-10-08 23:04:34 -06:00
moreHint := "get git: https://git-scm.com/downloads"
2022-05-02 06:30:24 -06:00
if runtime . GOOS == "linux" {
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
2024-05-06 10:34:16 -06:00
if _ , err := os . Stat ( "/etc/redhat-release" ) ; err == nil {
2022-05-02 06:30:24 -06:00
// ius.io is the recommended official(git-scm.com) method to install git
2024-10-08 23:04:34 -06:00
moreHint = "get git: https://git-scm.com/downloads/linux and https://ius.io"
2022-05-02 06:30:24 -06:00
}
}
2024-05-06 10:34:16 -06:00
return fmt . Errorf ( "installed git version %q is not supported, Gitea requires git version >= %q, %s" , DefaultFeatures ( ) . gitVersion . Original ( ) , RequiredVersion , moreHint )
2017-04-28 08:20:58 -06:00
}
2019-07-07 01:26:56 -06:00
2024-05-06 10:34:16 -06:00
if err := checkGitVersionCompatibility ( DefaultFeatures ( ) . gitVersion ) ; err != nil {
return fmt . Errorf ( "installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git" , DefaultFeatures ( ) . gitVersion . String ( ) , err )
2022-06-16 09:47:44 -06:00
}
return nil
}
2024-11-20 12:26:12 -07:00
// SetExecutablePath changes the path of git executable and checks the file permission and version.
func SetExecutablePath ( path string ) error {
// If path is empty, we use the default value of GitExecutable "git" to search for the location of git.
if path != "" {
GitExecutable = path
}
absPath , err := exec . LookPath ( GitExecutable )
if err != nil {
return fmt . Errorf ( "git not found: %w" , err )
}
GitExecutable = absPath
return nil
}
2022-06-09 19:57:49 -06:00
// HomeDir is the home dir for git to store the global config file used by Gitea internally
func HomeDir ( ) string {
2022-07-08 02:09:07 -06:00
if setting . Git . HomePath == "" {
2022-06-16 09:47:44 -06:00
// strict check, make sure the git module is initialized correctly.
2022-08-08 21:22:24 -06:00
// attention: when the git module is called in gitea sub-command (serv/hook), the log module might not obviously show messages to users/developers.
2022-06-16 09:47:44 -06:00
// for example: if there is gitea git hook code calling git.NewCommand before git.InitXxx, the integration test won't show the real failure reasons.
2022-07-08 02:09:07 -06:00
log . Fatal ( "Unable to init Git's HomeDir, incorrect initialization of the setting and git modules" )
2022-06-16 09:47:44 -06:00
return ""
2022-06-09 19:57:49 -06:00
}
2022-07-08 02:09:07 -06:00
return setting . Git . HomePath
2022-06-09 19:57:49 -06:00
}
2022-06-10 21:56:27 -06:00
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
2022-08-08 21:22:24 -06:00
// This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands.
2022-06-10 21:56:27 -06:00
func InitSimple ( ctx context . Context ) error {
2024-05-06 10:34:16 -06:00
if setting . Git . HomePath == "" {
return errors . New ( "unable to init Git's HomeDir, incorrect initialization of the setting and git modules" )
}
if DefaultContext != nil && ( ! setting . IsProd || setting . IsInTesting ) {
log . Warn ( "git module has been initialized already, duplicate init may work but it's better to fix it" )
2022-06-16 09:47:44 -06:00
}
2019-12-15 02:51:28 -07:00
DefaultContext = ctx
2022-08-08 21:22:24 -06:00
globalCommandArgs = nil
2020-09-05 10:42:58 -06:00
2022-03-31 05:56:22 -06:00
if setting . Git . Timeout . Default > 0 {
defaultCommandExecutionTimeout = time . Duration ( setting . Git . Timeout . Default ) * time . Second
}
2021-06-26 05:28:55 -06:00
2024-05-06 10:34:16 -06:00
if err := SetExecutablePath ( setting . Git . Path ) ; err != nil {
return err
}
2022-06-09 19:57:49 -06:00
2024-05-06 10:34:16 -06:00
var err error
defaultFeatures , err = loadGitVersionFeatures ( )
if err != nil {
return err
}
if err = ensureGitVersion ( ) ; err != nil {
2023-07-09 05:58:06 -06:00
return err
2022-08-08 21:22:24 -06:00
}
2022-06-09 19:57:49 -06:00
2022-08-08 21:22:24 -06:00
// 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 ( HomeDir ( ) , ".gnupg" ) )
}
2024-05-06 10:34:16 -06:00
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 ( ctx context . Context ) ( err error ) {
if err = InitSimple ( ctx ) ; err != nil {
return err
}
2022-07-08 02:09:07 -06:00
2022-08-08 21:22:24 -06:00
// Since git wire protocol has been released from git v2.18
2024-05-06 10:34:16 -06:00
if setting . Git . EnableAutoGitWireProtocol && DefaultFeatures ( ) . CheckVersionAtLeast ( "2.18" ) {
2022-08-08 21:22:24 -06:00
globalCommandArgs = append ( globalCommandArgs , "-c" , "protocol.version=2" )
}
2022-06-10 21:56:27 -06:00
2022-08-08 21:22:24 -06:00
// Explicitly disable credential helper, otherwise Git credentials might leak
2024-05-06 10:34:16 -06:00
if DefaultFeatures ( ) . CheckVersionAtLeast ( "2.9" ) {
2022-08-08 21:22:24 -06:00
globalCommandArgs = append ( globalCommandArgs , "-c" , "credential.helper=" )
}
2024-01-19 09:05:02 -07:00
2022-08-08 21:22:24 -06:00
if setting . LFS . StartServer {
2024-05-06 10:34:16 -06:00
if ! DefaultFeatures ( ) . CheckVersionAtLeast ( "2.1.2" ) {
2022-08-08 21:22:24 -06:00
return errors . New ( "LFS server support requires Git >= 2.1.2" )
}
globalCommandArgs = append ( globalCommandArgs , "-c" , "filter.lfs.required=" , "-c" , "filter.lfs.smudge=" , "-c" , "filter.lfs.clean=" )
2021-06-26 05:28:55 -06:00
}
2022-08-08 21:22:24 -06:00
2022-06-10 21:56:27 -06:00
return syncGitConfig ( )
}