diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go
index f9770d50e7..93fb64a4d3 100644
--- a/cmd/migrate_storage.go
+++ b/cmd/migrate_storage.go
@@ -9,8 +9,8 @@ import (
 	"fmt"
 	"strings"
 
-	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/migrations"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
@@ -88,7 +88,7 @@ func migrateAttachments(dstStorage storage.ObjectStorage) error {
 }
 
 func migrateLFS(dstStorage storage.ObjectStorage) error {
-	return models.IterateLFS(func(mo *models.LFSMetaObject) error {
+	return git_model.IterateLFS(func(mo *git_model.LFSMetaObject) error {
 		_, err := storage.Copy(dstStorage, mo.RelativePath(), storage.LFS, mo.RelativePath())
 		return err
 	})
diff --git a/cmd/serv.go b/cmd/serv.go
index 6e067a48a5..b00c3962f4 100644
--- a/cmd/serv.go
+++ b/cmd/serv.go
@@ -17,8 +17,8 @@ import (
 	"strings"
 	"time"
 
-	"code.gitea.io/gitea/models"
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/perm"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/json"
@@ -276,7 +276,7 @@ func runServ(c *cli.Context) error {
 			return fail("Internal error", "Failed to sign JWT token: %v", err)
 		}
 
-		tokenAuthentication := &models.LFSTokenResponse{
+		tokenAuthentication := &git_model.LFSTokenResponse{
 			Header: make(map[string]string),
 			Href:   url,
 		}
diff --git a/integrations/api_repo_lfs_test.go b/integrations/api_repo_lfs_test.go
index 947d157d5b..6a2fccebb5 100644
--- a/integrations/api_repo_lfs_test.go
+++ b/integrations/api_repo_lfs_test.go
@@ -12,7 +12,7 @@ import (
 	"strings"
 	"testing"
 
-	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
@@ -76,7 +76,7 @@ func TestAPILFSBatch(t *testing.T) {
 
 	content := []byte("dummy1")
 	oid := storeObjectInRepo(t, repo.ID, &content)
-	defer models.RemoveLFSMetaObjectByOid(repo.ID, oid)
+	defer git_model.RemoveLFSMetaObjectByOid(repo.ID, oid)
 
 	session := loginUser(t, "user2")
 
@@ -260,9 +260,9 @@ func TestAPILFSBatch(t *testing.T) {
 			content := []byte("dummy0")
 			storeObjectInRepo(t, repo2.ID, &content)
 
-			meta, err := models.GetLFSMetaObjectByOid(repo.ID, p.Oid)
+			meta, err := git_model.GetLFSMetaObjectByOid(repo.ID, p.Oid)
 			assert.Nil(t, meta)
-			assert.Equal(t, models.ErrLFSObjectNotExist, err)
+			assert.Equal(t, git_model.ErrLFSObjectNotExist, err)
 
 			req := newRequest(t, &lfs.BatchRequest{
 				Operation: "upload",
@@ -275,7 +275,7 @@ func TestAPILFSBatch(t *testing.T) {
 			assert.Nil(t, br.Objects[0].Error)
 			assert.Empty(t, br.Objects[0].Actions)
 
-			meta, err = models.GetLFSMetaObjectByOid(repo.ID, p.Oid)
+			meta, err = git_model.GetLFSMetaObjectByOid(repo.ID, p.Oid)
 			assert.NoError(t, err)
 			assert.NotNil(t, meta)
 
@@ -336,7 +336,7 @@ func TestAPILFSUpload(t *testing.T) {
 
 	content := []byte("dummy3")
 	oid := storeObjectInRepo(t, repo.ID, &content)
-	defer models.RemoveLFSMetaObjectByOid(repo.ID, oid)
+	defer git_model.RemoveLFSMetaObjectByOid(repo.ID, oid)
 
 	session := loginUser(t, "user2")
 
@@ -365,9 +365,9 @@ func TestAPILFSUpload(t *testing.T) {
 		err = contentStore.Put(p, bytes.NewReader([]byte("dummy5")))
 		assert.NoError(t, err)
 
-		meta, err := models.GetLFSMetaObjectByOid(repo.ID, p.Oid)
+		meta, err := git_model.GetLFSMetaObjectByOid(repo.ID, p.Oid)
 		assert.Nil(t, meta)
-		assert.Equal(t, models.ErrLFSObjectNotExist, err)
+		assert.Equal(t, git_model.ErrLFSObjectNotExist, err)
 
 		t.Run("InvalidAccess", func(t *testing.T) {
 			req := newRequest(t, p, "invalid")
@@ -378,7 +378,7 @@ func TestAPILFSUpload(t *testing.T) {
 			req := newRequest(t, p, "dummy5")
 
 			session.MakeRequest(t, req, http.StatusOK)
-			meta, err = models.GetLFSMetaObjectByOid(repo.ID, p.Oid)
+			meta, err = git_model.GetLFSMetaObjectByOid(repo.ID, p.Oid)
 			assert.NoError(t, err)
 			assert.NotNil(t, meta)
 		})
@@ -426,7 +426,7 @@ func TestAPILFSUpload(t *testing.T) {
 		assert.NoError(t, err)
 		assert.True(t, exist)
 
-		meta, err := models.GetLFSMetaObjectByOid(repo.ID, p.Oid)
+		meta, err := git_model.GetLFSMetaObjectByOid(repo.ID, p.Oid)
 		assert.NoError(t, err)
 		assert.NotNil(t, meta)
 	})
@@ -441,7 +441,7 @@ func TestAPILFSVerify(t *testing.T) {
 
 	content := []byte("dummy3")
 	oid := storeObjectInRepo(t, repo.ID, &content)
-	defer models.RemoveLFSMetaObjectByOid(repo.ID, oid)
+	defer git_model.RemoveLFSMetaObjectByOid(repo.ID, oid)
 
 	session := loginUser(t, "user2")
 
diff --git a/integrations/lfs_getobject_test.go b/integrations/lfs_getobject_test.go
index e9f0c58022..4b6bb140d3 100644
--- a/integrations/lfs_getobject_test.go
+++ b/integrations/lfs_getobject_test.go
@@ -12,7 +12,7 @@ import (
 	"net/http/httptest"
 	"testing"
 
-	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/json"
@@ -28,7 +28,7 @@ func storeObjectInRepo(t *testing.T, repositoryID int64, content *[]byte) string
 	pointer, err := lfs.GeneratePointer(bytes.NewReader(*content))
 	assert.NoError(t, err)
 
-	_, err = models.NewLFSMetaObject(&models.LFSMetaObject{Pointer: pointer, RepositoryID: repositoryID})
+	_, err = git_model.NewLFSMetaObject(&git_model.LFSMetaObject{Pointer: pointer, RepositoryID: repositoryID})
 	assert.NoError(t, err)
 	contentStore := lfs.NewContentStore()
 	exist, err := contentStore.Exists(pointer)
@@ -44,7 +44,7 @@ func storeAndGetLfs(t *testing.T, content *[]byte, extraHeader *http.Header, exp
 	repo, err := repo_model.GetRepositoryByOwnerAndName("user2", "repo1")
 	assert.NoError(t, err)
 	oid := storeObjectInRepo(t, repo.ID, content)
-	defer models.RemoveLFSMetaObjectByOid(repo.ID, oid)
+	defer git_model.RemoveLFSMetaObjectByOid(repo.ID, oid)
 
 	session := loginUser(t, "user2")
 
diff --git a/integrations/repo_tag_test.go b/integrations/repo_tag_test.go
index ef7a11422e..793cf724eb 100644
--- a/integrations/repo_tag_test.go
+++ b/integrations/repo_tag_test.go
@@ -10,6 +10,7 @@ import (
 	"testing"
 
 	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
@@ -32,12 +33,12 @@ func TestCreateNewTagProtected(t *testing.T) {
 		err := release.CreateNewTag(git.DefaultContext, owner, repo, "master", "v-1", "first tag")
 		assert.NoError(t, err)
 
-		err = models.InsertProtectedTag(&models.ProtectedTag{
+		err = git_model.InsertProtectedTag(&git_model.ProtectedTag{
 			RepoID:      repo.ID,
 			NamePattern: "v-*",
 		})
 		assert.NoError(t, err)
-		err = models.InsertProtectedTag(&models.ProtectedTag{
+		err = git_model.InsertProtectedTag(&git_model.ProtectedTag{
 			RepoID:           repo.ID,
 			NamePattern:      "v-1.1",
 			AllowlistUserIDs: []int64{repo.OwnerID},
@@ -87,11 +88,11 @@ func TestCreateNewTagProtected(t *testing.T) {
 		assert.NoError(t, err)
 	}
 
-	protectedTags, err := models.GetProtectedTags(repo.ID)
+	protectedTags, err := git_model.GetProtectedTags(repo.ID)
 	assert.NoError(t, err)
 
 	for _, protectedTag := range protectedTags {
-		err = models.DeleteProtectedTag(protectedTag)
+		err = git_model.DeleteProtectedTag(protectedTag)
 		assert.NoError(t, err)
 	}
 }
diff --git a/models/branch.go b/models/branch.go
new file mode 100644
index 0000000000..3d6e7d82e2
--- /dev/null
+++ b/models/branch.go
@@ -0,0 +1,80 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+import (
+	"context"
+
+	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
+	"code.gitea.io/gitea/modules/log"
+)
+
+// HasEnoughApprovals returns true if pr has enough granted approvals.
+func HasEnoughApprovals(ctx context.Context, protectBranch *git_model.ProtectedBranch, pr *PullRequest) bool {
+	if protectBranch.RequiredApprovals == 0 {
+		return true
+	}
+	return GetGrantedApprovalsCount(ctx, protectBranch, pr) >= protectBranch.RequiredApprovals
+}
+
+// GetGrantedApprovalsCount returns the number of granted approvals for pr. A granted approval must be authored by a user in an approval whitelist.
+func GetGrantedApprovalsCount(ctx context.Context, protectBranch *git_model.ProtectedBranch, pr *PullRequest) int64 {
+	sess := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID).
+		And("type = ?", ReviewTypeApprove).
+		And("official = ?", true).
+		And("dismissed = ?", false)
+	if protectBranch.DismissStaleApprovals {
+		sess = sess.And("stale = ?", false)
+	}
+	approvals, err := sess.Count(new(Review))
+	if err != nil {
+		log.Error("GetGrantedApprovalsCount: %v", err)
+		return 0
+	}
+
+	return approvals
+}
+
+// MergeBlockedByRejectedReview returns true if merge is blocked by rejected reviews
+func MergeBlockedByRejectedReview(ctx context.Context, protectBranch *git_model.ProtectedBranch, pr *PullRequest) bool {
+	if !protectBranch.BlockOnRejectedReviews {
+		return false
+	}
+	rejectExist, err := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID).
+		And("type = ?", ReviewTypeReject).
+		And("official = ?", true).
+		And("dismissed = ?", false).
+		Exist(new(Review))
+	if err != nil {
+		log.Error("MergeBlockedByRejectedReview: %v", err)
+		return true
+	}
+
+	return rejectExist
+}
+
+// MergeBlockedByOfficialReviewRequests block merge because of some review request to official reviewer
+// of from official review
+func MergeBlockedByOfficialReviewRequests(ctx context.Context, protectBranch *git_model.ProtectedBranch, pr *PullRequest) bool {
+	if !protectBranch.BlockOnOfficialReviewRequests {
+		return false
+	}
+	has, err := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID).
+		And("type = ?", ReviewTypeRequest).
+		And("official = ?", true).
+		Exist(new(Review))
+	if err != nil {
+		log.Error("MergeBlockedByOfficialReviewRequests: %v", err)
+		return true
+	}
+
+	return has
+}
+
+// MergeBlockedByOutdatedBranch returns true if merge is blocked by an outdated head branch
+func MergeBlockedByOutdatedBranch(protectBranch *git_model.ProtectedBranch, pr *PullRequest) bool {
+	return protectBranch.BlockOnOutdatedBranch && pr.CommitsBehind > 0
+}
diff --git a/models/commit.go b/models/commit.go
deleted file mode 100644
index 92a839b780..0000000000
--- a/models/commit.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2021 Gitea. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package models
-
-import (
-	asymkey_model "code.gitea.io/gitea/models/asymkey"
-	repo_model "code.gitea.io/gitea/models/repo"
-	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/git"
-)
-
-// ConvertFromGitCommit converts git commits into SignCommitWithStatuses
-func ConvertFromGitCommit(commits []*git.Commit, repo *repo_model.Repository) []*SignCommitWithStatuses {
-	return ParseCommitsWithStatus(
-		asymkey_model.ParseCommitsWithSignature(
-			user_model.ValidateCommitsWithEmails(commits),
-			repo.GetTrustModel(),
-			func(user *user_model.User) (bool, error) {
-				return IsOwnerMemberCollaborator(repo, user.ID)
-			},
-		),
-		repo,
-	)
-}
diff --git a/models/error.go b/models/error.go
index 0dc14c3e31..16ae52fc43 100644
--- a/models/error.go
+++ b/models/error.go
@@ -8,7 +8,6 @@ package models
 import (
 	"fmt"
 
-	"code.gitea.io/gitea/models/perm"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/git"
 )
@@ -145,83 +144,6 @@ func (err ErrAccessTokenEmpty) Error() string {
 	return "access token is empty"
 }
 
-//.____   ____________________
-//|    |  \_   _____/   _____/
-//|    |   |    __) \_____  \
-//|    |___|     \  /        \
-//|_______ \___  / /_______  /
-//        \/   \/          \/
-
-// ErrLFSLockNotExist represents a "LFSLockNotExist" kind of error.
-type ErrLFSLockNotExist struct {
-	ID     int64
-	RepoID int64
-	Path   string
-}
-
-// IsErrLFSLockNotExist checks if an error is a ErrLFSLockNotExist.
-func IsErrLFSLockNotExist(err error) bool {
-	_, ok := err.(ErrLFSLockNotExist)
-	return ok
-}
-
-func (err ErrLFSLockNotExist) Error() string {
-	return fmt.Sprintf("lfs lock does not exist [id: %d, rid: %d, path: %s]", err.ID, err.RepoID, err.Path)
-}
-
-// ErrLFSUnauthorizedAction represents a "LFSUnauthorizedAction" kind of error.
-type ErrLFSUnauthorizedAction struct {
-	RepoID   int64
-	UserName string
-	Mode     perm.AccessMode
-}
-
-// IsErrLFSUnauthorizedAction checks if an error is a ErrLFSUnauthorizedAction.
-func IsErrLFSUnauthorizedAction(err error) bool {
-	_, ok := err.(ErrLFSUnauthorizedAction)
-	return ok
-}
-
-func (err ErrLFSUnauthorizedAction) Error() string {
-	if err.Mode == perm.AccessModeWrite {
-		return fmt.Sprintf("User %s doesn't have write access for lfs lock [rid: %d]", err.UserName, err.RepoID)
-	}
-	return fmt.Sprintf("User %s doesn't have read access for lfs lock [rid: %d]", err.UserName, err.RepoID)
-}
-
-// ErrLFSLockAlreadyExist represents a "LFSLockAlreadyExist" kind of error.
-type ErrLFSLockAlreadyExist struct {
-	RepoID int64
-	Path   string
-}
-
-// IsErrLFSLockAlreadyExist checks if an error is a ErrLFSLockAlreadyExist.
-func IsErrLFSLockAlreadyExist(err error) bool {
-	_, ok := err.(ErrLFSLockAlreadyExist)
-	return ok
-}
-
-func (err ErrLFSLockAlreadyExist) Error() string {
-	return fmt.Sprintf("lfs lock already exists [rid: %d, path: %s]", err.RepoID, err.Path)
-}
-
-// ErrLFSFileLocked represents a "LFSFileLocked" kind of error.
-type ErrLFSFileLocked struct {
-	RepoID   int64
-	Path     string
-	UserName string
-}
-
-// IsErrLFSFileLocked checks if an error is a ErrLFSFileLocked.
-func IsErrLFSFileLocked(err error) bool {
-	_, ok := err.(ErrLFSFileLocked)
-	return ok
-}
-
-func (err ErrLFSFileLocked) Error() string {
-	return fmt.Sprintf("File is lfs locked [repo: %d, locked by: %s, path: %s]", err.RepoID, err.UserName, err.Path)
-}
-
 // ErrNoPendingRepoTransfer is an error type for repositories without a pending
 // transfer request
 type ErrNoPendingRepoTransfer struct {
diff --git a/models/branches.go b/models/git/branches.go
similarity index 87%
rename from models/branches.go
rename to models/git/branches.go
index 98d2c3a993..0a44c0a68d 100644
--- a/models/branches.go
+++ b/models/git/branches.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package git
 
 import (
 	"context"
@@ -129,10 +129,11 @@ func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch,
 
 // IsUserOfficialReviewer check if user is official reviewer for the branch (counts towards required approvals)
 func IsUserOfficialReviewer(protectBranch *ProtectedBranch, user *user_model.User) (bool, error) {
-	return isUserOfficialReviewer(db.DefaultContext, protectBranch, user)
+	return IsUserOfficialReviewerCtx(db.DefaultContext, protectBranch, user)
 }
 
-func isUserOfficialReviewer(ctx context.Context, protectBranch *ProtectedBranch, user *user_model.User) (bool, error) {
+// IsUserOfficialReviewerCtx check if user is official reviewer for the branch (counts towards required approvals)
+func IsUserOfficialReviewerCtx(ctx context.Context, protectBranch *ProtectedBranch, user *user_model.User) (bool, error) {
 	repo, err := repo_model.GetRepositoryByIDCtx(ctx, protectBranch.RepoID)
 	if err != nil {
 		return false, err
@@ -159,73 +160,6 @@ func isUserOfficialReviewer(ctx context.Context, protectBranch *ProtectedBranch,
 	return inTeam, nil
 }
 
-// HasEnoughApprovals returns true if pr has enough granted approvals.
-func (protectBranch *ProtectedBranch) HasEnoughApprovals(ctx context.Context, pr *PullRequest) bool {
-	if protectBranch.RequiredApprovals == 0 {
-		return true
-	}
-	return protectBranch.GetGrantedApprovalsCount(ctx, pr) >= protectBranch.RequiredApprovals
-}
-
-// GetGrantedApprovalsCount returns the number of granted approvals for pr. A granted approval must be authored by a user in an approval whitelist.
-func (protectBranch *ProtectedBranch) GetGrantedApprovalsCount(ctx context.Context, pr *PullRequest) int64 {
-	sess := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID).
-		And("type = ?", ReviewTypeApprove).
-		And("official = ?", true).
-		And("dismissed = ?", false)
-	if protectBranch.DismissStaleApprovals {
-		sess = sess.And("stale = ?", false)
-	}
-	approvals, err := sess.Count(new(Review))
-	if err != nil {
-		log.Error("GetGrantedApprovalsCount: %v", err)
-		return 0
-	}
-
-	return approvals
-}
-
-// MergeBlockedByRejectedReview returns true if merge is blocked by rejected reviews
-func (protectBranch *ProtectedBranch) MergeBlockedByRejectedReview(ctx context.Context, pr *PullRequest) bool {
-	if !protectBranch.BlockOnRejectedReviews {
-		return false
-	}
-	rejectExist, err := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID).
-		And("type = ?", ReviewTypeReject).
-		And("official = ?", true).
-		And("dismissed = ?", false).
-		Exist(new(Review))
-	if err != nil {
-		log.Error("MergeBlockedByRejectedReview: %v", err)
-		return true
-	}
-
-	return rejectExist
-}
-
-// MergeBlockedByOfficialReviewRequests block merge because of some review request to official reviewer
-// of from official review
-func (protectBranch *ProtectedBranch) MergeBlockedByOfficialReviewRequests(ctx context.Context, pr *PullRequest) bool {
-	if !protectBranch.BlockOnOfficialReviewRequests {
-		return false
-	}
-	has, err := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID).
-		And("type = ?", ReviewTypeRequest).
-		And("official = ?", true).
-		Exist(new(Review))
-	if err != nil {
-		log.Error("MergeBlockedByOfficialReviewRequests: %v", err)
-		return true
-	}
-
-	return has
-}
-
-// MergeBlockedByOutdatedBranch returns true if merge is blocked by an outdated head branch
-func (protectBranch *ProtectedBranch) MergeBlockedByOutdatedBranch(pr *PullRequest) bool {
-	return protectBranch.BlockOnOutdatedBranch && pr.CommitsBehind > 0
-}
-
 // GetProtectedFilePatterns parses a semicolon separated list of protected file patterns and returns a glob.Glob slice
 func (protectBranch *ProtectedBranch) GetProtectedFilePatterns() []glob.Glob {
 	return getFilePatterns(protectBranch.ProtectedFilePatterns)
@@ -252,13 +186,13 @@ func getFilePatterns(filePatterns string) []glob.Glob {
 }
 
 // MergeBlockedByProtectedFiles returns true if merge is blocked by protected files change
-func (protectBranch *ProtectedBranch) MergeBlockedByProtectedFiles(pr *PullRequest) bool {
+func (protectBranch *ProtectedBranch) MergeBlockedByProtectedFiles(changedProtectedFiles []string) bool {
 	glob := protectBranch.GetProtectedFilePatterns()
 	if len(glob) == 0 {
 		return false
 	}
 
-	return len(pr.ChangedProtectedFiles) > 0
+	return len(changedProtectedFiles) > 0
 }
 
 // IsProtectedFile return if path is protected
@@ -642,7 +576,7 @@ func RenameBranch(repo *repo_model.Repository, from, to string, gitAction func(i
 	}
 
 	// 3. Update all not merged pull request base branch name
-	_, err = sess.Table(new(PullRequest)).Where("base_repo_id=? AND base_branch=? AND has_merged=?",
+	_, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?",
 		repo.ID, from, false).
 		Update(map[string]interface{}{"base_branch": to})
 	if err != nil {
diff --git a/models/branches_test.go b/models/git/branches_test.go
similarity index 65%
rename from models/branches_test.go
rename to models/git/branches_test.go
index 0a0f125cc6..1e0b1a98b6 100644
--- a/models/branches_test.go
+++ b/models/git/branches_test.go
@@ -2,12 +2,14 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package git_test
 
 import (
 	"testing"
 
+	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 
@@ -17,24 +19,24 @@ import (
 func TestAddDeletedBranch(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
-	firstBranch := unittest.AssertExistsAndLoadBean(t, &DeletedBranch{ID: 1}).(*DeletedBranch)
+	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}).(*git_model.DeletedBranch)
 
-	assert.Error(t, AddDeletedBranch(repo.ID, firstBranch.Name, firstBranch.Commit, firstBranch.DeletedByID))
-	assert.NoError(t, AddDeletedBranch(repo.ID, "test", "5655464564554545466464656", int64(1)))
+	assert.Error(t, git_model.AddDeletedBranch(repo.ID, firstBranch.Name, firstBranch.Commit, firstBranch.DeletedByID))
+	assert.NoError(t, git_model.AddDeletedBranch(repo.ID, "test", "5655464564554545466464656", int64(1)))
 }
 
 func TestGetDeletedBranches(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
 
-	branches, err := GetDeletedBranches(repo.ID)
+	branches, err := git_model.GetDeletedBranches(repo.ID)
 	assert.NoError(t, err)
 	assert.Len(t, branches, 2)
 }
 
 func TestGetDeletedBranch(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	firstBranch := unittest.AssertExistsAndLoadBean(t, &DeletedBranch{ID: 1}).(*DeletedBranch)
+	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}).(*git_model.DeletedBranch)
 
 	assert.NotNil(t, getDeletedBranch(t, firstBranch))
 }
@@ -42,8 +44,8 @@ func TestGetDeletedBranch(t *testing.T) {
 func TestDeletedBranchLoadUser(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	firstBranch := unittest.AssertExistsAndLoadBean(t, &DeletedBranch{ID: 1}).(*DeletedBranch)
-	secondBranch := unittest.AssertExistsAndLoadBean(t, &DeletedBranch{ID: 2}).(*DeletedBranch)
+	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}).(*git_model.DeletedBranch)
+	secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2}).(*git_model.DeletedBranch)
 
 	branch := getDeletedBranch(t, firstBranch)
 	assert.Nil(t, branch.DeletedBy)
@@ -62,18 +64,18 @@ func TestRemoveDeletedBranch(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
 
-	firstBranch := unittest.AssertExistsAndLoadBean(t, &DeletedBranch{ID: 1}).(*DeletedBranch)
+	firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}).(*git_model.DeletedBranch)
 
-	err := RemoveDeletedBranchByID(repo.ID, 1)
+	err := git_model.RemoveDeletedBranchByID(repo.ID, 1)
 	assert.NoError(t, err)
 	unittest.AssertNotExistsBean(t, firstBranch)
-	unittest.AssertExistsAndLoadBean(t, &DeletedBranch{ID: 2})
+	unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2})
 }
 
-func getDeletedBranch(t *testing.T, branch *DeletedBranch) *DeletedBranch {
+func getDeletedBranch(t *testing.T, branch *git_model.DeletedBranch) *git_model.DeletedBranch {
 	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
 
-	deletedBranch, err := GetDeletedBranchByID(repo.ID, branch.ID)
+	deletedBranch, err := git_model.GetDeletedBranchByID(repo.ID, branch.ID)
 	assert.NoError(t, err)
 	assert.Equal(t, branch.ID, deletedBranch.ID)
 	assert.Equal(t, branch.Name, deletedBranch.Name)
@@ -85,12 +87,12 @@ func getDeletedBranch(t *testing.T, branch *DeletedBranch) *DeletedBranch {
 
 func TestFindRenamedBranch(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
-	branch, exist, err := FindRenamedBranch(1, "dev")
+	branch, exist, err := git_model.FindRenamedBranch(1, "dev")
 	assert.NoError(t, err)
 	assert.Equal(t, true, exist)
 	assert.Equal(t, "master", branch.To)
 
-	_, exist, err = FindRenamedBranch(1, "unknow")
+	_, exist, err = git_model.FindRenamedBranch(1, "unknow")
 	assert.NoError(t, err)
 	assert.Equal(t, false, exist)
 }
@@ -103,13 +105,13 @@ func TestRenameBranch(t *testing.T) {
 	ctx, committer, err := db.TxContext()
 	defer committer.Close()
 	assert.NoError(t, err)
-	assert.NoError(t, UpdateProtectBranch(ctx, repo1, &ProtectedBranch{
+	assert.NoError(t, git_model.UpdateProtectBranch(ctx, repo1, &git_model.ProtectedBranch{
 		RepoID:     repo1.ID,
 		BranchName: "master",
-	}, WhitelistOptions{}))
+	}, git_model.WhitelistOptions{}))
 	assert.NoError(t, committer.Commit())
 
-	assert.NoError(t, RenameBranch(repo1, "master", "main", func(isDefault bool) error {
+	assert.NoError(t, git_model.RenameBranch(repo1, "master", "main", func(isDefault bool) error {
 		_isDefault = isDefault
 		return nil
 	}))
@@ -118,18 +120,18 @@ func TestRenameBranch(t *testing.T) {
 	repo1 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
 	assert.Equal(t, "main", repo1.DefaultBranch)
 
-	pull := unittest.AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest) // merged
+	pull := unittest.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 1}).(*models.PullRequest) // merged
 	assert.Equal(t, "master", pull.BaseBranch)
 
-	pull = unittest.AssertExistsAndLoadBean(t, &PullRequest{ID: 2}).(*PullRequest) // open
+	pull = unittest.AssertExistsAndLoadBean(t, &models.PullRequest{ID: 2}).(*models.PullRequest) // open
 	assert.Equal(t, "main", pull.BaseBranch)
 
-	renamedBranch := unittest.AssertExistsAndLoadBean(t, &RenamedBranch{ID: 2}).(*RenamedBranch)
+	renamedBranch := unittest.AssertExistsAndLoadBean(t, &git_model.RenamedBranch{ID: 2}).(*git_model.RenamedBranch)
 	assert.Equal(t, "master", renamedBranch.From)
 	assert.Equal(t, "main", renamedBranch.To)
 	assert.Equal(t, int64(1), renamedBranch.RepoID)
 
-	unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{
+	unittest.AssertExistsAndLoadBean(t, &git_model.ProtectedBranch{
 		RepoID:     repo1.ID,
 		BranchName: "main",
 	})
@@ -143,7 +145,7 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) {
 	// is actually on repo with ID 1.
 	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
 
-	deletedBranch, err := GetDeletedBranchByID(repo2.ID, 1)
+	deletedBranch, err := git_model.GetDeletedBranchByID(repo2.ID, 1)
 
 	// Expect no error, and the returned branch is nil.
 	assert.NoError(t, err)
@@ -153,7 +155,7 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) {
 	// This should return the deletedBranch.
 	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
 
-	deletedBranch, err = GetDeletedBranchByID(repo1.ID, 1)
+	deletedBranch, err = git_model.GetDeletedBranchByID(repo1.ID, 1)
 
 	// Expect no error, and the returned branch to be not nil.
 	assert.NoError(t, err)
diff --git a/models/commit_status.go b/models/git/commit_status.go
similarity index 95%
rename from models/commit_status.go
rename to models/git/commit_status.go
index ef92c5847a..54a7b43199 100644
--- a/models/commit_status.go
+++ b/models/git/commit_status.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package git
 
 import (
 	"context"
@@ -16,6 +16,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
@@ -178,7 +179,7 @@ func GetCommitStatuses(repo *repo_model.Repository, sha string, opts *CommitStat
 		opts.Page = 1
 	}
 	if opts.PageSize <= 0 {
-		opts.Page = ItemsPerPage
+		opts.Page = setting.ItemsPerPage
 	}
 
 	countSession := listCommitStatusesStatement(repo, sha, opts)
@@ -353,3 +354,17 @@ func ParseCommitsWithStatus(oldCommits []*asymkey_model.SignCommit, repo *repo_m
 func hashCommitStatusContext(context string) string {
 	return fmt.Sprintf("%x", sha1.Sum([]byte(context)))
 }
+
+// ConvertFromGitCommit converts git commits into SignCommitWithStatuses
+func ConvertFromGitCommit(commits []*git.Commit, repo *repo_model.Repository) []*SignCommitWithStatuses {
+	return ParseCommitsWithStatus(
+		asymkey_model.ParseCommitsWithSignature(
+			user_model.ValidateCommitsWithEmails(commits),
+			repo.GetTrustModel(),
+			func(user *user_model.User) (bool, error) {
+				return repo_model.IsOwnerMemberCollaborator(repo, user.ID)
+			},
+		),
+		repo,
+	)
+}
diff --git a/models/commit_status_test.go b/models/git/commit_status_test.go
similarity index 90%
rename from models/commit_status_test.go
rename to models/git/commit_status_test.go
index bb9e375072..9919297430 100644
--- a/models/commit_status_test.go
+++ b/models/git/commit_status_test.go
@@ -2,12 +2,13 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package git_test
 
 import (
 	"testing"
 
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	"code.gitea.io/gitea/modules/structs"
@@ -22,7 +23,7 @@ func TestGetCommitStatuses(t *testing.T) {
 
 	sha1 := "1234123412341234123412341234123412341234"
 
-	statuses, maxResults, err := GetCommitStatuses(repo1, sha1, &CommitStatusOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 50}})
+	statuses, maxResults, err := git_model.GetCommitStatuses(repo1, sha1, &git_model.CommitStatusOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 50}})
 	assert.NoError(t, err)
 	assert.Equal(t, int(maxResults), 5)
 	assert.Len(t, statuses, 5)
diff --git a/models/lfs.go b/models/git/lfs.go
similarity index 77%
rename from models/lfs.go
rename to models/git/lfs.go
index d9eea6bb89..13b8b234f9 100644
--- a/models/lfs.go
+++ b/models/git/lfs.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package git
 
 import (
 	"context"
@@ -10,6 +10,7 @@ import (
 	"fmt"
 
 	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/models/perm"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/lfs"
@@ -19,6 +20,76 @@ import (
 	"xorm.io/builder"
 )
 
+// ErrLFSLockNotExist represents a "LFSLockNotExist" kind of error.
+type ErrLFSLockNotExist struct {
+	ID     int64
+	RepoID int64
+	Path   string
+}
+
+// IsErrLFSLockNotExist checks if an error is a ErrLFSLockNotExist.
+func IsErrLFSLockNotExist(err error) bool {
+	_, ok := err.(ErrLFSLockNotExist)
+	return ok
+}
+
+func (err ErrLFSLockNotExist) Error() string {
+	return fmt.Sprintf("lfs lock does not exist [id: %d, rid: %d, path: %s]", err.ID, err.RepoID, err.Path)
+}
+
+// ErrLFSUnauthorizedAction represents a "LFSUnauthorizedAction" kind of error.
+type ErrLFSUnauthorizedAction struct {
+	RepoID   int64
+	UserName string
+	Mode     perm.AccessMode
+}
+
+// IsErrLFSUnauthorizedAction checks if an error is a ErrLFSUnauthorizedAction.
+func IsErrLFSUnauthorizedAction(err error) bool {
+	_, ok := err.(ErrLFSUnauthorizedAction)
+	return ok
+}
+
+func (err ErrLFSUnauthorizedAction) Error() string {
+	if err.Mode == perm.AccessModeWrite {
+		return fmt.Sprintf("User %s doesn't have write access for lfs lock [rid: %d]", err.UserName, err.RepoID)
+	}
+	return fmt.Sprintf("User %s doesn't have read access for lfs lock [rid: %d]", err.UserName, err.RepoID)
+}
+
+// ErrLFSLockAlreadyExist represents a "LFSLockAlreadyExist" kind of error.
+type ErrLFSLockAlreadyExist struct {
+	RepoID int64
+	Path   string
+}
+
+// IsErrLFSLockAlreadyExist checks if an error is a ErrLFSLockAlreadyExist.
+func IsErrLFSLockAlreadyExist(err error) bool {
+	_, ok := err.(ErrLFSLockAlreadyExist)
+	return ok
+}
+
+func (err ErrLFSLockAlreadyExist) Error() string {
+	return fmt.Sprintf("lfs lock already exists [rid: %d, path: %s]", err.RepoID, err.Path)
+}
+
+// ErrLFSFileLocked represents a "LFSFileLocked" kind of error.
+type ErrLFSFileLocked struct {
+	RepoID   int64
+	Path     string
+	UserName string
+}
+
+// IsErrLFSFileLocked checks if an error is a ErrLFSFileLocked.
+func IsErrLFSFileLocked(err error) bool {
+	_, ok := err.(ErrLFSFileLocked)
+	return ok
+}
+
+func (err ErrLFSFileLocked) Error() string {
+	return fmt.Sprintf("File is lfs locked [repo: %d, locked by: %s, path: %s]", err.RepoID, err.UserName, err.Path)
+}
+
 // LFSMetaObject stores metadata for LFS tracked files.
 type LFSMetaObject struct {
 	ID           int64 `xorm:"pk autoincr"`
@@ -239,7 +310,7 @@ func CopyLFS(ctx context.Context, newRepo, oldRepo *repo_model.Repository) error
 	for _, v := range lfsObjects {
 		v.ID = 0
 		v.RepositoryID = newRepo.ID
-		if _, err := db.GetEngine(ctx).Insert(v); err != nil {
+		if err := db.Insert(ctx, v); err != nil {
 			return err
 		}
 	}
diff --git a/models/lfs_lock.go b/models/git/lfs_lock.go
similarity index 99%
rename from models/lfs_lock.go
rename to models/git/lfs_lock.go
index 4995305393..016db899a9 100644
--- a/models/lfs_lock.go
+++ b/models/git/lfs_lock.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package git
 
 import (
 	"context"
diff --git a/models/git/main_test.go b/models/git/main_test.go
new file mode 100644
index 0000000000..02401e5204
--- /dev/null
+++ b/models/git/main_test.go
@@ -0,0 +1,18 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package git_test
+
+import (
+	"path/filepath"
+	"testing"
+
+	"code.gitea.io/gitea/models/unittest"
+)
+
+func TestMain(m *testing.M) {
+	unittest.MainTest(m, &unittest.TestOptions{
+		GiteaRootPath: filepath.Join("..", ".."),
+	})
+}
diff --git a/models/protected_tag.go b/models/git/protected_tag.go
similarity index 99%
rename from models/protected_tag.go
rename to models/git/protected_tag.go
index db6ff50462..7c3881643d 100644
--- a/models/protected_tag.go
+++ b/models/git/protected_tag.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package git
 
 import (
 	"regexp"
diff --git a/models/protected_tag_test.go b/models/git/protected_tag_test.go
similarity index 74%
rename from models/protected_tag_test.go
rename to models/git/protected_tag_test.go
index bbd5086092..b496688b25 100644
--- a/models/protected_tag_test.go
+++ b/models/git/protected_tag_test.go
@@ -2,11 +2,12 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package git_test
 
 import (
 	"testing"
 
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/unittest"
 
 	"github.com/stretchr/testify/assert"
@@ -15,42 +16,42 @@ import (
 func TestIsUserAllowed(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
-	pt := &ProtectedTag{}
-	allowed, err := IsUserAllowedModifyTag(pt, 1)
+	pt := &git_model.ProtectedTag{}
+	allowed, err := git_model.IsUserAllowedModifyTag(pt, 1)
 	assert.NoError(t, err)
 	assert.False(t, allowed)
 
-	pt = &ProtectedTag{
+	pt = &git_model.ProtectedTag{
 		AllowlistUserIDs: []int64{1},
 	}
-	allowed, err = IsUserAllowedModifyTag(pt, 1)
+	allowed, err = git_model.IsUserAllowedModifyTag(pt, 1)
 	assert.NoError(t, err)
 	assert.True(t, allowed)
 
-	allowed, err = IsUserAllowedModifyTag(pt, 2)
+	allowed, err = git_model.IsUserAllowedModifyTag(pt, 2)
 	assert.NoError(t, err)
 	assert.False(t, allowed)
 
-	pt = &ProtectedTag{
+	pt = &git_model.ProtectedTag{
 		AllowlistTeamIDs: []int64{1},
 	}
-	allowed, err = IsUserAllowedModifyTag(pt, 1)
+	allowed, err = git_model.IsUserAllowedModifyTag(pt, 1)
 	assert.NoError(t, err)
 	assert.False(t, allowed)
 
-	allowed, err = IsUserAllowedModifyTag(pt, 2)
+	allowed, err = git_model.IsUserAllowedModifyTag(pt, 2)
 	assert.NoError(t, err)
 	assert.True(t, allowed)
 
-	pt = &ProtectedTag{
+	pt = &git_model.ProtectedTag{
 		AllowlistUserIDs: []int64{1},
 		AllowlistTeamIDs: []int64{1},
 	}
-	allowed, err = IsUserAllowedModifyTag(pt, 1)
+	allowed, err = git_model.IsUserAllowedModifyTag(pt, 1)
 	assert.NoError(t, err)
 	assert.True(t, allowed)
 
-	allowed, err = IsUserAllowedModifyTag(pt, 2)
+	allowed, err = git_model.IsUserAllowedModifyTag(pt, 2)
 	assert.NoError(t, err)
 	assert.True(t, allowed)
 }
@@ -119,7 +120,7 @@ func TestIsUserAllowedToControlTag(t *testing.T) {
 	}
 
 	t.Run("Glob", func(t *testing.T) {
-		protectedTags := []*ProtectedTag{
+		protectedTags := []*git_model.ProtectedTag{
 			{
 				NamePattern:      `*gitea`,
 				AllowlistUserIDs: []int64{1},
@@ -134,14 +135,14 @@ func TestIsUserAllowedToControlTag(t *testing.T) {
 		}
 
 		for n, c := range cases {
-			isAllowed, err := IsUserAllowedToControlTag(protectedTags, c.name, c.userid)
+			isAllowed, err := git_model.IsUserAllowedToControlTag(protectedTags, c.name, c.userid)
 			assert.NoError(t, err)
 			assert.Equal(t, c.allowed, isAllowed, "case %d: error should match", n)
 		}
 	})
 
 	t.Run("Regex", func(t *testing.T) {
-		protectedTags := []*ProtectedTag{
+		protectedTags := []*git_model.ProtectedTag{
 			{
 				NamePattern:      `/gitea\z/`,
 				AllowlistUserIDs: []int64{1},
@@ -156,7 +157,7 @@ func TestIsUserAllowedToControlTag(t *testing.T) {
 		}
 
 		for n, c := range cases {
-			isAllowed, err := IsUserAllowedToControlTag(protectedTags, c.name, c.userid)
+			isAllowed, err := git_model.IsUserAllowedToControlTag(protectedTags, c.name, c.userid)
 			assert.NoError(t, err)
 			assert.Equal(t, c.allowed, isAllowed, "case %d: error should match", n)
 		}
diff --git a/models/issue_comment.go b/models/issue_comment.go
index 90c95afa4e..21cd87108d 100644
--- a/models/issue_comment.go
+++ b/models/issue_comment.go
@@ -15,6 +15,7 @@ import (
 	"unicode/utf8"
 
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
 	project_model "code.gitea.io/gitea/models/project"
@@ -271,11 +272,11 @@ type Comment struct {
 	RefIssue   *Issue                 `xorm:"-"`
 	RefComment *Comment               `xorm:"-"`
 
-	Commits     []*SignCommitWithStatuses `xorm:"-"`
-	OldCommit   string                    `xorm:"-"`
-	NewCommit   string                    `xorm:"-"`
-	CommitsNum  int64                     `xorm:"-"`
-	IsForcePush bool                      `xorm:"-"`
+	Commits     []*git_model.SignCommitWithStatuses `xorm:"-"`
+	OldCommit   string                              `xorm:"-"`
+	NewCommit   string                              `xorm:"-"`
+	CommitsNum  int64                               `xorm:"-"`
+	IsForcePush bool                                `xorm:"-"`
 }
 
 func init() {
@@ -761,7 +762,7 @@ func (c *Comment) LoadPushCommits(ctx context.Context) (err error) {
 		}
 		defer closer.Close()
 
-		c.Commits = ConvertFromGitCommit(gitRepo.GetCommitsFromIDs(data.CommitIDs), c.Issue.Repo)
+		c.Commits = git_model.ConvertFromGitCommit(gitRepo.GetCommitsFromIDs(data.CommitIDs), c.Issue.Repo)
 		c.CommitsNum = int64(len(c.Commits))
 	}
 
diff --git a/models/org_team.go b/models/org_team.go
index f1d35ee189..7ff3095273 100644
--- a/models/org_team.go
+++ b/models/org_team.go
@@ -12,6 +12,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/organization"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
@@ -412,7 +413,7 @@ func DeleteTeam(t *organization.Team) error {
 
 	// update branch protections
 	{
-		protections := make([]*ProtectedBranch, 0, 10)
+		protections := make([]*git_model.ProtectedBranch, 0, 10)
 		err := sess.In("repo_id",
 			builder.Select("id").From("repository").Where(builder.Eq{"owner_id": t.OrgID})).
 			Find(&protections)
diff --git a/models/pull.go b/models/pull.go
index df96e2dc74..238eb16636 100644
--- a/models/pull.go
+++ b/models/pull.go
@@ -12,6 +12,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	pull_model "code.gitea.io/gitea/models/pull"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
@@ -78,9 +79,9 @@ type PullRequest struct {
 	HeadBranch          string
 	HeadCommitID        string `xorm:"-"`
 	BaseBranch          string
-	ProtectedBranch     *ProtectedBranch `xorm:"-"`
-	MergeBase           string           `xorm:"VARCHAR(40)"`
-	AllowMaintainerEdit bool             `xorm:"NOT NULL DEFAULT false"`
+	ProtectedBranch     *git_model.ProtectedBranch `xorm:"-"`
+	MergeBase           string                     `xorm:"VARCHAR(40)"`
+	AllowMaintainerEdit bool                       `xorm:"NOT NULL DEFAULT false"`
 
 	HasMerged      bool               `xorm:"INDEX"`
 	MergedCommitID string             `xorm:"VARCHAR(40)"`
@@ -242,7 +243,7 @@ func (pr *PullRequest) LoadProtectedBranchCtx(ctx context.Context) (err error) {
 				return
 			}
 		}
-		pr.ProtectedBranch, err = GetProtectedBranchBy(ctx, pr.BaseRepo.ID, pr.BaseBranch)
+		pr.ProtectedBranch, err = git_model.GetProtectedBranchBy(ctx, pr.BaseRepo.ID, pr.BaseBranch)
 	}
 	return
 }
diff --git a/models/repo.go b/models/repo.go
index fff9cc5271..a8aa18381d 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -15,6 +15,7 @@ import (
 	admin_model "code.gitea.io/gitea/models/admin"
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
@@ -34,9 +35,6 @@ import (
 	"xorm.io/builder"
 )
 
-// ItemsPerPage maximum items per page in forks, watchers and stars of a repo
-var ItemsPerPage = 40
-
 // NewRepoContext creates a new repository context
 func NewRepoContext() {
 	unit.LoadUnitConfig()
@@ -284,16 +282,16 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
 		&Action{RepoID: repo.ID},
 		&repo_model.Collaboration{RepoID: repoID},
 		&Comment{RefRepoID: repoID},
-		&CommitStatus{RepoID: repoID},
-		&DeletedBranch{RepoID: repoID},
+		&git_model.CommitStatus{RepoID: repoID},
+		&git_model.DeletedBranch{RepoID: repoID},
 		&webhook.HookTask{RepoID: repoID},
-		&LFSLock{RepoID: repoID},
+		&git_model.LFSLock{RepoID: repoID},
 		&repo_model.LanguageStat{RepoID: repoID},
 		&issues_model.Milestone{RepoID: repoID},
 		&repo_model.Mirror{RepoID: repoID},
 		&Notification{RepoID: repoID},
-		&ProtectedBranch{RepoID: repoID},
-		&ProtectedTag{RepoID: repoID},
+		&git_model.ProtectedBranch{RepoID: repoID},
+		&git_model.ProtectedTag{RepoID: repoID},
 		&repo_model.PushMirror{RepoID: repoID},
 		&Release{RepoID: repoID},
 		&repo_model.RepoIndexerStatus{RepoID: repoID},
@@ -357,14 +355,14 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
 	}
 
 	// Remove LFS objects
-	var lfsObjects []*LFSMetaObject
+	var lfsObjects []*git_model.LFSMetaObject
 	if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil {
 		return err
 	}
 
 	lfsPaths := make([]string, 0, len(lfsObjects))
 	for _, v := range lfsObjects {
-		count, err := db.CountByBean(ctx, &LFSMetaObject{Pointer: lfs.Pointer{Oid: v.Oid}})
+		count, err := db.CountByBean(ctx, &git_model.LFSMetaObject{Pointer: lfs.Pointer{Oid: v.Oid}})
 		if err != nil {
 			return err
 		}
@@ -375,7 +373,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
 		lfsPaths = append(lfsPaths, v.RelativePath())
 	}
 
-	if _, err := db.DeleteByBean(ctx, &LFSMetaObject{RepositoryID: repoID}); err != nil {
+	if _, err := db.DeleteByBean(ctx, &git_model.LFSMetaObject{RepositoryID: repoID}); err != nil {
 		return err
 	}
 
diff --git a/models/repo/collaboration.go b/models/repo/collaboration.go
index 09397dd172..be05eba74c 100644
--- a/models/repo/collaboration.go
+++ b/models/repo/collaboration.go
@@ -10,6 +10,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/perm"
+	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/timeutil"
@@ -149,3 +150,23 @@ func ChangeCollaborationAccessMode(repo *Repository, uid int64, mode perm.Access
 
 	return committer.Commit()
 }
+
+// IsOwnerMemberCollaborator checks if a provided user is the owner, a collaborator or a member of a team in a repository
+func IsOwnerMemberCollaborator(repo *Repository, userID int64) (bool, error) {
+	if repo.OwnerID == userID {
+		return true, nil
+	}
+	teamMember, err := db.GetEngine(db.DefaultContext).Join("INNER", "team_repo", "team_repo.team_id = team_user.team_id").
+		Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id").
+		Where("team_repo.repo_id = ?", repo.ID).
+		And("team_unit.`type` = ?", unit.TypeCode).
+		And("team_user.uid = ?", userID).Table("team_user").Exist()
+	if err != nil {
+		return false, err
+	}
+	if teamMember {
+		return true, nil
+	}
+
+	return db.GetEngine(db.DefaultContext).Get(&Collaboration{RepoID: repo.ID, UserID: userID})
+}
diff --git a/models/repo_collaboration.go b/models/repo_collaboration.go
index 8cbd836a42..7d43115b23 100644
--- a/models/repo_collaboration.go
+++ b/models/repo_collaboration.go
@@ -10,11 +10,9 @@ import (
 	"fmt"
 
 	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
-	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 
 	"xorm.io/builder"
@@ -120,23 +118,3 @@ func reconsiderWatches(ctx context.Context, repo *repo_model.Repository, uid int
 	// Remove all IssueWatches a user has subscribed to in the repository
 	return removeIssueWatchersByRepoID(ctx, uid, repo.ID)
 }
-
-// IsOwnerMemberCollaborator checks if a provided user is the owner, a collaborator or a member of a team in a repository
-func IsOwnerMemberCollaborator(repo *repo_model.Repository, userID int64) (bool, error) {
-	if repo.OwnerID == userID {
-		return true, nil
-	}
-	teamMember, err := db.GetEngine(db.DefaultContext).Join("INNER", "team_repo", "team_repo.team_id = team_user.team_id").
-		Join("INNER", "team_unit", "team_unit.team_id = team_user.team_id").
-		Where("team_repo.repo_id = ?", repo.ID).
-		And("team_unit.`type` = ?", unit.TypeCode).
-		And("team_user.uid = ?", userID).Table("team_user").Exist(&organization.TeamUser{})
-	if err != nil {
-		return false, err
-	}
-	if teamMember {
-		return true, nil
-	}
-
-	return db.GetEngine(db.DefaultContext).Get(&repo_model.Collaboration{RepoID: repo.ID, UserID: userID})
-}
diff --git a/models/review.go b/models/review.go
index 296c9ce040..e92caba938 100644
--- a/models/review.go
+++ b/models/review.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
 	access_model "code.gitea.io/gitea/models/perm/access"
@@ -234,7 +235,7 @@ func IsOfficialReviewer(ctx context.Context, issue *Issue, reviewers ...*user_mo
 	}
 
 	for _, reviewer := range reviewers {
-		official, err := isUserOfficialReviewer(ctx, pr.ProtectedBranch, reviewer)
+		official, err := git_model.IsUserOfficialReviewerCtx(ctx, pr.ProtectedBranch, reviewer)
 		if official || err != nil {
 			return official, err
 		}
diff --git a/models/user.go b/models/user.go
index e8a412ca29..59ec643d55 100644
--- a/models/user.go
+++ b/models/user.go
@@ -15,6 +15,7 @@ import (
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	auth_model "code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
 	access_model "code.gitea.io/gitea/models/perm/access"
@@ -125,7 +126,7 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
 	{
 		const batchSize = 50
 		for start := 0; ; start += batchSize {
-			protections := make([]*ProtectedBranch, 0, batchSize)
+			protections := make([]*git_model.ProtectedBranch, 0, batchSize)
 			// @perf: We can't filter on DB side by u.ID, as those IDs are serialized as JSON strings.
 			//   We could filter down with `WHERE repo_id IN (reposWithPushPermission(u))`,
 			//   though that query will be quite complex and tricky to maintain (compare `getRepoAssignees()`).
diff --git a/modules/context/repo.go b/modules/context/repo.go
index 5f4af114ff..05ced909a8 100644
--- a/modules/context/repo.go
+++ b/modules/context/repo.go
@@ -17,6 +17,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	unit_model "code.gitea.io/gitea/models/unit"
@@ -118,7 +119,7 @@ type CanCommitToBranchResults struct {
 // CanCommitToBranch returns true if repository is editable and user has proper access level
 //   and branch is not protected for push
 func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.User) (CanCommitToBranchResults, error) {
-	protectedBranch, err := models.GetProtectedBranchBy(ctx, r.Repository.ID, r.BranchName)
+	protectedBranch, err := git_model.GetProtectedBranchBy(ctx, r.Repository.ID, r.BranchName)
 	if err != nil {
 		return CanCommitToBranchResults{}, err
 	}
@@ -825,7 +826,7 @@ func getRefName(ctx *Context, pathType RepoRefType) string {
 		if len(ref) == 0 {
 			// maybe it's a renamed branch
 			return getRefNameFromPath(ctx, path, func(s string) bool {
-				b, exist, err := models.FindRenamedBranch(ctx.Repo.Repository.ID, s)
+				b, exist, err := git_model.FindRenamedBranch(ctx.Repo.Repository.ID, s)
 				if err != nil {
 					log.Error("FindRenamedBranch", err)
 					return false
diff --git a/modules/convert/convert.go b/modules/convert/convert.go
index 67b3902cd7..4e8aa59067 100644
--- a/modules/convert/convert.go
+++ b/modules/convert/convert.go
@@ -15,6 +15,7 @@ import (
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
 	access_model "code.gitea.io/gitea/models/perm/access"
@@ -39,7 +40,7 @@ func ToEmail(email *user_model.EmailAddress) *api.Email {
 }
 
 // ToBranch convert a git.Commit and git.Branch to an api.Branch
-func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *models.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) {
+func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) {
 	if bp == nil {
 		var hasPerm bool
 		var canPush bool
@@ -88,14 +89,14 @@ func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *mod
 			return nil, err
 		}
 		branch.UserCanPush = bp.CanUserPush(user.ID)
-		branch.UserCanMerge = models.IsUserMergeWhitelisted(db.DefaultContext, bp, user.ID, permission)
+		branch.UserCanMerge = git_model.IsUserMergeWhitelisted(db.DefaultContext, bp, user.ID, permission)
 	}
 
 	return branch, nil
 }
 
 // ToBranchProtection convert a ProtectedBranch to api.BranchProtection
-func ToBranchProtection(bp *models.ProtectedBranch) *api.BranchProtection {
+func ToBranchProtection(bp *git_model.ProtectedBranch) *api.BranchProtection {
 	pushWhitelistUsernames, err := user_model.GetUserNamesByIDs(bp.WhitelistUserIDs)
 	if err != nil {
 		log.Error("GetUserNamesByIDs (WhitelistUserIDs): %v", err)
@@ -399,7 +400,7 @@ func ToOAuth2Application(app *auth.OAuth2Application) *api.OAuth2Application {
 }
 
 // ToLFSLock convert a LFSLock to api.LFSLock
-func ToLFSLock(l *models.LFSLock) *api.LFSLock {
+func ToLFSLock(l *git_model.LFSLock) *api.LFSLock {
 	u, err := user_model.GetUserByID(l.OwnerID)
 	if err != nil {
 		return nil
diff --git a/modules/convert/status.go b/modules/convert/status.go
index 1ac2a0f3e1..7ed28a9c10 100644
--- a/modules/convert/status.go
+++ b/modules/convert/status.go
@@ -5,13 +5,13 @@
 package convert
 
 import (
-	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	user_model "code.gitea.io/gitea/models/user"
 	api "code.gitea.io/gitea/modules/structs"
 )
 
-// ToCommitStatus converts models.CommitStatus to api.CommitStatus
-func ToCommitStatus(status *models.CommitStatus) *api.CommitStatus {
+// ToCommitStatus converts git_model.CommitStatus to api.CommitStatus
+func ToCommitStatus(status *git_model.CommitStatus) *api.CommitStatus {
 	apiStatus := &api.CommitStatus{
 		Created:     status.CreatedUnix.AsTime(),
 		Updated:     status.CreatedUnix.AsTime(),
@@ -32,7 +32,7 @@ func ToCommitStatus(status *models.CommitStatus) *api.CommitStatus {
 }
 
 // ToCombinedStatus converts List of CommitStatus to a CombinedStatus
-func ToCombinedStatus(statuses []*models.CommitStatus, repo *api.Repository) *api.CombinedStatus {
+func ToCombinedStatus(statuses []*git_model.CommitStatus, repo *api.Repository) *api.CombinedStatus {
 	if len(statuses) == 0 {
 		return nil
 	}
diff --git a/modules/gitgraph/graph_models.go b/modules/gitgraph/graph_models.go
index 551e56f63e..c153e5d3be 100644
--- a/modules/gitgraph/graph_models.go
+++ b/modules/gitgraph/graph_models.go
@@ -9,9 +9,9 @@ import (
 	"fmt"
 	"strings"
 
-	"code.gitea.io/gitea/models"
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
@@ -117,14 +117,14 @@ func (graph *Graph) LoadAndProcessCommits(repository *repo_model.Repository, git
 		c.Verification = asymkey_model.ParseCommitWithSignature(c.Commit)
 
 		_ = asymkey_model.CalculateTrustStatus(c.Verification, repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
-			return models.IsOwnerMemberCollaborator(repository, user.ID)
+			return repo_model.IsOwnerMemberCollaborator(repository, user.ID)
 		}, &keyMap)
 
-		statuses, _, err := models.GetLatestCommitStatus(db.DefaultContext, repository.ID, c.Commit.ID.String(), db.ListOptions{})
+		statuses, _, err := git_model.GetLatestCommitStatus(db.DefaultContext, repository.ID, c.Commit.ID.String(), db.ListOptions{})
 		if err != nil {
 			log.Error("GetLatestCommitStatus: %v", err)
 		} else {
-			c.Status = models.CalcCommitStatus(statuses)
+			c.Status = git_model.CalcCommitStatus(statuses)
 		}
 	}
 	return nil
@@ -240,7 +240,7 @@ type Commit struct {
 	Commit       *git.Commit
 	User         *user_model.User
 	Verification *asymkey_model.CommitVerification
-	Status       *models.CommitStatus
+	Status       *git_model.CommitStatus
 	Flow         int64
 	Row          int
 	Column       int
diff --git a/modules/lfs/content_store.go b/modules/lfs/content_store.go
index 9fa2c7e3b2..c794a1fecc 100644
--- a/modules/lfs/content_store.go
+++ b/modules/lfs/content_store.go
@@ -113,7 +113,7 @@ func (s *ContentStore) Verify(pointer Pointer) (bool, error) {
 	return true, nil
 }
 
-// ReadMetaObject will read a models.LFSMetaObject and return a reader
+// ReadMetaObject will read a git_model.LFSMetaObject and return a reader
 func ReadMetaObject(pointer Pointer) (io.ReadCloser, error) {
 	contentStore := NewContentStore()
 	return contentStore.Get(pointer)
diff --git a/modules/repository/create.go b/modules/repository/create.go
index 95bb825403..3d04dba70f 100644
--- a/modules/repository/create.go
+++ b/modules/repository/create.go
@@ -14,6 +14,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
@@ -146,7 +147,7 @@ func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error {
 		return fmt.Errorf("updateSize: %v", err)
 	}
 
-	lfsSize, err := models.GetRepoLFSSize(ctx, repo.ID)
+	lfsSize, err := git_model.GetRepoLFSSize(ctx, repo.ID)
 	if err != nil {
 		return fmt.Errorf("updateSize: GetLFSMetaObjects: %v", err)
 	}
diff --git a/modules/repository/generate.go b/modules/repository/generate.go
index 94bb6e6429..8f7b4c885d 100644
--- a/modules/repository/generate.go
+++ b/modules/repository/generate.go
@@ -16,6 +16,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
@@ -278,7 +279,7 @@ func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_mo
 		return fmt.Errorf("failed to update size for repository: %v", err)
 	}
 
-	if err := models.CopyLFS(ctx, generateRepo, templateRepo); err != nil {
+	if err := git_model.CopyLFS(ctx, generateRepo, templateRepo); err != nil {
 		return fmt.Errorf("failed to copy LFS: %v", err)
 	}
 	return nil
diff --git a/modules/repository/repo.go b/modules/repository/repo.go
index 281999a1eb..436045146a 100644
--- a/modules/repository/repo.go
+++ b/modules/repository/repo.go
@@ -15,6 +15,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/organization"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
@@ -390,7 +391,7 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re
 
 			defer content.Close()
 
-			_, err := models.NewLFSMetaObject(&models.LFSMetaObject{Pointer: p, RepositoryID: repo.ID})
+			_, err := git_model.NewLFSMetaObject(&git_model.LFSMetaObject{Pointer: p, RepositoryID: repo.ID})
 			if err != nil {
 				log.Error("Repo[%-v]: Error creating LFS meta object %-v: %v", repo, p, err)
 				return err
@@ -398,7 +399,7 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re
 
 			if err := contentStore.Put(p, content); err != nil {
 				log.Error("Repo[%-v]: Error storing content for LFS meta object %-v: %v", repo, p, err)
-				if _, err2 := models.RemoveLFSMetaObjectByOid(repo.ID, p.Oid); err2 != nil {
+				if _, err2 := git_model.RemoveLFSMetaObjectByOid(repo.ID, p.Oid); err2 != nil {
 					log.Error("Repo[%-v]: Error removing LFS meta object %-v: %v", repo, p, err2)
 				}
 				return err
@@ -417,8 +418,8 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re
 
 	var batch []lfs.Pointer
 	for pointerBlob := range pointerChan {
-		meta, err := models.GetLFSMetaObjectByOid(repo.ID, pointerBlob.Oid)
-		if err != nil && err != models.ErrLFSObjectNotExist {
+		meta, err := git_model.GetLFSMetaObjectByOid(repo.ID, pointerBlob.Oid)
+		if err != nil && err != git_model.ErrLFSObjectNotExist {
 			log.Error("Repo[%-v]: Error querying LFS meta object %-v: %v", repo, pointerBlob.Pointer, err)
 			return err
 		}
@@ -437,7 +438,7 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re
 
 		if exist {
 			log.Trace("Repo[%-v]: LFS object %-v already present; creating meta object", repo, pointerBlob.Pointer)
-			_, err := models.NewLFSMetaObject(&models.LFSMetaObject{Pointer: pointerBlob.Pointer, RepositoryID: repo.ID})
+			_, err := git_model.NewLFSMetaObject(&git_model.LFSMetaObject{Pointer: pointerBlob.Pointer, RepositoryID: repo.ID})
 			if err != nil {
 				log.Error("Repo[%-v]: Error creating LFS meta object %-v: %v", repo, pointerBlob.Pointer, err)
 				return err
diff --git a/modules/setting/repository.go b/modules/setting/repository.go
index 0b90fbc67c..733bc6d90e 100644
--- a/modules/setting/repository.go
+++ b/modules/setting/repository.go
@@ -20,6 +20,9 @@ const (
 	RepoCreatingPublic             = "public"
 )
 
+// ItemsPerPage maximum items per page in forks, watchers and stars of a repo
+const ItemsPerPage = 40
+
 // Repository settings
 var (
 	Repository = struct {
diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index 09e6ccf238..84a172e92b 100644
--- a/routers/api/v1/repo/branch.go
+++ b/routers/api/v1/repo/branch.go
@@ -11,6 +11,7 @@ import (
 	"net/http"
 
 	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/organization"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
@@ -70,7 +71,7 @@ func GetBranch(ctx *context.APIContext) {
 		return
 	}
 
-	branchProtection, err := models.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branchName)
+	branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branchName)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
 		return
@@ -206,7 +207,7 @@ func CreateBranch(ctx *context.APIContext) {
 		return
 	}
 
-	branchProtection, err := models.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branch.Name)
+	branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branch.Name)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
 		return
@@ -271,7 +272,7 @@ func ListBranches(ctx *context.APIContext) {
 			ctx.Error(http.StatusInternalServerError, "GetCommit", err)
 			return
 		}
-		branchProtection, err := models.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branches[i].Name)
+		branchProtection, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branches[i].Name)
 		if err != nil {
 			ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
 			return
@@ -320,7 +321,7 @@ func GetBranchProtection(ctx *context.APIContext) {
 
 	repo := ctx.Repo.Repository
 	bpName := ctx.Params(":name")
-	bp, err := models.GetProtectedBranchBy(ctx, repo.ID, bpName)
+	bp, err := git_model.GetProtectedBranchBy(ctx, repo.ID, bpName)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
 		return
@@ -356,7 +357,7 @@ func ListBranchProtections(ctx *context.APIContext) {
 	//     "$ref": "#/responses/BranchProtectionList"
 
 	repo := ctx.Repo.Repository
-	bps, err := models.GetProtectedBranches(repo.ID)
+	bps, err := git_model.GetProtectedBranches(repo.ID)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetProtectedBranches", err)
 		return
@@ -412,7 +413,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
 		return
 	}
 
-	protectBranch, err := models.GetProtectedBranchBy(ctx, repo.ID, form.BranchName)
+	protectBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, form.BranchName)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetProtectBranchOfRepoByName", err)
 		return
@@ -484,7 +485,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
 		}
 	}
 
-	protectBranch = &models.ProtectedBranch{
+	protectBranch = &git_model.ProtectedBranch{
 		RepoID:                        ctx.Repo.Repository.ID,
 		BranchName:                    form.BranchName,
 		CanPush:                       form.EnablePush,
@@ -504,7 +505,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
 		BlockOnOutdatedBranch:         form.BlockOnOutdatedBranch,
 	}
 
-	err = models.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
+	err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
 		UserIDs:          whitelistUsers,
 		TeamIDs:          whitelistTeams,
 		MergeUserIDs:     mergeWhitelistUsers,
@@ -523,7 +524,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
 	}
 
 	// Reload from db to get all whitelists
-	bp, err := models.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, form.BranchName)
+	bp, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, form.BranchName)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
 		return
@@ -575,7 +576,7 @@ func EditBranchProtection(ctx *context.APIContext) {
 	form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
 	repo := ctx.Repo.Repository
 	bpName := ctx.Params(":name")
-	protectBranch, err := models.GetProtectedBranchBy(ctx, repo.ID, bpName)
+	protectBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, bpName)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
 		return
@@ -739,7 +740,7 @@ func EditBranchProtection(ctx *context.APIContext) {
 		}
 	}
 
-	err = models.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
+	err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
 		UserIDs:          whitelistUsers,
 		TeamIDs:          whitelistTeams,
 		MergeUserIDs:     mergeWhitelistUsers,
@@ -758,7 +759,7 @@ func EditBranchProtection(ctx *context.APIContext) {
 	}
 
 	// Reload from db to ensure get all whitelists
-	bp, err := models.GetProtectedBranchBy(ctx, repo.ID, bpName)
+	bp, err := git_model.GetProtectedBranchBy(ctx, repo.ID, bpName)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetProtectedBranchBy", err)
 		return
@@ -802,7 +803,7 @@ func DeleteBranchProtection(ctx *context.APIContext) {
 
 	repo := ctx.Repo.Repository
 	bpName := ctx.Params(":name")
-	bp, err := models.GetProtectedBranchBy(ctx, repo.ID, bpName)
+	bp, err := git_model.GetProtectedBranchBy(ctx, repo.ID, bpName)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
 		return
@@ -812,7 +813,7 @@ func DeleteBranchProtection(ctx *context.APIContext) {
 		return
 	}
 
-	if err := models.DeleteProtectedBranch(ctx.Repo.Repository.ID, bp.ID); err != nil {
+	if err := git_model.DeleteProtectedBranch(ctx.Repo.Repository.ID, bp.ID); err != nil {
 		ctx.Error(http.StatusInternalServerError, "DeleteProtectedBranch", err)
 		return
 	}
diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go
index ab337e66e3..ffa3ddc784 100644
--- a/routers/api/v1/repo/file.go
+++ b/routers/api/v1/repo/file.go
@@ -15,6 +15,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/modules/cache"
@@ -173,10 +174,10 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
 	}
 
 	// Now check if there is a meta object for this pointer
-	meta, err := models.GetLFSMetaObjectByOid(ctx.Repo.Repository.ID, pointer.Oid)
+	meta, err := git_model.GetLFSMetaObjectByOid(ctx.Repo.Repository.ID, pointer.Oid)
 
 	// If there isn't one just serve the data directly
-	if err == models.ErrLFSObjectNotExist {
+	if err == git_model.ErrLFSObjectNotExist {
 		// Handle caching for the blob SHA (not the LFS object OID)
 		if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
 			return
diff --git a/routers/api/v1/repo/status.go b/routers/api/v1/repo/status.go
index 09597dc4e8..f7bc2c5c88 100644
--- a/routers/api/v1/repo/status.go
+++ b/routers/api/v1/repo/status.go
@@ -8,7 +8,7 @@ import (
 	"fmt"
 	"net/http"
 
-	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/convert"
 	api "code.gitea.io/gitea/modules/structs"
@@ -56,7 +56,7 @@ func NewCommitStatus(ctx *context.APIContext) {
 		ctx.Error(http.StatusBadRequest, "sha not given", nil)
 		return
 	}
-	status := &models.CommitStatus{
+	status := &git_model.CommitStatus{
 		State:       api.CommitStatusState(form.State),
 		TargetURL:   form.TargetURL,
 		Description: form.Description,
@@ -188,7 +188,7 @@ func getCommitStatuses(ctx *context.APIContext, sha string) {
 
 	listOptions := utils.GetListOptions(ctx)
 
-	statuses, maxResults, err := models.GetCommitStatuses(repo, sha, &models.CommitStatusOptions{
+	statuses, maxResults, err := git_model.GetCommitStatuses(repo, sha, &git_model.CommitStatusOptions{
 		ListOptions: listOptions,
 		SortType:    ctx.FormTrim("sort"),
 		State:       ctx.FormTrim("state"),
@@ -253,7 +253,7 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
 
 	repo := ctx.Repo.Repository
 
-	statuses, count, err := models.GetLatestCommitStatus(ctx, repo.ID, sha, utils.GetListOptions(ctx))
+	statuses, count, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, utils.GetListOptions(ctx))
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetLatestCommitStatus", fmt.Errorf("GetLatestCommitStatus[%s, %s]: %v", repo.FullName(), sha, err))
 		return
diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go
index 1f005d35bf..411319f2e6 100644
--- a/routers/private/hook_pre_receive.go
+++ b/routers/private/hook_pre_receive.go
@@ -13,6 +13,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
+	git_model "code.gitea.io/gitea/models/git"
 	perm_model "code.gitea.io/gitea/models/perm"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	"code.gitea.io/gitea/models/unit"
@@ -40,7 +41,7 @@ type preReceiveContext struct {
 	canWriteCode        bool
 	checkedCanWriteCode bool
 
-	protectedTags    []*models.ProtectedTag
+	protectedTags    []*git_model.ProtectedTag
 	gotProtectedTags bool
 
 	env []string
@@ -155,7 +156,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID, refFullN
 		return
 	}
 
-	protectBranch, err := models.GetProtectedBranchBy(ctx, repo.ID, branchName)
+	protectBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, branchName)
 	if err != nil {
 		log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err)
 		ctx.JSON(http.StatusInternalServerError, private.Response{
@@ -370,7 +371,7 @@ func preReceiveTag(ctx *preReceiveContext, oldCommitID, newCommitID, refFullName
 
 	if !ctx.gotProtectedTags {
 		var err error
-		ctx.protectedTags, err = models.GetProtectedTags(ctx.Repo.Repository.ID)
+		ctx.protectedTags, err = git_model.GetProtectedTags(ctx.Repo.Repository.ID)
 		if err != nil {
 			log.Error("Unable to get protected tags for %-v Error: %v", ctx.Repo.Repository, err)
 			ctx.JSON(http.StatusInternalServerError, private.Response{
@@ -381,7 +382,7 @@ func preReceiveTag(ctx *preReceiveContext, oldCommitID, newCommitID, refFullName
 		ctx.gotProtectedTags = true
 	}
 
-	isAllowed, err := models.IsUserAllowedToControlTag(ctx.protectedTags, tagName, ctx.opts.UserID)
+	isAllowed, err := git_model.IsUserAllowedToControlTag(ctx.protectedTags, tagName, ctx.opts.UserID)
 	if err != nil {
 		ctx.JSON(http.StatusInternalServerError, private.Response{
 			Err: err.Error(),
diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go
index 08f388bf3c..84ad803ee5 100644
--- a/routers/web/repo/branch.go
+++ b/routers/web/repo/branch.go
@@ -12,6 +12,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/modules/base"
@@ -40,7 +41,7 @@ type Branch struct {
 	IsProtected       bool
 	IsDeleted         bool
 	IsIncluded        bool
-	DeletedBranch     *models.DeletedBranch
+	DeletedBranch     *git_model.DeletedBranch
 	CommitsAhead      int
 	CommitsBehind     int
 	LatestPullRequest *models.PullRequest
@@ -119,7 +120,7 @@ func RestoreBranchPost(ctx *context.Context) {
 	branchID := ctx.FormInt64("branch_id")
 	branchName := ctx.FormString("name")
 
-	deletedBranch, err := models.GetDeletedBranchByID(ctx.Repo.Repository.ID, branchID)
+	deletedBranch, err := git_model.GetDeletedBranchByID(ctx.Repo.Repository.ID, branchID)
 	if err != nil {
 		log.Error("GetDeletedBranchByID: %v", err)
 		ctx.Flash.Error(ctx.Tr("repo.branch.restore_failed", branchName))
@@ -184,7 +185,7 @@ func loadBranches(ctx *context.Context, skip, limit int) (*Branch, []*Branch, in
 		return nil, nil, 0
 	}
 
-	protectedBranches, err := models.GetProtectedBranches(ctx.Repo.Repository.ID)
+	protectedBranches, err := git_model.GetProtectedBranches(ctx.Repo.Repository.ID)
 	if err != nil {
 		ctx.ServerError("GetProtectedBranches", err)
 		return nil, nil, 0
@@ -231,7 +232,7 @@ func loadBranches(ctx *context.Context, skip, limit int) (*Branch, []*Branch, in
 	return defaultBranchBranch, branches, totalNumOfBranches
 }
 
-func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, protectedBranches []*models.ProtectedBranch,
+func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, protectedBranches []*git_model.ProtectedBranch,
 	repoIDToRepo map[int64]*repo_model.Repository,
 	repoIDToGitRepo map[int64]*git.Repository,
 ) *Branch {
@@ -326,7 +327,7 @@ func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, p
 func getDeletedBranches(ctx *context.Context) ([]*Branch, error) {
 	branches := []*Branch{}
 
-	deletedBranches, err := models.GetDeletedBranches(ctx.Repo.Repository.ID)
+	deletedBranches, err := git_model.GetDeletedBranches(ctx.Repo.Repository.ID)
 	if err != nil {
 		return branches, err
 	}
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index 1636a6c293..164dc181f0 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -11,9 +11,10 @@ import (
 	"net/http"
 	"strings"
 
-	"code.gitea.io/gitea/models"
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
+	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/charset"
@@ -75,7 +76,7 @@ func Commits(ctx *context.Context) {
 		ctx.ServerError("CommitsByRange", err)
 		return
 	}
-	ctx.Data["Commits"] = models.ConvertFromGitCommit(commits, ctx.Repo.Repository)
+	ctx.Data["Commits"] = git_model.ConvertFromGitCommit(commits, ctx.Repo.Repository)
 
 	ctx.Data["Username"] = ctx.Repo.Owner.Name
 	ctx.Data["Reponame"] = ctx.Repo.Repository.Name
@@ -194,7 +195,7 @@ func SearchCommits(ctx *context.Context) {
 		return
 	}
 	ctx.Data["CommitCount"] = len(commits)
-	ctx.Data["Commits"] = models.ConvertFromGitCommit(commits, ctx.Repo.Repository)
+	ctx.Data["Commits"] = git_model.ConvertFromGitCommit(commits, ctx.Repo.Repository)
 
 	ctx.Data["Keyword"] = query
 	if all {
@@ -235,7 +236,7 @@ func FileHistory(ctx *context.Context) {
 		ctx.ServerError("CommitsByFileAndRange", err)
 		return
 	}
-	ctx.Data["Commits"] = models.ConvertFromGitCommit(commits, ctx.Repo.Repository)
+	ctx.Data["Commits"] = git_model.ConvertFromGitCommit(commits, ctx.Repo.Repository)
 
 	ctx.Data["Username"] = ctx.Repo.Owner.Name
 	ctx.Data["Reponame"] = ctx.Repo.Repository.Name
@@ -335,12 +336,12 @@ func Diff(ctx *context.Context) {
 	ctx.Data["Commit"] = commit
 	ctx.Data["Diff"] = diff
 
-	statuses, _, err := models.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptions{})
+	statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptions{})
 	if err != nil {
 		log.Error("GetLatestCommitStatus: %v", err)
 	}
 
-	ctx.Data["CommitStatus"] = models.CalcCommitStatus(statuses)
+	ctx.Data["CommitStatus"] = git_model.CalcCommitStatus(statuses)
 	ctx.Data["CommitStatuses"] = statuses
 
 	verification := asymkey_model.ParseCommitWithSignature(commit)
@@ -350,7 +351,7 @@ func Diff(ctx *context.Context) {
 	ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0
 
 	if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
-		return models.IsOwnerMemberCollaborator(ctx.Repo.Repository, user.ID)
+		return repo_model.IsOwnerMemberCollaborator(ctx.Repo.Repository, user.ID)
 	}, nil); err != nil {
 		ctx.ServerError("CalculateTrustStatus", err)
 		return
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index d3653f04e9..44e89062e8 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -18,6 +18,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
@@ -636,7 +637,7 @@ func PrepareCompareDiff(
 		return false
 	}
 
-	commits := models.ConvertFromGitCommit(ci.CompareInfo.Commits, ci.HeadRepo)
+	commits := git_model.ConvertFromGitCommit(ci.CompareInfo.Commits, ci.HeadRepo)
 	ctx.Data["Commits"] = commits
 	ctx.Data["CommitCount"] = len(commits)
 
diff --git a/routers/web/repo/download.go b/routers/web/repo/download.go
index eae61bb8e7..2a83bafa85 100644
--- a/routers/web/repo/download.go
+++ b/routers/web/repo/download.go
@@ -9,7 +9,7 @@ import (
 	"path"
 	"time"
 
-	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/modules/cache"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/git"
@@ -43,7 +43,7 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified time.Time
 
 	pointer, _ := lfs.ReadPointer(dataRc)
 	if pointer.IsValid() {
-		meta, _ := models.GetLFSMetaObjectByOid(ctx.Repo.Repository.ID, pointer.Oid)
+		meta, _ := git_model.GetLFSMetaObjectByOid(ctx.Repo.Repository.ID, pointer.Oid)
 		if meta == nil {
 			if err = dataRc.Close(); err != nil {
 				log.Error("ServeBlobOrLFS: Close: %v", err)
diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go
index 1253721d34..b510fd504d 100644
--- a/routers/web/repo/editor.go
+++ b/routers/web/repo/editor.go
@@ -12,6 +12,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/charset"
@@ -253,9 +254,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
 		// This is where we handle all the errors thrown by files_service.CreateOrUpdateRepoFile
 		if git.IsErrNotExist(err) {
 			ctx.RenderWithErr(ctx.Tr("repo.editor.file_editing_no_longer_exists", ctx.Repo.TreePath), tplEditFile, &form)
-		} else if models.IsErrLFSFileLocked(err) {
+		} else if git_model.IsErrLFSFileLocked(err) {
 			ctx.Data["Err_TreePath"] = true
-			ctx.RenderWithErr(ctx.Tr("repo.editor.upload_file_is_locked", err.(models.ErrLFSFileLocked).Path, err.(models.ErrLFSFileLocked).UserName), tplEditFile, &form)
+			ctx.RenderWithErr(ctx.Tr("repo.editor.upload_file_is_locked", err.(git_model.ErrLFSFileLocked).Path, err.(git_model.ErrLFSFileLocked).UserName), tplEditFile, &form)
 		} else if models.IsErrFilenameInvalid(err) {
 			ctx.Data["Err_TreePath"] = true
 			ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", form.TreePath), tplEditFile, &form)
@@ -660,9 +661,9 @@ func UploadFilePost(ctx *context.Context) {
 		Files:        form.Files,
 		Signoff:      form.Signoff,
 	}); err != nil {
-		if models.IsErrLFSFileLocked(err) {
+		if git_model.IsErrLFSFileLocked(err) {
 			ctx.Data["Err_TreePath"] = true
-			ctx.RenderWithErr(ctx.Tr("repo.editor.upload_file_is_locked", err.(models.ErrLFSFileLocked).Path, err.(models.ErrLFSFileLocked).UserName), tplUploadFile, &form)
+			ctx.RenderWithErr(ctx.Tr("repo.editor.upload_file_is_locked", err.(git_model.ErrLFSFileLocked).Path, err.(git_model.ErrLFSFileLocked).UserName), tplUploadFile, &form)
 		} else if models.IsErrFilenameInvalid(err) {
 			ctx.Data["Err_TreePath"] = true
 			ctx.RenderWithErr(ctx.Tr("repo.editor.filename_is_invalid", form.TreePath), tplUploadFile, &form)
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 4a732ba454..38ee933044 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -21,6 +21,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
 	access_model "code.gitea.io/gitea/models/perm/access"
@@ -1536,7 +1537,7 @@ func ViewIssue(ctx *context.Context) {
 				if perm.CanWrite(unit.TypeCode) {
 					// Check if branch is not protected
 					if pull.HeadBranch != pull.HeadRepo.DefaultBranch {
-						if protected, err := models.IsProtectedBranch(pull.HeadRepo.ID, pull.HeadBranch); err != nil {
+						if protected, err := git_model.IsProtectedBranch(pull.HeadRepo.ID, pull.HeadBranch); err != nil {
 							log.Error("IsProtectedBranch: %v", err)
 						} else if !protected {
 							canDelete = true
@@ -1620,11 +1621,11 @@ func ViewIssue(ctx *context.Context) {
 			if ctx.Doer != nil {
 				showMergeInstructions = pull.ProtectedBranch.CanUserPush(ctx.Doer.ID)
 			}
-			ctx.Data["IsBlockedByApprovals"] = !pull.ProtectedBranch.HasEnoughApprovals(ctx, pull)
-			ctx.Data["IsBlockedByRejection"] = pull.ProtectedBranch.MergeBlockedByRejectedReview(ctx, pull)
-			ctx.Data["IsBlockedByOfficialReviewRequests"] = pull.ProtectedBranch.MergeBlockedByOfficialReviewRequests(ctx, pull)
-			ctx.Data["IsBlockedByOutdatedBranch"] = pull.ProtectedBranch.MergeBlockedByOutdatedBranch(pull)
-			ctx.Data["GrantedApprovals"] = pull.ProtectedBranch.GetGrantedApprovalsCount(ctx, pull)
+			ctx.Data["IsBlockedByApprovals"] = !models.HasEnoughApprovals(ctx, pull.ProtectedBranch, pull)
+			ctx.Data["IsBlockedByRejection"] = models.MergeBlockedByRejectedReview(ctx, pull.ProtectedBranch, pull)
+			ctx.Data["IsBlockedByOfficialReviewRequests"] = models.MergeBlockedByOfficialReviewRequests(ctx, pull.ProtectedBranch, pull)
+			ctx.Data["IsBlockedByOutdatedBranch"] = models.MergeBlockedByOutdatedBranch(pull.ProtectedBranch, pull)
+			ctx.Data["GrantedApprovals"] = models.GetGrantedApprovalsCount(ctx, pull.ProtectedBranch, pull)
 			ctx.Data["RequireSigned"] = pull.ProtectedBranch.RequireSignedCommits
 			ctx.Data["ChangedProtectedFiles"] = pull.ChangedProtectedFiles
 			ctx.Data["IsBlockedByChangedProtectedFiles"] = len(pull.ChangedProtectedFiles) != 0
diff --git a/routers/web/repo/lfs.go b/routers/web/repo/lfs.go
index e2421f1389..0e446f2de0 100644
--- a/routers/web/repo/lfs.go
+++ b/routers/web/repo/lfs.go
@@ -15,7 +15,7 @@ import (
 	"strconv"
 	"strings"
 
-	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/charset"
 	"code.gitea.io/gitea/modules/context"
@@ -48,7 +48,7 @@ func LFSFiles(ctx *context.Context) {
 	if page <= 1 {
 		page = 1
 	}
-	total, err := models.CountLFSMetaObjects(ctx.Repo.Repository.ID)
+	total, err := git_model.CountLFSMetaObjects(ctx.Repo.Repository.ID)
 	if err != nil {
 		ctx.ServerError("LFSFiles", err)
 		return
@@ -58,7 +58,7 @@ func LFSFiles(ctx *context.Context) {
 	pager := context.NewPagination(int(total), setting.UI.ExplorePagingNum, page, 5)
 	ctx.Data["Title"] = ctx.Tr("repo.settings.lfs")
 	ctx.Data["PageIsSettingsLFS"] = true
-	lfsMetaObjects, err := models.GetLFSMetaObjects(ctx.Repo.Repository.ID, pager.Paginater.Current(), setting.UI.ExplorePagingNum)
+	lfsMetaObjects, err := git_model.GetLFSMetaObjects(ctx.Repo.Repository.ID, pager.Paginater.Current(), setting.UI.ExplorePagingNum)
 	if err != nil {
 		ctx.ServerError("LFSFiles", err)
 		return
@@ -80,7 +80,7 @@ func LFSLocks(ctx *context.Context) {
 	if page <= 1 {
 		page = 1
 	}
-	total, err := models.CountLFSLockByRepoID(ctx.Repo.Repository.ID)
+	total, err := git_model.CountLFSLockByRepoID(ctx.Repo.Repository.ID)
 	if err != nil {
 		ctx.ServerError("LFSLocks", err)
 		return
@@ -90,7 +90,7 @@ func LFSLocks(ctx *context.Context) {
 	pager := context.NewPagination(int(total), setting.UI.ExplorePagingNum, page, 5)
 	ctx.Data["Title"] = ctx.Tr("repo.settings.lfs_locks")
 	ctx.Data["PageIsSettingsLFS"] = true
-	lfsLocks, err := models.GetLFSLockByRepoID(ctx.Repo.Repository.ID, pager.Paginater.Current(), setting.UI.ExplorePagingNum)
+	lfsLocks, err := git_model.GetLFSLockByRepoID(ctx.Repo.Repository.ID, pager.Paginater.Current(), setting.UI.ExplorePagingNum)
 	if err != nil {
 		ctx.ServerError("LFSLocks", err)
 		return
@@ -216,12 +216,12 @@ func LFSLockFile(ctx *context.Context) {
 		return
 	}
 
-	_, err := models.CreateLFSLock(ctx.Repo.Repository, &models.LFSLock{
+	_, err := git_model.CreateLFSLock(ctx.Repo.Repository, &git_model.LFSLock{
 		Path:    lockPath,
 		OwnerID: ctx.Doer.ID,
 	})
 	if err != nil {
-		if models.IsErrLFSLockAlreadyExist(err) {
+		if git_model.IsErrLFSLockAlreadyExist(err) {
 			ctx.Flash.Error(ctx.Tr("repo.settings.lfs_lock_already_exists", originalPath))
 			ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks")
 			return
@@ -238,7 +238,7 @@ func LFSUnlock(ctx *context.Context) {
 		ctx.NotFound("LFSUnlock", nil)
 		return
 	}
-	_, err := models.DeleteLFSLockByID(ctx.ParamsInt64("lid"), ctx.Repo.Repository, ctx.Doer, true)
+	_, err := git_model.DeleteLFSLockByID(ctx.ParamsInt64("lid"), ctx.Repo.Repository, ctx.Doer, true)
 	if err != nil {
 		ctx.ServerError("LFSUnlock", err)
 		return
@@ -263,9 +263,9 @@ func LFSFileGet(ctx *context.Context) {
 
 	ctx.Data["Title"] = oid
 	ctx.Data["PageIsSettingsLFS"] = true
-	meta, err := models.GetLFSMetaObjectByOid(ctx.Repo.Repository.ID, oid)
+	meta, err := git_model.GetLFSMetaObjectByOid(ctx.Repo.Repository.ID, oid)
 	if err != nil {
-		if err == models.ErrLFSObjectNotExist {
+		if err == git_model.ErrLFSObjectNotExist {
 			ctx.NotFound("LFSFileGet", nil)
 			return
 		}
@@ -357,7 +357,7 @@ func LFSDelete(ctx *context.Context) {
 		return
 	}
 
-	count, err := models.RemoveLFSMetaObjectByOid(ctx.Repo.Repository.ID, oid)
+	count, err := git_model.RemoveLFSMetaObjectByOid(ctx.Repo.Repository.ID, oid)
 	if err != nil {
 		ctx.ServerError("LFSDelete", err)
 		return
@@ -456,8 +456,8 @@ func LFSPointerFiles(ctx *context.Context) {
 				Size: pointerBlob.Size,
 			}
 
-			if _, err := models.GetLFSMetaObjectByOid(repo.ID, pointerBlob.Oid); err != nil {
-				if err != models.ErrLFSObjectNotExist {
+			if _, err := git_model.GetLFSMetaObjectByOid(repo.ID, pointerBlob.Oid); err != nil {
+				if err != git_model.ErrLFSObjectNotExist {
 					return err
 				}
 			} else {
@@ -474,12 +474,12 @@ func LFSPointerFiles(ctx *context.Context) {
 					// Can we fix?
 					// OK well that's "simple"
 					// - we need to check whether current user has access to a repo that has access to the file
-					result.Associatable, err = models.LFSObjectAccessible(ctx.Doer, pointerBlob.Oid)
+					result.Associatable, err = git_model.LFSObjectAccessible(ctx.Doer, pointerBlob.Oid)
 					if err != nil {
 						return err
 					}
 					if !result.Associatable {
-						associated, err := models.LFSObjectIsAssociated(pointerBlob.Oid)
+						associated, err := git_model.LFSObjectIsAssociated(pointerBlob.Oid)
 						if err != nil {
 							return err
 						}
@@ -532,7 +532,7 @@ func LFSAutoAssociate(ctx *context.Context) {
 		return
 	}
 	oids := ctx.FormStrings("oid")
-	metas := make([]*models.LFSMetaObject, len(oids))
+	metas := make([]*git_model.LFSMetaObject, len(oids))
 	for i, oid := range oids {
 		idx := strings.IndexRune(oid, ' ')
 		if idx < 0 || idx+1 > len(oid) {
@@ -540,7 +540,7 @@ func LFSAutoAssociate(ctx *context.Context) {
 			return
 		}
 		var err error
-		metas[i] = &models.LFSMetaObject{}
+		metas[i] = &git_model.LFSMetaObject{}
 		metas[i].Size, err = strconv.ParseInt(oid[idx+1:], 10, 64)
 		if err != nil {
 			ctx.ServerError("LFSAutoAssociate", fmt.Errorf("illegal oid input: %s %v", oid, err))
@@ -549,7 +549,7 @@ func LFSAutoAssociate(ctx *context.Context) {
 		metas[i].Oid = oid[:idx]
 		// metas[i].RepositoryID = ctx.Repo.Repository.ID
 	}
-	if err := models.LFSAutoAssociate(metas, ctx.Doer, ctx.Repo.Repository.ID); err != nil {
+	if err := git_model.LFSAutoAssociate(metas, ctx.Doer, ctx.Repo.Repository.ID); err != nil {
 		ctx.ServerError("LFSAutoAssociate", err)
 		return
 	}
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index d698f1c49a..c1a59ca8c0 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -18,6 +18,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/organization"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	pull_model "code.gitea.io/gitea/models/pull"
@@ -379,14 +380,14 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *models.Issue) *git.C
 
 	if len(compareInfo.Commits) != 0 {
 		sha := compareInfo.Commits[0].ID.String()
-		commitStatuses, _, err := models.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptions{})
+		commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptions{})
 		if err != nil {
 			ctx.ServerError("GetLatestCommitStatus", err)
 			return nil
 		}
 		if len(commitStatuses) != 0 {
 			ctx.Data["LatestCommitStatuses"] = commitStatuses
-			ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
+			ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)
 		}
 	}
 
@@ -440,14 +441,14 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
 			ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitRefName()), err)
 			return nil
 		}
-		commitStatuses, _, err := models.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{})
+		commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{})
 		if err != nil {
 			ctx.ServerError("GetLatestCommitStatus", err)
 			return nil
 		}
 		if len(commitStatuses) > 0 {
 			ctx.Data["LatestCommitStatuses"] = commitStatuses
-			ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
+			ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)
 		}
 
 		compareInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(),
@@ -530,14 +531,14 @@ func PrepareViewPullInfo(ctx *context.Context, issue *models.Issue) *git.Compare
 		return nil
 	}
 
-	commitStatuses, _, err := models.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{})
+	commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{})
 	if err != nil {
 		ctx.ServerError("GetLatestCommitStatus", err)
 		return nil
 	}
 	if len(commitStatuses) > 0 {
 		ctx.Data["LatestCommitStatuses"] = commitStatuses
-		ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(commitStatuses)
+		ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)
 	}
 
 	if pull.ProtectedBranch != nil && pull.ProtectedBranch.EnableStatusCheck {
@@ -629,7 +630,7 @@ func ViewPullCommits(ctx *context.Context) {
 	ctx.Data["Username"] = ctx.Repo.Owner.Name
 	ctx.Data["Reponame"] = ctx.Repo.Repository.Name
 
-	commits := models.ConvertFromGitCommit(prInfo.Commits, ctx.Repo.Repository)
+	commits := git_model.ConvertFromGitCommit(prInfo.Commits, ctx.Repo.Repository)
 	ctx.Data["Commits"] = commits
 	ctx.Data["CommitCount"] = len(commits)
 
diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting_protected_branch.go
index 6c2f3ce01d..c4cd3486aa 100644
--- a/routers/web/repo/setting_protected_branch.go
+++ b/routers/web/repo/setting_protected_branch.go
@@ -10,7 +10,7 @@ import (
 	"strings"
 	"time"
 
-	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
 	access_model "code.gitea.io/gitea/models/perm/access"
@@ -32,7 +32,7 @@ func ProtectedBranch(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("repo.settings")
 	ctx.Data["PageIsSettingsBranches"] = true
 
-	protectedBranches, err := models.GetProtectedBranches(ctx.Repo.Repository.ID)
+	protectedBranches, err := git_model.GetProtectedBranches(ctx.Repo.Repository.ID)
 	if err != nil {
 		ctx.ServerError("GetProtectedBranches", err)
 		return
@@ -111,7 +111,7 @@ func SettingsProtectedBranch(c *context.Context) {
 	c.Data["Title"] = c.Tr("repo.settings.protected_branch") + " - " + branch
 	c.Data["PageIsSettingsBranches"] = true
 
-	protectBranch, err := models.GetProtectedBranchBy(c, c.Repo.Repository.ID, branch)
+	protectBranch, err := git_model.GetProtectedBranchBy(c, c.Repo.Repository.ID, branch)
 	if err != nil {
 		if !git.IsErrBranchNotExist(err) {
 			c.ServerError("GetProtectBranchOfRepoByName", err)
@@ -121,7 +121,7 @@ func SettingsProtectedBranch(c *context.Context) {
 
 	if protectBranch == nil {
 		// No options found, create defaults.
-		protectBranch = &models.ProtectedBranch{
+		protectBranch = &git_model.ProtectedBranch{
 			BranchName: branch,
 		}
 	}
@@ -135,7 +135,7 @@ func SettingsProtectedBranch(c *context.Context) {
 	c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.WhitelistUserIDs), ",")
 	c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.MergeWhitelistUserIDs), ",")
 	c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(protectBranch.ApprovalsWhitelistUserIDs), ",")
-	contexts, _ := models.FindRepoRecentCommitStatusContexts(c.Repo.Repository.ID, 7*24*time.Hour) // Find last week status check contexts
+	contexts, _ := git_model.FindRepoRecentCommitStatusContexts(c.Repo.Repository.ID, 7*24*time.Hour) // Find last week status check contexts
 	for _, ctx := range protectBranch.StatusCheckContexts {
 		var found bool
 		for i := range contexts {
@@ -184,7 +184,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
 		return
 	}
 
-	protectBranch, err := models.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branch)
+	protectBranch, err := git_model.GetProtectedBranchBy(ctx, ctx.Repo.Repository.ID, branch)
 	if err != nil {
 		if !git.IsErrBranchNotExist(err) {
 			ctx.ServerError("GetProtectBranchOfRepoByName", err)
@@ -195,7 +195,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
 	if f.Protected {
 		if protectBranch == nil {
 			// No options found, create defaults.
-			protectBranch = &models.ProtectedBranch{
+			protectBranch = &git_model.ProtectedBranch{
 				RepoID:     ctx.Repo.Repository.ID,
 				BranchName: branch,
 			}
@@ -262,7 +262,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
 		protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns
 		protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch
 
-		err = models.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, models.WhitelistOptions{
+		err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
 			UserIDs:          whitelistUsers,
 			TeamIDs:          whitelistTeams,
 			MergeUserIDs:     mergeWhitelistUsers,
@@ -282,7 +282,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
 		ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, util.PathEscapeSegments(branch)))
 	} else {
 		if protectBranch != nil {
-			if err := models.DeleteProtectedBranch(ctx.Repo.Repository.ID, protectBranch.ID); err != nil {
+			if err := git_model.DeleteProtectedBranch(ctx.Repo.Repository.ID, protectBranch.ID); err != nil {
 				ctx.ServerError("DeleteProtectedBranch", err)
 				return
 			}
diff --git a/routers/web/repo/tag.go b/routers/web/repo/tag.go
index 3c47a0604f..f63a50782b 100644
--- a/routers/web/repo/tag.go
+++ b/routers/web/repo/tag.go
@@ -9,7 +9,7 @@ import (
 	"net/http"
 	"strings"
 
-	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
 	access_model "code.gitea.io/gitea/models/perm/access"
@@ -43,7 +43,7 @@ func NewProtectedTagPost(ctx *context.Context) {
 	repo := ctx.Repo.Repository
 	form := web.GetForm(ctx).(*forms.ProtectTagForm)
 
-	pt := &models.ProtectedTag{
+	pt := &git_model.ProtectedTag{
 		RepoID:      repo.ID,
 		NamePattern: strings.TrimSpace(form.NamePattern),
 	}
@@ -55,7 +55,7 @@ func NewProtectedTagPost(ctx *context.Context) {
 		pt.AllowlistTeamIDs, _ = base.StringsToInt64s(strings.Split(form.AllowlistTeams, ","))
 	}
 
-	if err := models.InsertProtectedTag(pt); err != nil {
+	if err := git_model.InsertProtectedTag(pt); err != nil {
 		ctx.ServerError("InsertProtectedTag", err)
 		return
 	}
@@ -108,7 +108,7 @@ func EditProtectedTagPost(ctx *context.Context) {
 	pt.AllowlistUserIDs, _ = base.StringsToInt64s(strings.Split(form.AllowlistUsers, ","))
 	pt.AllowlistTeamIDs, _ = base.StringsToInt64s(strings.Split(form.AllowlistTeams, ","))
 
-	if err := models.UpdateProtectedTag(pt); err != nil {
+	if err := git_model.UpdateProtectedTag(pt); err != nil {
 		ctx.ServerError("UpdateProtectedTag", err)
 		return
 	}
@@ -124,7 +124,7 @@ func DeleteProtectedTagPost(ctx *context.Context) {
 		return
 	}
 
-	if err := models.DeleteProtectedTag(pt); err != nil {
+	if err := git_model.DeleteProtectedTag(pt); err != nil {
 		ctx.ServerError("DeleteProtectedTag", err)
 		return
 	}
@@ -137,7 +137,7 @@ func setTagsContext(ctx *context.Context) error {
 	ctx.Data["Title"] = ctx.Tr("repo.settings")
 	ctx.Data["PageIsSettingsTags"] = true
 
-	protectedTags, err := models.GetProtectedTags(ctx.Repo.Repository.ID)
+	protectedTags, err := git_model.GetProtectedTags(ctx.Repo.Repository.ID)
 	if err != nil {
 		ctx.ServerError("GetProtectedTags", err)
 		return err
@@ -163,13 +163,13 @@ func setTagsContext(ctx *context.Context) error {
 	return nil
 }
 
-func selectProtectedTagByContext(ctx *context.Context) *models.ProtectedTag {
+func selectProtectedTagByContext(ctx *context.Context) *git_model.ProtectedTag {
 	id := ctx.FormInt64("id")
 	if id == 0 {
 		id = ctx.ParamsInt64(":id")
 	}
 
-	tag, err := models.GetProtectedTagByID(id)
+	tag, err := git_model.GetProtectedTagByID(id)
 	if err != nil {
 		ctx.ServerError("GetProtectedTagByID", err)
 		return nil
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index 48565cb62b..01bd2d8923 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -22,6 +22,7 @@ import (
 	"code.gitea.io/gitea/models"
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	unit_model "code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -294,8 +295,8 @@ func renderReadmeFile(ctx *context.Context, readmeFile *namedBlob, readmeTreelin
 	if isTextFile && setting.LFS.StartServer {
 		pointer, _ := lfs.ReadPointerFromBuffer(buf)
 		if pointer.IsValid() {
-			meta, err := models.GetLFSMetaObjectByOid(ctx.Repo.Repository.ID, pointer.Oid)
-			if err != nil && err != models.ErrLFSObjectNotExist {
+			meta, err := git_model.GetLFSMetaObjectByOid(ctx.Repo.Repository.ID, pointer.Oid)
+			if err != nil && err != git_model.ErrLFSObjectNotExist {
 				ctx.ServerError("GetLFSMetaObject", err)
 				return
 			}
@@ -417,8 +418,8 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
 	if isTextFile && setting.LFS.StartServer {
 		pointer, _ := lfs.ReadPointerFromBuffer(buf)
 		if pointer.IsValid() {
-			meta, err := models.GetLFSMetaObjectByOid(ctx.Repo.Repository.ID, pointer.Oid)
-			if err != nil && err != models.ErrLFSObjectNotExist {
+			meta, err := git_model.GetLFSMetaObjectByOid(ctx.Repo.Repository.ID, pointer.Oid)
+			if err != nil && err != git_model.ErrLFSObjectNotExist {
 				ctx.ServerError("GetLFSMetaObject", err)
 				return
 			}
@@ -466,7 +467,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
 	ctx.Data["IsTextSource"] = isTextFile || isDisplayingSource
 
 	// Check LFS Lock
-	lfsLock, err := models.GetTreePathLock(ctx.Repo.Repository.ID, ctx.Repo.TreePath)
+	lfsLock, err := git_model.GetTreePathLock(ctx.Repo.Repository.ID, ctx.Repo.TreePath)
 	ctx.Data["LFSLock"] = lfsLock
 	if err != nil {
 		ctx.ServerError("GetTreePathLock", err)
@@ -842,7 +843,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
 		verification := asymkey_model.ParseCommitWithSignature(latestCommit)
 
 		if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
-			return models.IsOwnerMemberCollaborator(ctx.Repo.Repository, user.ID)
+			return repo_model.IsOwnerMemberCollaborator(ctx.Repo.Repository, user.ID)
 		}, nil); err != nil {
 			ctx.ServerError("CalculateTrustStatus", err)
 			return nil
@@ -851,12 +852,12 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
 		ctx.Data["LatestCommitUser"] = user_model.ValidateCommitWithEmail(latestCommit)
 	}
 
-	statuses, _, err := models.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, ctx.Repo.Commit.ID.String(), db.ListOptions{})
+	statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, ctx.Repo.Commit.ID.String(), db.ListOptions{})
 	if err != nil {
 		log.Error("GetLatestCommitStatus: %v", err)
 	}
 
-	ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(statuses)
+	ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(statuses)
 	ctx.Data["LatestCommitStatuses"] = statuses
 
 	branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
@@ -991,12 +992,12 @@ func RenderUserCards(ctx *context.Context, total int, getter func(opts db.ListOp
 	if page <= 0 {
 		page = 1
 	}
-	pager := context.NewPagination(total, models.ItemsPerPage, page, 5)
+	pager := context.NewPagination(total, setting.ItemsPerPage, page, 5)
 	ctx.Data["Page"] = pager
 
 	items, err := getter(db.ListOptions{
 		Page:     pager.Paginater.Current(),
-		PageSize: models.ItemsPerPage,
+		PageSize: setting.ItemsPerPage,
 	})
 	if err != nil {
 		ctx.ServerError("getter", err)
@@ -1037,12 +1038,12 @@ func Forks(ctx *context.Context) {
 		page = 1
 	}
 
-	pager := context.NewPagination(ctx.Repo.Repository.NumForks, models.ItemsPerPage, page, 5)
+	pager := context.NewPagination(ctx.Repo.Repository.NumForks, setting.ItemsPerPage, page, 5)
 	ctx.Data["Page"] = pager
 
 	forks, err := repo_model.GetForks(ctx.Repo.Repository, db.ListOptions{
 		Page:     pager.Paginater.Current(),
-		PageSize: models.ItemsPerPage,
+		PageSize: setting.ItemsPerPage,
 	})
 	if err != nil {
 		ctx.ServerError("GetForks", err)
diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go
index f4aabbf480..e4134028aa 100644
--- a/routers/web/repo/wiki.go
+++ b/routers/web/repo/wiki.go
@@ -16,6 +16,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/charset"
@@ -350,7 +351,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
 		ctx.ServerError("CommitsByFileAndRangeNoFollow", err)
 		return nil, nil
 	}
-	ctx.Data["Commits"] = models.ConvertFromGitCommit(commitsHistory, ctx.Repo.Repository)
+	ctx.Data["Commits"] = git_model.ConvertFromGitCommit(commitsHistory, ctx.Repo.Repository)
 
 	pager := context.NewPagination(int(commitsCount), setting.Git.CommitsRangeSize, page, 5)
 	pager.SetDefaultParams(ctx)
diff --git a/services/asymkey/sign.go b/services/asymkey/sign.go
index 2431146f97..0f74cd4b2a 100644
--- a/services/asymkey/sign.go
+++ b/services/asymkey/sign.go
@@ -13,6 +13,7 @@ import (
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
@@ -310,14 +311,14 @@ Loop:
 				return false, "", nil, &ErrWontSign{twofa}
 			}
 		case approved:
-			protectedBranch, err := models.GetProtectedBranchBy(ctx, repo.ID, pr.BaseBranch)
+			protectedBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, pr.BaseBranch)
 			if err != nil {
 				return false, "", nil, err
 			}
 			if protectedBranch == nil {
 				return false, "", nil, &ErrWontSign{approved}
 			}
-			if protectedBranch.GetGrantedApprovalsCount(ctx, pr) < 1 {
+			if models.GetGrantedApprovalsCount(ctx, protectedBranch, pr) < 1 {
 				return false, "", nil, &ErrWontSign{approved}
 			}
 		case baseSigned:
diff --git a/services/cron/tasks_basic.go b/services/cron/tasks_basic.go
index 39fda34ea2..0d7ef4af03 100644
--- a/services/cron/tasks_basic.go
+++ b/services/cron/tasks_basic.go
@@ -9,6 +9,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/models/webhook"
 	"code.gitea.io/gitea/modules/setting"
@@ -109,7 +110,7 @@ func registerDeletedBranchesCleanup() {
 		OlderThan: 24 * time.Hour,
 	}, func(ctx context.Context, _ *user_model.User, config Config) error {
 		realConfig := config.(*OlderThanConfig)
-		models.RemoveOldDeletedBranches(ctx, realConfig.OlderThan)
+		git_model.RemoveOldDeletedBranches(ctx, realConfig.OlderThan)
 		return nil
 	})
 }
diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go
index d611ff513e..e56c2de8fa 100644
--- a/services/gitdiff/gitdiff.go
+++ b/services/gitdiff/gitdiff.go
@@ -22,6 +22,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	pull_model "code.gitea.io/gitea/models/pull"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/analyze"
@@ -1219,7 +1220,7 @@ func parseHunks(curFile *DiffFile, maxLines, maxLineCharacters int, input *bufio
 		} else if curFileLFSPrefix && strings.HasPrefix(line[1:], lfs.MetaFileOidPrefix) {
 			oid := strings.TrimPrefix(line[1:], lfs.MetaFileOidPrefix)
 			if len(oid) == 64 {
-				m := &models.LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}}
+				m := &git_model.LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}}
 				count, err := db.CountByBean(db.DefaultContext, m)
 
 				if err == nil && count > 0 {
diff --git a/services/lfs/locks.go b/services/lfs/locks.go
index 0299452205..e87589d124 100644
--- a/services/lfs/locks.go
+++ b/services/lfs/locks.go
@@ -9,7 +9,7 @@ import (
 	"strconv"
 	"strings"
 
-	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/convert"
@@ -20,9 +20,9 @@ import (
 	api "code.gitea.io/gitea/modules/structs"
 )
 
-func handleLockListOut(ctx *context.Context, repo *repo_model.Repository, lock *models.LFSLock, err error) {
+func handleLockListOut(ctx *context.Context, repo *repo_model.Repository, lock *git_model.LFSLock, err error) {
 	if err != nil {
-		if models.IsErrLFSLockNotExist(err) {
+		if git_model.IsErrLFSLockNotExist(err) {
 			ctx.JSON(http.StatusOK, api.LFSLockList{
 				Locks: []*api.LFSLock{},
 			})
@@ -88,8 +88,8 @@ func GetListLockHandler(ctx *context.Context) {
 			})
 			return
 		}
-		lock, err := models.GetLFSLockByID(ctx, v)
-		if err != nil && !models.IsErrLFSLockNotExist(err) {
+		lock, err := git_model.GetLFSLockByID(ctx, v)
+		if err != nil && !git_model.IsErrLFSLockNotExist(err) {
 			log.Error("Unable to get lock with ID[%s]: Error: %v", v, err)
 		}
 		handleLockListOut(ctx, repository, lock, err)
@@ -98,8 +98,8 @@ func GetListLockHandler(ctx *context.Context) {
 
 	path := ctx.FormString("path")
 	if path != "" { // Case where we request a specific id
-		lock, err := models.GetLFSLock(ctx, repository, path)
-		if err != nil && !models.IsErrLFSLockNotExist(err) {
+		lock, err := git_model.GetLFSLock(ctx, repository, path)
+		if err != nil && !git_model.IsErrLFSLockNotExist(err) {
 			log.Error("Unable to get lock for repository %-v with path %s: Error: %v", repository, path, err)
 		}
 		handleLockListOut(ctx, repository, lock, err)
@@ -107,7 +107,7 @@ func GetListLockHandler(ctx *context.Context) {
 	}
 
 	// If no query params path or id
-	lockList, err := models.GetLFSLockByRepoID(repository.ID, cursor, limit)
+	lockList, err := git_model.GetLFSLockByRepoID(repository.ID, cursor, limit)
 	if err != nil {
 		log.Error("Unable to list locks for repository ID[%d]: Error: %v", repository.ID, err)
 		ctx.JSON(http.StatusInternalServerError, api.LFSLockError{
@@ -168,19 +168,19 @@ func PostLockHandler(ctx *context.Context) {
 		return
 	}
 
-	lock, err := models.CreateLFSLock(repository, &models.LFSLock{
+	lock, err := git_model.CreateLFSLock(repository, &git_model.LFSLock{
 		Path:    req.Path,
 		OwnerID: ctx.Doer.ID,
 	})
 	if err != nil {
-		if models.IsErrLFSLockAlreadyExist(err) {
+		if git_model.IsErrLFSLockAlreadyExist(err) {
 			ctx.JSON(http.StatusConflict, api.LFSLockError{
 				Lock:    convert.ToLFSLock(lock),
 				Message: "already created lock",
 			})
 			return
 		}
-		if models.IsErrLFSUnauthorizedAction(err) {
+		if git_model.IsErrLFSUnauthorizedAction(err) {
 			ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
 			ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
 				Message: "You must have push access to create locks : " + err.Error(),
@@ -234,7 +234,7 @@ func VerifyLockHandler(ctx *context.Context) {
 	} else if limit < 0 {
 		limit = 0
 	}
-	lockList, err := models.GetLFSLockByRepoID(repository.ID, cursor, limit)
+	lockList, err := git_model.GetLFSLockByRepoID(repository.ID, cursor, limit)
 	if err != nil {
 		log.Error("Unable to list locks for repository ID[%d]: Error: %v", repository.ID, err)
 		ctx.JSON(http.StatusInternalServerError, api.LFSLockError{
@@ -301,9 +301,9 @@ func UnLockHandler(ctx *context.Context) {
 		return
 	}
 
-	lock, err := models.DeleteLFSLockByID(ctx.ParamsInt64("lid"), repository, ctx.Doer, req.Force)
+	lock, err := git_model.DeleteLFSLockByID(ctx.ParamsInt64("lid"), repository, ctx.Doer, req.Force)
 	if err != nil {
-		if models.IsErrLFSUnauthorizedAction(err) {
+		if git_model.IsErrLFSUnauthorizedAction(err) {
 			ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
 			ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
 				Message: "You must have push access to delete locks : " + err.Error(),
diff --git a/services/lfs/server.go b/services/lfs/server.go
index f5c57a7dab..b868db39db 100644
--- a/services/lfs/server.go
+++ b/services/lfs/server.go
@@ -18,7 +18,7 @@ import (
 	"strconv"
 	"strings"
 
-	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/perm"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
@@ -197,8 +197,8 @@ func BatchHandler(ctx *context.Context) {
 			return
 		}
 
-		meta, err := models.GetLFSMetaObjectByOid(repository.ID, p.Oid)
-		if err != nil && err != models.ErrLFSObjectNotExist {
+		meta, err := git_model.GetLFSMetaObjectByOid(repository.ID, p.Oid)
+		if err != nil && err != git_model.ErrLFSObjectNotExist {
 			log.Error("Unable to get LFS MetaObject [%s] for %s/%s. Error: %v", p.Oid, rc.User, rc.Repo, err)
 			writeStatus(ctx, http.StatusInternalServerError)
 			return
@@ -223,14 +223,14 @@ func BatchHandler(ctx *context.Context) {
 			}
 
 			if exists && meta == nil {
-				accessible, err := models.LFSObjectAccessible(ctx.Doer, p.Oid)
+				accessible, err := git_model.LFSObjectAccessible(ctx.Doer, p.Oid)
 				if err != nil {
 					log.Error("Unable to check if LFS MetaObject [%s] is accessible. Error: %v", p.Oid, err)
 					writeStatus(ctx, http.StatusInternalServerError)
 					return
 				}
 				if accessible {
-					_, err := models.NewLFSMetaObject(&models.LFSMetaObject{Pointer: p, RepositoryID: repository.ID})
+					_, err := git_model.NewLFSMetaObject(&git_model.LFSMetaObject{Pointer: p, RepositoryID: repository.ID})
 					if err != nil {
 						log.Error("Unable to create LFS MetaObject [%s] for %s/%s. Error: %v", p.Oid, rc.User, rc.Repo, err)
 						writeStatus(ctx, http.StatusInternalServerError)
@@ -297,7 +297,7 @@ func UploadHandler(ctx *context.Context) {
 
 	uploadOrVerify := func() error {
 		if exists {
-			accessible, err := models.LFSObjectAccessible(ctx.Doer, p.Oid)
+			accessible, err := git_model.LFSObjectAccessible(ctx.Doer, p.Oid)
 			if err != nil {
 				log.Error("Unable to check if LFS MetaObject [%s] is accessible. Error: %v", p.Oid, err)
 				return err
@@ -323,7 +323,7 @@ func UploadHandler(ctx *context.Context) {
 			log.Error("Error putting LFS MetaObject [%s] into content store. Error: %v", p.Oid, err)
 			return err
 		}
-		_, err := models.NewLFSMetaObject(&models.LFSMetaObject{Pointer: p, RepositoryID: repository.ID})
+		_, err := git_model.NewLFSMetaObject(&git_model.LFSMetaObject{Pointer: p, RepositoryID: repository.ID})
 		return err
 	}
 
@@ -335,7 +335,7 @@ func UploadHandler(ctx *context.Context) {
 		} else {
 			writeStatus(ctx, http.StatusInternalServerError)
 		}
-		if _, err = models.RemoveLFSMetaObjectByOid(repository.ID, p.Oid); err != nil {
+		if _, err = git_model.RemoveLFSMetaObjectByOid(repository.ID, p.Oid); err != nil {
 			log.Error("Error whilst removing metaobject for LFS OID[%s]: %v", p.Oid, err)
 		}
 		return
@@ -386,7 +386,7 @@ func getRequestContext(ctx *context.Context) *requestContext {
 	}
 }
 
-func getAuthenticatedMeta(ctx *context.Context, rc *requestContext, p lfs_module.Pointer, requireWrite bool) *models.LFSMetaObject {
+func getAuthenticatedMeta(ctx *context.Context, rc *requestContext, p lfs_module.Pointer, requireWrite bool) *git_model.LFSMetaObject {
 	if !p.IsValid() {
 		log.Info("Attempt to access invalid LFS OID[%s] in %s/%s", p.Oid, rc.User, rc.Repo)
 		writeStatusMessage(ctx, http.StatusUnprocessableEntity, "Oid or size are invalid")
@@ -398,7 +398,7 @@ func getAuthenticatedMeta(ctx *context.Context, rc *requestContext, p lfs_module
 		return nil
 	}
 
-	meta, err := models.GetLFSMetaObjectByOid(repository.ID, p.Oid)
+	meta, err := git_model.GetLFSMetaObjectByOid(repository.ID, p.Oid)
 	if err != nil {
 		log.Error("Unable to get LFS OID[%s] Error: %v", p.Oid, err)
 		writeStatus(ctx, http.StatusNotFound)
diff --git a/services/pull/commit_status.go b/services/pull/commit_status.go
index 539b3c8520..c0894c6c98 100644
--- a/services/pull/commit_status.go
+++ b/services/pull/commit_status.go
@@ -10,6 +10,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/structs"
 
@@ -17,9 +18,9 @@ import (
 )
 
 // MergeRequiredContextsCommitStatus returns a commit status state for given required contexts
-func MergeRequiredContextsCommitStatus(commitStatuses []*models.CommitStatus, requiredContexts []string) structs.CommitStatusState {
+func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus, requiredContexts []string) structs.CommitStatusState {
 	if len(requiredContexts) == 0 {
-		status := models.CalcCommitStatus(commitStatuses)
+		status := git_model.CalcCommitStatus(commitStatuses)
 		if status != nil {
 			return status.State
 		}
@@ -38,7 +39,7 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*models.CommitStatus, re
 
 		if targetStatus == "" {
 			targetStatus = structs.CommitStatusPending
-			commitStatuses = append(commitStatuses, &models.CommitStatus{
+			commitStatuses = append(commitStatuses, &git_model.CommitStatus{
 				State:       targetStatus,
 				Context:     ctx,
 				Description: "Pending",
@@ -52,10 +53,10 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*models.CommitStatus, re
 }
 
 // IsCommitStatusContextSuccess returns true if all required status check contexts succeed.
-func IsCommitStatusContextSuccess(commitStatuses []*models.CommitStatus, requiredContexts []string) bool {
+func IsCommitStatusContextSuccess(commitStatuses []*git_model.CommitStatus, requiredContexts []string) bool {
 	// If no specific context is required, require that last commit status is a success
 	if len(requiredContexts) == 0 {
-		status := models.CalcCommitStatus(commitStatuses)
+		status := git_model.CalcCommitStatus(commitStatuses)
 		if status == nil || status.State != structs.CommitStatusSuccess {
 			return false
 		}
@@ -132,7 +133,7 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *models.PullRequest
 		return "", errors.Wrap(err, "LoadBaseRepo")
 	}
 
-	commitStatuses, _, err := models.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptions{})
+	commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptions{})
 	if err != nil {
 		return "", errors.Wrap(err, "GetLatestCommitStatus")
 	}
diff --git a/services/pull/lfs.go b/services/pull/lfs.go
index fada9b6121..490a904584 100644
--- a/services/pull/lfs.go
+++ b/services/pull/lfs.go
@@ -13,6 +13,7 @@ import (
 	"sync"
 
 	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/modules/git/pipeline"
 	"code.gitea.io/gitea/modules/lfs"
 	"code.gitea.io/gitea/modules/log"
@@ -115,8 +116,8 @@ func createLFSMetaObjectsFromCatFileBatch(catFileBatchReader *io.PipeReader, wg
 		}
 
 		// Then we need to check that this pointer is in the db
-		if _, err := models.GetLFSMetaObjectByOid(pr.HeadRepo.ID, pointer.Oid); err != nil {
-			if err == models.ErrLFSObjectNotExist {
+		if _, err := git_model.GetLFSMetaObjectByOid(pr.HeadRepo.ID, pointer.Oid); err != nil {
+			if err == git_model.ErrLFSObjectNotExist {
 				log.Warn("During merge of: %d in %-v, there is a pointer to LFS Oid: %s which although present in the LFS store is not associated with the head repo %-v", pr.Index, pr.BaseRepo, pointer.Oid, pr.HeadRepo)
 				continue
 			}
@@ -126,9 +127,9 @@ func createLFSMetaObjectsFromCatFileBatch(catFileBatchReader *io.PipeReader, wg
 		// OK we have a pointer that is associated with the head repo
 		// and is actually a file in the LFS
 		// Therefore it should be associated with the base repo
-		meta := &models.LFSMetaObject{Pointer: pointer}
+		meta := &git_model.LFSMetaObject{Pointer: pointer}
 		meta.RepositoryID = pr.BaseRepoID
-		if _, err := models.NewLFSMetaObject(meta); err != nil {
+		if _, err := git_model.NewLFSMetaObject(meta); err != nil {
 			_ = catFileBatchReader.CloseWithError(err)
 			break
 		}
diff --git a/services/pull/merge.go b/services/pull/merge.go
index 76798d73ed..eef1d17b64 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -19,6 +19,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	pull_model "code.gitea.io/gitea/models/pull"
 	repo_model "code.gitea.io/gitea/models/repo"
@@ -756,7 +757,7 @@ func IsUserAllowedToMerge(ctx context.Context, pr *models.PullRequest, p access_
 		return false, err
 	}
 
-	if (p.CanWrite(unit.TypeCode) && pr.ProtectedBranch == nil) || (pr.ProtectedBranch != nil && models.IsUserMergeWhitelisted(ctx, pr.ProtectedBranch, user.ID, p)) {
+	if (p.CanWrite(unit.TypeCode) && pr.ProtectedBranch == nil) || (pr.ProtectedBranch != nil && git_model.IsUserMergeWhitelisted(ctx, pr.ProtectedBranch, user.ID, p)) {
 		return true, nil
 	}
 
@@ -786,23 +787,23 @@ func CheckPullBranchProtections(ctx context.Context, pr *models.PullRequest, ski
 		}
 	}
 
-	if !pr.ProtectedBranch.HasEnoughApprovals(ctx, pr) {
+	if !models.HasEnoughApprovals(ctx, pr.ProtectedBranch, pr) {
 		return models.ErrDisallowedToMerge{
 			Reason: "Does not have enough approvals",
 		}
 	}
-	if pr.ProtectedBranch.MergeBlockedByRejectedReview(ctx, pr) {
+	if models.MergeBlockedByRejectedReview(ctx, pr.ProtectedBranch, pr) {
 		return models.ErrDisallowedToMerge{
 			Reason: "There are requested changes",
 		}
 	}
-	if pr.ProtectedBranch.MergeBlockedByOfficialReviewRequests(ctx, pr) {
+	if models.MergeBlockedByOfficialReviewRequests(ctx, pr.ProtectedBranch, pr) {
 		return models.ErrDisallowedToMerge{
 			Reason: "There are official review requests",
 		}
 	}
 
-	if pr.ProtectedBranch.MergeBlockedByOutdatedBranch(pr) {
+	if models.MergeBlockedByOutdatedBranch(pr.ProtectedBranch, pr) {
 		return models.ErrDisallowedToMerge{
 			Reason: "The head branch is behind the base branch",
 		}
@@ -812,7 +813,7 @@ func CheckPullBranchProtections(ctx context.Context, pr *models.PullRequest, ski
 		return nil
 	}
 
-	if pr.ProtectedBranch.MergeBlockedByProtectedFiles(pr) {
+	if pr.ProtectedBranch.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) {
 		return models.ErrDisallowedToMerge{
 			Reason: "Changed protected files",
 		}
diff --git a/services/pull/pull.go b/services/pull/pull.go
index efac3f019e..736520fda2 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -16,6 +16,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
@@ -735,13 +736,13 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *models.PullRequest) s
 }
 
 // GetIssuesLastCommitStatus returns a map of issue ID to the most recent commit's latest status
-func GetIssuesLastCommitStatus(ctx context.Context, issues models.IssueList) (map[int64]*models.CommitStatus, error) {
+func GetIssuesLastCommitStatus(ctx context.Context, issues models.IssueList) (map[int64]*git_model.CommitStatus, error) {
 	_, lastStatus, err := GetIssuesAllCommitStatus(ctx, issues)
 	return lastStatus, err
 }
 
 // GetIssuesAllCommitStatus returns a map of issue ID to a list of all statuses for the most recent commit as well as a map of issue ID to only the commit's latest status
-func GetIssuesAllCommitStatus(ctx context.Context, issues models.IssueList) (map[int64][]*models.CommitStatus, map[int64]*models.CommitStatus, error) {
+func GetIssuesAllCommitStatus(ctx context.Context, issues models.IssueList) (map[int64][]*git_model.CommitStatus, map[int64]*git_model.CommitStatus, error) {
 	if err := issues.LoadPullRequests(); err != nil {
 		return nil, nil, err
 	}
@@ -751,8 +752,8 @@ func GetIssuesAllCommitStatus(ctx context.Context, issues models.IssueList) (map
 
 	var (
 		gitRepos = make(map[int64]*git.Repository)
-		res      = make(map[int64][]*models.CommitStatus)
-		lastRes  = make(map[int64]*models.CommitStatus)
+		res      = make(map[int64][]*git_model.CommitStatus)
+		lastRes  = make(map[int64]*git_model.CommitStatus)
 		err      error
 	)
 	defer func() {
@@ -787,14 +788,14 @@ func GetIssuesAllCommitStatus(ctx context.Context, issues models.IssueList) (map
 }
 
 // getAllCommitStatus get pr's commit statuses.
-func getAllCommitStatus(gitRepo *git.Repository, pr *models.PullRequest) (statuses []*models.CommitStatus, lastStatus *models.CommitStatus, err error) {
+func getAllCommitStatus(gitRepo *git.Repository, pr *models.PullRequest) (statuses []*git_model.CommitStatus, lastStatus *git_model.CommitStatus, err error) {
 	sha, shaErr := gitRepo.GetRefCommitID(pr.GetGitRefName())
 	if shaErr != nil {
 		return nil, nil, shaErr
 	}
 
-	statuses, _, err = models.GetLatestCommitStatus(db.DefaultContext, pr.BaseRepo.ID, sha, db.ListOptions{})
-	lastStatus = models.CalcCommitStatus(statuses)
+	statuses, _, err = git_model.GetLatestCommitStatus(db.DefaultContext, pr.BaseRepo.ID, sha, db.ListOptions{})
+	lastStatus = git_model.CalcCommitStatus(statuses)
 	return statuses, lastStatus, err
 }
 
diff --git a/services/release/release.go b/services/release/release.go
index b2cbceb12d..a3d7d47201 100644
--- a/services/release/release.go
+++ b/services/release/release.go
@@ -12,6 +12,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
@@ -32,11 +33,11 @@ func createTag(gitRepo *git.Repository, rel *models.Release, msg string) (bool,
 				return false, err
 			}
 
-			protectedTags, err := models.GetProtectedTags(rel.Repo.ID)
+			protectedTags, err := git_model.GetProtectedTags(rel.Repo.ID)
 			if err != nil {
 				return false, fmt.Errorf("GetProtectedTags: %v", err)
 			}
-			isAllowed, err := models.IsUserAllowedToControlTag(protectedTags, rel.TagName, rel.PublisherID)
+			isAllowed, err := git_model.IsUserAllowedToControlTag(protectedTags, rel.TagName, rel.PublisherID)
 			if err != nil {
 				return false, err
 			}
diff --git a/services/repository/branch.go b/services/repository/branch.go
index 88fc67fa8c..6c9a5d76ca 100644
--- a/services/repository/branch.go
+++ b/services/repository/branch.go
@@ -11,6 +11,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
@@ -118,7 +119,7 @@ func RenameBranch(repo *repo_model.Repository, doer *user_model.User, gitRepo *g
 		return "from_not_exist", nil
 	}
 
-	if err := models.RenameBranch(repo, from, to, func(isDefault bool) error {
+	if err := git_model.RenameBranch(repo, from, to, func(isDefault bool) error {
 		err2 := gitRepo.RenameBranch(from, to)
 		if err2 != nil {
 			return err2
@@ -158,7 +159,7 @@ func DeleteBranch(doer *user_model.User, repo *repo_model.Repository, gitRepo *g
 		return ErrBranchIsDefault
 	}
 
-	isProtected, err := models.IsProtectedBranch(repo.ID, branchName)
+	isProtected, err := git_model.IsProtectedBranch(repo.ID, branchName)
 	if err != nil {
 		return err
 	}
@@ -196,7 +197,7 @@ func DeleteBranch(doer *user_model.User, repo *repo_model.Repository, gitRepo *g
 		log.Error("Update: %v", err)
 	}
 
-	if err := models.AddDeletedBranch(repo.ID, branchName, commit.ID.String(), doer.ID); err != nil {
+	if err := git_model.AddDeletedBranch(repo.ID, branchName, commit.ID.String(), doer.ID); err != nil {
 		log.Warn("AddDeletedBranch: %v", err)
 	}
 
diff --git a/services/repository/files/commit.go b/services/repository/files/commit.go
index 6ecabb4020..ecd981b5ff 100644
--- a/services/repository/files/commit.go
+++ b/services/repository/files/commit.go
@@ -8,8 +8,8 @@ import (
 	"context"
 	"fmt"
 
-	"code.gitea.io/gitea/models"
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
@@ -20,7 +20,7 @@ import (
 // CreateCommitStatus creates a new CommitStatus given a bunch of parameters
 // NOTE: All text-values will be trimmed from whitespaces.
 // Requires: Repo, Creator, SHA
-func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creator *user_model.User, sha string, status *models.CommitStatus) error {
+func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creator *user_model.User, sha string, status *git_model.CommitStatus) error {
 	repoPath := repo.RepoPath()
 
 	// confirm that commit is exist
@@ -36,7 +36,7 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
 	}
 	gitRepo.Close()
 
-	if err := models.NewCommitStatus(models.NewCommitStatusOptions{
+	if err := git_model.NewCommitStatus(git_model.NewCommitStatusOptions{
 		Repo:         repo,
 		Creator:      creator,
 		SHA:          sha,
diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go
index 73464f31f3..d55d793f28 100644
--- a/services/repository/files/patch.go
+++ b/services/repository/files/patch.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
@@ -66,7 +67,7 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode
 			return err
 		}
 	} else {
-		protectedBranch, err := models.GetProtectedBranchBy(ctx, repo.ID, opts.OldBranch)
+		protectedBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, opts.OldBranch)
 		if err != nil {
 			return err
 		}
diff --git a/services/repository/files/update.go b/services/repository/files/update.go
index a093ee5da7..4615a9153a 100644
--- a/services/repository/files/update.go
+++ b/services/repository/files/update.go
@@ -13,6 +13,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/charset"
@@ -75,8 +76,8 @@ func detectEncodingAndBOM(entry *git.TreeEntry, repo *repo_model.Repository) (st
 	if setting.LFS.StartServer {
 		pointer, _ := lfs.ReadPointerFromBuffer(buf)
 		if pointer.IsValid() {
-			meta, err := models.GetLFSMetaObjectByOid(repo.ID, pointer.Oid)
-			if err != nil && err != models.ErrLFSObjectNotExist {
+			meta, err := git_model.GetLFSMetaObjectByOid(repo.ID, pointer.Oid)
+			if err != nil && err != git_model.ErrLFSObjectNotExist {
 				// return default
 				return "UTF-8", false
 			}
@@ -364,7 +365,7 @@ func CreateOrUpdateRepoFile(ctx context.Context, repo *repo_model.Repository, do
 	}
 	// Reset the opts.Content to our adjusted content to ensure that LFS gets the correct content
 	opts.Content = content
-	var lfsMetaObject *models.LFSMetaObject
+	var lfsMetaObject *git_model.LFSMetaObject
 
 	if setting.LFS.StartServer && hasOldBranch {
 		// Check there is no way this can return multiple infos
@@ -383,7 +384,7 @@ func CreateOrUpdateRepoFile(ctx context.Context, repo *repo_model.Repository, do
 			if err != nil {
 				return nil, err
 			}
-			lfsMetaObject = &models.LFSMetaObject{Pointer: pointer, RepositoryID: repo.ID}
+			lfsMetaObject = &git_model.LFSMetaObject{Pointer: pointer, RepositoryID: repo.ID}
 			content = pointer.StringContent()
 		}
 	}
@@ -423,7 +424,7 @@ func CreateOrUpdateRepoFile(ctx context.Context, repo *repo_model.Repository, do
 
 	if lfsMetaObject != nil {
 		// We have an LFS object - create it
-		lfsMetaObject, err = models.NewLFSMetaObject(lfsMetaObject)
+		lfsMetaObject, err = git_model.NewLFSMetaObject(lfsMetaObject)
 		if err != nil {
 			return nil, err
 		}
@@ -434,7 +435,7 @@ func CreateOrUpdateRepoFile(ctx context.Context, repo *repo_model.Repository, do
 		}
 		if !exist {
 			if err := contentStore.Put(lfsMetaObject.Pointer, strings.NewReader(opts.Content)); err != nil {
-				if _, err2 := models.RemoveLFSMetaObjectByOid(repo.ID, lfsMetaObject.Oid); err2 != nil {
+				if _, err2 := git_model.RemoveLFSMetaObjectByOid(repo.ID, lfsMetaObject.Oid); err2 != nil {
 					return nil, fmt.Errorf("Error whilst removing failed inserted LFS object %s: %v (Prev Error: %v)", lfsMetaObject.Oid, err2, err)
 				}
 				return nil, err
@@ -462,7 +463,7 @@ func CreateOrUpdateRepoFile(ctx context.Context, repo *repo_model.Repository, do
 
 // VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch
 func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, branchName, treePath string) error {
-	protectedBranch, err := models.GetProtectedBranchBy(ctx, repo.ID, branchName)
+	protectedBranch, err := git_model.GetProtectedBranchBy(ctx, repo.ID, branchName)
 	if err != nil {
 		return err
 	}
diff --git a/services/repository/files/upload.go b/services/repository/files/upload.go
index ff0448fc87..ffc1f4efe9 100644
--- a/services/repository/files/upload.go
+++ b/services/repository/files/upload.go
@@ -12,6 +12,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
@@ -32,7 +33,7 @@ type UploadRepoFileOptions struct {
 
 type uploadInfo struct {
 	upload        *models.Upload
-	lfsMetaObject *models.LFSMetaObject
+	lfsMetaObject *git_model.LFSMetaObject
 }
 
 func cleanUpAfterFailure(infos *[]uploadInfo, t *TemporaryUploadRepository, original error) error {
@@ -41,7 +42,7 @@ func cleanUpAfterFailure(infos *[]uploadInfo, t *TemporaryUploadRepository, orig
 			continue
 		}
 		if !info.lfsMetaObject.Existing {
-			if _, err := models.RemoveLFSMetaObjectByOid(t.repo.ID, info.lfsMetaObject.Oid); err != nil {
+			if _, err := git_model.RemoveLFSMetaObjectByOid(t.repo.ID, info.lfsMetaObject.Oid); err != nil {
 				original = fmt.Errorf("%v, %v", original, err)
 			}
 		}
@@ -65,7 +66,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
 	for i, upload := range uploads {
 		// Check file is not lfs locked, will return nil if lock setting not enabled
 		filepath := path.Join(opts.TreePath, upload.Name)
-		lfsLock, err := models.GetTreePathLock(repo.ID, filepath)
+		lfsLock, err := git_model.GetTreePathLock(repo.ID, filepath)
 		if err != nil {
 			return err
 		}
@@ -74,7 +75,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
 			if err != nil {
 				return err
 			}
-			return models.ErrLFSFileLocked{RepoID: repo.ID, Path: filepath, UserName: u.Name}
+			return git_model.ErrLFSFileLocked{RepoID: repo.ID, Path: filepath, UserName: u.Name}
 		}
 
 		names[i] = upload.Name
@@ -133,7 +134,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
 		if infos[i].lfsMetaObject == nil {
 			continue
 		}
-		infos[i].lfsMetaObject, err = models.NewLFSMetaObject(infos[i].lfsMetaObject)
+		infos[i].lfsMetaObject, err = git_model.NewLFSMetaObject(infos[i].lfsMetaObject)
 		if err != nil {
 			// OK Now we need to cleanup
 			return cleanUpAfterFailure(&infos, t, err)
@@ -175,7 +176,7 @@ func copyUploadedLFSFileIntoRepository(info *uploadInfo, filename2attribute2info
 			return err
 		}
 
-		info.lfsMetaObject = &models.LFSMetaObject{Pointer: pointer, RepositoryID: t.repo.ID}
+		info.lfsMetaObject = &git_model.LFSMetaObject{Pointer: pointer, RepositoryID: t.repo.ID}
 
 		if objectHash, err = t.HashObject(strings.NewReader(pointer.StringContent())); err != nil {
 			return err
diff --git a/services/repository/fork.go b/services/repository/fork.go
index f4888cba1d..b274585ed5 100644
--- a/services/repository/fork.go
+++ b/services/repository/fork.go
@@ -12,6 +12,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
@@ -101,7 +102,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
 		}
 
 		// copy lfs files failure should not be ignored
-		if err = models.CopyLFS(txCtx, repo, opts.BaseRepo); err != nil {
+		if err = git_model.CopyLFS(txCtx, repo, opts.BaseRepo); err != nil {
 			return err
 		}
 
diff --git a/services/repository/push.go b/services/repository/push.go
index b6741c5ab4..65ac7b660c 100644
--- a/services/repository/push.go
+++ b/services/repository/push.go
@@ -13,6 +13,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/cache"
@@ -252,7 +253,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
 
 				notification.NotifyPushCommits(pusher, repo, opts, commits)
 
-				if err = models.RemoveDeletedBranchByName(repo.ID, branch); err != nil {
+				if err = git_model.RemoveDeletedBranchByName(repo.ID, branch); err != nil {
 					log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err)
 				}