2016-11-03 18:16:01 -04:00
// Copyright 2015 The Gogs Authors. All rights reserved.
2017-04-28 10:20:58 -04:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2016-11-03 18:16:01 -04:00
package git
import (
2019-11-30 09:40:22 -05:00
"context"
2022-06-16 11:47:44 -04:00
"errors"
2016-11-03 18:16:01 -04:00
"fmt"
2022-05-02 08:30:24 -04:00
"os"
2019-04-17 07:11:37 -04:00
"os/exec"
2022-07-08 04:09:07 -04:00
"path/filepath"
2022-06-19 07:56:22 -04:00
"regexp"
2019-11-02 01:40:49 -04:00
"runtime"
2016-11-03 18:16:01 -04:00
"strings"
"time"
2017-04-28 10:20:58 -04:00
2022-06-09 21:57:49 -04:00
"code.gitea.io/gitea/modules/log"
2021-06-26 07:28:55 -04:00
"code.gitea.io/gitea/modules/setting"
2022-07-15 09:01:32 -04:00
2020-09-05 12:42:58 -04:00
"github.com/hashicorp/go-version"
2016-11-03 18:16:01 -04:00
)
2022-08-08 23:22:24 -04:00
// RequiredVersion is the minimum Git version required
const RequiredVersion = "2.0.0"
2019-04-17 07:11:37 -04:00
2022-06-16 11:47:44 -04:00
var (
2019-04-17 07:11:37 -04:00
// GitExecutable is the command name of git
// Could be updated to an absolute path while initialization
GitExecutable = "git"
2019-05-14 21:57:00 -04:00
2022-06-16 11:47:44 -04:00
// DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx
DefaultContext context . Context
2019-11-30 09:40:22 -05:00
2024-03-24 07:44:30 -04:00
SupportProcReceive bool // >= 2.29
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘ experimental curiosity’
InvertedGitFlushEnv bool // 2.43.1
SupportCheckAttrOnBare bool // >= 2.40
2016-11-03 18:16:01 -04:00
2024-08-26 02:54:16 -04:00
HasSSHExecutable bool
2022-06-09 21:57:49 -04:00
gitVersion * version . Version
)
2020-09-05 12:42:58 -04:00
2022-06-09 21:57:49 -04:00
// loadGitVersion returns current Git version from shell. Internal usage only.
2024-06-11 14:47:45 -04:00
func loadGitVersion ( ) error {
2022-08-08 23:22:24 -04:00
// doesn't need RWMutex because it's executed by Init()
2020-09-05 12:42:58 -04:00
if gitVersion != nil {
2024-06-11 14:47:45 -04:00
return nil
2016-11-03 18:16:01 -04:00
}
2022-06-09 21:57:49 -04:00
stdout , _ , runErr := NewCommand ( DefaultContext , "version" ) . RunStdString ( nil )
2022-03-31 22:55:30 -04:00
if runErr != nil {
2024-06-11 14:47:45 -04:00
return runErr
2016-11-03 18:16:01 -04:00
}
fields := strings . Fields ( stdout )
if len ( fields ) < 3 {
2024-06-11 14:47:45 -04:00
return fmt . Errorf ( "invalid git version output: %s" , stdout )
2016-11-03 18:16:01 -04:00
}
2020-09-05 12:42:58 -04:00
var versionString string
2016-11-03 18:16:01 -04:00
// Handle special case on Windows.
i := strings . Index ( fields [ 2 ] , "windows" )
if i >= 1 {
2020-09-05 12:42:58 -04:00
versionString = fields [ 2 ] [ : i - 1 ]
} else {
versionString = fields [ 2 ]
2016-11-03 18:16:01 -04:00
}
2022-03-31 22:55:30 -04:00
var err error
2020-09-05 12:42:58 -04:00
gitVersion , err = version . NewVersion ( versionString )
2024-06-11 14:47:45 -04:00
return err
2016-11-03 18:16:01 -04:00
}
2019-07-07 03:26:56 -04: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 07:11:37 -04:00
absPath , err := exec . LookPath ( GitExecutable )
if err != nil {
2022-05-02 08:30:24 -04:00
return fmt . Errorf ( "git not found: %w" , err )
2019-04-17 07:11:37 -04:00
}
GitExecutable = absPath
2024-06-11 14:47:45 -04:00
err = loadGitVersion ( )
2017-04-28 10:20:58 -04:00
if err != nil {
2022-05-02 08:30:24 -04:00
return fmt . Errorf ( "unable to load git version: %w" , err )
2017-04-28 10:20:58 -04:00
}
2020-09-05 12:42:58 -04:00
2022-08-08 23:22:24 -04:00
versionRequired , err := version . NewVersion ( RequiredVersion )
2020-09-05 12:42:58 -04:00
if err != nil {
return err
}
if gitVersion . LessThan ( versionRequired ) {
2024-10-09 05:28:37 -04:00
moreHint := "get git: https://git-scm.com/downloads"
2022-05-02 08:30:24 -04:00
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
2024-10-09 05:28:37 -04:00
moreHint = "get git: https://git-scm.com/downloads/linux and https://ius.io"
2022-05-02 08:30:24 -04:00
}
}
2022-08-08 23:22:24 -04:00
return fmt . Errorf ( "installed git version %q is not supported, Gitea requires git version >= %q, %s" , gitVersion . Original ( ) , RequiredVersion , moreHint )
2017-04-28 10:20:58 -04:00
}
2019-07-07 03:26:56 -04:00
return nil
2019-06-19 12:53:37 -04:00
}
2019-05-14 21:57:00 -04:00
2021-06-26 07:28:55 -04:00
// VersionInfo returns git version information
func VersionInfo ( ) string {
2022-06-09 21:57:49 -04:00
if gitVersion == nil {
return "(git not found)"
}
format := "%s"
2023-07-04 14:36:08 -04:00
args := [ ] any { gitVersion . Original ( ) }
2021-06-26 07:28:55 -04:00
// 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 ... )
}
2022-06-16 11:47:44 -04:00
func checkInit ( ) error {
2022-07-08 04:09:07 -04:00
if setting . Git . HomePath == "" {
return errors . New ( "unable to init Git's HomeDir, incorrect initialization of the setting and git modules" )
2022-06-16 11:47:44 -04:00
}
if DefaultContext != nil {
2022-08-08 23:22:24 -04:00
log . Warn ( "git module has been initialized already, duplicate init may work but it's better to fix it" )
2022-06-16 11:47:44 -04:00
}
return nil
}
2022-06-09 21:57:49 -04:00
// HomeDir is the home dir for git to store the global config file used by Gitea internally
func HomeDir ( ) string {
2022-07-08 04:09:07 -04:00
if setting . Git . HomePath == "" {
2022-06-16 11:47:44 -04:00
// strict check, make sure the git module is initialized correctly.
2022-08-08 23:22:24 -04: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 11:47:44 -04: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 04:09:07 -04:00
log . Fatal ( "Unable to init Git's HomeDir, incorrect initialization of the setting and git modules" )
2022-06-16 11:47:44 -04:00
return ""
2022-06-09 21:57:49 -04:00
}
2022-07-08 04:09:07 -04:00
return setting . Git . HomePath
2022-06-09 21:57:49 -04:00
}
2022-06-10 23:56:27 -04:00
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
2022-08-08 23:22:24 -04:00
// This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands.
2022-06-10 23:56:27 -04:00
func InitSimple ( ctx context . Context ) error {
2022-06-16 11:47:44 -04:00
if err := checkInit ( ) ; err != nil {
return err
}
2019-12-15 04:51:28 -05:00
DefaultContext = ctx
2022-08-08 23:22:24 -04:00
globalCommandArgs = nil
2020-09-05 12:42:58 -04:00
2022-03-31 07:56:22 -04:00
if setting . Git . Timeout . Default > 0 {
defaultCommandExecutionTimeout = time . Duration ( setting . Git . Timeout . Default ) * time . Second
}
2021-06-26 07:28:55 -04:00
2022-06-16 11:47:44 -04:00
return SetExecutablePath ( setting . Git . Path )
2022-06-09 21:57:49 -04:00
}
2022-08-08 23:22:24 -04:00
// 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 {
2023-07-09 07:58:06 -04:00
return err
2022-08-08 23:22:24 -04:00
}
2022-06-09 21:57:49 -04:00
2022-08-08 23:22:24 -04: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" ) )
}
2022-07-08 04:09:07 -04:00
2022-08-08 23:22:24 -04:00
// 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" )
}
2022-06-10 23:56:27 -04:00
2022-08-08 23:22:24 -04:00
// Explicitly disable credential helper, otherwise Git credentials might leak
if CheckGitVersionAtLeast ( "2.9" ) == nil {
globalCommandArgs = append ( globalCommandArgs , "-c" , "credential.helper=" )
}
SupportProcReceive = CheckGitVersionAtLeast ( "2.29" ) == nil
2024-08-12 11:16:55 -04:00
SupportHashSha256 = CheckGitVersionAtLeast ( "2.42" ) == nil
2024-03-24 07:44:30 -04:00
SupportCheckAttrOnBare = CheckGitVersionAtLeast ( "2.40" ) == nil
2024-01-19 11:05:02 -05:00
if SupportHashSha256 {
SupportedObjectFormats = append ( SupportedObjectFormats , Sha256ObjectFormat )
} else {
2024-08-12 11:16:55 -04:00
log . Warn ( "sha256 hash support is disabled - requires Git >= 2.42" )
2024-01-19 11:05:02 -05:00
}
2024-02-13 03:03:22 -05:00
InvertedGitFlushEnv = CheckGitVersionEqual ( "2.43.1" ) == nil
2022-08-08 23:22:24 -04:00
if setting . LFS . StartServer {
if CheckGitVersionAtLeast ( "2.1.2" ) != nil {
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 07:28:55 -04:00
}
2022-08-08 23:22:24 -04:00
2024-08-26 02:54:16 -04:00
// Detect the presence of the ssh executable in $PATH.
_ , err = exec . LookPath ( "ssh" )
HasSSHExecutable = err == nil
2022-06-10 23:56:27 -04:00
return syncGitConfig ( )
}
2021-06-26 07:28:55 -04:00
2022-06-10 23:56:27 -04:00
// syncGitConfig only modifies gitconfig, won't change global variables (otherwise there will be data-race problem)
func syncGitConfig ( ) ( err error ) {
if err = os . MkdirAll ( HomeDir ( ) , os . ModePerm ) ; err != nil {
2022-07-08 04:09:07 -04:00
return fmt . Errorf ( "unable to prepare git home directory %s, err: %w" , HomeDir ( ) , err )
2022-01-06 00:38:38 -05:00
}
2023-05-23 12:30:19 -04:00
// first, write user's git config options to git config file
// user config options could be overwritten by builtin values later, because if a value is builtin, it must have some special purposes
for k , v := range setting . GitConfig . Options {
if err = configSet ( strings . ToLower ( k ) , v ) ; err != nil {
return err
}
}
2022-06-09 21:57:49 -04:00
// Git requires setting user.name and user.email in order to commit changes - old comment: "if they're not set just add some defaults"
// TODO: need to confirm whether users really need to change these values manually. It seems that these values are dummy only and not really used.
// If these values are not really used, then they can be set (overwritten) directly without considering about existence.
for configKey , defaultValue := range map [ string ] string {
"user.name" : "Gitea" ,
"user.email" : "gitea@fake.local" ,
} {
if err := configSetNonExist ( configKey , defaultValue ) ; err != nil {
2020-06-13 17:47:31 -04:00
return err
2019-05-14 21:57:00 -04:00
}
}
2020-06-13 17:47:31 -04:00
// Set git some configurations - these must be set to these values for gitea to work correctly
2022-06-09 21:57:49 -04:00
if err := configSet ( "core.quotePath" , "false" ) ; err != nil {
2020-06-13 17:47:31 -04:00
return err
2019-05-14 21:57:00 -04:00
}
2019-06-29 07:46:25 -04:00
2020-10-21 11:42:08 -04:00
if CheckGitVersionAtLeast ( "2.10" ) == nil {
2022-06-09 21:57:49 -04:00
if err := configSet ( "receive.advertisePushOptions" , "true" ) ; err != nil {
2020-08-23 12:02:35 -04:00
return err
}
}
2020-10-21 11:42:08 -04:00
if CheckGitVersionAtLeast ( "2.18" ) == nil {
2022-06-09 21:57:49 -04:00
if err := configSet ( "core.commitGraph" , "true" ) ; err != nil {
2020-06-13 17:47:31 -04:00
return err
2019-06-29 07:46:25 -04:00
}
2022-06-09 21:57:49 -04:00
if err := configSet ( "gc.writeCommitGraph" , "true" ) ; err != nil {
2020-06-13 17:47:31 -04:00
return err
2019-06-29 07:46:25 -04:00
}
2022-06-17 16:18:35 -04:00
if err := configSet ( "fetch.writeCommitGraph" , "true" ) ; err != nil {
return err
}
2019-06-29 07:46:25 -04:00
}
2019-11-02 01:40:49 -04:00
2022-06-10 23:56:27 -04:00
if SupportProcReceive {
2021-07-28 05:42:56 -04:00
// set support for AGit flow
2022-06-09 21:57:49 -04:00
if err := configAddNonExist ( "receive.procReceiveRefs" , "refs/for" ) ; err != nil {
2021-07-28 05:42:56 -04:00
return err
}
} else {
2022-06-09 21:57:49 -04:00
if err := configUnsetAll ( "receive.procReceiveRefs" , "refs/for" ) ; err != nil {
2021-07-28 05:42:56 -04:00
return err
}
}
2022-06-17 01:49:38 -04:00
// Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user
// however, some docker users and samba users find it difficult to configure their systems so that Gitea's git repositories are owned by the Gitea user. (Possibly Windows Service users - but ownership in this case should really be set correctly on the filesystem.)
// see issue: https://github.com/go-gitea/gitea/issues/19455
// Fundamentally the problem lies with the uid-gid-mapping mechanism for filesystems in docker on windows (and to a lesser extent samba).
// Docker's configuration mechanism for local filesystems provides no way of setting this mapping and although there is a mechanism for setting this uid through using cifs mounting it is complicated and essentially undocumented
// Thus the owner uid/gid for files on these filesystems will be marked as root.
// As Gitea now always use its internal git config file, and access to the git repositories is managed through Gitea,
// it is now safe to set "safe.directory=*" for internal usage only.
// Please note: the wildcard "*" is only supported by Git 2.30.4/2.31.3/2.32.2/2.33.3/2.34.3/2.35.3/2.36 and later
// Although only supported by Git 2.30.4/2.31.3/2.32.2/2.33.3/2.34.3/2.35.3/2.36 and later - this setting is tolerated by earlier versions
if err := configAddNonExist ( "safe.directory" , "*" ) ; err != nil {
return err
}
2019-11-02 01:40:49 -04:00
if runtime . GOOS == "windows" {
2022-06-09 21:57:49 -04:00
if err := configSet ( "core.longpaths" , "true" ) ; err != nil {
2020-06-13 17:47:31 -04:00
return err
}
2022-06-09 21:57:49 -04:00
if setting . Git . DisableCoreProtectNTFS {
err = configSet ( "core.protectNTFS" , "false" )
} else {
err = configUnsetAll ( "core.protectNTFS" , "false" )
}
if err != nil {
2021-10-13 14:20:11 -04:00
return err
}
}
2022-06-09 21:57:49 -04:00
2022-09-04 11:13:23 -04:00
// By default partial clones are disabled, enable them from git v2.22
if ! setting . Git . DisablePartialClone && CheckGitVersionAtLeast ( "2.22" ) == nil {
if err = configSet ( "uploadpack.allowfilter" , "true" ) ; err != nil {
return err
}
err = configSet ( "uploadpack.allowAnySHA1InWant" , "true" )
} else {
if err = configUnsetAll ( "uploadpack.allowfilter" , "true" ) ; err != nil {
return err
}
err = configUnsetAll ( "uploadpack.allowAnySHA1InWant" , "true" )
}
return err
2020-06-13 17:47:31 -04:00
}
2020-10-21 11:42:08 -04:00
// CheckGitVersionAtLeast check git version is at least the constraint version
func CheckGitVersionAtLeast ( atLeast string ) error {
2024-06-11 14:47:45 -04:00
if err := loadGitVersion ( ) ; err != nil {
2020-09-05 12:42:58 -04:00
return err
}
2020-10-21 11:42:08 -04:00
atLeastVersion , err := version . NewVersion ( atLeast )
2020-09-05 12:42:58 -04:00
if err != nil {
return err
}
2020-10-21 11:42:08 -04: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 12:42:58 -04:00
}
return nil
}
2024-02-13 03:03:22 -05:00
// CheckGitVersionEqual checks if the git version is equal to the constraint version.
func CheckGitVersionEqual ( equal string ) error {
2024-06-11 14:47:45 -04:00
if err := loadGitVersion ( ) ; err != nil {
2024-02-13 03:03:22 -05:00
return err
}
atLeastVersion , err := version . NewVersion ( equal )
if err != nil {
return err
}
if ! gitVersion . Equal ( atLeastVersion ) {
return fmt . Errorf ( "installed git binary version %s is not equal to %s" , gitVersion . Original ( ) , equal )
}
return nil
}
2022-06-09 21:57:49 -04:00
func configSet ( key , value string ) error {
2023-02-28 16:26:19 -05:00
stdout , _ , err := NewCommand ( DefaultContext , "config" , "--global" , "--get" ) . AddDynamicArguments ( key ) . RunStdString ( nil )
2024-03-24 12:05:00 -04:00
if err != nil && ! IsErrorExitCode ( err , 1 ) {
2022-06-09 21:57:49 -04:00
return fmt . Errorf ( "failed to get git config %s, err: %w" , key , err )
2020-06-13 17:47:31 -04:00
}
currValue := strings . TrimSpace ( stdout )
2022-06-09 21:57:49 -04:00
if currValue == value {
2020-06-13 17:47:31 -04:00
return nil
2019-11-02 01:40:49 -04:00
}
2020-06-13 17:47:31 -04:00
2022-10-23 10:44:45 -04:00
_ , _ , err = NewCommand ( DefaultContext , "config" , "--global" ) . AddDynamicArguments ( key , value ) . RunStdString ( nil )
2022-06-09 21:57:49 -04:00
if err != nil {
return fmt . Errorf ( "failed to set git global config %s, err: %w" , key , err )
2020-06-13 17:47:31 -04:00
}
2019-06-19 12:53:37 -04:00
return nil
2016-11-03 18:16:01 -04:00
}
2022-06-09 21:57:49 -04:00
func configSetNonExist ( key , value string ) error {
2023-02-28 16:26:19 -05:00
_ , _ , err := NewCommand ( DefaultContext , "config" , "--global" , "--get" ) . AddDynamicArguments ( key ) . RunStdString ( nil )
2022-06-09 21:57:49 -04:00
if err == nil {
// already exist
return nil
}
2024-03-24 12:05:00 -04:00
if IsErrorExitCode ( err , 1 ) {
2022-06-09 21:57:49 -04:00
// not exist, set new config
2022-10-23 10:44:45 -04:00
_ , _ , err = NewCommand ( DefaultContext , "config" , "--global" ) . AddDynamicArguments ( key , value ) . RunStdString ( nil )
2022-06-09 21:57:49 -04:00
if err != nil {
return fmt . Errorf ( "failed to set git global config %s, err: %w" , key , err )
2021-07-28 05:42:56 -04:00
}
2022-06-09 21:57:49 -04:00
return nil
2021-07-28 05:42:56 -04:00
}
2022-06-09 21:57:49 -04:00
return fmt . Errorf ( "failed to get git config %s, err: %w" , key , err )
2021-07-28 05:42:56 -04:00
}
2022-06-09 21:57:49 -04:00
func configAddNonExist ( key , value string ) error {
2023-02-28 16:26:19 -05:00
_ , _ , err := NewCommand ( DefaultContext , "config" , "--global" , "--get" ) . AddDynamicArguments ( key , regexp . QuoteMeta ( value ) ) . RunStdString ( nil )
2022-06-09 21:57:49 -04:00
if err == nil {
// already exist
return nil
}
2024-03-24 12:05:00 -04:00
if IsErrorExitCode ( err , 1 ) {
2022-06-09 21:57:49 -04:00
// not exist, add new config
2022-10-23 10:44:45 -04:00
_ , _ , err = NewCommand ( DefaultContext , "config" , "--global" , "--add" ) . AddDynamicArguments ( key , value ) . RunStdString ( nil )
2022-06-09 21:57:49 -04:00
if err != nil {
return fmt . Errorf ( "failed to add git global config %s, err: %w" , key , err )
2021-07-28 05:42:56 -04:00
}
2022-06-09 21:57:49 -04:00
return nil
2021-07-28 05:42:56 -04:00
}
2022-06-09 21:57:49 -04:00
return fmt . Errorf ( "failed to get git config %s, err: %w" , key , err )
}
2021-07-28 05:42:56 -04:00
2022-06-09 21:57:49 -04:00
func configUnsetAll ( key , value string ) error {
2023-02-28 16:26:19 -05:00
_ , _ , err := NewCommand ( DefaultContext , "config" , "--global" , "--get" ) . AddDynamicArguments ( key ) . RunStdString ( nil )
2022-06-09 21:57:49 -04:00
if err == nil {
// exist, need to remove
2022-10-23 10:44:45 -04:00
_ , _ , err = NewCommand ( DefaultContext , "config" , "--global" , "--unset-all" ) . AddDynamicArguments ( key , regexp . QuoteMeta ( value ) ) . RunStdString ( nil )
2022-06-09 21:57:49 -04:00
if err != nil {
return fmt . Errorf ( "failed to unset git global config %s, err: %w" , key , err )
}
return nil
2021-07-28 05:42:56 -04:00
}
2024-03-24 12:05:00 -04:00
if IsErrorExitCode ( err , 1 ) {
2022-06-09 21:57:49 -04:00
// not exist
return nil
}
return fmt . Errorf ( "failed to get git config %s, err: %w" , key , err )
2021-07-28 05:42:56 -04:00
}
2016-11-03 18:16:01 -04:00
// Fsck verifies the connectivity and validity of the objects in the database
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-03 21:30:43 -05:00
func Fsck ( ctx context . Context , repoPath string , timeout time . Duration , args TrustedCmdArgs ) error {
2022-03-31 22:55:30 -04:00
return NewCommand ( ctx , "fsck" ) . AddArguments ( args ... ) . Run ( & RunOpts { Timeout : timeout , Dir : repoPath } )
2016-11-03 18:16:01 -04:00
}