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"
2017-04-28 17:20:58 +03:00
"github.com/mcuadros/go-version"
2016-11-04 01:16:01 +03:00
)
2016-12-22 12:30:52 +03:00
// Version return this package's current version
2016-11-04 01:16:01 +03:00
func Version ( ) string {
2016-12-22 12:30:52 +03:00
return "0.4.2"
2016-11-04 01:16:01 +03:00
}
var (
// Debug enables verbose logging on everything.
// This should be false in case Gogs starts in SSH mode.
2016-12-22 12:30:52 +03:00
Debug = false
// 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
DefaultContext = context . Background ( )
2019-05-15 04:57:00 +03:00
gitVersion string
2016-11-04 01:16:01 +03:00
)
func log ( format string , args ... interface { } ) {
if ! Debug {
return
}
fmt . Print ( Prefix )
if len ( args ) == 0 {
fmt . Println ( format )
} else {
fmt . Printf ( format + "\n" , args ... )
}
}
2016-12-22 12:30:52 +03:00
// BinVersion returns current Git version from shell.
2016-11-04 01:16:01 +03:00
func BinVersion ( ) ( string , error ) {
if len ( gitVersion ) > 0 {
return gitVersion , nil
}
stdout , err := NewCommand ( "version" ) . Run ( )
if err != nil {
return "" , err
}
fields := strings . Fields ( stdout )
if len ( fields ) < 3 {
return "" , fmt . Errorf ( "not enough output: %s" , stdout )
}
// Handle special case on Windows.
i := strings . Index ( fields [ 2 ] , "windows" )
if i >= 1 {
gitVersion = fields [ 2 ] [ : i - 1 ]
return gitVersion , nil
}
gitVersion = fields [ 2 ]
return gitVersion , nil
}
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
2017-04-28 17:20:58 +03:00
gitVersion , err := BinVersion ( )
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
}
if version . Compare ( gitVersion , GitVersionRequired , "<" ) {
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
2019-06-19 19:53:37 +03:00
// Init initializes git module
func Init ( ) error {
2019-05-15 04:57:00 +03:00
// Git requires setting user.name and user.email in order to commit changes.
for configKey , defaultValue := range map [ string ] string { "user.name" : "Gitea" , "user.email" : "gitea@fake.local" } {
if stdout , stderr , err := process . GetManager ( ) . Exec ( "git.Init(get setting)" , GitExecutable , "config" , "--get" , configKey ) ; err != nil || strings . TrimSpace ( stdout ) == "" {
// ExitError indicates this config is not set
if _ , ok := err . ( * exec . ExitError ) ; ok || strings . TrimSpace ( stdout ) == "" {
if _ , stderr , gerr := process . GetManager ( ) . Exec ( "git.Init(set " + configKey + ")" , "git" , "config" , "--global" , configKey , defaultValue ) ; gerr != nil {
2019-06-19 19:53:37 +03:00
return fmt . Errorf ( "Failed to set git %s(%s): %s" , configKey , gerr , stderr )
2019-05-15 04:57:00 +03:00
}
} else {
2019-06-19 19:53:37 +03:00
return fmt . Errorf ( "Failed to get git %s(%s): %s" , configKey , err , stderr )
2019-05-15 04:57:00 +03:00
}
}
}
// Set git some configurations.
if _ , stderr , err := process . GetManager ( ) . Exec ( "git.Init(git config --global core.quotepath false)" ,
GitExecutable , "config" , "--global" , "core.quotepath" , "false" ) ; err != nil {
2019-06-19 19:53:37 +03:00
return fmt . Errorf ( "Failed to execute 'git config --global core.quotepath false': %s" , stderr )
2019-05-15 04:57:00 +03:00
}
2019-06-29 14:46:25 +03:00
if version . Compare ( gitVersion , "2.18" , ">=" ) {
if _ , stderr , err := process . GetManager ( ) . Exec ( "git.Init(git config --global core.commitGraph true)" ,
GitExecutable , "config" , "--global" , "core.commitGraph" , "true" ) ; err != nil {
return fmt . Errorf ( "Failed to execute 'git config --global core.commitGraph true': %s" , stderr )
}
if _ , stderr , err := process . GetManager ( ) . Exec ( "git.Init(git config --global gc.writeCommitGraph true)" ,
GitExecutable , "config" , "--global" , "gc.writeCommitGraph" , "true" ) ; err != nil {
return fmt . Errorf ( "Failed to execute 'git config --global gc.writeCommitGraph true': %s" , stderr )
}
}
2019-11-02 08:40:49 +03:00
if runtime . GOOS == "windows" {
if _ , stderr , err := process . GetManager ( ) . Exec ( "git.Init(git config --global core.longpaths true)" ,
GitExecutable , "config" , "--global" , "core.longpaths" , "true" ) ; err != nil {
return fmt . Errorf ( "Failed to execute 'git config --global core.longpaths true': %s" , stderr )
}
}
2019-06-19 19:53:37 +03:00
return nil
2016-11-04 01:16:01 +03:00
}
// Fsck verifies the connectivity and validity of the objects in the database
func Fsck ( repoPath string , timeout time . Duration , args ... string ) error {
// Make sure timeout makes sense.
if timeout <= 0 {
timeout = - 1
}
_ , err := NewCommand ( "fsck" ) . AddArguments ( args ... ) . RunInDirTimeout ( timeout , repoPath )
return err
}