summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwxiaoguang <wxiaoguang@gmail.com>2022-06-10 09:57:49 +0800
committerGitHub <noreply@github.com>2022-06-10 09:57:49 +0800
commita0051634b982608d94399033a8b76e7e3b1537ac (patch)
tree174bf860eb23c80bff16dc0e94f8d7d1a3437f0a
parente26f84a9b75f250f951e0efa5c5c62860b3c8370 (diff)
downloadgitea-a0051634b982608d94399033a8b76e7e3b1537ac.tar.gz
gitea-a0051634b982608d94399033a8b76e7e3b1537ac.zip
Refactor git module, make Gitea use internal git config (#19732)
* Refactor git module, make Gitea use internal git config, add safe.directory config * introduce git.InitSimple and git.InitWithConfigSync, make serv cmd use gitconfig * use HOME instead of GIT_CONFIG_GLOBAL, because git always needs a correct HOME * fix cmd env in cmd/serv.go * fine tune error message * Fix a incorrect test case * fix configAddNonExist * fix configAddNonExist logic, add `--fixed-value` flag, add tests * add configSetNonExist function in case it's needed. * use configSetNonExist for `user.name` and `user.email` * add some comments * Update cmd/serv.go Co-authored-by: zeripath <art27@cantab.net> * Update cmd/serv.go Co-authored-by: zeripath <art27@cantab.net> * Update modules/git/git.go Co-authored-by: zeripath <art27@cantab.net> * Update modules/setting/setting.go Co-authored-by: zeripath <art27@cantab.net> * Update modules/git/repo_attribute.go Co-authored-by: zeripath <art27@cantab.net> * fix spaces in messages * use `configSet("core.protectNTFS", ...)` instead of `globalCommandArgs` * remove GIT_CONFIG_NOSYSTEM, continue to use system's git config * Update cmd/serv.go Co-authored-by: zeripath <art27@cantab.net> * fix merge * remove code for safe.directory * separate git.CommonEnvs to CommonGitCmdEnvs and CommonCmdServEnvs * avoid Golang's data race error Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
-rw-r--r--cmd/serv.go60
-rw-r--r--integrations/integration_test.go3
-rw-r--r--integrations/migration-test/migration_test.go1
-rw-r--r--models/migrations/migrations_test.go5
-rw-r--r--models/unittest/testdb.go5
-rw-r--r--modules/git/command.go40
-rw-r--r--modules/git/commit.go5
-rw-r--r--modules/git/git.go229
-rw-r--r--modules/git/git_test.go67
-rw-r--r--modules/git/lfs.go6
-rw-r--r--modules/git/remote.go4
-rw-r--r--modules/git/repo_attribute.go7
-rw-r--r--modules/git/repo_tree.go8
-rw-r--r--modules/indexer/stats/indexer_test.go2
-rw-r--r--modules/repository/init.go5
-rw-r--r--modules/util/path.go1
-rw-r--r--routers/init.go4
-rw-r--r--routers/web/admin/admin.go5
-rw-r--r--routers/web/repo/lfs.go5
-rw-r--r--services/pull/merge.go6
-rw-r--r--services/repository/files/temp_repo.go5
21 files changed, 283 insertions, 190 deletions
diff --git a/cmd/serv.go b/cmd/serv.go
index 6ba3e9de01..6e067a48a5 100644
--- a/cmd/serv.go
+++ b/cmd/serv.go
@@ -6,6 +6,7 @@
package cmd
import (
+ "context"
"fmt"
"net/http"
"net/url"
@@ -65,6 +66,21 @@ func setup(logPath string, debug bool) {
if debug {
setting.RunMode = "dev"
}
+
+ // Check if setting.RepoRootPath exists. It could be the case that it doesn't exist, this can happen when
+ // `[repository]` `ROOT` is a relative path and $GITEA_WORK_DIR isn't passed to the SSH connection.
+ if _, err := os.Stat(setting.RepoRootPath); err != nil {
+ if os.IsNotExist(err) {
+ _ = fail("Incorrect configuration, no repository directory.", "Directory `[repository].ROOT` %q was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository].ROOT` an absolute value.", setting.RepoRootPath)
+ } else {
+ _ = fail("Incorrect configuration, repository directory is inaccessible", "Directory `[repository].ROOT` %q is inaccessible. err: %v", setting.RepoRootPath, err)
+ }
+ return
+ }
+
+ if err := git.InitSimple(context.Background()); err != nil {
+ _ = fail("Failed to init git", "Failed to init git, err: %v", err)
+ }
}
var (
@@ -80,12 +96,12 @@ var (
func fail(userMessage, logMessage string, args ...interface{}) error {
// There appears to be a chance to cause a zombie process and failure to read the Exit status
// if nothing is outputted on stdout.
- fmt.Fprintln(os.Stdout, "")
- fmt.Fprintln(os.Stderr, "Gitea:", userMessage)
+ _, _ = fmt.Fprintln(os.Stdout, "")
+ _, _ = fmt.Fprintln(os.Stderr, "Gitea:", userMessage)
if len(logMessage) > 0 {
if !setting.IsProd {
- fmt.Fprintf(os.Stderr, logMessage+"\n", args...)
+ _, _ = fmt.Fprintf(os.Stderr, logMessage+"\n", args...)
}
}
ctx, cancel := installSignals()
@@ -237,17 +253,6 @@ func runServ(c *cli.Context) error {
}
return fail("Internal Server Error", "%s", err.Error())
}
- os.Setenv(repo_module.EnvRepoIsWiki, strconv.FormatBool(results.IsWiki))
- os.Setenv(repo_module.EnvRepoName, results.RepoName)
- os.Setenv(repo_module.EnvRepoUsername, results.OwnerName)
- os.Setenv(repo_module.EnvPusherName, results.UserName)
- os.Setenv(repo_module.EnvPusherEmail, results.UserEmail)
- os.Setenv(repo_module.EnvPusherID, strconv.FormatInt(results.UserID, 10))
- os.Setenv(repo_module.EnvRepoID, strconv.FormatInt(results.RepoID, 10))
- os.Setenv(repo_module.EnvPRID, fmt.Sprintf("%d", 0))
- os.Setenv(repo_module.EnvDeployKeyID, fmt.Sprintf("%d", results.DeployKeyID))
- os.Setenv(repo_module.EnvKeyID, fmt.Sprintf("%d", results.KeyID))
- os.Setenv(repo_module.EnvAppURL, setting.AppURL)
// LFS token authentication
if verb == lfsAuthenticateVerb {
@@ -298,20 +303,29 @@ func runServ(c *cli.Context) error {
gitcmd = exec.CommandContext(ctx, verb, repoPath)
}
- // Check if setting.RepoRootPath exists. It could be the case that it doesn't exist, this can happen when
- // `[repository]` `ROOT` is a relative path and $GITEA_WORK_DIR isn't passed to the SSH connection.
- if _, err := os.Stat(setting.RepoRootPath); err != nil {
- if os.IsNotExist(err) {
- return fail("Incorrect configuration.",
- "Directory `[repository]` `ROOT` %s was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository]` `ROOT` an absolute value.", setting.RepoRootPath)
- }
- }
-
process.SetSysProcAttribute(gitcmd)
gitcmd.Dir = setting.RepoRootPath
gitcmd.Stdout = os.Stdout
gitcmd.Stdin = os.Stdin
gitcmd.Stderr = os.Stderr
+ gitcmd.Env = append(gitcmd.Env, os.Environ()...)
+ gitcmd.Env = append(gitcmd.Env,
+ repo_module.EnvRepoIsWiki+"="+strconv.FormatBool(results.IsWiki),
+ repo_module.EnvRepoName+"="+results.RepoName,
+ repo_module.EnvRepoUsername+"="+results.OwnerName,
+ repo_module.EnvPusherName+"="+results.UserName,
+ repo_module.EnvPusherEmail+"="+results.UserEmail,
+ repo_module.EnvPusherID+"="+strconv.FormatInt(results.UserID, 10),
+ repo_module.EnvRepoID+"="+strconv.FormatInt(results.RepoID, 10),
+ repo_module.EnvPRID+"="+fmt.Sprintf("%d", 0),
+ repo_module.EnvDeployKeyID+"="+fmt.Sprintf("%d", results.DeployKeyID),
+ repo_module.EnvKeyID+"="+fmt.Sprintf("%d", results.KeyID),
+ repo_module.EnvAppURL+"="+setting.AppURL,
+ )
+ // to avoid breaking, here only use the minimal environment variables for the "gitea serv" command.
+ // it could be re-considered whether to use the same git.CommonGitCmdEnvs() as "git" command later.
+ gitcmd.Env = append(gitcmd.Env, git.CommonCmdServEnvs()...)
+
if err = gitcmd.Run(); err != nil {
return fail("Internal error", "Failed to execute git command: %v", err)
}
diff --git a/integrations/integration_test.go b/integrations/integration_test.go
index 687591d5fa..0a41546554 100644
--- a/integrations/integration_test.go
+++ b/integrations/integration_test.go
@@ -274,8 +274,8 @@ func prepareTestEnv(t testing.TB, skip ...int) func() {
deferFn := PrintCurrentTest(t, ourSkip)
assert.NoError(t, unittest.LoadFixtures())
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
-
assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
+ assert.NoError(t, git.InitWithConfigSync(context.Background()))
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
if err != nil {
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
@@ -576,6 +576,7 @@ func resetFixtures(t *testing.T) {
assert.NoError(t, unittest.LoadFixtures())
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
+ assert.NoError(t, git.InitWithConfigSync(context.Background()))
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
if err != nil {
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
diff --git a/integrations/migration-test/migration_test.go b/integrations/migration-test/migration_test.go
index 6a431504a0..45e31ff9ac 100644
--- a/integrations/migration-test/migration_test.go
+++ b/integrations/migration-test/migration_test.go
@@ -62,6 +62,7 @@ func initMigrationTest(t *testing.T) func() {
assert.True(t, len(setting.RepoRootPath) != 0)
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
+ assert.NoError(t, git.InitWithConfigSync(context.Background()))
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
if err != nil {
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
diff --git a/models/migrations/migrations_test.go b/models/migrations/migrations_test.go
index a1fd49a8b9..2c4d21d974 100644
--- a/models/migrations/migrations_test.go
+++ b/models/migrations/migrations_test.go
@@ -202,9 +202,8 @@ func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.En
ourSkip += skip
deferFn := PrintCurrentTest(t, ourSkip)
assert.NoError(t, os.RemoveAll(setting.RepoRootPath))
-
- assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"),
- setting.RepoRootPath))
+ assert.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
+ assert.NoError(t, git.InitWithConfigSync(context.Background()))
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
if err != nil {
assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go
index 117614a7a4..2a366836fe 100644
--- a/models/unittest/testdb.go
+++ b/models/unittest/testdb.go
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
@@ -116,6 +117,9 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
if err = CopyDir(filepath.Join(testOpts.GiteaRootPath, "integrations", "gitea-repositories-meta"), setting.RepoRootPath); err != nil {
fatalTestError("util.CopyDir: %v\n", err)
}
+ if err = git.InitWithConfigSync(context.Background()); err != nil {
+ fatalTestError("git.Init: %v\n", err)
+ }
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
if err != nil {
@@ -198,6 +202,7 @@ func PrepareTestEnv(t testing.TB) {
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
metaPath := filepath.Join(giteaRoot, "integrations", "gitea-repositories-meta")
assert.NoError(t, CopyDir(metaPath, setting.RepoRootPath))
+ assert.NoError(t, git.InitWithConfigSync(context.Background()))
ownerDirs, err := os.ReadDir(setting.RepoRootPath)
assert.NoError(t, err)
diff --git a/modules/git/command.go b/modules/git/command.go
index f6344dbfd1..d71497f1d7 100644
--- a/modules/git/command.go
+++ b/modules/git/command.go
@@ -8,6 +8,7 @@ package git
import (
"bytes"
"context"
+ "errors"
"fmt"
"io"
"os"
@@ -104,6 +105,25 @@ type RunOpts struct {
PipelineFunc func(context.Context, context.CancelFunc) error
}
+// CommonGitCmdEnvs returns the common environment variables for a "git" command.
+func CommonGitCmdEnvs() []string {
+ // at the moment, do not set "GIT_CONFIG_NOSYSTEM", users may have put some configs like "receive.certNonceSeed" in it
+ return []string{
+ fmt.Sprintf("LC_ALL=%s", DefaultLocale),
+ "GIT_TERMINAL_PROMPT=0", // avoid prompting for credentials interactively, supported since git v2.3
+ "GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace)
+ "HOME=" + HomeDir(), // make Gitea use internal git config only, to prevent conflicts with user's git config
+ }
+}
+
+// CommonCmdServEnvs is like CommonGitCmdEnvs but it only returns minimal required environment variables for the "gitea serv" command
+func CommonCmdServEnvs() []string {
+ return []string{
+ "GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace)
+ "HOME=" + HomeDir(), // make Gitea use internal git config only, to prevent conflicts with user's git config
+ }
+}
+
// Run runs the command with the RunOpts
func (c *Command) Run(opts *RunOpts) error {
if opts == nil {
@@ -148,16 +168,8 @@ func (c *Command) Run(opts *RunOpts) error {
cmd.Env = opts.Env
}
- cmd.Env = append(
- cmd.Env,
- fmt.Sprintf("LC_ALL=%s", DefaultLocale),
- // avoid prompting for credentials interactively, supported since git v2.3
- "GIT_TERMINAL_PROMPT=0",
- // ignore replace references (https://git-scm.com/docs/git-replace)
- "GIT_NO_REPLACE_OBJECTS=1",
- )
-
process.SetSysProcAttribute(cmd)
+ cmd.Env = append(cmd.Env, CommonGitCmdEnvs()...)
cmd.Dir = opts.Dir
cmd.Stdout = opts.Stdout
cmd.Stderr = opts.Stderr
@@ -184,7 +196,9 @@ func (c *Command) Run(opts *RunOpts) error {
type RunStdError interface {
error
+ Unwrap() error
Stderr() string
+ IsExitCode(code int) bool
}
type runStdError struct {
@@ -209,6 +223,14 @@ func (r *runStdError) Stderr() string {
return r.stderr
}
+func (r *runStdError) IsExitCode(code int) bool {
+ var exitError *exec.ExitError
+ if errors.As(r.err, &exitError) {
+ return exitError.ExitCode() == code
+ }
+ return false
+}
+
func bytesToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b)) // that's what Golang's strings.Builder.String() does (go/src/strings/builder.go)
}
diff --git a/modules/git/commit.go b/modules/git/commit.go
index 8c194ef502..99fbbd0cfc 100644
--- a/modules/git/commit.go
+++ b/modules/git/commit.go
@@ -398,11 +398,6 @@ func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
func (c *Commit) GetBranchName() (string, error) {
- err := LoadGitVersion()
- if err != nil {
- return "", fmt.Errorf("Git version missing: %v", err)
- }
-
args := []string{
"name-rev",
}
diff --git a/modules/git/git.go b/modules/git/git.go
index 8fad070330..5817bd2c7f 100644
--- a/modules/git/git.go
+++ b/modules/git/git.go
@@ -10,11 +10,13 @@ import (
"fmt"
"os"
"os/exec"
+ "path/filepath"
"runtime"
"strings"
+ "sync"
"time"
- "code.gitea.io/gitea/modules/process"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/hashicorp/go-version"
@@ -32,38 +34,33 @@ var (
GitExecutable = "git"
// DefaultContext is the default context to run git commands in
- // will be overwritten by Init with HammerContext
+ // will be overwritten by InitWithConfigSync with HammerContext
DefaultContext = context.Background()
- gitVersion *version.Version
-
// SupportProcReceive version >= 2.29.0
SupportProcReceive bool
-)
-// LocalVersion returns current Git version from shell.
-func LocalVersion() (*version.Version, error) {
- if err := LoadGitVersion(); err != nil {
- return nil, err
- }
- return gitVersion, nil
-}
+ // initMutex is used to avoid Golang's data race error. see the comments below.
+ initMutex sync.Mutex
+
+ gitVersion *version.Version
+)
-// LoadGitVersion returns current Git version from shell.
-func LoadGitVersion() error {
+// loadGitVersion returns current Git version from shell. Internal usage only.
+func loadGitVersion() (*version.Version, error) {
// doesn't need RWMutex because its exec by Init()
if gitVersion != nil {
- return nil
+ return gitVersion, nil
}
- stdout, _, runErr := NewCommand(context.Background(), "version").RunStdString(nil)
+ stdout, _, runErr := NewCommand(DefaultContext, "version").RunStdString(nil)
if runErr != nil {
- return runErr
+ return nil, runErr
}
fields := strings.Fields(stdout)
if len(fields) < 3 {
- return fmt.Errorf("not enough output: %s", stdout)
+ return nil, fmt.Errorf("invalid git version output: %s", stdout)
}
var versionString string
@@ -78,7 +75,7 @@ func LoadGitVersion() error {
var err error
gitVersion, err = version.NewVersion(versionString)
- return err
+ return gitVersion, err
}
// SetExecutablePath changes the path of git executable and checks the file permission and version.
@@ -93,7 +90,7 @@ func SetExecutablePath(path string) error {
}
GitExecutable = absPath
- err = LoadGitVersion()
+ _, err = loadGitVersion()
if err != nil {
return fmt.Errorf("unable to load git version: %w", err)
}
@@ -120,7 +117,10 @@ func SetExecutablePath(path string) error {
// VersionInfo returns git version information
func VersionInfo() string {
- format := "Git Version: %s"
+ if gitVersion == nil {
+ return "(git not found)"
+ }
+ format := "%s"
args := []interface{}{gitVersion.Original()}
// Since git wire protocol has been released from git v2.18
if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
@@ -131,8 +131,33 @@ func VersionInfo() string {
return fmt.Sprintf(format, args...)
}
-// Init initializes git module
-func Init(ctx context.Context) error {
+// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
+// This method doesn't change anything to filesystem
+func InitSimple(ctx context.Context) error {
+ initMutex.Lock()
+ defer initMutex.Unlock()
+
+ return initSimpleInternal(ctx)
+}
+
+// HomeDir is the home dir for git to store the global config file used by Gitea internally
+func HomeDir() string {
+ if setting.RepoRootPath == "" {
+ // TODO: now, some unit test code call the git module directly without initialization, which is incorrect.
+ // at the moment, we just use a temp HomeDir to prevent from conflicting with user's git config
+ // in the future, the git module should be initialized first before use.
+ tmpHomeDir := filepath.Join(os.TempDir(), "gitea-temp-home")
+ log.Error("Git's HomeDir is empty (RepoRootPath is empty), the git module is not initialized correctly, using a temp HomeDir (%s) temporarily", tmpHomeDir)
+ return tmpHomeDir
+ }
+ return setting.RepoRootPath
+}
+
+func initSimpleInternal(ctx context.Context) error {
+ // at the moment, when running integration tests, the git.InitXxx would be called twice.
+ // one is called by the GlobalInitInstalled, one is called by TestMain.
+ // so the init functions should be protected by a mutex to avoid Golang's data race error.
+
DefaultContext = ctx
if setting.Git.Timeout.Default > 0 {
@@ -146,6 +171,23 @@ func Init(ctx context.Context) error {
// force cleanup args
globalCommandArgs = []string{}
+ return nil
+}
+
+// InitWithConfigSync initializes git module. This method may create directories or write files into filesystem
+func InitWithConfigSync(ctx context.Context) error {
+ initMutex.Lock()
+ defer initMutex.Unlock()
+
+ err := initSimpleInternal(ctx)
+ if err != nil {
+ return err
+ }
+
+ if err = os.MkdirAll(setting.RepoRootPath, os.ModePerm); err != nil {
+ return fmt.Errorf("unable to create directory %s, err: %w", setting.RepoRootPath, err)
+ }
+
if CheckGitVersionAtLeast("2.9") == nil {
// Explicitly disable credential helper, otherwise Git credentials might leak
globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
@@ -161,68 +203,71 @@ func Init(ctx context.Context) error {
globalCommandArgs = append(globalCommandArgs, "-c", "uploadpack.allowfilter=true", "-c", "uploadpack.allowAnySHA1InWant=true")
}
- // Save current git version on init to gitVersion otherwise it would require an RWMutex
- if err := LoadGitVersion(); err != nil {
- return err
- }
-
- // Git requires setting user.name and user.email in order to commit changes - if they're not set just add some defaults
- for configKey, defaultValue := range map[string]string{"user.name": "Gitea", "user.email": "gitea@fake.local"} {
- if err := checkAndSetConfig(configKey, defaultValue, false); err != nil {
+ // 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 {
return err
}
}
// 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 {
+ if err := configSet("core.quotePath", "false"); err != nil {
return err
}
if CheckGitVersionAtLeast("2.10") == nil {
- if err := checkAndSetConfig("receive.advertisePushOptions", "true", true); err != nil {
+ if err := configSet("receive.advertisePushOptions", "true"); err != nil {
return err
}
}
if CheckGitVersionAtLeast("2.18") == nil {
- if err := checkAndSetConfig("core.commitGraph", "true", true); err != nil {
+ if err := configSet("core.commitGraph", "true"); err != nil {
return err
}
- if err := checkAndSetConfig("gc.writeCommitGraph", "true", true); err != nil {
+ if err := configSet("gc.writeCommitGraph", "true"); err != nil {
return err
}
}
if CheckGitVersionAtLeast("2.29") == nil {
// set support for AGit flow
- if err := checkAndAddConfig("receive.procReceiveRefs", "refs/for"); err != nil {
+ if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
return err
}
SupportProcReceive = true
} else {
- if err := checkAndRemoveConfig("receive.procReceiveRefs", "refs/for"); err != nil {
+ if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil {
return err
}
SupportProcReceive = false
}
if runtime.GOOS == "windows" {
- if err := checkAndSetConfig("core.longpaths", "true", true); err != nil {
+ if err := configSet("core.longpaths", "true"); err != nil {
return err
}
- }
- if setting.Git.DisableCoreProtectNTFS {
- if err := checkAndSetConfig("core.protectntfs", "false", true); err != nil {
+ if setting.Git.DisableCoreProtectNTFS {
+ err = configSet("core.protectNTFS", "false")
+ } else {
+ err = configUnsetAll("core.protectNTFS", "false")
+ }
+ if err != nil {
return err
}
- globalCommandArgs = append(globalCommandArgs, "-c", "core.protectntfs=false")
}
+
return nil
}
// CheckGitVersionAtLeast check git version is at least the constraint version
func CheckGitVersionAtLeast(atLeast string) error {
- if err := LoadGitVersion(); err != nil {
+ if _, err := loadGitVersion(); err != nil {
return err
}
atLeastVersion, err := version.NewVersion(atLeast)
@@ -235,75 +280,75 @@ func CheckGitVersionAtLeast(atLeast string) error {
return nil
}
-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)
- }
- 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)
- }
+func configSet(key, value string) error {
+ stdout, _, err := NewCommand(DefaultContext, "config", "--get", key).RunStdString(nil)
+ if err != nil && !err.IsExitCode(1) {
+ return fmt.Errorf("failed to get git config %s, err: %w", key, err)
}
currValue := strings.TrimSpace(stdout)
-
- if currValue == defaultValue || (!forceToDefault && len(currValue) > 0) {
+ if currValue == value {
return nil
}
- 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)
+ _, _, err = NewCommand(DefaultContext, "config", "--global", key, value).RunStdString(nil)
+ if err != nil {
+ return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
}
return nil
}
-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
+func configSetNonExist(key, value string) error {
+ _, _, err := NewCommand(DefaultContext, "config", "--get", key).RunStdString(nil)
+ if err == nil {
+ // already exist
+ return nil
+ }
+ if err.IsExitCode(1) {
+ // not exist, set new config
+ _, _, err = NewCommand(DefaultContext, "config", "--global", key, value).RunStdString(nil)
+ if err != nil {
+ return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
}
+ return nil
}
- return nil
+ return fmt.Errorf("failed to get git config %s, err: %w", key, err)
}
-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
+func configAddNonExist(key, value string) error {
+ _, _, err := NewCommand(DefaultContext, "config", "--fixed-value", "--get", key, value).RunStdString(nil)
+ if err == nil {
+ // already exist
+ return nil
+ }
+ if err.IsExitCode(1) {
+ // not exist, add new config
+ _, _, err = NewCommand(DefaultContext, "config", "--global", "--add", key, value).RunStdString(nil)
+ if err != nil {
+ return fmt.Errorf("failed to add git global config %s, err: %w", key, err)
}
+ return nil
}
+ return fmt.Errorf("failed to get git config %s, err: %w", key, err)
+}
- 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)
+func configUnsetAll(key, value string) error {
+ _, _, err := NewCommand(DefaultContext, "config", "--get", key).RunStdString(nil)
+ if err == nil {
+ // exist, need to remove
+ _, _, err = NewCommand(DefaultContext, "config", "--global", "--fixed-value", "--unset-all", key, value).RunStdString(nil)
+ if err != nil {
+ return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
+ }
+ return nil
}
-
- return nil
+ if err.IsExitCode(1) {
+ // not exist
+ return nil
+ }
+ return fmt.Errorf("failed to get git config %s, err: %w", key, err)
}
// Fsck verifies the connectivity and validity of the objects in the database
diff --git a/modules/git/git_test.go b/modules/git/git_test.go
index c62a55badc..5b1cd820e8 100644
--- a/modules/git/git_test.go
+++ b/modules/git/git_test.go
@@ -8,23 +8,74 @@ import (
"context"
"fmt"
"os"
+ "strings"
"testing"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
+
+ "github.com/stretchr/testify/assert"
)
-func fatalTestError(fmtStr string, args ...interface{}) {
- fmt.Fprintf(os.Stderr, fmtStr, args...)
- os.Exit(1)
+func testRun(m *testing.M) error {
+ _ = log.NewLogger(1000, "console", "console", `{"level":"trace","stacktracelevel":"NONE","stderr":true}`)
+
+ repoRootPath, err := os.MkdirTemp(os.TempDir(), "repos")
+ if err != nil {
+ return fmt.Errorf("unable to create temp dir: %w", err)
+ }
+ defer util.RemoveAll(repoRootPath)
+ setting.RepoRootPath = repoRootPath
+
+ if err = InitWithConfigSync(context.Background()); err != nil {
+ return fmt.Errorf("failed to call Init: %w", err)
+ }
+
+ exitCode := m.Run()
+ if exitCode != 0 {
+ return fmt.Errorf("run test failed, ExitCode=%d", exitCode)
+ }
+ return nil
}
func TestMain(m *testing.M) {
- _ = log.NewLogger(1000, "console", "console", `{"level":"trace","stacktracelevel":"NONE","stderr":true}`)
+ if err := testRun(m); err != nil {
+ _, _ = fmt.Fprintf(os.Stderr, "Test failed: %v", err)
+ os.Exit(1)
+ }
+}
- if err := Init(context.Background()); err != nil {
- fatalTestError("Init failed: %v", err)
+func TestGitConfig(t *testing.T) {
+ gitConfigContains := func(sub string) bool {
+ if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil {
+ return strings.Contains(string(b), sub)
+ }
+ return false
}
- exitStatus := m.Run()
- os.Exit(exitStatus)
+ assert.False(t, gitConfigContains("key-a"))
+
+ assert.NoError(t, configSetNonExist("test.key-a", "val-a"))
+ assert.True(t, gitConfigContains("key-a = val-a"))
+
+ assert.NoError(t, configSetNonExist("test.key-a", "val-a-changed"))
+ assert.False(t, gitConfigContains("key-a = val-a-changed"))
+
+ assert.NoError(t, configSet("test.key-a", "val-a-changed"))
+ assert.True(t, gitConfigContains("key-a = val-a-changed"))
+
+ assert.NoError(t, configAddNonExist("test.key-b", "val-b"))
+ assert.True(t, gitConfigContains("key-b = val-b"))
+
+ assert.NoError(t, configAddNonExist("test.key-b", "val-2b"))
+ assert.True(t, gitConfigContains("key-b = val-b"))
+ assert.True(t, gitConfigContains("key-b = val-2b"))
+
+ assert.NoError(t, configUnsetAll("test.key-b", "val-b"))
+ assert.False(t, gitConfigContains("key-b = val-b"))
+ assert.True(t, gitConfigContains("key-b = val-2b"))
+
+ assert.NoError(t, configUnsetAll("test.key-b", "val-2b"))
+ assert.False(t, gitConfigContains("key-b = val-2b"))
}
diff --git a/modules/git/lfs.go b/modules/git/lfs.go
index cdd9d15b2e..c5d8354b6d 100644
--- a/modules/git/lfs.go
+++ b/modules/git/lfs.go
@@ -18,12 +18,6 @@ func CheckLFSVersion() {
if setting.LFS.StartServer {
// Disable LFS client hooks if installed for the current OS user
// Needs at least git v2.1.2
-
- err := LoadGitVersion()
- if err != nil {
- logger.Fatal("Error retrieving git version: %v", err)
- }
-
if CheckGitVersionAtLeast("2.1.2") != nil {
setting.LFS.StartServer = false
logger.Error("LFS server support needs at least Git v2.1.2")
diff --git a/modules/git/remote.go b/modules/git/remote.go
index 536b1681ce..b2a2e6d7ab 100644
--- a/modules/git/remote.go
+++ b/modules/git/remote.go
@@ -11,10 +11,6 @@ import (
// GetRemoteAddress returns the url of a specific remote of the repository.
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (*url.URL, error) {
- err := LoadGitVersion()
- if err != nil {
- return nil, err
- }
var cmd *Command
if CheckGitVersionAtLeast("2.7") == nil {
cmd = NewCommand(ctx, "remote", "get-url", remoteName)
diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go
index a18c80c3f1..38818788f3 100644
--- a/modules/git/repo_attribute.go
+++ b/modules/git/repo_attribute.go
@@ -28,11 +28,6 @@ type CheckAttributeOpts struct {
// CheckAttribute return the Blame object of file
func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[string]string, error) {
- err := LoadGitVersion()
- if err != nil {
- return nil, fmt.Errorf("git version missing: %v", err)
- }
-
env := []string{}
if len(opts.IndexFile) > 0 && CheckGitVersionAtLeast("1.7.8") == nil {
@@ -126,7 +121,7 @@ type CheckAttributeReader struct {
cancel context.CancelFunc
}
-// Init initializes the cmd
+// Init initializes the CheckAttributeReader
func (c *CheckAttributeReader) Init(ctx context.Context) error {
cmdArgs := []string{"check-attr", "--stdin", "-z"}
diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go
index 3e7a9c2cfb..2e139daddf 100644
--- a/modules/git/repo_tree.go
+++ b/modules/git/repo_tree.go
@@ -24,11 +24,6 @@ type CommitTreeOpts struct {
// CommitTree creates a commit from a given tree id for the user with provided message
func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opts CommitTreeOpts) (SHA1, error) {
- err := LoadGitVersion()
- if err != nil {
- return SHA1{}, err
- }
-
commitTimeStr := time.Now().Format(time.RFC3339)
// Because this may call hooks we should pass in the environment
@@ -60,14 +55,13 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
- err = cmd.Run(&RunOpts{
+ err := cmd.Run(&RunOpts{
Env: env,
Dir: repo.Path,
Stdin: messageBytes,
Stdout: stdout,
Stderr: stderr,
})
-
if err != nil {
return SHA1{}, ConcatenateError(err, stderr.String())
}
diff --git a/modules/indexer/stats/indexer_test.go b/modules/indexer/stats/indexer_test.go
index 9d9de5413c..a335972c21 100644
--- a/modules/indexer/stats/indexer_test.go
+++ b/modules/indexer/stats/indexer_test.go
@@ -30,7 +30,7 @@ func TestMain(m *testing.M) {
}
func TestRepoStatsIndex(t *testing.T) {
- if err := git.Init(context.Background()); !assert.NoError(t, err) {
+ if err := git.InitWithConfigSync(context.Background()); !assert.NoError(t, err) {
return
}
diff --git a/modules/repository/init.go b/modules/repository/init.go
index f8c7a89552..285fe81dbe 100644
--- a/modules/repository/init.go
+++ b/modules/repository/init.go
@@ -317,11 +317,6 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi
return fmt.Errorf("git add --all: %v", err)
}
- err = git.LoadGitVersion()
- if err != nil {
- return fmt.Errorf("Unable to get git version: %v", err)
- }
-
args := []string{
"commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
"-m", "Initial commit",
diff --git a/modules/util/path.go b/modules/util/path.go
index ed7cc62699..0ccc7a1dc2 100644
--- a/modules/util/path.go
+++ b/modules/util/path.go
@@ -182,6 +182,7 @@ func FileURLToPath(u *url.URL) (string, error) {
// it returns error when the variable does not exist.
func HomeDir() (home string, err error) {
// TODO: some users run Gitea with mismatched uid and "HOME=xxx" (they set HOME=xxx by environment manually)
+ // TODO: when running gitea as a sub command inside git, the HOME directory is not the user's home directory
// so at the moment we can not use `user.Current().HomeDir`
if isOSWindows() {
home = os.Getenv("USERPROFILE")
diff --git a/routers/init.go b/routers/init.go
index 6036499362..9b6a770f27 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -102,8 +102,8 @@ func GlobalInitInstalled(ctx context.Context) {
log.Fatal("Gitea is not installed")
}
- mustInitCtx(ctx, git.Init)
- log.Info(git.VersionInfo())
+ mustInitCtx(ctx, git.InitWithConfigSync)
+ log.Info("Git Version: %s (home: %s)", git.VersionInfo(), git.HomeDir())
git.CheckLFSVersion()
log.Info("AppPath: %s", setting.AppPath)
diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go
index 78347e67c4..24c07b5c1c 100644
--- a/routers/web/admin/admin.go
+++ b/routers/web/admin/admin.go
@@ -247,9 +247,8 @@ func Config(ctx *context.Context) {
ctx.Data["DisableRouterLog"] = setting.DisableRouterLog
ctx.Data["RunUser"] = setting.RunUser
ctx.Data["RunMode"] = util.ToTitleCase(setting.RunMode)
- if version, err := git.LocalVersion(); err == nil {
- ctx.Data["GitVersion"] = version.Original()
- }
+ ctx.Data["GitVersion"] = git.VersionInfo()
+
ctx.Data["RepoRootPath"] = setting.RepoRootPath
ctx.Data["CustomRootPath"] = setting.CustomPath
ctx.Data["StaticRootPath"] = setting.StaticRootPath
diff --git a/routers/web/repo/lfs.go b/routers/web/repo/lfs.go
index 7c2ff1cfae..e2421f1389 100644
--- a/routers/web/repo/lfs.go
+++ b/routers/web/repo/lfs.go
@@ -421,12 +421,9 @@ func LFSPointerFiles(ctx *context.Context) {
return
}
ctx.Data["PageIsSettingsLFS"] = true
- err := git.LoadGitVersion()
- if err != nil {
- log.Fatal("Error retrieving git version: %v", err)
- }
ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs"
+ var err error
err = func() error {
pointerChan := make(chan lfs.PointerBlob)
errChan := make(chan error, 1)
diff --git a/services/pull/merge.go b/services/pull/merge.go
index fcced65cdf..76798d73ed 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -223,12 +223,6 @@ func Merge(ctx context.Context, pr *models.PullRequest, doer *user_model.User, b
// rawMerge perform the merge operation without changing any pull information in database
func rawMerge(ctx context.Context, pr *models.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string) (string, error) {
- err := git.LoadGitVersion()
- if err != nil {
- log.Error("git.LoadGitVersion: %v", err)
- return "", fmt.Errorf("Unable to get git version: %v", err)
- }
-
// Clone base repo.
tmpBasePath, err := createTemporaryRepo(ctx, pr)
if err != nil {
diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go
index 9c7d9aafec..97a80a96bd 100644
--- a/services/repository/files/temp_repo.go
+++ b/services/repository/files/temp_repo.go
@@ -229,11 +229,6 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, co
authorSig := author.NewGitSig()
committerSig := committer.NewGitSig()
- err := git.LoadGitVersion()
- if err != nil {
- return "", fmt.Errorf("Unable to get git version: %v", err)
- }
-
// Because this may call hooks we should pass in the environment
env := append(os.Environ(),
"GIT_AUTHOR_NAME="+authorSig.Name,