2016-11-04 01:16:01 +03:00
// Copyright 2015 The Gogs Authors. All rights reserved.
2017-04-28 17:20:58 +03:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2016-11-04 01:16:01 +03:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
2019-11-30 17:40:22 +03:00
"context"
2016-11-04 01:16:01 +03:00
"fmt"
2019-04-17 14:11:37 +03:00
"os/exec"
2019-11-02 08:40:49 +03:00
"runtime"
2016-11-04 01:16:01 +03:00
"strings"
"time"
2017-04-28 17:20:58 +03:00
2019-05-15 04:57:00 +03:00
"code.gitea.io/gitea/modules/process"
2021-06-26 14:28:55 +03:00
"code.gitea.io/gitea/modules/setting"
2019-05-15 04:57:00 +03:00
2020-09-05 19:42:58 +03:00
"github.com/hashicorp/go-version"
2016-11-04 01:16:01 +03:00
)
var (
2016-12-22 12:30:52 +03:00
// Prefix the log prefix
2016-11-04 01:16:01 +03:00
Prefix = "[git-module] "
2017-04-28 17:20:58 +03:00
// GitVersionRequired is the minimum Git version required
2017-11-29 04:50:39 +03:00
GitVersionRequired = "1.7.2"
2019-04-17 14:11:37 +03:00
// GitExecutable is the command name of git
// Could be updated to an absolute path while initialization
GitExecutable = "git"
2019-05-15 04:57:00 +03:00
2019-11-30 17:40:22 +03:00
// DefaultContext is the default context to run git commands in
2020-12-02 21:36:06 +03:00
// will be overwritten by Init with HammerContext
2019-11-30 17:40:22 +03:00
DefaultContext = context . Background ( )
2020-09-05 19:42:58 +03:00
gitVersion * version . Version
// will be checked on Init
goVersionLessThan115 = true
2021-07-28 12:42:56 +03:00
// SupportProcReceive version >= 2.29.0
SupportProcReceive bool
2016-11-04 01:16:01 +03:00
)
2020-09-05 19:42:58 +03:00
// LocalVersion returns current Git version from shell.
func LocalVersion ( ) ( * version . Version , error ) {
if err := LoadGitVersion ( ) ; err != nil {
return nil , err
}
return gitVersion , nil
}
// LoadGitVersion returns current Git version from shell.
func LoadGitVersion ( ) error {
// doesn't need RWMutex because its exec by Init()
if gitVersion != nil {
return nil
2016-11-04 01:16:01 +03:00
}
stdout , err := NewCommand ( "version" ) . Run ( )
if err != nil {
2020-09-05 19:42:58 +03:00
return err
2016-11-04 01:16:01 +03:00
}
fields := strings . Fields ( stdout )
if len ( fields ) < 3 {
2020-09-05 19:42:58 +03:00
return fmt . Errorf ( "not enough output: %s" , stdout )
2016-11-04 01:16:01 +03:00
}
2020-09-05 19:42:58 +03:00
var versionString string
2016-11-04 01:16:01 +03:00
// Handle special case on Windows.
i := strings . Index ( fields [ 2 ] , "windows" )
if i >= 1 {
2020-09-05 19:42:58 +03:00
versionString = fields [ 2 ] [ : i - 1 ]
} else {
versionString = fields [ 2 ]
2016-11-04 01:16:01 +03:00
}
2020-09-05 19:42:58 +03:00
gitVersion , err = version . NewVersion ( versionString )
return err
2016-11-04 01:16:01 +03:00
}
2019-07-07 10:26:56 +03: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
}
2019-04-17 14:11:37 +03:00
absPath , err := exec . LookPath ( GitExecutable )
if err != nil {
2019-07-07 10:26:56 +03:00
return fmt . Errorf ( "Git not found: %v" , err )
2019-04-17 14:11:37 +03:00
}
GitExecutable = absPath
2020-09-05 19:42:58 +03:00
err = LoadGitVersion ( )
2017-04-28 17:20:58 +03:00
if err != nil {
2019-07-07 10:26:56 +03:00
return fmt . Errorf ( "Git version missing: %v" , err )
2017-04-28 17:20:58 +03:00
}
2020-09-05 19:42:58 +03:00
versionRequired , err := version . NewVersion ( GitVersionRequired )
if err != nil {
return err
}
if gitVersion . LessThan ( versionRequired ) {
2019-07-07 10:26:56 +03:00
return fmt . Errorf ( "Git version not supported. Requires version > %v" , GitVersionRequired )
2017-04-28 17:20:58 +03:00
}
2019-07-07 10:26:56 +03:00
return nil
2019-06-19 19:53:37 +03:00
}
2019-05-15 04:57:00 +03:00
2021-06-26 14:28:55 +03:00
// VersionInfo returns git version information
func VersionInfo ( ) string {
var format = "Git Version: %s"
var args = [ ] interface { } { gitVersion . Original ( ) }
// Since git wire protocol has been released from git v2.18
if setting . Git . EnableAutoGitWireProtocol && CheckGitVersionAtLeast ( "2.18" ) == nil {
format += ", Wire Protocol %s Enabled"
args = append ( args , "Version 2" ) // for focus color
}
return fmt . Sprintf ( format , args ... )
}
2019-06-19 19:53:37 +03:00
// Init initializes git module
2019-12-15 12:51:28 +03:00
func Init ( ctx context . Context ) error {
DefaultContext = ctx
2020-09-05 19:42:58 +03:00
2021-06-26 14:28:55 +03:00
defaultCommandExecutionTimeout = time . Duration ( setting . Git . Timeout . Default ) * time . Second
if err := SetExecutablePath ( setting . Git . Path ) ; err != nil {
return err
}
// force cleanup args
GlobalCommandArgs = [ ] string { }
if CheckGitVersionAtLeast ( "2.9" ) == nil {
// Explicitly disable credential helper, otherwise Git credentials might leak
GlobalCommandArgs = append ( GlobalCommandArgs , "-c" , "credential.helper=" )
}
// Since git wire protocol has been released from git v2.18
if setting . Git . EnableAutoGitWireProtocol && CheckGitVersionAtLeast ( "2.18" ) == nil {
GlobalCommandArgs = append ( GlobalCommandArgs , "-c" , "protocol.version=2" )
}
2020-09-05 19:42:58 +03:00
// Save current git version on init to gitVersion otherwise it would require an RWMutex
if err := LoadGitVersion ( ) ; err != nil {
return err
}
// Save if the go version used to compile gitea is greater or equal 1.15
runtimeVersion , err := version . NewVersion ( strings . TrimPrefix ( runtime . Version ( ) , "go" ) )
if err != nil {
return err
}
version115 , _ := version . NewVersion ( "1.15" )
goVersionLessThan115 = runtimeVersion . LessThan ( version115 )
2020-06-14 00:47:31 +03:00
// Git requires setting user.name and user.email in order to commit changes - if they're not set just add some defaults
2019-05-15 04:57:00 +03:00
for configKey , defaultValue := range map [ string ] string { "user.name" : "Gitea" , "user.email" : "gitea@fake.local" } {
2020-06-14 00:47:31 +03:00
if err := checkAndSetConfig ( configKey , defaultValue , false ) ; err != nil {
return err
2019-05-15 04:57:00 +03:00
}
}
2020-06-14 00:47:31 +03:00
// Set git some configurations - these must be set to these values for gitea to work correctly
if err := checkAndSetConfig ( "core.quotePath" , "false" , true ) ; err != nil {
return err
2019-05-15 04:57:00 +03:00
}
2019-06-29 14:46:25 +03:00
2020-10-21 18:42:08 +03:00
if CheckGitVersionAtLeast ( "2.10" ) == nil {
2020-08-23 19:02:35 +03:00
if err := checkAndSetConfig ( "receive.advertisePushOptions" , "true" , true ) ; err != nil {
return err
}
}
2020-10-21 18:42:08 +03:00
if CheckGitVersionAtLeast ( "2.18" ) == nil {
2020-06-14 00:47:31 +03:00
if err := checkAndSetConfig ( "core.commitGraph" , "true" , true ) ; err != nil {
return err
2019-06-29 14:46:25 +03:00
}
2020-06-14 00:47:31 +03:00
if err := checkAndSetConfig ( "gc.writeCommitGraph" , "true" , true ) ; err != nil {
return err
2019-06-29 14:46:25 +03:00
}
}
2019-11-02 08:40:49 +03:00
2021-07-28 12:42:56 +03:00
if CheckGitVersionAtLeast ( "2.29" ) == nil {
// set support for AGit flow
if err := checkAndAddConfig ( "receive.procReceiveRefs" , "refs/for" ) ; err != nil {
return err
}
SupportProcReceive = true
} else {
if err := checkAndRemoveConfig ( "receive.procReceiveRefs" , "refs/for" ) ; err != nil {
return err
}
SupportProcReceive = false
}
2019-11-02 08:40:49 +03:00
if runtime . GOOS == "windows" {
2020-06-14 00:47:31 +03:00
if err := checkAndSetConfig ( "core.longpaths" , "true" , true ) ; err != nil {
return err
}
}
2021-10-13 21:20:11 +03:00
if setting . Git . DisableCoreProtectNTFS {
if err := checkAndSetConfig ( "core.protectntfs" , "false" , true ) ; err != nil {
return err
}
GlobalCommandArgs = append ( GlobalCommandArgs , "-c" , "core.protectntfs=false" )
}
2020-06-14 00:47:31 +03:00
return nil
}
2020-10-21 18:42:08 +03:00
// CheckGitVersionAtLeast check git version is at least the constraint version
func CheckGitVersionAtLeast ( atLeast string ) error {
2020-09-05 19:42:58 +03:00
if err := LoadGitVersion ( ) ; err != nil {
return err
}
2020-10-21 18:42:08 +03:00
atLeastVersion , err := version . NewVersion ( atLeast )
2020-09-05 19:42:58 +03:00
if err != nil {
return err
}
2020-10-21 18:42:08 +03:00
if gitVersion . Compare ( atLeastVersion ) < 0 {
return fmt . Errorf ( "installed git binary version %s is not at least %s" , gitVersion . Original ( ) , atLeast )
2020-09-05 19:42:58 +03:00
}
return nil
}
2020-06-14 00:47:31 +03:00
func checkAndSetConfig ( key , defaultValue string , forceToDefault bool ) error {
stdout , stderr , err := process . GetManager ( ) . Exec ( "git.Init(get setting)" , GitExecutable , "config" , "--get" , key )
if err != nil {
perr , ok := err . ( * process . Error )
if ! ok {
return fmt . Errorf ( "Failed to get git %s(%v) errType %T: %s" , key , err , err , stderr )
2019-11-02 08:40:49 +03:00
}
2020-06-14 00:47:31 +03:00
eerr , ok := perr . Err . ( * exec . ExitError )
if ! ok || eerr . ExitCode ( ) != 1 {
return fmt . Errorf ( "Failed to get git %s(%v) errType %T: %s" , key , err , err , stderr )
}
}
currValue := strings . TrimSpace ( stdout )
if currValue == defaultValue || ( ! forceToDefault && len ( currValue ) > 0 ) {
return nil
2019-11-02 08:40:49 +03:00
}
2020-06-14 00:47:31 +03:00
if _ , stderr , err = process . GetManager ( ) . Exec ( fmt . Sprintf ( "git.Init(set %s)" , key ) , "git" , "config" , "--global" , key , defaultValue ) ; err != nil {
return fmt . Errorf ( "Failed to set git %s(%s): %s" , key , err , stderr )
}
2019-06-19 19:53:37 +03:00
return nil
2016-11-04 01:16:01 +03:00
}
2021-07-28 12:42:56 +03:00
func checkAndAddConfig ( key , value string ) error {
_ , stderr , err := process . GetManager ( ) . Exec ( "git.Init(get setting)" , GitExecutable , "config" , "--get" , key , value )
if err != nil {
perr , ok := err . ( * process . Error )
if ! ok {
return fmt . Errorf ( "Failed to get git %s(%v) errType %T: %s" , key , err , err , stderr )
}
eerr , ok := perr . Err . ( * exec . ExitError )
if ! ok || eerr . ExitCode ( ) != 1 {
return fmt . Errorf ( "Failed to get git %s(%v) errType %T: %s" , key , err , err , stderr )
}
if eerr . ExitCode ( ) == 1 {
if _ , stderr , err = process . GetManager ( ) . Exec ( fmt . Sprintf ( "git.Init(set %s)" , key ) , "git" , "config" , "--global" , "--add" , key , value ) ; err != nil {
return fmt . Errorf ( "Failed to set git %s(%s): %s" , key , err , stderr )
}
return nil
}
}
return nil
}
func checkAndRemoveConfig ( key , value string ) error {
_ , stderr , err := process . GetManager ( ) . Exec ( "git.Init(get setting)" , GitExecutable , "config" , "--get" , key , value )
if err != nil {
perr , ok := err . ( * process . Error )
if ! ok {
return fmt . Errorf ( "Failed to get git %s(%v) errType %T: %s" , key , err , err , stderr )
}
eerr , ok := perr . Err . ( * exec . ExitError )
if ! ok || eerr . ExitCode ( ) != 1 {
return fmt . Errorf ( "Failed to get git %s(%v) errType %T: %s" , key , err , err , stderr )
}
if eerr . ExitCode ( ) == 1 {
return nil
}
}
if _ , stderr , err = process . GetManager ( ) . Exec ( fmt . Sprintf ( "git.Init(set %s)" , key ) , "git" , "config" , "--global" , "--unset-all" , key , value ) ; err != nil {
return fmt . Errorf ( "Failed to set git %s(%s): %s" , key , err , stderr )
}
return nil
}
2016-11-04 01:16:01 +03:00
// Fsck verifies the connectivity and validity of the objects in the database
2020-05-17 02:31:38 +03:00
func Fsck ( ctx context . Context , repoPath string , timeout time . Duration , args ... string ) error {
2016-11-04 01:16:01 +03:00
// Make sure timeout makes sense.
if timeout <= 0 {
timeout = - 1
}
2020-05-17 02:31:38 +03:00
_ , err := NewCommandContext ( ctx , "fsck" ) . AddArguments ( args ... ) . RunInDirTimeout ( timeout , repoPath )
2016-11-04 01:16:01 +03:00
return err
}