From 157b4057531b99b2d3b5c086f385c830aa38354f Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Thu, 16 Jun 2022 23:47:44 +0800
Subject: [PATCH] Remove legacy git code (ver < 2.0), fine tune markup tests
 (#19930)

* clean git support for ver < 2.0

* fine tune tests for markup (which requires git module)

* remove unnecessary comments

* try to fix tests

* try test again

* use const for GitVersionRequired instead of var

* try to fix integration test

* Refactor CheckAttributeReader to make a *git.Repository version

* update document for commit signing with Gitea's internal gitconfig

* update document for commit signing with Gitea's internal gitconfig

Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
---
 cmd/hook.go                                   |  4 +-
 docs/content/doc/advanced/signing.en-us.md    | 15 +++--
 integrations/integration_test.go              |  9 ++-
 integrations/migration-test/migration_test.go |  2 +-
 models/migrations/migrations_test.go          |  6 +-
 models/unittest/testdb.go                     |  4 +-
 modules/git/commit.go                         | 27 +++------
 modules/git/git.go                            | 55 +++++++++--------
 modules/git/repo_attribute.go                 | 60 +++++++++++++------
 modules/git/repo_compare.go                   |  8 +--
 modules/git/repo_language_stats_gogit.go      | 31 +---------
 modules/git/repo_language_stats_nogogit.go    | 33 +---------
 modules/git/repo_tree.go                      |  4 +-
 modules/markup/html_test.go                   |  9 +++
 modules/markup/markdown/markdown_test.go      |  8 +++
 modules/repository/init.go                    | 20 +++----
 services/gitdiff/gitdiff.go                   | 33 +---------
 services/pull/merge.go                        | 27 +++------
 services/repository/files/temp_repo.go        | 49 +++++++--------
 19 files changed, 178 insertions(+), 226 deletions(-)

diff --git a/cmd/hook.go b/cmd/hook.go
index 8078763b18..73386038b3 100644
--- a/cmd/hook.go
+++ b/cmd/hook.go
@@ -308,6 +308,8 @@ func runHookPostReceive(c *cli.Context) error {
 	ctx, cancel := installSignals()
 	defer cancel()
 
+	setup("hooks/post-receive.log", c.Bool("debug"))
+
 	// First of all run update-server-info no matter what
 	if _, _, err := git.NewCommand(ctx, "update-server-info").RunStdString(nil); err != nil {
 		return fmt.Errorf("Failed to call 'git update-server-info': %v", err)
@@ -318,8 +320,6 @@ func runHookPostReceive(c *cli.Context) error {
 		return nil
 	}
 
-	setup("hooks/post-receive.log", c.Bool("debug"))
-
 	if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
 		if setting.OnlyAllowPushIfGiteaEnvironmentSet {
 			return fail(`Rejecting changes as Gitea environment not set.
diff --git a/docs/content/doc/advanced/signing.en-us.md b/docs/content/doc/advanced/signing.en-us.md
index aaaeb71034..8ae2f94e9e 100644
--- a/docs/content/doc/advanced/signing.en-us.md
+++ b/docs/content/doc/advanced/signing.en-us.md
@@ -83,8 +83,7 @@ The first option to discuss is the `SIGNING_KEY`. There are three main
 options:
 
 - `none` - this prevents Gitea from signing any commits
-- `default` - Gitea will default to the key configured within
-  `git config`
+- `default` - Gitea will default to the key configured within `git config`
 - `KEYID` - Gitea will sign commits with the gpg key with the ID
   `KEYID`. In this case you should provide a `SIGNING_NAME` and
   `SIGNING_EMAIL` to be displayed for this key.
@@ -98,6 +97,12 @@ repositories, `SIGNING_KEY=default` could be used to provide different
 signing keys on a per-repository basis. However, this is clearly not an
 ideal UI and therefore subject to change.
 
+**Since 1.17**, Gitea runs git in its own home directory `[repository].ROOT` and uses its own config `{[repository].ROOT}/.gitconfig`.
+If you have your own customized git config for Gitea, you should set these configs in system git config (aka `/etc/gitconfig`)
+or the Gitea internal git config `{[repository].ROOT}/.gitconfig`. 
+Related home files for git command (like `.gnupg`) should also be put in Gitea's git home directory `[repository].ROOT`. 
+
+
 ### `INITIAL_COMMIT`
 
 This option determines whether Gitea should sign the initial commit
@@ -118,7 +123,7 @@ The possible values are:
 
 - `never`: Never sign
 - `pubkey`: Only sign if the user has a public key
-- `twofa`: Only sign if the user logs in with two factor authentication
+- `twofa`: Only sign if the user logs in with two-factor authentication
 - `parentsigned`: Only sign if the parent commit is signed.
 - `always`: Always sign
 
@@ -132,7 +137,7 @@ editor or API CRUD actions. The possible values are:
 
 - `never`: Never sign
 - `pubkey`: Only sign if the user has a public key
-- `twofa`: Only sign if the user logs in with two factor authentication
+- `twofa`: Only sign if the user logs in with two-factor authentication
 - `parentsigned`: Only sign if the parent commit is signed.
 - `always`: Always sign
 
@@ -146,7 +151,7 @@ The possible options are:
 
 - `never`: Never sign
 - `pubkey`: Only sign if the user has a public key
-- `twofa`: Only sign if the user logs in with two factor authentication
+- `twofa`: Only sign if the user logs in with two-factor authentication
 - `basesigned`: Only sign if the parent commit in the base repo is signed.
 - `headsigned`: Only sign if the head commit in the head branch is signed.
 - `commitssigned`: Only sign if all the commits in the head branch to the merge point are signed.
diff --git a/integrations/integration_test.go b/integrations/integration_test.go
index ce21eb2ef7..b0004927f7 100644
--- a/integrations/integration_test.go
+++ b/integrations/integration_test.go
@@ -174,7 +174,12 @@ func initIntegrationTest() {
 	setting.LoadForTest()
 	setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master"
 	_ = util.RemoveAll(repo_module.LocalCopyPath())
+
+	if err := git.InitOnceWithSync(context.Background()); err != nil {
+		log.Fatal("git.InitOnceWithSync: %v", err)
+	}
 	git.CheckLFSVersion()
+
 	setting.InitDBConfig()
 	if err := storage.Init(); err != nil {
 		fmt.Printf("Init storage failed: %v", err)
@@ -275,7 +280,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.InitOnceWithSync(context.Background()))
+	assert.NoError(t, git.InitOnceWithSync(context.Background())) // the gitconfig has been removed above, so sync the gitconfig again
 	ownerDirs, err := os.ReadDir(setting.RepoRootPath)
 	if err != nil {
 		assert.NoError(t, err, "unable to read the new repo root: %v\n", err)
@@ -576,7 +581,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.InitOnceWithSync(context.Background()))
+	assert.NoError(t, git.InitOnceWithSync(context.Background())) // the gitconfig has been removed above, so sync the gitconfig again
 	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 83c31d8018..20a5c903a9 100644
--- a/integrations/migration-test/migration_test.go
+++ b/integrations/migration-test/migration_test.go
@@ -62,7 +62,6 @@ 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.InitOnceWithSync(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)
@@ -83,6 +82,7 @@ func initMigrationTest(t *testing.T) func() {
 		}
 	}
 
+	assert.NoError(t, git.InitOnceWithSync(context.Background()))
 	git.CheckLFSVersion()
 	setting.InitDBConfig()
 	setting.NewLogServices(true)
diff --git a/models/migrations/migrations_test.go b/models/migrations/migrations_test.go
index 0c8e74f734..46782f24a1 100644
--- a/models/migrations/migrations_test.go
+++ b/models/migrations/migrations_test.go
@@ -66,6 +66,10 @@ func TestMain(m *testing.M) {
 
 	setting.SetCustomPathAndConf("", "", "")
 	setting.LoadForTest()
+	if err = git.InitOnceWithSync(context.Background()); err != nil {
+		fmt.Printf("Unable to InitOnceWithSync: %v\n", err)
+		os.Exit(1)
+	}
 	git.CheckLFSVersion()
 	setting.InitDBConfig()
 	setting.NewLogServices(true)
@@ -203,7 +207,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.InitOnceWithSync(context.Background()))
+	assert.NoError(t, git.InitOnceWithSync(context.Background())) // the gitconfig has been removed above, so sync the gitconfig again
 	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 d1a4498510..baea46dbce 100644
--- a/models/unittest/testdb.go
+++ b/models/unittest/testdb.go
@@ -117,9 +117,11 @@ 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.InitOnceWithSync(context.Background()); err != nil {
 		fatalTestError("git.Init: %v\n", err)
 	}
+	git.CheckLFSVersion()
 
 	ownerDirs, err := os.ReadDir(setting.RepoRootPath)
 	if err != nil {
@@ -202,7 +204,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.InitOnceWithSync(context.Background()))
+	assert.NoError(t, git.InitOnceWithSync(context.Background())) // the gitconfig has been removed above, so sync the gitconfig again
 
 	ownerDirs, err := os.ReadDir(setting.RepoRootPath)
 	assert.NoError(t, err)
diff --git a/modules/git/commit.go b/modules/git/commit.go
index 99fbbd0cfc..82b5e0b25d 100644
--- a/modules/git/commit.go
+++ b/modules/git/commit.go
@@ -206,26 +206,17 @@ func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) {
 		return false, nil
 	}
 
-	if err := CheckGitVersionAtLeast("1.8"); err == nil {
-		_, _, err := NewCommand(c.repo.Ctx, "merge-base", "--is-ancestor", that, this).RunStdString(&RunOpts{Dir: c.repo.Path})
-		if err == nil {
-			return true, nil
-		}
-		var exitError *exec.ExitError
-		if errors.As(err, &exitError) {
-			if exitError.ProcessState.ExitCode() == 1 && len(exitError.Stderr) == 0 {
-				return false, nil
-			}
-		}
-		return false, err
+	_, _, err := NewCommand(c.repo.Ctx, "merge-base", "--is-ancestor", that, this).RunStdString(&RunOpts{Dir: c.repo.Path})
+	if err == nil {
+		return true, nil
 	}
-
-	result, _, err := NewCommand(c.repo.Ctx, "rev-list", "--ancestry-path", "-n1", that+".."+this, "--").RunStdString(&RunOpts{Dir: c.repo.Path})
-	if err != nil {
-		return false, err
+	var exitError *exec.ExitError
+	if errors.As(err, &exitError) {
+		if exitError.ProcessState.ExitCode() == 1 && len(exitError.Stderr) == 0 {
+			return false, nil
+		}
 	}
-
-	return len(strings.TrimSpace(result)) > 0, nil
+	return false, err
 }
 
 // CommitsBeforeLimit returns num commits before current revision
diff --git a/modules/git/git.go b/modules/git/git.go
index 0459f57dde..d789a576ba 100644
--- a/modules/git/git.go
+++ b/modules/git/git.go
@@ -7,10 +7,10 @@ package git
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"os"
 	"os/exec"
-	"path/filepath"
 	"runtime"
 	"strings"
 	"sync"
@@ -22,20 +22,16 @@ import (
 	"github.com/hashicorp/go-version"
 )
 
-var (
-	// GitVersionRequired is the minimum Git version required
-	// At the moment, all code for git 1.x are not changed, if some users want to test with old git client
-	// or bypass the check, they still have a chance to edit this variable manually.
-	// If everything works fine, the code for git 1.x could be removed in a separate PR before 1.17 frozen.
-	GitVersionRequired = "2.0.0"
+// GitVersionRequired is the minimum Git version required
+const GitVersionRequired = "2.0.0"
 
+var (
 	// GitExecutable is the command name of git
 	// Could be updated to an absolute path while initialization
 	GitExecutable = "git"
 
-	// DefaultContext is the default context to run git commands in
-	// will be overwritten by InitXxx with HammerContext
-	DefaultContext = context.Background()
+	// DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx
+	DefaultContext context.Context
 
 	// SupportProcReceive version >= 2.29.0
 	SupportProcReceive bool
@@ -128,36 +124,43 @@ func VersionInfo() string {
 	return fmt.Sprintf(format, args...)
 }
 
+func checkInit() error {
+	if setting.RepoRootPath == "" {
+		return errors.New("can not init Git's HomeDir (RepoRootPath is empty), the setting and git modules are not initialized correctly")
+	}
+	if DefaultContext != nil {
+		log.Warn("git module has been initialized already, duplicate init should be fixed")
+	}
+	return nil
+}
+
 // 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
+		// strict check, make sure the git module is initialized correctly.
+		// attention: when the git module is called in gitea sub-command (serv/hook), the log module is not able to show messages to users.
+		// for example: if there is gitea git hook code calling git.NewCommand before git.InitXxx, the integration test won't show the real failure reasons.
+		log.Fatal("can not get Git's HomeDir (RepoRootPath is empty), the setting and git modules are not initialized correctly")
+		return ""
 	}
 	return setting.RepoRootPath
 }
 
 // InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
 // This method doesn't change anything to filesystem. At the moment, it is only used by "git serv" sub-command, no data-race
+// However, in integration test, the sub-command function may be called in the current process, so the InitSimple would be called multiple times, too
 func InitSimple(ctx context.Context) error {
+	if err := checkInit(); err != nil {
+		return err
+	}
+
 	DefaultContext = ctx
 
 	if setting.Git.Timeout.Default > 0 {
 		defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second
 	}
 
-	if err := SetExecutablePath(setting.Git.Path); err != nil {
-		return err
-	}
-
-	// force cleanup args
-	globalCommandArgs = []string{}
-
-	return nil
+	return SetExecutablePath(setting.Git.Path)
 }
 
 var initOnce sync.Once
@@ -166,6 +169,10 @@ var initOnce sync.Once
 // 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.
 func InitOnceWithSync(ctx context.Context) (err error) {
+	if err = checkInit(); err != nil {
+		return err
+	}
+
 	initOnce.Do(func() {
 		err = InitSimple(ctx)
 		if err != nil {
diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go
index 38818788f3..596a91e803 100644
--- a/modules/git/repo_attribute.go
+++ b/modules/git/repo_attribute.go
@@ -30,10 +30,10 @@ type CheckAttributeOpts struct {
 func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[string]string, error) {
 	env := []string{}
 
-	if len(opts.IndexFile) > 0 && CheckGitVersionAtLeast("1.7.8") == nil {
+	if len(opts.IndexFile) > 0 {
 		env = append(env, "GIT_INDEX_FILE="+opts.IndexFile)
 	}
-	if len(opts.WorkTree) > 0 && CheckGitVersionAtLeast("1.7.8") == nil {
+	if len(opts.WorkTree) > 0 {
 		env = append(env, "GIT_WORK_TREE="+opts.WorkTree)
 	}
 
@@ -56,8 +56,7 @@ func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[
 		}
 	}
 
-	// git check-attr --cached first appears in git 1.7.8
-	if opts.CachedOnly && CheckGitVersionAtLeast("1.7.8") == nil {
+	if opts.CachedOnly {
 		cmdArgs = append(cmdArgs, "--cached")
 	}
 
@@ -125,12 +124,12 @@ type CheckAttributeReader struct {
 func (c *CheckAttributeReader) Init(ctx context.Context) error {
 	cmdArgs := []string{"check-attr", "--stdin", "-z"}
 
-	if len(c.IndexFile) > 0 && CheckGitVersionAtLeast("1.7.8") == nil {
+	if len(c.IndexFile) > 0 {
 		cmdArgs = append(cmdArgs, "--cached")
 		c.env = append(c.env, "GIT_INDEX_FILE="+c.IndexFile)
 	}
 
-	if len(c.WorkTree) > 0 && CheckGitVersionAtLeast("1.7.8") == nil {
+	if len(c.WorkTree) > 0 {
 		c.env = append(c.env, "GIT_WORK_TREE="+c.WorkTree)
 	}
 
@@ -160,17 +159,10 @@ func (c *CheckAttributeReader) Init(ctx context.Context) error {
 		return err
 	}
 
-	if CheckGitVersionAtLeast("1.8.5") == nil {
-		lw := new(nulSeparatedAttributeWriter)
-		lw.attributes = make(chan attributeTriple, 5)
-		lw.closed = make(chan struct{})
-		c.stdOut = lw
-	} else {
-		lw := new(lineSeparatedAttributeWriter)
-		lw.attributes = make(chan attributeTriple, 5)
-		lw.closed = make(chan struct{})
-		c.stdOut = lw
-	}
+	lw := new(nulSeparatedAttributeWriter)
+	lw.attributes = make(chan attributeTriple, 5)
+	lw.closed = make(chan struct{})
+	c.stdOut = lw
 	return nil
 }
 
@@ -400,3 +392,37 @@ func (wr *lineSeparatedAttributeWriter) Close() error {
 	close(wr.closed)
 	return nil
 }
+
+// Create a check attribute reader for the current repository and provided commit ID
+func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeReader, context.CancelFunc) {
+	indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID)
+	if err != nil {
+		return nil, func() {}
+	}
+
+	checker := &CheckAttributeReader{
+		Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"},
+		Repo:       repo,
+		IndexFile:  indexFilename,
+		WorkTree:   worktree,
+	}
+	ctx, cancel := context.WithCancel(repo.Ctx)
+	if err := checker.Init(ctx); err != nil {
+		log.Error("Unable to open checker for %s. Error: %v", commitID, err)
+	} else {
+		go func() {
+			err := checker.Run()
+			if err != nil && err != ctx.Err() {
+				log.Error("Unable to open checker for %s. Error: %v", commitID, err)
+			}
+			cancel()
+		}()
+	}
+	deferable := func() {
+		_ = checker.Close()
+		cancel()
+		deleteTemporaryFile()
+	}
+
+	return checker, deferable
+}
diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go
index 4b0cc8536b..3c7af73000 100644
--- a/modules/git/repo_compare.go
+++ b/modules/git/repo_compare.go
@@ -255,13 +255,7 @@ func (repo *Repository) GetDiff(base, head string, w io.Writer) error {
 
 // GetDiffBinary generates and returns patch data between given revisions, including binary diffs.
 func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error {
-	if CheckGitVersionAtLeast("1.7.7") == nil {
-		return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram", base, head).Run(&RunOpts{
-			Dir:    repo.Path,
-			Stdout: w,
-		})
-	}
-	return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--patience", base, head).Run(&RunOpts{
+	return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram", base, head).Run(&RunOpts{
 		Dir:    repo.Path,
 		Stdout: w,
 	})
diff --git a/modules/git/repo_language_stats_gogit.go b/modules/git/repo_language_stats_gogit.go
index 3c9f026b7a..34b0dc45d3 100644
--- a/modules/git/repo_language_stats_gogit.go
+++ b/modules/git/repo_language_stats_gogit.go
@@ -8,12 +8,10 @@ package git
 
 import (
 	"bytes"
-	"context"
 	"io"
 	"strings"
 
 	"code.gitea.io/gitea/modules/analyze"
-	"code.gitea.io/gitea/modules/log"
 
 	"github.com/go-enry/go-enry/v2"
 	"github.com/go-git/go-git/v5"
@@ -43,33 +41,8 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
 		return nil, err
 	}
 
-	var checker *CheckAttributeReader
-
-	if CheckGitVersionAtLeast("1.7.8") == nil {
-		indexFilename, workTree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID)
-		if err == nil {
-			defer deleteTemporaryFile()
-			checker = &CheckAttributeReader{
-				Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"},
-				Repo:       repo,
-				IndexFile:  indexFilename,
-				WorkTree:   workTree,
-			}
-			ctx, cancel := context.WithCancel(DefaultContext)
-			if err := checker.Init(ctx); err != nil {
-				log.Error("Unable to open checker for %s. Error: %v", commitID, err)
-			} else {
-				go func() {
-					err = checker.Run()
-					if err != nil {
-						log.Error("Unable to open checker for %s. Error: %v", commitID, err)
-						cancel()
-					}
-				}()
-			}
-			defer cancel()
-		}
-	}
+	checker, deferable := repo.CheckAttributeReader(commitID)
+	defer deferable()
 
 	sizes := make(map[string]int64)
 	err = tree.Files().ForEach(func(f *object.File) error {
diff --git a/modules/git/repo_language_stats_nogogit.go b/modules/git/repo_language_stats_nogogit.go
index 41b176f816..d237924f92 100644
--- a/modules/git/repo_language_stats_nogogit.go
+++ b/modules/git/repo_language_stats_nogogit.go
@@ -9,7 +9,6 @@ package git
 import (
 	"bufio"
 	"bytes"
-	"context"
 	"io"
 	"math"
 	"strings"
@@ -63,36 +62,8 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
 		return nil, err
 	}
 
-	var checker *CheckAttributeReader
-
-	if CheckGitVersionAtLeast("1.7.8") == nil {
-		indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID)
-		if err == nil {
-			defer deleteTemporaryFile()
-			checker = &CheckAttributeReader{
-				Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"},
-				Repo:       repo,
-				IndexFile:  indexFilename,
-				WorkTree:   worktree,
-			}
-			ctx, cancel := context.WithCancel(repo.Ctx)
-			if err := checker.Init(ctx); err != nil {
-				log.Error("Unable to open checker for %s. Error: %v", commitID, err)
-			} else {
-				go func() {
-					err = checker.Run()
-					if err != nil {
-						log.Error("Unable to open checker for %s. Error: %v", commitID, err)
-						cancel()
-					}
-				}()
-			}
-			defer func() {
-				_ = checker.Close()
-				cancel()
-			}()
-		}
-	}
+	checker, deferable := repo.CheckAttributeReader(commitID)
+	defer deferable()
 
 	contentBuf := bytes.Buffer{}
 	var content []byte
diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go
index 2e139daddf..2ea3f0187a 100644
--- a/modules/git/repo_tree.go
+++ b/modules/git/repo_tree.go
@@ -45,11 +45,11 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
 	_, _ = messageBytes.WriteString(opts.Message)
 	_, _ = messageBytes.WriteString("\n")
 
-	if CheckGitVersionAtLeast("1.7.9") == nil && (opts.KeyID != "" || opts.AlwaysSign) {
+	if opts.KeyID != "" || opts.AlwaysSign {
 		cmd.AddArguments(fmt.Sprintf("-S%s", opts.KeyID))
 	}
 
-	if CheckGitVersionAtLeast("2.0.0") == nil && opts.NoGPGSign {
+	if opts.NoGPGSign {
 		cmd.AddArguments("--no-gpg-sign")
 	}
 
diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go
index f494998c59..a7cf81250b 100644
--- a/modules/markup/html_test.go
+++ b/modules/markup/html_test.go
@@ -5,12 +5,14 @@
 package markup_test
 
 import (
+	"context"
 	"io"
 	"strings"
 	"testing"
 
 	"code.gitea.io/gitea/modules/emoji"
 	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/log"
 	. "code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/setting"
@@ -25,6 +27,13 @@ var localMetas = map[string]string{
 	"repoPath": "../../integrations/gitea-repositories-meta/user13/repo11.git/",
 }
 
+func TestMain(m *testing.M) {
+	setting.LoadAllowEmpty()
+	if err := git.InitSimple(context.Background()); err != nil {
+		log.Fatal("git init failed, err: %v", err)
+	}
+}
+
 func TestRender_Commits(t *testing.T) {
 	setting.AppURL = TestAppURL
 	test := func(input, expected string) {
diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
index a069d402bb..732fe1a6be 100644
--- a/modules/markup/markdown/markdown_test.go
+++ b/modules/markup/markdown/markdown_test.go
@@ -5,6 +5,7 @@
 package markdown_test
 
 import (
+	"context"
 	"strings"
 	"testing"
 
@@ -31,6 +32,13 @@ var localMetas = map[string]string{
 	"repoPath": "../../../integrations/gitea-repositories-meta/user13/repo11.git/",
 }
 
+func TestMain(m *testing.M) {
+	setting.LoadAllowEmpty()
+	if err := git.InitSimple(context.Background()); err != nil {
+		log.Fatal("git init failed, err: %v", err)
+	}
+}
+
 func TestRender_StandardLinks(t *testing.T) {
 	setting.AppURL = AppURL
 	setting.AppSubURL = AppSubURL
diff --git a/modules/repository/init.go b/modules/repository/init.go
index f5cef3301d..e984697cda 100644
--- a/modules/repository/init.go
+++ b/modules/repository/init.go
@@ -323,19 +323,17 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi
 		"-m", "Initial commit",
 	}
 
-	if git.CheckGitVersionAtLeast("1.7.9") == nil {
-		sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u)
-		if sign {
-			args = append(args, "-S"+keyID)
+	sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u)
+	if sign {
+		args = append(args, "-S"+keyID)
 
-			if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
-				// need to set the committer to the KeyID owner
-				committerName = signer.Name
-				committerEmail = signer.Email
-			}
-		} else if git.CheckGitVersionAtLeast("2.0.0") == nil {
-			args = append(args, "--no-gpg-sign")
+		if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
+			// need to set the committer to the KeyID owner
+			committerName = signer.Name
+			committerEmail = signer.Email
 		}
+	} else {
+		args = append(args, "--no-gpg-sign")
 	}
 
 	env = append(env,
diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go
index 97daadbc67..37dc0e114d 100644
--- a/services/gitdiff/gitdiff.go
+++ b/services/gitdiff/gitdiff.go
@@ -1417,37 +1417,8 @@ func GetDiff(gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff
 	}
 	diff.Start = opts.SkipTo
 
-	var checker *git.CheckAttributeReader
-
-	if git.CheckGitVersionAtLeast("1.7.8") == nil {
-		indexFilename, worktree, deleteTemporaryFile, err := gitRepo.ReadTreeToTemporaryIndex(opts.AfterCommitID)
-		if err == nil {
-			defer deleteTemporaryFile()
-
-			checker = &git.CheckAttributeReader{
-				Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"},
-				Repo:       gitRepo,
-				IndexFile:  indexFilename,
-				WorkTree:   worktree,
-			}
-			ctx, cancel := context.WithCancel(gitRepo.Ctx)
-			if err := checker.Init(ctx); err != nil {
-				log.Error("Unable to open checker for %s. Error: %v", opts.AfterCommitID, err)
-			} else {
-				go func() {
-					err := checker.Run()
-					if err != nil && err != ctx.Err() {
-						log.Error("Unable to open checker for %s. Error: %v", opts.AfterCommitID, err)
-					}
-					cancel()
-				}()
-			}
-			defer func() {
-				_ = checker.Close()
-				cancel()
-			}()
-		}
-	}
+	checker, deferable := gitRepo.CheckAttributeReader(opts.AfterCommitID)
+	defer deferable()
 
 	for _, diffFile := range diff.Files {
 
diff --git a/services/pull/merge.go b/services/pull/merge.go
index aff800a1b6..e8bb3a1cdd 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -276,15 +276,8 @@ func rawMerge(ctx context.Context, pr *issues_model.PullRequest, doer *user_mode
 		return "", fmt.Errorf("Unable to write .git/info/sparse-checkout file in tmpBasePath: %v", err)
 	}
 
-	var gitConfigCommand func() *git.Command
-	if git.CheckGitVersionAtLeast("1.8.0") == nil {
-		gitConfigCommand = func() *git.Command {
-			return git.NewCommand(ctx, "config", "--local")
-		}
-	} else {
-		gitConfigCommand = func() *git.Command {
-			return git.NewCommand(ctx, "config")
-		}
+	gitConfigCommand := func() *git.Command {
+		return git.NewCommand(ctx, "config", "--local")
 	}
 
 	// Switch off LFS process (set required, clean and smudge here also)
@@ -366,16 +359,14 @@ func rawMerge(ctx context.Context, pr *issues_model.PullRequest, doer *user_mode
 
 	// Determine if we should sign
 	signArg := ""
-	if git.CheckGitVersionAtLeast("1.7.9") == nil {
-		sign, keyID, signer, _ := asymkey_service.SignMerge(ctx, pr, doer, tmpBasePath, "HEAD", trackingBranch)
-		if sign {
-			signArg = "-S" + keyID
-			if pr.BaseRepo.GetTrustModel() == repo_model.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
-				committer = signer
-			}
-		} else if git.CheckGitVersionAtLeast("2.0.0") == nil {
-			signArg = "--no-gpg-sign"
+	sign, keyID, signer, _ := asymkey_service.SignMerge(ctx, pr, doer, tmpBasePath, "HEAD", trackingBranch)
+	if sign {
+		signArg = "-S" + keyID
+		if pr.BaseRepo.GetTrustModel() == repo_model.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
+			committer = signer
 		}
+	} else {
+		signArg = "--no-gpg-sign"
 	}
 
 	commitTimeStr := time.Now().Format(time.RFC3339)
diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go
index 97a80a96bd..1e60d55613 100644
--- a/services/repository/files/temp_repo.go
+++ b/services/repository/files/temp_repo.go
@@ -248,34 +248,31 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(parent string, author, co
 		args = []string{"commit-tree", treeHash}
 	}
 
-	// Determine if we should sign
-	if git.CheckGitVersionAtLeast("1.7.9") == nil {
-		var sign bool
-		var keyID string
-		var signer *git.Signature
-		if parent != "" {
-			sign, keyID, signer, _ = asymkey_service.SignCRUDAction(t.ctx, t.repo.RepoPath(), author, t.basePath, parent)
-		} else {
-			sign, keyID, signer, _ = asymkey_service.SignInitialCommit(t.ctx, t.repo.RepoPath(), author)
-		}
-		if sign {
-			args = append(args, "-S"+keyID)
-			if t.repo.GetTrustModel() == repo_model.CommitterTrustModel || t.repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
-				if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email {
-					// Add trailers
-					_, _ = messageBytes.WriteString("\n")
-					_, _ = messageBytes.WriteString("Co-authored-by: ")
-					_, _ = messageBytes.WriteString(committerSig.String())
-					_, _ = messageBytes.WriteString("\n")
-					_, _ = messageBytes.WriteString("Co-committed-by: ")
-					_, _ = messageBytes.WriteString(committerSig.String())
-					_, _ = messageBytes.WriteString("\n")
-				}
-				committerSig = signer
+	var sign bool
+	var keyID string
+	var signer *git.Signature
+	if parent != "" {
+		sign, keyID, signer, _ = asymkey_service.SignCRUDAction(t.ctx, t.repo.RepoPath(), author, t.basePath, parent)
+	} else {
+		sign, keyID, signer, _ = asymkey_service.SignInitialCommit(t.ctx, t.repo.RepoPath(), author)
+	}
+	if sign {
+		args = append(args, "-S"+keyID)
+		if t.repo.GetTrustModel() == repo_model.CommitterTrustModel || t.repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
+			if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email {
+				// Add trailers
+				_, _ = messageBytes.WriteString("\n")
+				_, _ = messageBytes.WriteString("Co-authored-by: ")
+				_, _ = messageBytes.WriteString(committerSig.String())
+				_, _ = messageBytes.WriteString("\n")
+				_, _ = messageBytes.WriteString("Co-committed-by: ")
+				_, _ = messageBytes.WriteString(committerSig.String())
+				_, _ = messageBytes.WriteString("\n")
 			}
-		} else if git.CheckGitVersionAtLeast("2.0.0") == nil {
-			args = append(args, "--no-gpg-sign")
+			committerSig = signer
 		}
+	} else {
+		args = append(args, "--no-gpg-sign")
 	}
 
 	if signoff {