diff --git a/modules/git/command.go b/modules/git/command.go
index d71497f1d7..a1bacbb707 100644
--- a/modules/git/command.go
+++ b/modules/git/command.go
@@ -105,23 +105,36 @@ type RunOpts struct {
 	PipelineFunc   func(context.Context, context.CancelFunc) error
 }
 
+func commonBaseEnvs() []string {
+	// at the moment, do not set "GIT_CONFIG_NOSYSTEM", users may have put some configs like "receive.certNonceSeed" in it
+	envs := []string{
+		"HOME=" + HomeDir(),        // make Gitea use internal git config only, to prevent conflicts with user's git config
+		"GIT_NO_REPLACE_OBJECTS=1", // ignore replace references (https://git-scm.com/docs/git-replace)
+	}
+
+	// some environment variables should be passed to git command
+	passThroughEnvKeys := []string{
+		"GNUPGHOME", // git may call gnupg to do commit signing
+	}
+	for _, key := range passThroughEnvKeys {
+		if val, ok := os.LookupEnv(key); ok {
+			envs = append(envs, key+"="+val)
+		}
+	}
+	return envs
+}
+
 // 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
-	}
+	return append(commonBaseEnvs(), []string{
+		"LC_ALL=" + DefaultLocale,
+		"GIT_TERMINAL_PROMPT=0", // avoid prompting for credentials interactively, supported since git v2.3
+	}...)
 }
 
 // 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
-	}
+	return commonBaseEnvs()
 }
 
 // Run runs the command with the RunOpts
diff --git a/modules/git/git.go b/modules/git/git.go
index 8ee31e815e..96e4efb486 100644
--- a/modules/git/git.go
+++ b/modules/git/git.go
@@ -20,6 +20,7 @@ import (
 
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/util"
 
 	"github.com/hashicorp/go-version"
 )
@@ -167,6 +168,47 @@ func InitSimple(ctx context.Context) error {
 
 var initOnce sync.Once
 
+func initFixGitHome117rc() error {
+	// Gitea 1.17-rc uses "setting.RepoRootPath" for Git HOME, which is incorrect.
+	// Do this check to make sure there is no legacy file in the RepoRootPath. This check might be able to be removed with 1.19 release.
+
+	// remove the auto generated git config file (it will be moved to new home)
+	gitConfigNewPath := filepath.Join(HomeDir(), ".gitconfig")
+	gitConfigLegacyPath := filepath.Join(setting.RepoRootPath, ".gitconfig")
+	if ok, err := util.IsExist(gitConfigLegacyPath); ok && err == nil {
+		if err = os.MkdirAll(HomeDir(), os.ModePerm); err != nil {
+			return err
+		}
+		if ok, err = util.IsExist(gitConfigNewPath); !ok && err == nil {
+			err = util.CopyFile(gitConfigLegacyPath, gitConfigNewPath)
+		} else {
+			err = util.CopyFile(gitConfigLegacyPath, gitConfigNewPath+".bak")
+		}
+		if err != nil {
+			return err
+		}
+		_ = os.Remove(gitConfigLegacyPath)
+	}
+
+	// remove the empty directories, if some directories are non-empty, warn users and exit
+	var hasCheckErr bool
+	for _, wellDirName := range []string{".ssh", ".gnupg"} {
+		checkLegacyDir := filepath.Join(setting.RepoRootPath, wellDirName)
+		_ = os.Remove(checkLegacyDir)          // try to remove the empty dummy directory first
+		_, checkErr := os.Stat(checkLegacyDir) // if the directory is not empty, then it won't be removed, it should be handled manually
+		if checkErr == nil || !errors.Is(checkErr, os.ErrNotExist) {
+			log.Error(`Git HOME has been moved to [git].HOME_PATH, but there are legacy file in old place. Please backup and remove the legacy files %q`, checkLegacyDir)
+			hasCheckErr = true
+		}
+	}
+
+	if hasCheckErr {
+		log.Fatal("Please fix errors above, remove legacy files.")
+	}
+
+	return nil
+}
+
 // InitOnceWithSync initializes git module with version check and change global variables, sync gitconfig.
 // This method will update the global variables ONLY ONCE (just like git.CheckLFSVersion -- which is not ideal too),
 // otherwise there will be data-race problem at the moment.
@@ -176,28 +218,12 @@ func InitOnceWithSync(ctx context.Context) (err error) {
 	}
 
 	initOnce.Do(func() {
-		err = InitSimple(ctx)
-		if err != nil {
+		if err = InitSimple(ctx); err != nil {
 			return
 		}
-
-		// Gitea 1.17-rc uses "setting.RepoRootPath" for Git HOME, which is incorrect.
-		// Do this check to make sure there is no legacy file in the RepoRootPath. This check might be able to be removed with 1.19 release.
-		var hasCheckErr bool
-		_ = os.Remove(filepath.Join(setting.RepoRootPath, ".gitconfig")) // remove the auto generated git config file
-		_ = os.Remove(filepath.Join(setting.RepoRootPath, ".ssh"))       // remove the empty dummy ".ssh" directory
-		for _, wellKnownName := range []string{".ssh", ".gnupg"} {
-			checkLegacyFile := filepath.Join(setting.RepoRootPath, wellKnownName)
-			_, checkErr := os.Stat(checkLegacyFile)
-			if checkErr == nil || !errors.Is(checkErr, os.ErrNotExist) {
-				log.Error(`Git HOME has been moved to [git].HOME_PATH, but there are legacy file in old place. Please backup and remove the legacy files %q`, checkLegacyFile)
-				hasCheckErr = true
-			}
+		if err = initFixGitHome117rc(); err != nil {
+			return
 		}
-		if hasCheckErr {
-			log.Fatal("Please fix errors above, remove legacy files")
-		}
-		// end of legacy Gitea 1.17-rc check
 
 		// Since git wire protocol has been released from git v2.18
 		if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {