From dac2594e113a32641ff5ca3f3805b3acc55033de Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 17 May 2022 14:56:11 +0800 Subject: [PATCH 01/24] Refactor git module, make Gitea use internal git config, add safe.directory config --- integrations/integration_test.go | 3 +- integrations/migration-test/migration_test.go | 1 + models/migrations/migrations_test.go | 5 +- models/unittest/testdb.go | 6 + modules/git/command.go | 28 ++- modules/git/commit.go | 5 - modules/git/git.go | 167 +++++++++--------- modules/git/git_test.go | 58 +++++- modules/git/lfs.go | 6 - modules/git/remote.go | 4 - modules/git/repo_attribute.go | 5 - modules/git/repo_tree.go | 8 +- modules/repository/init.go | 5 - routers/init.go | 2 +- routers/web/admin/admin.go | 5 +- routers/web/repo/lfs.go | 5 +- services/pull/merge.go | 6 - services/repository/files/temp_repo.go | 5 - 18 files changed, 171 insertions(+), 153 deletions(-) diff --git a/integrations/integration_test.go b/integrations/integration_test.go index 4df485a6e8e1f..4cb685ec12fa7 100644 --- a/integrations/integration_test.go +++ b/integrations/integration_test.go @@ -261,8 +261,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.Init(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) @@ -563,6 +563,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.Init(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 6e55807c271e3..cc1ab2785c3d4 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.Init(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 a1fd49a8b9f20..3f09ecfa2b722 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.Init(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 117614a7a4f30..c9b9302bdb02f 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" @@ -106,6 +107,10 @@ func MainTest(m *testing.M, testOpts *TestOptions) { setting.Packages.Storage.Path = filepath.Join(setting.AppDataPath, "packages") + if err = git.Init(context.Background()); err != nil { + fatalTestError("git.Init: %v\n", err) + } + if err = storage.Init(); err != nil { fatalTestError("storage.Init: %v\n", err) } @@ -198,6 +203,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.Init(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 3dd12e421e409..6237518c98a8a 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -8,10 +8,12 @@ package git import ( "bytes" "context" + "errors" "fmt" "io" "os" "os/exec" + "path/filepath" "strings" "time" "unsafe" @@ -148,13 +150,21 @@ func (c *Command) Run(opts *RunOpts) error { cmd.Env = opts.Env } + if GlobalConfigFile == "" { + // TODO: now, some unit test code call the git module directly without initialization, which is incorrect. + // at the moment, we just use a temp gitconfig to prevent from conflicting with user's gitconfig + // in future, the git module should be initialized first before use. + GlobalConfigFile = filepath.Join(os.TempDir(), "/gitea-temp-gitconfig") + log.Warn("GlobalConfigFile is empty, the git module is not initialized correctly, using a temp gitconfig (%s) temporarily", GlobalConfigFile) + } + 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", + "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) + "GIT_CONFIG_NOSYSTEM=1", // https://git-scm.com/docs/git-config + "GIT_CONFIG_GLOBAL="+GlobalConfigFile, // make Gitea use internal git config only, to prevent conflicts with user's git config ) cmd.Dir = opts.Dir @@ -183,7 +193,9 @@ func (c *Command) Run(opts *RunOpts) error { type RunStdError interface { error + Unwrap() error Stderr() string + IsExitCode(code int) bool } type runStdError struct { @@ -208,6 +220,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 8c194ef502946..99fbbd0cfcb1e 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 8fad07033006a..78576b936f3aa 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -7,6 +7,7 @@ package git import ( "context" + "errors" "fmt" "os" "os/exec" @@ -14,7 +15,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" "github.com/hashicorp/go-version" @@ -31,39 +31,34 @@ var ( // Could be updated to an absolute path while initialization GitExecutable = "git" + // GlobalConfigFile is the global config file used by Gitea internally + GlobalConfigFile string + // DefaultContext is the default context to run git commands in // will be overwritten by Init 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 -} + 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 +73,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 +88,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 +115,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 { @@ -139,6 +137,15 @@ func Init(ctx context.Context) error { defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second } + if setting.RepoRootPath == "" { + return errors.New("RepoRootPath is empty, git module needs that setting before initialization") + } + + if err := os.MkdirAll(setting.RepoRootPath, os.ModePerm); err != nil { + return fmt.Errorf("unable to create directory %s, err:%w", setting.RepoRootPath, err) + } + GlobalConfigFile = setting.RepoRootPath + "/gitconfig" + if err := SetExecutablePath(setting.Git.Path); err != nil { return err } @@ -161,58 +168,64 @@ 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 { + if err := configSet(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 CheckGitVersionAtLeast("2.35.2") == nil { + // since Git 2.35.2, git adds a protection for CVE-2022-24765, the protection denies the git directories which are not owned by current user + // however, some docker users and samba users (maybe more, issue #19455) have difficulty to set their Gitea git repositories to the correct owner. + // the reason behind the problem is: docker/samba uses some uid-mapping mechanism, which are unstable/unfixable in some cases. + // now Gitea always use its customized git config file, and all the accesses to the git repositories can be managed, + // so it's safe to set "safe.directory=*" for internal usage only. + if err := configSet("safe.directory", "*"); err != nil { + return err + } + } + 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 err := configSet("core.protectntfs", "false"); err != nil { return err } globalCommandArgs = append(globalCommandArgs, "-c", "core.protectntfs=false") @@ -222,7 +235,7 @@ func Init(ctx context.Context) error { // 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 +248,57 @@ 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 configAddNonExist(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, add 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 configUnsetAll(key, valueRegex string) error { + _, _, err := NewCommand(DefaultContext, "config", "--get", key).RunStdString(nil) + if err == nil { + // exist, need to remove + _, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all", key, valueRegex).RunStdString(nil) + if err != nil { + return fmt.Errorf("failed to unset git global config %s, err:%w", key, err) } + 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) + if err.IsExitCode(1) { + // not exist + return nil } - - 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 c62a55badc682..61e8a9a463e42 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -8,23 +8,65 @@ 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 = Init(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(GlobalConfigFile); err == nil { + return strings.Contains(string(b), sub) + } + return false } - exitStatus := m.Run() - os.Exit(exitStatus) + assert.False(t, gitConfigContains("test.key-a")) + assert.NoError(t, configSet("test.key-a", "val-a")) + assert.True(t, gitConfigContains("key-a = val-a")) + + assert.NoError(t, configAddNonExist("test.key-a", "val-a-new")) + assert.False(t, gitConfigContains("key-a = val-a-new")) + + 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, configUnsetAll("test.key-b", "val-b")) + assert.False(t, gitConfigContains("key-b = val-b")) } diff --git a/modules/git/lfs.go b/modules/git/lfs.go index cdd9d15b2e869..c5d8354b6dc8c 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 536b1681cecf9..b2a2e6d7ab41a 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 a18c80c3f12f3..1ad6e25fdce47 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 { diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go index 3e7a9c2cfbcea..2e139daddfc72 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/repository/init.go b/modules/repository/init.go index 845a61ed0a0ce..4248a26c0daa6 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/routers/init.go b/routers/init.go index 6036499362944..faa2bb68dc359 100644 --- a/routers/init.go +++ b/routers/init.go @@ -103,7 +103,7 @@ func GlobalInitInstalled(ctx context.Context) { } mustInitCtx(ctx, git.Init) - log.Info(git.VersionInfo()) + log.Info("Git Version: %s (config: %s)", git.VersionInfo(), git.GlobalConfigFile) git.CheckLFSVersion() log.Info("AppPath: %s", setting.AppPath) diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go index 78347e67c4bd0..24c07b5c1c19d 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 7c2ff1cfae124..e2421f1389261 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 fcced65cdf8ca..76798d73edc28 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 9c7d9aafec71a..97a80a96bd6ab 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, From 45a9375c7b453612b996bbe2d3504263227ab0c5 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 17 May 2022 17:15:45 +0800 Subject: [PATCH 02/24] introduce git.InitSimple and git.InitWithConfigSync, make serv cmd use gitconfig --- cmd/serv.go | 28 +++++++++------- integrations/integration_test.go | 4 +-- integrations/migration-test/migration_test.go | 2 +- models/migrations/migrations_test.go | 2 +- models/unittest/testdb.go | 4 +-- modules/git/command.go | 20 ++++++------ modules/git/git.go | 32 ++++++++++++------- modules/git/git_test.go | 2 +- modules/git/repo_attribute.go | 2 +- modules/indexer/stats/indexer_test.go | 2 +- routers/init.go | 2 +- 11 files changed, 59 insertions(+), 41 deletions(-) diff --git a/cmd/serv.go b/cmd/serv.go index 340f591dce0b3..4d9211b7af145 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -6,6 +6,7 @@ package cmd import ( + "context" "fmt" "net/http" "net/url" @@ -64,6 +65,19 @@ 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.", "Directory `[repository].ROOT` was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository].ROOT` an absolute value.") + return + } + } + + if err := git.InitSimple(context.Background()); err != nil { + _ = fail("Failed to init git", "Failed to init git") + } } var ( @@ -79,8 +93,8 @@ 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 { @@ -297,19 +311,11 @@ 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` was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository]` `ROOT` an absolute value.") - } - } - gitcmd.Dir = setting.RepoRootPath gitcmd.Stdout = os.Stdout gitcmd.Stdin = os.Stdin gitcmd.Stderr = os.Stderr + gitcmd.Env = append(gitcmd.Env, git.CommonEnvs()...) 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 4cb685ec12fa7..4e481747704c0 100644 --- a/integrations/integration_test.go +++ b/integrations/integration_test.go @@ -262,7 +262,7 @@ func prepareTestEnv(t testing.TB, skip ...int) func() { 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.Init(context.Background())) + 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) @@ -563,7 +563,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.Init(context.Background())) + 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 cc1ab2785c3d4..a8db49dd0167c 100644 --- a/integrations/migration-test/migration_test.go +++ b/integrations/migration-test/migration_test.go @@ -62,7 +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.Init(context.Background())) + 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 3f09ecfa2b722..2c4d21d974bf6 100644 --- a/models/migrations/migrations_test.go +++ b/models/migrations/migrations_test.go @@ -203,7 +203,7 @@ func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.En 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, git.Init(context.Background())) + 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 c9b9302bdb02f..f1399c4519514 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -107,7 +107,7 @@ func MainTest(m *testing.M, testOpts *TestOptions) { setting.Packages.Storage.Path = filepath.Join(setting.AppDataPath, "packages") - if err = git.Init(context.Background()); err != nil { + if err = git.InitWithConfigSync(context.Background()); err != nil { fatalTestError("git.Init: %v\n", err) } @@ -203,7 +203,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.Init(context.Background())) + 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 6237518c98a8a..9b79f197f40ae 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -106,6 +106,16 @@ type RunOpts struct { PipelineFunc func(context.Context, context.CancelFunc) error } +func CommonEnvs() []string { + 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) + "GIT_CONFIG_NOSYSTEM=1", // https://git-scm.com/docs/git-config + "GIT_CONFIG_GLOBAL=" + GlobalConfigFile, // 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 { @@ -158,15 +168,7 @@ func (c *Command) Run(opts *RunOpts) error { log.Warn("GlobalConfigFile is empty, the git module is not initialized correctly, using a temp gitconfig (%s) temporarily", GlobalConfigFile) } - cmd.Env = append( - cmd.Env, - 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) - "GIT_CONFIG_NOSYSTEM=1", // https://git-scm.com/docs/git-config - "GIT_CONFIG_GLOBAL="+GlobalConfigFile, // make Gitea use internal git config only, to prevent conflicts with user's git config - ) - + cmd.Env = append(cmd.Env, CommonEnvs()...) cmd.Dir = opts.Dir cmd.Stdout = opts.Stdout cmd.Stderr = opts.Stderr diff --git a/modules/git/git.go b/modules/git/git.go index 78576b936f3aa..faaa30fb59a69 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -35,7 +35,7 @@ var ( GlobalConfigFile string // 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() // SupportProcReceive version >= 2.29.0 @@ -129,8 +129,9 @@ 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 { DefaultContext = ctx if setting.Git.Timeout.Default > 0 { @@ -141,9 +142,6 @@ func Init(ctx context.Context) error { return errors.New("RepoRootPath is empty, git module needs that setting before initialization") } - if err := os.MkdirAll(setting.RepoRootPath, os.ModePerm); err != nil { - return fmt.Errorf("unable to create directory %s, err:%w", setting.RepoRootPath, err) - } GlobalConfigFile = setting.RepoRootPath + "/gitconfig" if err := SetExecutablePath(setting.Git.Path); err != nil { @@ -153,6 +151,20 @@ 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 { + err := InitSimple(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=") @@ -223,13 +235,11 @@ func Init(ctx context.Context) error { if err := configSet("core.longpaths", "true"); err != nil { return err } - } - if setting.Git.DisableCoreProtectNTFS { - if err := configSet("core.protectntfs", "false"); err != nil { - return err + if setting.Git.DisableCoreProtectNTFS { + globalCommandArgs = append(globalCommandArgs, "-c", "core.protectntfs=false") } - globalCommandArgs = append(globalCommandArgs, "-c", "core.protectntfs=false") } + return nil } diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 61e8a9a463e42..18c72ef9884f9 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -28,7 +28,7 @@ func testRun(m *testing.M) error { defer util.RemoveAll(repoRootPath) setting.RepoRootPath = repoRootPath - if err = Init(context.Background()); err != nil { + if err = InitWithConfigSync(context.Background()); err != nil { return fmt.Errorf("failed to call Init: %w", err) } diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go index 1ad6e25fdce47..4919c9b717160 100644 --- a/modules/git/repo_attribute.go +++ b/modules/git/repo_attribute.go @@ -121,7 +121,7 @@ type CheckAttributeReader struct { cancel context.CancelFunc } -// Init initializes the cmd +// InitWithConfigSync initializes the cmd func (c *CheckAttributeReader) Init(ctx context.Context) error { cmdArgs := []string{"check-attr", "--stdin", "-z"} diff --git a/modules/indexer/stats/indexer_test.go b/modules/indexer/stats/indexer_test.go index c8bd8d1783d2f..6d76bea82e949 100644 --- a/modules/indexer/stats/indexer_test.go +++ b/modules/indexer/stats/indexer_test.go @@ -29,7 +29,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/routers/init.go b/routers/init.go index faa2bb68dc359..4c977730b1e12 100644 --- a/routers/init.go +++ b/routers/init.go @@ -102,7 +102,7 @@ func GlobalInitInstalled(ctx context.Context) { log.Fatal("Gitea is not installed") } - mustInitCtx(ctx, git.Init) + mustInitCtx(ctx, git.InitWithConfigSync) log.Info("Git Version: %s (config: %s)", git.VersionInfo(), git.GlobalConfigFile) git.CheckLFSVersion() From 22d62bcf639529258d73f993680c61dbce3c7752 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 17 May 2022 17:50:44 +0800 Subject: [PATCH 03/24] use HOME instead of GIT_CONFIG_GLOBAL, because git always needs a correct HOME --- models/unittest/testdb.go | 7 +++---- modules/git/command.go | 16 ++++++++-------- modules/git/git.go | 6 +++--- modules/git/git_test.go | 2 +- modules/setting/setting.go | 1 + modules/util/path.go | 1 + routers/init.go | 2 +- 7 files changed, 18 insertions(+), 17 deletions(-) diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index f1399c4519514..2a366836fe32f 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -107,10 +107,6 @@ func MainTest(m *testing.M, testOpts *TestOptions) { setting.Packages.Storage.Path = filepath.Join(setting.AppDataPath, "packages") - if err = git.InitWithConfigSync(context.Background()); err != nil { - fatalTestError("git.Init: %v\n", err) - } - if err = storage.Init(); err != nil { fatalTestError("storage.Init: %v\n", err) } @@ -121,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 { diff --git a/modules/git/command.go b/modules/git/command.go index 9b79f197f40ae..d1ce64c8898bd 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -109,10 +109,10 @@ type RunOpts struct { func CommonEnvs() []string { 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) - "GIT_CONFIG_NOSYSTEM=1", // https://git-scm.com/docs/git-config - "GIT_CONFIG_GLOBAL=" + GlobalConfigFile, // make Gitea use internal git config only, to prevent conflicts with user's git config + "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) + "GIT_CONFIG_NOSYSTEM=1", // https://git-scm.com/docs/git-config, and GIT_CONFIG_GLOBAL, they require git >= 2.32 + "HOME=" + HomeDir, // make Gitea use internal git config only, to prevent conflicts with user's git config } } @@ -160,12 +160,12 @@ func (c *Command) Run(opts *RunOpts) error { cmd.Env = opts.Env } - if GlobalConfigFile == "" { + if HomeDir == "" { // TODO: now, some unit test code call the git module directly without initialization, which is incorrect. - // at the moment, we just use a temp gitconfig to prevent from conflicting with user's gitconfig + // at the moment, we just use a temp HomeDir to prevent from conflicting with user's git config // in future, the git module should be initialized first before use. - GlobalConfigFile = filepath.Join(os.TempDir(), "/gitea-temp-gitconfig") - log.Warn("GlobalConfigFile is empty, the git module is not initialized correctly, using a temp gitconfig (%s) temporarily", GlobalConfigFile) + HomeDir = filepath.Join(os.TempDir(), "/gitea-temp-home") + log.Warn("Git's HomeDir is empty, the git module is not initialized correctly, using a temp HomeDir (%s) temporarily", HomeDir) } cmd.Env = append(cmd.Env, CommonEnvs()...) diff --git a/modules/git/git.go b/modules/git/git.go index faaa30fb59a69..3b46d20fa6234 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -31,8 +31,8 @@ var ( // Could be updated to an absolute path while initialization GitExecutable = "git" - // GlobalConfigFile is the global config file used by Gitea internally - GlobalConfigFile string + // HomeDir is the home dir for git to store the global config file used by Gitea internally + HomeDir string // DefaultContext is the default context to run git commands in // will be overwritten by InitWithConfigSync with HammerContext @@ -142,7 +142,7 @@ func InitSimple(ctx context.Context) error { return errors.New("RepoRootPath is empty, git module needs that setting before initialization") } - GlobalConfigFile = setting.RepoRootPath + "/gitconfig" + HomeDir = setting.RepoRootPath if err := SetExecutablePath(setting.Git.Path); err != nil { return err diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 18c72ef9884f9..5fd46ba574bef 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -48,7 +48,7 @@ func TestMain(m *testing.M) { func TestGitConfig(t *testing.T) { gitConfigContains := func(sub string) bool { - if b, err := os.ReadFile(GlobalConfigFile); err == nil { + if b, err := os.ReadFile(HomeDir + "/.gitconfig"); err == nil { return strings.Contains(string(b), sub) } return false diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 5e317b39ea289..5c07a73735a02 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -612,6 +612,7 @@ func loadFromConf(allowEmpty bool, extraConfig string) { Cfg.NameMapper = ini.SnackCase + // TODO: when running gitea as a sub command inside git, the HOME directory is not the user's home directory homeDir, err := util.HomeDir() if err != nil { log.Fatal("Failed to get home directory: %v", err) diff --git a/modules/util/path.go b/modules/util/path.go index ed7cc62699446..0ccc7a1dc2aca 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 4c977730b1e12..0ab2382e33003 100644 --- a/routers/init.go +++ b/routers/init.go @@ -103,7 +103,7 @@ func GlobalInitInstalled(ctx context.Context) { } mustInitCtx(ctx, git.InitWithConfigSync) - log.Info("Git Version: %s (config: %s)", git.VersionInfo(), git.GlobalConfigFile) + log.Info("Git Version: %s (home: %s)", git.VersionInfo(), git.HomeDir) git.CheckLFSVersion() log.Info("AppPath: %s", setting.AppPath) From b7f928cf8e2021d031a14ddb28f1775c9f0e3057 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 17 May 2022 18:42:52 +0800 Subject: [PATCH 04/24] fix cmd env in cmd/serv.go --- cmd/serv.go | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/cmd/serv.go b/cmd/serv.go index 4d9211b7af145..96784ea6ac2cd 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -250,17 +250,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 { @@ -315,7 +304,22 @@ func runServ(c *cli.Context) error { gitcmd.Stdout = os.Stdout gitcmd.Stdin = os.Stdin gitcmd.Stderr = os.Stderr + gitcmd.Env = append(gitcmd.Env, os.Environ()...) // FIXME: the legacy code passes the whole env. in the future, should only pass the env which are really needed + 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, + ) gitcmd.Env = append(gitcmd.Env, git.CommonEnvs()...) + if err = gitcmd.Run(); err != nil { return fail("Internal error", "Failed to execute git command: %v", err) } From 0841cc6a5ebf059dbe37361cc54b251422e5f2c9 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 17 May 2022 19:28:20 +0800 Subject: [PATCH 05/24] fine tune error message --- cmd/serv.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/serv.go b/cmd/serv.go index 96784ea6ac2cd..ec1d8426fa3be 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -70,13 +70,15 @@ func setup(logPath string, debug bool) { // `[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.", "Directory `[repository].ROOT` was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository].ROOT` an absolute value.") - return + _ = fail("Incorrect configuration, no repository directory.", "Directory `[repository].ROOT` was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository].ROOT` an absolute value.") + } else { + _ = fail("Incorrect configuration, repository directory is inaccessible", "Directory `[repository].ROOT` is inaccessible. err:%v", err) } + return } if err := git.InitSimple(context.Background()); err != nil { - _ = fail("Failed to init git", "Failed to init git") + _ = fail("Failed to init git", "Failed to init git, err:%v", err) } } @@ -98,7 +100,7 @@ func fail(userMessage, logMessage string, args ...interface{}) error { if len(logMessage) > 0 { if !setting.IsProd { - fmt.Fprintf(os.Stderr, logMessage+"\n", args...) + _, _ = fmt.Fprintf(os.Stderr, logMessage+"\n", args...) } } ctx, cancel := installSignals() From 6274dc9a8cacc310a35214ebfef0eef2f02f6a77 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 17 May 2022 23:57:10 +0800 Subject: [PATCH 06/24] Fix a incorrect test case --- modules/git/git_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 5fd46ba574bef..2900e43104611 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -54,7 +54,7 @@ func TestGitConfig(t *testing.T) { return false } - assert.False(t, gitConfigContains("test.key-a")) + assert.False(t, gitConfigContains("key-a")) assert.NoError(t, configSet("test.key-a", "val-a")) assert.True(t, gitConfigContains("key-a = val-a")) From b06a4ef175c406b6f049535ddda7cdb4d8b17215 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 18 May 2022 00:11:04 +0800 Subject: [PATCH 07/24] fix configAddNonExist --- modules/git/git.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/git/git.go b/modules/git/git.go index 3b46d20fa6234..1df3ee0865e6c 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -278,7 +278,7 @@ func configSet(key, value string) error { } func configAddNonExist(key, value string) error { - _, _, err := NewCommand(DefaultContext, "config", "--get", key).RunStdString(nil) + _, _, err := NewCommand(DefaultContext, "config", "--get", key, value).RunStdString(nil) if err == nil { // already exist return nil From fba95dc61e50f06e5e9056a2498f251657d2dfe0 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 18 May 2022 00:22:57 +0800 Subject: [PATCH 08/24] fix configAddNonExist logic, add `--fixed-value` flag, add tests --- modules/git/git.go | 8 ++++---- modules/git/git_test.go | 11 ++++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/modules/git/git.go b/modules/git/git.go index 1df3ee0865e6c..d5f47356faa9d 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -278,14 +278,14 @@ func configSet(key, value string) error { } func configAddNonExist(key, value string) error { - _, _, err := NewCommand(DefaultContext, "config", "--get", key, value).RunStdString(nil) + _, _, 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", key, value).RunStdString(nil) + _, _, err = NewCommand(DefaultContext, "config", "--global", "--add", key, value).RunStdString(nil) if err != nil { return fmt.Errorf("failed to set git global config %s, err:%w", key, err) } @@ -294,11 +294,11 @@ func configAddNonExist(key, value string) error { return fmt.Errorf("failed to get git config %s, err:%w", key, err) } -func configUnsetAll(key, valueRegex string) error { +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", "--unset-all", key, valueRegex).RunStdString(nil) + _, _, 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) } diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 2900e43104611..6d5b492adbc7c 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -58,15 +58,20 @@ func TestGitConfig(t *testing.T) { assert.NoError(t, configSet("test.key-a", "val-a")) assert.True(t, gitConfigContains("key-a = val-a")) - assert.NoError(t, configAddNonExist("test.key-a", "val-a-new")) - assert.False(t, gitConfigContains("key-a = val-a-new")) - 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")) } From 241104d5b2b45769c02020defe230f033739ce74 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 18 May 2022 00:34:06 +0800 Subject: [PATCH 09/24] add configSetNonExist function in case it's needed. --- modules/git/git.go | 20 +++++++++++++++++++- modules/git/git_test.go | 6 +++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/modules/git/git.go b/modules/git/git.go index d5f47356faa9d..fa84beed65adf 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -277,6 +277,24 @@ func configSet(key, value string) error { 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 fmt.Errorf("failed to get git config %s, err:%w", key, err) +} + func configAddNonExist(key, value string) error { _, _, err := NewCommand(DefaultContext, "config", "--fixed-value", "--get", key, value).RunStdString(nil) if err == nil { @@ -287,7 +305,7 @@ func configAddNonExist(key, value string) error { // not exist, add new config _, _, err = NewCommand(DefaultContext, "config", "--global", "--add", key, value).RunStdString(nil) if err != nil { - return fmt.Errorf("failed to set git global config %s, err:%w", key, err) + return fmt.Errorf("failed to add git global config %s, err:%w", key, err) } return nil } diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 6d5b492adbc7c..3f876e9a9320f 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -55,9 +55,13 @@ func TestGitConfig(t *testing.T) { } assert.False(t, gitConfigContains("key-a")) - assert.NoError(t, configSet("test.key-a", "val-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")) From 3b1e6d1deaa12c1415ffd15718a0c00979569acb Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 18 May 2022 00:35:05 +0800 Subject: [PATCH 10/24] use configSetNonExist for `user.name` and `user.email` --- modules/git/git.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/git/git.go b/modules/git/git.go index fa84beed65adf..f7fa1726cdc22 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -181,8 +181,11 @@ func InitWithConfigSync(ctx context.Context) error { } // 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 := configSet(configKey, defaultValue); err != nil { + for configKey, defaultValue := range map[string]string{ + "user.name": "Gitea", + "user.email": "gitea@fake.local", + } { + if err := configSetNonExist(configKey, defaultValue); err != nil { return err } } From 4e2679f9527c71e022fa66fae27ba71da98fc4a3 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 18 May 2022 00:50:17 +0800 Subject: [PATCH 11/24] add some comments --- modules/git/git.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/git/git.go b/modules/git/git.go index f7fa1726cdc22..d8b0a43ae3667 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -180,7 +180,9 @@ func InitWithConfigSync(ctx context.Context) error { globalCommandArgs = append(globalCommandArgs, "-c", "uploadpack.allowfilter=true", "-c", "uploadpack.allowAnySHA1InWant=true") } - // Git requires setting user.name and user.email in order to commit changes - if they're not set just add some defaults + // 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", From 9866adda2c741a846003fbedf24ac0c3b9b24fd6 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 18 May 2022 11:58:01 +0800 Subject: [PATCH 12/24] Update cmd/serv.go Co-authored-by: zeripath --- cmd/serv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/serv.go b/cmd/serv.go index ec1d8426fa3be..e8e904d8f4a73 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -72,7 +72,7 @@ func setup(logPath string, debug bool) { if os.IsNotExist(err) { _ = fail("Incorrect configuration, no repository directory.", "Directory `[repository].ROOT` was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository].ROOT` an absolute value.") } else { - _ = fail("Incorrect configuration, repository directory is inaccessible", "Directory `[repository].ROOT` is inaccessible. err:%v", err) + _ = fail("Incorrect configuration, repository directory is inaccessible", "Directory `[repository].ROOT` is inaccessible. err: %v", err) } return } From 5d6cd68d529702224900ed5e8f74ee20b216cbd4 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 18 May 2022 11:58:09 +0800 Subject: [PATCH 13/24] Update cmd/serv.go Co-authored-by: zeripath --- cmd/serv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/serv.go b/cmd/serv.go index e8e904d8f4a73..e84787387b6b2 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -78,7 +78,7 @@ func setup(logPath string, debug bool) { } if err := git.InitSimple(context.Background()); err != nil { - _ = fail("Failed to init git", "Failed to init git, err:%v", err) + _ = fail("Failed to init git", "Failed to init git, err: %v", err) } } From 7d96b3902bac6518416cc67404dc81ec116b935a Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 18 May 2022 11:58:58 +0800 Subject: [PATCH 14/24] Update modules/git/git.go Co-authored-by: zeripath --- modules/git/git.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/git/git.go b/modules/git/git.go index d8b0a43ae3667..81abf387580b2 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -266,7 +266,7 @@ func CheckGitVersionAtLeast(atLeast string) error { 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) + return fmt.Errorf("failed to get git config %s, err: %w", key, err) } currValue := strings.TrimSpace(stdout) From c09f9c9915c69cd1aac768fe00f3b63f28843298 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 18 May 2022 12:09:26 +0800 Subject: [PATCH 15/24] Update modules/setting/setting.go Co-authored-by: zeripath --- modules/setting/setting.go | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 5c07a73735a02..5e317b39ea289 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -612,7 +612,6 @@ func loadFromConf(allowEmpty bool, extraConfig string) { Cfg.NameMapper = ini.SnackCase - // TODO: when running gitea as a sub command inside git, the HOME directory is not the user's home directory homeDir, err := util.HomeDir() if err != nil { log.Fatal("Failed to get home directory: %v", err) From 57d9b1b14720171a298d53b21eb4c954ca5f185c Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 18 May 2022 12:10:22 +0800 Subject: [PATCH 16/24] Update modules/git/repo_attribute.go Co-authored-by: zeripath --- modules/git/repo_attribute.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go index 4919c9b717160..38818788f32a3 100644 --- a/modules/git/repo_attribute.go +++ b/modules/git/repo_attribute.go @@ -121,7 +121,7 @@ type CheckAttributeReader struct { cancel context.CancelFunc } -// InitWithConfigSync initializes the cmd +// Init initializes the CheckAttributeReader func (c *CheckAttributeReader) Init(ctx context.Context) error { cmdArgs := []string{"check-attr", "--stdin", "-z"} From ae9d7861f0292a1895d3e5f9f1160f11ac7badab Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 18 May 2022 12:13:41 +0800 Subject: [PATCH 17/24] fix spaces in messages --- modules/git/git.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/git/git.go b/modules/git/git.go index 81abf387580b2..046d42c7d16fb 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -162,7 +162,7 @@ func InitWithConfigSync(ctx context.Context) error { } if err = os.MkdirAll(setting.RepoRootPath, os.ModePerm); err != nil { - return fmt.Errorf("unable to create directory %s, err:%w", setting.RepoRootPath, err) + return fmt.Errorf("unable to create directory %s, err: %w", setting.RepoRootPath, err) } if CheckGitVersionAtLeast("2.9") == nil { @@ -276,7 +276,7 @@ func configSet(key, value string) error { _, _, 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 fmt.Errorf("failed to set git global config %s, err: %w", key, err) } return nil @@ -292,12 +292,12 @@ func configSetNonExist(key, value string) error { // 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 fmt.Errorf("failed to set git global config %s, err: %w", key, err) } return nil } - return fmt.Errorf("failed to get git config %s, err:%w", key, err) + return fmt.Errorf("failed to get git config %s, err: %w", key, err) } func configAddNonExist(key, value string) error { @@ -310,11 +310,11 @@ func configAddNonExist(key, value string) error { // 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 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) + return fmt.Errorf("failed to get git config %s, err: %w", key, err) } func configUnsetAll(key, value string) error { @@ -323,7 +323,7 @@ func configUnsetAll(key, value string) error { // 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 fmt.Errorf("failed to unset git global config %s, err: %w", key, err) } return nil } @@ -331,7 +331,7 @@ func configUnsetAll(key, value string) error { // not exist return nil } - return fmt.Errorf("failed to get git config %s, err:%w", key, err) + 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 From 71eb7f426ea1b97cf4aeff4b7cad2d871b094e95 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 18 May 2022 12:23:19 +0800 Subject: [PATCH 18/24] use `configSet("core.protectNTFS", ...)` instead of `globalCommandArgs` --- modules/git/git.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/git/git.go b/modules/git/git.go index 046d42c7d16fb..47c378a76468e 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -241,7 +241,12 @@ func InitWithConfigSync(ctx context.Context) error { return err } if setting.Git.DisableCoreProtectNTFS { - globalCommandArgs = append(globalCommandArgs, "-c", "core.protectntfs=false") + err = configSet("core.protectNTFS", "false") + } else { + err = configUnsetAll("core.protectNTFS", "false") + } + if err != nil { + return err } } From 1da696d0a9625c53bef6d0c4551c8bcb66546128 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 19 May 2022 12:30:53 +0800 Subject: [PATCH 19/24] remove GIT_CONFIG_NOSYSTEM, continue to use system's git config --- modules/git/command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/git/command.go b/modules/git/command.go index d1ce64c8898bd..07f8cc27855e1 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -107,11 +107,11 @@ type RunOpts struct { } func CommonEnvs() []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) - "GIT_CONFIG_NOSYSTEM=1", // https://git-scm.com/docs/git-config, and GIT_CONFIG_GLOBAL, they require git >= 2.32 "HOME=" + HomeDir, // make Gitea use internal git config only, to prevent conflicts with user's git config } } From 733445c55deb4ea6490e4677d64b77a953978fd3 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 3 Jun 2022 00:00:12 +0800 Subject: [PATCH 20/24] Update cmd/serv.go Co-authored-by: zeripath --- cmd/serv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/serv.go b/cmd/serv.go index e84787387b6b2..591d209852c9a 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -306,7 +306,7 @@ func runServ(c *cli.Context) error { gitcmd.Stdout = os.Stdout gitcmd.Stdin = os.Stdin gitcmd.Stderr = os.Stderr - gitcmd.Env = append(gitcmd.Env, os.Environ()...) // FIXME: the legacy code passes the whole env. in the future, should only pass the env which are really needed + gitcmd.Env = append(gitcmd.Env, os.Environ()...) gitcmd.Env = append(gitcmd.Env, repo_module.EnvRepoIsWiki+"="+strconv.FormatBool(results.IsWiki), repo_module.EnvRepoName+"="+results.RepoName, From 30c49f372850f33a9d04ce5b61df122a790c2062 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 4 Jun 2022 22:26:57 +0800 Subject: [PATCH 21/24] fix merge --- cmd/serv.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/cmd/serv.go b/cmd/serv.go index 1430fe5cc17f6..99134adcfa728 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -71,9 +71,9 @@ func setup(logPath string, debug bool) { // `[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` was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository].ROOT` an absolute value.") + _ = 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` is inaccessible. err: %v", err) + _ = fail("Incorrect configuration, repository directory is inaccessible", "Directory `[repository].ROOT` %q is inaccessible. err: %v", setting.RepoRootPath, err) } return } @@ -303,15 +303,6 @@ 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 From c5f1e940b600e029c9c9ecf9d21ddfaf84183f52 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 4 Jun 2022 22:28:41 +0800 Subject: [PATCH 22/24] remove code for safe.directory --- modules/git/git.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/modules/git/git.go b/modules/git/git.go index 47c378a76468e..700d7141cca7c 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -225,17 +225,6 @@ func InitWithConfigSync(ctx context.Context) error { SupportProcReceive = false } - if CheckGitVersionAtLeast("2.35.2") == nil { - // since Git 2.35.2, git adds a protection for CVE-2022-24765, the protection denies the git directories which are not owned by current user - // however, some docker users and samba users (maybe more, issue #19455) have difficulty to set their Gitea git repositories to the correct owner. - // the reason behind the problem is: docker/samba uses some uid-mapping mechanism, which are unstable/unfixable in some cases. - // now Gitea always use its customized git config file, and all the accesses to the git repositories can be managed, - // so it's safe to set "safe.directory=*" for internal usage only. - if err := configSet("safe.directory", "*"); err != nil { - return err - } - } - if runtime.GOOS == "windows" { if err := configSet("core.longpaths", "true"); err != nil { return err From b3011979a3f60e668c7d2dba550795ca17e23fd3 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 5 Jun 2022 10:42:34 +0800 Subject: [PATCH 23/24] separate git.CommonEnvs to CommonGitCmdEnvs and CommonCmdServEnvs --- cmd/serv.go | 4 +++- modules/git/command.go | 13 +++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/cmd/serv.go b/cmd/serv.go index 99134adcfa728..6e067a48a570f 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -322,7 +322,9 @@ func runServ(c *cli.Context) error { repo_module.EnvKeyID+"="+fmt.Sprintf("%d", results.KeyID), repo_module.EnvAppURL+"="+setting.AppURL, ) - gitcmd.Env = append(gitcmd.Env, git.CommonEnvs()...) + // 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/modules/git/command.go b/modules/git/command.go index a4b341d7073b5..2be843b6f6673 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -106,7 +106,8 @@ type RunOpts struct { PipelineFunc func(context.Context, context.CancelFunc) error } -func CommonEnvs() []string { +// 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), @@ -116,6 +117,14 @@ func CommonEnvs() []string { } } +// 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 { @@ -169,7 +178,7 @@ func (c *Command) Run(opts *RunOpts) error { } process.SetSysProcAttribute(cmd) - cmd.Env = append(cmd.Env, CommonEnvs()...) + cmd.Env = append(cmd.Env, CommonGitCmdEnvs()...) cmd.Dir = opts.Dir cmd.Stdout = opts.Stdout cmd.Stderr = opts.Stderr From 26fa08779a41905eafe2f4713c4af3ea07363ba6 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 9 Jun 2022 21:22:05 +0800 Subject: [PATCH 24/24] avoid Golang's data race error --- models/repo/fork.go | 1 + models/repo/main_test.go | 4 ++-- modules/git/command.go | 13 ++---------- modules/git/git.go | 45 ++++++++++++++++++++++++++++++---------- modules/git/git_test.go | 2 +- routers/init.go | 2 +- 6 files changed, 41 insertions(+), 26 deletions(-) diff --git a/models/repo/fork.go b/models/repo/fork.go index 938bbae17e16b..b54c61c425fd7 100644 --- a/models/repo/fork.go +++ b/models/repo/fork.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" + "xorm.io/builder" ) diff --git a/models/repo/main_test.go b/models/repo/main_test.go index eb04aa8227d8d..f6d704ca65874 100644 --- a/models/repo/main_test.go +++ b/models/repo/main_test.go @@ -8,12 +8,12 @@ import ( "path/filepath" "testing" + "code.gitea.io/gitea/models/unittest" + _ "code.gitea.io/gitea/models" // register table model _ "code.gitea.io/gitea/models/perm/access" // register table model _ "code.gitea.io/gitea/models/repo" // register table model _ "code.gitea.io/gitea/models/user" // register table model - - "code.gitea.io/gitea/models/unittest" ) func TestMain(m *testing.M) { diff --git a/modules/git/command.go b/modules/git/command.go index 2be843b6f6673..d71497f1d791e 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -13,7 +13,6 @@ import ( "io" "os" "os/exec" - "path/filepath" "strings" "time" "unsafe" @@ -113,7 +112,7 @@ func CommonGitCmdEnvs() []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 + "HOME=" + HomeDir(), // make Gitea use internal git config only, to prevent conflicts with user's git config } } @@ -121,7 +120,7 @@ func CommonGitCmdEnvs() []string { 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 + "HOME=" + HomeDir(), // make Gitea use internal git config only, to prevent conflicts with user's git config } } @@ -169,14 +168,6 @@ func (c *Command) Run(opts *RunOpts) error { cmd.Env = opts.Env } - if HomeDir == "" { - // 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 future, the git module should be initialized first before use. - HomeDir = filepath.Join(os.TempDir(), "/gitea-temp-home") - log.Warn("Git's HomeDir is empty, the git module is not initialized correctly, using a temp HomeDir (%s) temporarily", HomeDir) - } - process.SetSysProcAttribute(cmd) cmd.Env = append(cmd.Env, CommonGitCmdEnvs()...) cmd.Dir = opts.Dir diff --git a/modules/git/git.go b/modules/git/git.go index 700d7141cca7c..5817bd2c7fe2d 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -7,14 +7,16 @@ package git import ( "context" - "errors" "fmt" "os" "os/exec" + "path/filepath" "runtime" "strings" + "sync" "time" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "github.com/hashicorp/go-version" @@ -31,9 +33,6 @@ var ( // Could be updated to an absolute path while initialization GitExecutable = "git" - // HomeDir is the home dir for git to store the global config file used by Gitea internally - HomeDir string - // DefaultContext is the default context to run git commands in // will be overwritten by InitWithConfigSync with HammerContext DefaultContext = context.Background() @@ -41,6 +40,9 @@ var ( // SupportProcReceive version >= 2.29.0 SupportProcReceive bool + // initMutex is used to avoid Golang's data race error. see the comments below. + initMutex sync.Mutex + gitVersion *version.Version ) @@ -132,18 +134,36 @@ func VersionInfo() string { // 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 { defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second } - if setting.RepoRootPath == "" { - return errors.New("RepoRootPath is empty, git module needs that setting before initialization") - } - - HomeDir = setting.RepoRootPath - if err := SetExecutablePath(setting.Git.Path); err != nil { return err } @@ -156,7 +176,10 @@ func InitSimple(ctx context.Context) error { // InitWithConfigSync initializes git module. This method may create directories or write files into filesystem func InitWithConfigSync(ctx context.Context) error { - err := InitSimple(ctx) + initMutex.Lock() + defer initMutex.Unlock() + + err := initSimpleInternal(ctx) if err != nil { return err } diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 3f876e9a9320f..5b1cd820e85e3 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -48,7 +48,7 @@ func TestMain(m *testing.M) { func TestGitConfig(t *testing.T) { gitConfigContains := func(sub string) bool { - if b, err := os.ReadFile(HomeDir + "/.gitconfig"); err == nil { + if b, err := os.ReadFile(HomeDir() + "/.gitconfig"); err == nil { return strings.Contains(string(b), sub) } return false diff --git a/routers/init.go b/routers/init.go index 0ab2382e33003..9b6a770f279b6 100644 --- a/routers/init.go +++ b/routers/init.go @@ -103,7 +103,7 @@ func GlobalInitInstalled(ctx context.Context) { } mustInitCtx(ctx, git.InitWithConfigSync) - log.Info("Git Version: %s (home: %s)", git.VersionInfo(), git.HomeDir) + log.Info("Git Version: %s (home: %s)", git.VersionInfo(), git.HomeDir()) git.CheckLFSVersion() log.Info("AppPath: %s", setting.AppPath)