diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go
index 1829ad00ba..9f1d9057f9 100644
--- a/cmd/migrate_storage.go
+++ b/cmd/migrate_storage.go
@@ -12,6 +12,7 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/migrations"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/storage"
@@ -79,7 +80,7 @@ var CmdMigrateStorage = cli.Command{
 }
 
 func migrateAttachments(dstStorage storage.ObjectStorage) error {
-	return models.IterateAttachment(func(attach *models.Attachment) error {
+	return repo_model.IterateAttachment(func(attach *repo_model.Attachment) error {
 		_, err := storage.Copy(dstStorage, attach.RelativePath(), storage.Attachments, attach.RelativePath())
 		return err
 	})
diff --git a/integrations/attachment_test.go b/integrations/attachment_test.go
index 052f27605a..481104d73f 100644
--- a/integrations/attachment_test.go
+++ b/integrations/attachment_test.go
@@ -14,7 +14,7 @@ import (
 	"strings"
 	"testing"
 
-	"code.gitea.io/gitea/models"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/storage"
 	"code.gitea.io/gitea/modules/test"
 
@@ -122,7 +122,7 @@ func TestGetAttachment(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			//Write empty file to be available for response
 			if tc.createFile {
-				_, err := storage.Attachments.Save(models.AttachmentRelativePath(tc.uuid), strings.NewReader("hello world"), -1)
+				_, err := storage.Attachments.Save(repo_model.AttachmentRelativePath(tc.uuid), strings.NewReader("hello world"), -1)
 				assert.NoError(t, err)
 			}
 			//Actual test
diff --git a/models/error.go b/models/error.go
index 7d9b2ae65b..862b5633ec 100644
--- a/models/error.go
+++ b/models/error.go
@@ -1679,29 +1679,6 @@ func (err ErrMilestoneNotExist) Error() string {
 	return fmt.Sprintf("milestone does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID)
 }
 
-//    _____   __    __                .__                           __
-//   /  _  \_/  |__/  |______    ____ |  |__   _____   ____   _____/  |_
-//  /  /_\  \   __\   __\__  \ _/ ___\|  |  \ /     \_/ __ \ /    \   __\
-// /    |    \  |  |  |  / __ \\  \___|   Y  \  Y Y  \  ___/|   |  \  |
-// \____|__  /__|  |__| (____  /\___  >___|  /__|_|  /\___  >___|  /__|
-//         \/                \/     \/     \/      \/     \/     \/
-
-// ErrAttachmentNotExist represents a "AttachmentNotExist" kind of error.
-type ErrAttachmentNotExist struct {
-	ID   int64
-	UUID string
-}
-
-// IsErrAttachmentNotExist checks if an error is a ErrAttachmentNotExist.
-func IsErrAttachmentNotExist(err error) bool {
-	_, ok := err.(ErrAttachmentNotExist)
-	return ok
-}
-
-func (err ErrAttachmentNotExist) Error() string {
-	return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
-}
-
 // ___________
 // \__    ___/___ _____    _____
 //   |    |_/ __ \\__  \  /     \
@@ -1758,7 +1735,7 @@ type ErrUploadNotExist struct {
 
 // IsErrUploadNotExist checks if an error is a ErrUploadNotExist.
 func IsErrUploadNotExist(err error) bool {
-	_, ok := err.(ErrAttachmentNotExist)
+	_, ok := err.(ErrUploadNotExist)
 	return ok
 }
 
diff --git a/models/issue.go b/models/issue.go
index ef31f416ad..1b9d35d1e3 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -15,6 +15,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/issues"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
@@ -60,11 +61,11 @@ type Issue struct {
 	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 	ClosedUnix  timeutil.TimeStamp `xorm:"INDEX"`
 
-	Attachments      []*Attachment `xorm:"-"`
-	Comments         []*Comment    `xorm:"-"`
-	Reactions        ReactionList  `xorm:"-"`
-	TotalTrackedTime int64         `xorm:"-"`
-	Assignees        []*User       `xorm:"-"`
+	Attachments      []*repo_model.Attachment `xorm:"-"`
+	Comments         []*Comment               `xorm:"-"`
+	Reactions        ReactionList             `xorm:"-"`
+	TotalTrackedTime int64                    `xorm:"-"`
+	Assignees        []*User                  `xorm:"-"`
 
 	// IsLocked limits commenting abilities to users on an issue
 	// with write access
@@ -273,7 +274,8 @@ func (issue *Issue) loadMilestone(e db.Engine) (err error) {
 	return nil
 }
 
-func (issue *Issue) loadAttributes(e db.Engine) (err error) {
+func (issue *Issue) loadAttributes(ctx context.Context) (err error) {
+	e := db.GetEngine(ctx)
 	if err = issue.loadRepo(e); err != nil {
 		return
 	}
@@ -304,7 +306,7 @@ func (issue *Issue) loadAttributes(e db.Engine) (err error) {
 	}
 
 	if issue.Attachments == nil {
-		issue.Attachments, err = getAttachmentsByIssueID(e, issue.ID)
+		issue.Attachments, err = repo_model.GetAttachmentsByIssueIDCtx(ctx, issue.ID)
 		if err != nil {
 			return fmt.Errorf("getAttachmentsByIssueID [%d]: %v", issue.ID, err)
 		}
@@ -328,7 +330,7 @@ func (issue *Issue) loadAttributes(e db.Engine) (err error) {
 
 // LoadAttributes loads the attribute of this issue.
 func (issue *Issue) LoadAttributes() error {
-	return issue.loadAttributes(db.GetEngine(db.DefaultContext))
+	return issue.loadAttributes(db.DefaultContext)
 }
 
 // LoadMilestone load milestone of this issue.
@@ -426,12 +428,12 @@ func (issue *Issue) HasLabel(labelID int64) bool {
 	return issue.hasLabel(db.GetEngine(db.DefaultContext), labelID)
 }
 
-func (issue *Issue) addLabel(e db.Engine, label *Label, doer *User) error {
-	return newIssueLabel(e, issue, label, doer)
+func (issue *Issue) addLabel(ctx context.Context, label *Label, doer *User) error {
+	return newIssueLabel(ctx, issue, label, doer)
 }
 
-func (issue *Issue) addLabels(e db.Engine, labels []*Label, doer *User) error {
-	return newIssueLabels(e, issue, labels, doer)
+func (issue *Issue) addLabels(ctx context.Context, labels []*Label, doer *User) error {
+	return newIssueLabels(ctx, issue, labels, doer)
 }
 
 func (issue *Issue) getLabels(e db.Engine) (err error) {
@@ -446,17 +448,17 @@ func (issue *Issue) getLabels(e db.Engine) (err error) {
 	return nil
 }
 
-func (issue *Issue) removeLabel(e db.Engine, doer *User, label *Label) error {
-	return deleteIssueLabel(e, issue, label, doer)
+func (issue *Issue) removeLabel(ctx context.Context, doer *User, label *Label) error {
+	return deleteIssueLabel(ctx, issue, label, doer)
 }
 
-func (issue *Issue) clearLabels(e db.Engine, doer *User) (err error) {
-	if err = issue.getLabels(e); err != nil {
+func (issue *Issue) clearLabels(ctx context.Context, doer *User) (err error) {
+	if err = issue.getLabels(db.GetEngine(ctx)); err != nil {
 		return fmt.Errorf("getLabels: %v", err)
 	}
 
 	for i := range issue.Labels {
-		if err = issue.removeLabel(e, doer, issue.Labels[i]); err != nil {
+		if err = issue.removeLabel(ctx, doer, issue.Labels[i]); err != nil {
 			return fmt.Errorf("removeLabel: %v", err)
 		}
 	}
@@ -487,7 +489,7 @@ func (issue *Issue) ClearLabels(doer *User) (err error) {
 		return ErrRepoLabelNotExist{}
 	}
 
-	if err = issue.clearLabels(db.GetEngine(ctx), doer); err != nil {
+	if err = issue.clearLabels(ctx, doer); err != nil {
 		return err
 	}
 
@@ -561,13 +563,13 @@ func (issue *Issue) ReplaceLabels(labels []*Label, doer *User) (err error) {
 	toRemove = append(toRemove, issue.Labels[removeIndex:]...)
 
 	if len(toAdd) > 0 {
-		if err = issue.addLabels(db.GetEngine(ctx), toAdd, doer); err != nil {
+		if err = issue.addLabels(ctx, toAdd, doer); err != nil {
 			return fmt.Errorf("addLabels: %v", err)
 		}
 	}
 
 	for _, l := range toRemove {
-		if err = issue.removeLabel(db.GetEngine(ctx), doer, l); err != nil {
+		if err = issue.removeLabel(ctx, doer, l); err != nil {
 			return fmt.Errorf("removeLabel: %v", err)
 		}
 	}
@@ -596,9 +598,9 @@ func updateIssueCols(e db.Engine, issue *Issue, cols ...string) error {
 	return nil
 }
 
-func (issue *Issue) changeStatus(e db.Engine, doer *User, isClosed, isMergePull bool) (*Comment, error) {
+func (issue *Issue) changeStatus(ctx context.Context, doer *User, isClosed, isMergePull bool) (*Comment, error) {
 	// Reload the issue
-	currentIssue, err := getIssueByID(e, issue.ID)
+	currentIssue, err := getIssueByID(db.GetEngine(ctx), issue.ID)
 	if err != nil {
 		return nil, err
 	}
@@ -616,10 +618,11 @@ func (issue *Issue) changeStatus(e db.Engine, doer *User, isClosed, isMergePull
 	}
 
 	issue.IsClosed = isClosed
-	return issue.doChangeStatus(e, doer, isMergePull)
+	return issue.doChangeStatus(ctx, doer, isMergePull)
 }
 
-func (issue *Issue) doChangeStatus(e db.Engine, doer *User, isMergePull bool) (*Comment, error) {
+func (issue *Issue) doChangeStatus(ctx context.Context, doer *User, isMergePull bool) (*Comment, error) {
+	e := db.GetEngine(ctx)
 	// Check for open dependencies
 	if issue.IsClosed && issue.Repo.isDependenciesEnabled(e) {
 		// only check if dependencies are enabled and we're about to close an issue, otherwise reopening an issue would fail when there are unsatisfied dependencies
@@ -672,7 +675,7 @@ func (issue *Issue) doChangeStatus(e db.Engine, doer *User, isMergePull bool) (*
 		cmtType = CommentTypeMergePull
 	}
 
-	return createComment(e, &CreateCommentOptions{
+	return createComment(ctx, &CreateCommentOptions{
 		Type:  cmtType,
 		Doer:  doer,
 		Repo:  issue.Repo,
@@ -695,7 +698,7 @@ func (issue *Issue) ChangeStatus(doer *User, isClosed bool) (*Comment, error) {
 		return nil, err
 	}
 
-	comment, err := issue.changeStatus(db.GetEngine(ctx), doer, isClosed, false)
+	comment, err := issue.changeStatus(ctx, doer, isClosed, false)
 	if err != nil {
 		return nil, err
 	}
@@ -731,10 +734,10 @@ func (issue *Issue) ChangeTitle(doer *User, oldTitle string) (err error) {
 		OldTitle: oldTitle,
 		NewTitle: issue.Title,
 	}
-	if _, err = createComment(db.GetEngine(ctx), opts); err != nil {
+	if _, err = createComment(ctx, opts); err != nil {
 		return fmt.Errorf("createComment: %v", err)
 	}
-	if err = issue.addCrossReferences(db.GetEngine(ctx), doer, true); err != nil {
+	if err = issue.addCrossReferences(ctx, doer, true); err != nil {
 		return err
 	}
 
@@ -767,7 +770,7 @@ func (issue *Issue) ChangeRef(doer *User, oldRef string) (err error) {
 		OldRef: oldRefFriendly,
 		NewRef: newRefFriendly,
 	}
-	if _, err = createComment(db.GetEngine(ctx), opts); err != nil {
+	if _, err = createComment(ctx, opts); err != nil {
 		return fmt.Errorf("createComment: %v", err)
 	}
 
@@ -792,7 +795,7 @@ func AddDeletePRBranchComment(doer *User, repo *Repository, issueID int64, branc
 		Issue:  issue,
 		OldRef: branchName,
 	}
-	if _, err = createComment(db.GetEngine(ctx), opts); err != nil {
+	if _, err = createComment(ctx, opts); err != nil {
 		return err
 	}
 
@@ -806,13 +809,13 @@ func (issue *Issue) UpdateAttachments(uuids []string) (err error) {
 		return err
 	}
 	defer committer.Close()
-	attachments, err := getAttachmentsByUUIDs(db.GetEngine(ctx), uuids)
+	attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, uuids)
 	if err != nil {
 		return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", uuids, err)
 	}
 	for i := 0; i < len(attachments); i++ {
 		attachments[i].IssueID = issue.ID
-		if err := updateAttachment(db.GetEngine(ctx), attachments[i]); err != nil {
+		if err := repo_model.UpdateAttachmentCtx(ctx, attachments[i]); err != nil {
 			return fmt.Errorf("update attachment [id: %d]: %v", attachments[i].ID, err)
 		}
 	}
@@ -838,7 +841,7 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
 		return fmt.Errorf("SaveIssueContentHistory: %v", err)
 	}
 
-	if err = issue.addCrossReferences(ctx.Engine(), doer, true); err != nil {
+	if err = issue.addCrossReferences(ctx, doer, true); err != nil {
 		return fmt.Errorf("addCrossReferences: %v", err)
 	}
 
@@ -908,7 +911,8 @@ type NewIssueOptions struct {
 	IsPull      bool
 }
 
-func newIssue(e db.Engine, doer *User, opts NewIssueOptions) (err error) {
+func newIssue(ctx context.Context, doer *User, opts NewIssueOptions) (err error) {
+	e := db.GetEngine(ctx)
 	opts.Issue.Title = strings.TrimSpace(opts.Issue.Title)
 
 	if opts.Issue.MilestoneID > 0 {
@@ -949,7 +953,7 @@ func newIssue(e db.Engine, doer *User, opts NewIssueOptions) (err error) {
 			OldMilestoneID: 0,
 			MilestoneID:    opts.Issue.MilestoneID,
 		}
-		if _, err = createComment(e, opts); err != nil {
+		if _, err = createComment(ctx, opts); err != nil {
 			return err
 		}
 	}
@@ -981,7 +985,7 @@ func newIssue(e db.Engine, doer *User, opts NewIssueOptions) (err error) {
 				continue
 			}
 
-			if err = opts.Issue.addLabel(e, label, opts.Issue.Poster); err != nil {
+			if err = opts.Issue.addLabel(ctx, label, opts.Issue.Poster); err != nil {
 				return fmt.Errorf("addLabel [id: %d]: %v", label.ID, err)
 			}
 		}
@@ -992,7 +996,7 @@ func newIssue(e db.Engine, doer *User, opts NewIssueOptions) (err error) {
 	}
 
 	if len(opts.Attachments) > 0 {
-		attachments, err := getAttachmentsByUUIDs(e, opts.Attachments)
+		attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
 		if err != nil {
 			return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", opts.Attachments, err)
 		}
@@ -1004,7 +1008,7 @@ func newIssue(e db.Engine, doer *User, opts NewIssueOptions) (err error) {
 			}
 		}
 	}
-	if err = opts.Issue.loadAttributes(e); err != nil {
+	if err = opts.Issue.loadAttributes(ctx); err != nil {
 		return err
 	}
 
@@ -1013,7 +1017,7 @@ func newIssue(e db.Engine, doer *User, opts NewIssueOptions) (err error) {
 		return err
 	}
 
-	return opts.Issue.addCrossReferences(e, doer, false)
+	return opts.Issue.addCrossReferences(ctx, doer, false)
 }
 
 // RecalculateIssueIndexForRepo create issue_index for repo if not exist and
@@ -1056,7 +1060,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
 	}
 	defer committer.Close()
 
-	if err = newIssue(db.GetEngine(ctx), issue.Poster, NewIssueOptions{
+	if err = newIssue(ctx, issue.Poster, NewIssueOptions{
 		Repo:        repo,
 		Issue:       issue,
 		LabelIDs:    labelIDs,
@@ -1119,7 +1123,7 @@ func GetIssueWithAttrsByID(id int64) (*Issue, error) {
 	if err != nil {
 		return nil, err
 	}
-	return issue, issue.loadAttributes(db.GetEngine(db.DefaultContext))
+	return issue, issue.loadAttributes(db.DefaultContext)
 }
 
 // GetIssueByID returns an issue by given ID.
@@ -1838,11 +1842,12 @@ func SearchIssueIDsByKeyword(kw string, repoIDs []int64, limit, start int) (int6
 // If the issue status is changed a statusChangeComment is returned
 // similarly if the title is changed the titleChanged bool is set to true
 func UpdateIssueByAPI(issue *Issue, doer *User) (statusChangeComment *Comment, titleChanged bool, err error) {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return nil, false, err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	if err := issue.loadRepo(sess); err != nil {
 		return nil, false, fmt.Errorf("loadRepo: %v", err)
@@ -1871,23 +1876,23 @@ func UpdateIssueByAPI(issue *Issue, doer *User) (statusChangeComment *Comment, t
 			OldTitle: currentIssue.Title,
 			NewTitle: issue.Title,
 		}
-		_, err := createComment(sess, opts)
+		_, err := createComment(ctx, opts)
 		if err != nil {
 			return nil, false, fmt.Errorf("createComment: %v", err)
 		}
 	}
 
 	if currentIssue.IsClosed != issue.IsClosed {
-		statusChangeComment, err = issue.doChangeStatus(sess, doer, false)
+		statusChangeComment, err = issue.doChangeStatus(ctx, doer, false)
 		if err != nil {
 			return nil, false, err
 		}
 	}
 
-	if err := issue.addCrossReferences(sess, doer, true); err != nil {
+	if err := issue.addCrossReferences(ctx, doer, true); err != nil {
 		return nil, false, err
 	}
-	return statusChangeComment, titleChanged, sess.Commit()
+	return statusChangeComment, titleChanged, committer.Commit()
 }
 
 // UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it.
@@ -1897,11 +1902,12 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix timeutil.TimeStamp, doer *Us
 		return nil
 	}
 
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	// Update the deadline
 	if err = updateIssueCols(sess, &Issue{ID: issue.ID, DeadlineUnix: deadlineUnix}, "deadline_unix"); err != nil {
@@ -1909,11 +1915,11 @@ func UpdateIssueDeadline(issue *Issue, deadlineUnix timeutil.TimeStamp, doer *Us
 	}
 
 	// Make the comment
-	if _, err = createDeadlineComment(sess, doer, issue, deadlineUnix); err != nil {
+	if _, err = createDeadlineComment(ctx, doer, issue, deadlineUnix); err != nil {
 		return fmt.Errorf("createRemovedDueDateComment: %v", err)
 	}
 
-	return sess.Commit()
+	return committer.Commit()
 }
 
 // DependencyInfo represents high level information about an issue which is a dependency of another issue.
@@ -2244,7 +2250,7 @@ func deleteIssuesByRepoID(sess db.Engine, repoID int64) (attachmentPaths []strin
 		return
 	}
 
-	var attachments []*Attachment
+	var attachments []*repo_model.Attachment
 	if err = sess.In("issue_id", deleteCond).
 		Find(&attachments); err != nil {
 		return
@@ -2255,7 +2261,7 @@ func deleteIssuesByRepoID(sess db.Engine, repoID int64) (attachmentPaths []strin
 	}
 
 	if _, err = sess.In("issue_id", deleteCond).
-		Delete(&Attachment{}); err != nil {
+		Delete(&repo_model.Attachment{}); err != nil {
 		return
 	}
 
diff --git a/models/issue_assignees.go b/models/issue_assignees.go
index 0f7ba2d702..996a3a8563 100644
--- a/models/issue_assignees.go
+++ b/models/issue_assignees.go
@@ -5,12 +5,11 @@
 package models
 
 import (
+	"context"
 	"fmt"
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/modules/util"
-
-	"xorm.io/xorm"
 )
 
 // IssueAssignees saves all issue assignees
@@ -94,26 +93,26 @@ func clearAssigneeByUserID(sess db.Engine, userID int64) (err error) {
 
 // ToggleAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it.
 func (issue *Issue) ToggleAssignee(doer *User, assigneeID int64) (removed bool, comment *Comment, err error) {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return false, nil, err
 	}
+	defer committer.Close()
 
-	removed, comment, err = issue.toggleAssignee(sess, doer, assigneeID, false)
+	removed, comment, err = issue.toggleAssignee(ctx, doer, assigneeID, false)
 	if err != nil {
 		return false, nil, err
 	}
 
-	if err := sess.Commit(); err != nil {
+	if err := committer.Commit(); err != nil {
 		return false, nil, err
 	}
 
 	return removed, comment, nil
 }
 
-func (issue *Issue) toggleAssignee(sess *xorm.Session, doer *User, assigneeID int64, isCreate bool) (removed bool, comment *Comment, err error) {
+func (issue *Issue) toggleAssignee(ctx context.Context, doer *User, assigneeID int64, isCreate bool) (removed bool, comment *Comment, err error) {
+	sess := db.GetEngine(ctx)
 	removed, err = toggleUserAssignee(sess, issue, assigneeID)
 	if err != nil {
 		return false, nil, fmt.Errorf("UpdateIssueUserByAssignee: %v", err)
@@ -133,7 +132,7 @@ func (issue *Issue) toggleAssignee(sess *xorm.Session, doer *User, assigneeID in
 		AssigneeID:      assigneeID,
 	}
 	// Comment
-	comment, err = createComment(sess, opts)
+	comment, err = createComment(ctx, opts)
 	if err != nil {
 		return false, nil, fmt.Errorf("createComment: %v", err)
 	}
@@ -147,7 +146,7 @@ func (issue *Issue) toggleAssignee(sess *xorm.Session, doer *User, assigneeID in
 }
 
 // toggles user assignee state in database
-func toggleUserAssignee(e *xorm.Session, issue *Issue, assigneeID int64) (removed bool, err error) {
+func toggleUserAssignee(e db.Engine, issue *Issue, assigneeID int64) (removed bool, err error) {
 	// Check if the user exists
 	assignee, err := getUserByID(e, assigneeID)
 	if err != nil {
diff --git a/models/issue_comment.go b/models/issue_comment.go
index 1936695732..fccc139cd0 100644
--- a/models/issue_comment.go
+++ b/models/issue_comment.go
@@ -7,6 +7,7 @@
 package models
 
 import (
+	"context"
 	"fmt"
 	"regexp"
 	"strconv"
@@ -15,6 +16,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/issues"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/log"
@@ -198,8 +200,8 @@ type Comment struct {
 	// Reference issue in commit message
 	CommitSHA string `xorm:"VARCHAR(40)"`
 
-	Attachments []*Attachment `xorm:"-"`
-	Reactions   ReactionList  `xorm:"-"`
+	Attachments []*repo_model.Attachment `xorm:"-"`
+	Reactions   ReactionList             `xorm:"-"`
 
 	// For view issue page.
 	ShowRole RoleDescriptor `xorm:"-"`
@@ -300,7 +302,7 @@ func (c *Comment) AfterDelete() {
 		return
 	}
 
-	_, err := DeleteAttachmentsByComment(c.ID, true)
+	_, err := repo_model.DeleteAttachmentsByComment(c.ID, true)
 	if err != nil {
 		log.Info("Could not delete files for comment %d on issue #%d: %s", c.ID, c.IssueID, err)
 	}
@@ -483,7 +485,7 @@ func (c *Comment) LoadAttachments() error {
 	}
 
 	var err error
-	c.Attachments, err = getAttachmentsByCommentID(db.GetEngine(db.DefaultContext), c.ID)
+	c.Attachments, err = repo_model.GetAttachmentsByCommentIDCtx(db.DefaultContext, c.ID)
 	if err != nil {
 		log.Error("getAttachmentsByCommentID[%d]: %v", c.ID, err)
 	}
@@ -492,23 +494,24 @@ func (c *Comment) LoadAttachments() error {
 
 // UpdateAttachments update attachments by UUIDs for the comment
 func (c *Comment) UpdateAttachments(uuids []string) error {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
-	attachments, err := getAttachmentsByUUIDs(sess, uuids)
+	defer committer.Close()
+
+	attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, uuids)
 	if err != nil {
 		return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", uuids, err)
 	}
 	for i := 0; i < len(attachments); i++ {
 		attachments[i].IssueID = c.IssueID
 		attachments[i].CommentID = c.ID
-		if err := updateAttachment(sess, attachments[i]); err != nil {
+		if err := repo_model.UpdateAttachmentCtx(ctx, attachments[i]); err != nil {
 			return fmt.Errorf("update attachment [id: %d]: %v", attachments[i].ID, err)
 		}
 	}
-	return sess.Commit()
+	return committer.Commit()
 }
 
 // LoadAssigneeUserAndTeam if comment.Type is CommentTypeAssignees, then load assignees
@@ -715,7 +718,8 @@ func (c *Comment) LoadPushCommits() (err error) {
 	return err
 }
 
-func createComment(e db.Engine, opts *CreateCommentOptions) (_ *Comment, err error) {
+func createComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment, err error) {
+	e := db.GetEngine(ctx)
 	var LabelID int64
 	if opts.Label != nil {
 		LabelID = opts.Label.ID
@@ -763,18 +767,19 @@ func createComment(e db.Engine, opts *CreateCommentOptions) (_ *Comment, err err
 		return nil, err
 	}
 
-	if err = updateCommentInfos(e, opts, comment); err != nil {
+	if err = updateCommentInfos(ctx, opts, comment); err != nil {
 		return nil, err
 	}
 
-	if err = comment.addCrossReferences(e, opts.Doer, false); err != nil {
+	if err = comment.addCrossReferences(ctx, opts.Doer, false); err != nil {
 		return nil, err
 	}
 
 	return comment, nil
 }
 
-func updateCommentInfos(e db.Engine, opts *CreateCommentOptions, comment *Comment) (err error) {
+func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment *Comment) (err error) {
+	e := db.GetEngine(ctx)
 	// Check comment type.
 	switch opts.Type {
 	case CommentTypeCode:
@@ -797,7 +802,7 @@ func updateCommentInfos(e db.Engine, opts *CreateCommentOptions, comment *Commen
 		}
 
 		// Check attachments
-		attachments, err := getAttachmentsByUUIDs(e, opts.Attachments)
+		attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
 		if err != nil {
 			return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", opts.Attachments, err)
 		}
@@ -819,7 +824,7 @@ func updateCommentInfos(e db.Engine, opts *CreateCommentOptions, comment *Commen
 	return updateIssueCols(e, opts.Issue, "updated_unix")
 }
 
-func createDeadlineComment(e *xorm.Session, doer *User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) {
+func createDeadlineComment(ctx context.Context, doer *User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) {
 	var content string
 	var commentType CommentType
 
@@ -837,7 +842,7 @@ func createDeadlineComment(e *xorm.Session, doer *User, issue *Issue, newDeadlin
 		content = newDeadlineUnix.Format("2006-01-02") + "|" + issue.DeadlineUnix.Format("2006-01-02")
 	}
 
-	if err := issue.loadRepo(e); err != nil {
+	if err := issue.loadRepo(db.GetEngine(ctx)); err != nil {
 		return nil, err
 	}
 
@@ -848,7 +853,7 @@ func createDeadlineComment(e *xorm.Session, doer *User, issue *Issue, newDeadlin
 		Issue:   issue,
 		Content: content,
 	}
-	comment, err := createComment(e, opts)
+	comment, err := createComment(ctx, opts)
 	if err != nil {
 		return nil, err
 	}
@@ -856,12 +861,12 @@ func createDeadlineComment(e *xorm.Session, doer *User, issue *Issue, newDeadlin
 }
 
 // Creates issue dependency comment
-func createIssueDependencyComment(e *xorm.Session, doer *User, issue, dependentIssue *Issue, add bool) (err error) {
+func createIssueDependencyComment(ctx context.Context, doer *User, issue, dependentIssue *Issue, add bool) (err error) {
 	cType := CommentTypeAddDependency
 	if !add {
 		cType = CommentTypeRemoveDependency
 	}
-	if err = issue.loadRepo(e); err != nil {
+	if err = issue.loadRepo(db.GetEngine(ctx)); err != nil {
 		return
 	}
 
@@ -873,7 +878,7 @@ func createIssueDependencyComment(e *xorm.Session, doer *User, issue, dependentI
 		Issue:            issue,
 		DependentIssueID: dependentIssue.ID,
 	}
-	if _, err = createComment(e, opts); err != nil {
+	if _, err = createComment(ctx, opts); err != nil {
 		return
 	}
 
@@ -884,7 +889,7 @@ func createIssueDependencyComment(e *xorm.Session, doer *User, issue, dependentI
 		Issue:            dependentIssue,
 		DependentIssueID: issue.ID,
 	}
-	_, err = createComment(e, opts)
+	_, err = createComment(ctx, opts)
 	return
 }
 
@@ -928,18 +933,18 @@ type CreateCommentOptions struct {
 
 // CreateComment creates comment of issue or commit.
 func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err = sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return nil, err
 	}
+	defer committer.Close()
 
-	comment, err = createComment(sess, opts)
+	comment, err = createComment(ctx, opts)
 	if err != nil {
 		return nil, err
 	}
 
-	if err = sess.Commit(); err != nil {
+	if err = committer.Commit(); err != nil {
 		return nil, err
 	}
 
@@ -1068,11 +1073,12 @@ func CountComments(opts *FindCommentsOptions) (int64, error) {
 
 // UpdateComment updates information of comment.
 func UpdateComment(c *Comment, doer *User) error {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	if _, err := sess.ID(c.ID).AllCols().Update(c); err != nil {
 		return err
@@ -1080,10 +1086,10 @@ func UpdateComment(c *Comment, doer *User) error {
 	if err := c.loadIssue(sess); err != nil {
 		return err
 	}
-	if err := c.addCrossReferences(sess, doer, true); err != nil {
+	if err := c.addCrossReferences(ctx, doer, true); err != nil {
 		return err
 	}
-	if err := sess.Commit(); err != nil {
+	if err := committer.Commit(); err != nil {
 		return fmt.Errorf("Commit: %v", err)
 	}
 
diff --git a/models/issue_comment_list.go b/models/issue_comment_list.go
index bd1b48f8e5..3c587fc8f7 100644
--- a/models/issue_comment_list.go
+++ b/models/issue_comment_list.go
@@ -4,7 +4,10 @@
 
 package models
 
-import "code.gitea.io/gitea/models/db"
+import (
+	"code.gitea.io/gitea/models/db"
+	repo_model "code.gitea.io/gitea/models/repo"
+)
 
 // CommentList defines a list of comments
 type CommentList []*Comment
@@ -393,7 +396,7 @@ func (comments CommentList) loadAttachments(e db.Engine) (err error) {
 		return nil
 	}
 
-	attachments := make(map[int64][]*Attachment, len(comments))
+	attachments := make(map[int64][]*repo_model.Attachment, len(comments))
 	commentsIDs := comments.getCommentIDs()
 	left := len(commentsIDs)
 	for left > 0 {
@@ -404,13 +407,13 @@ func (comments CommentList) loadAttachments(e db.Engine) (err error) {
 		rows, err := e.Table("attachment").
 			Join("INNER", "comment", "comment.id = attachment.comment_id").
 			In("comment.id", commentsIDs[:limit]).
-			Rows(new(Attachment))
+			Rows(new(repo_model.Attachment))
 		if err != nil {
 			return err
 		}
 
 		for rows.Next() {
-			var attachment Attachment
+			var attachment repo_model.Attachment
 			err = rows.Scan(&attachment)
 			if err != nil {
 				_ = rows.Close()
diff --git a/models/issue_dependency.go b/models/issue_dependency.go
index f58bb3da0a..ee984fbe3e 100644
--- a/models/issue_dependency.go
+++ b/models/issue_dependency.go
@@ -37,11 +37,12 @@ const (
 
 // CreateIssueDependency creates a new dependency for an issue
 func CreateIssueDependency(user *User, issue, dep *Issue) error {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	// Check if it aleready exists
 	exists, err := issueDepExists(sess, issue.ID, dep.ID)
@@ -69,20 +70,20 @@ func CreateIssueDependency(user *User, issue, dep *Issue) error {
 	}
 
 	// Add comment referencing the new dependency
-	if err = createIssueDependencyComment(sess, user, issue, dep, true); err != nil {
+	if err = createIssueDependencyComment(ctx, user, issue, dep, true); err != nil {
 		return err
 	}
 
-	return sess.Commit()
+	return committer.Commit()
 }
 
 // RemoveIssueDependency removes a dependency from an issue
 func RemoveIssueDependency(user *User, issue, dep *Issue, depType DependencyType) (err error) {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err = sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
 
 	var issueDepToDelete IssueDependency
 
@@ -95,7 +96,7 @@ func RemoveIssueDependency(user *User, issue, dep *Issue, depType DependencyType
 		return ErrUnknownDependencyType{depType}
 	}
 
-	affected, err := sess.Delete(&issueDepToDelete)
+	affected, err := db.GetEngine(ctx).Delete(&issueDepToDelete)
 	if err != nil {
 		return err
 	}
@@ -106,10 +107,10 @@ func RemoveIssueDependency(user *User, issue, dep *Issue, depType DependencyType
 	}
 
 	// Add comment referencing the removed dependency
-	if err = createIssueDependencyComment(sess, user, issue, dep, false); err != nil {
+	if err = createIssueDependencyComment(ctx, user, issue, dep, false); err != nil {
 		return err
 	}
-	return sess.Commit()
+	return committer.Commit()
 }
 
 // Check if the dependency already exists
diff --git a/models/issue_label.go b/models/issue_label.go
index 293b7140f7..492e298600 100644
--- a/models/issue_label.go
+++ b/models/issue_label.go
@@ -663,7 +663,8 @@ func HasIssueLabel(issueID, labelID int64) bool {
 
 // newIssueLabel this function creates a new label it does not check if the label is valid for the issue
 // YOU MUST CHECK THIS BEFORE THIS FUNCTION
-func newIssueLabel(e db.Engine, issue *Issue, label *Label, doer *User) (err error) {
+func newIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *User) (err error) {
+	e := db.GetEngine(ctx)
 	if _, err = e.Insert(&IssueLabel{
 		IssueID: issue.ID,
 		LabelID: label.ID,
@@ -683,7 +684,7 @@ func newIssueLabel(e db.Engine, issue *Issue, label *Label, doer *User) (err err
 		Label:   label,
 		Content: "1",
 	}
-	if _, err = createComment(e, opts); err != nil {
+	if _, err = createComment(ctx, opts); err != nil {
 		return err
 	}
 
@@ -696,11 +697,12 @@ func NewIssueLabel(issue *Issue, label *Label, doer *User) (err error) {
 		return nil
 	}
 
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err = sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	if err = issue.loadRepo(sess); err != nil {
 		return err
@@ -711,7 +713,7 @@ func NewIssueLabel(issue *Issue, label *Label, doer *User) (err error) {
 		return nil
 	}
 
-	if err = newIssueLabel(sess, issue, label, doer); err != nil {
+	if err = newIssueLabel(ctx, issue, label, doer); err != nil {
 		return err
 	}
 
@@ -720,11 +722,12 @@ func NewIssueLabel(issue *Issue, label *Label, doer *User) (err error) {
 		return err
 	}
 
-	return sess.Commit()
+	return committer.Commit()
 }
 
 // newIssueLabels add labels to an issue. It will check if the labels are valid for the issue
-func newIssueLabels(e db.Engine, issue *Issue, labels []*Label, doer *User) (err error) {
+func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *User) (err error) {
+	e := db.GetEngine(ctx)
 	if err = issue.loadRepo(e); err != nil {
 		return err
 	}
@@ -735,7 +738,7 @@ func newIssueLabels(e db.Engine, issue *Issue, labels []*Label, doer *User) (err
 			continue
 		}
 
-		if err = newIssueLabel(e, issue, label, doer); err != nil {
+		if err = newIssueLabel(ctx, issue, label, doer); err != nil {
 			return fmt.Errorf("newIssueLabel: %v", err)
 		}
 	}
@@ -751,7 +754,7 @@ func NewIssueLabels(issue *Issue, labels []*Label, doer *User) (err error) {
 	}
 	defer committer.Close()
 
-	if err = newIssueLabels(db.GetEngine(ctx), issue, labels, doer); err != nil {
+	if err = newIssueLabels(ctx, issue, labels, doer); err != nil {
 		return err
 	}
 
@@ -763,7 +766,8 @@ func NewIssueLabels(issue *Issue, labels []*Label, doer *User) (err error) {
 	return committer.Commit()
 }
 
-func deleteIssueLabel(e db.Engine, issue *Issue, label *Label, doer *User) (err error) {
+func deleteIssueLabel(ctx context.Context, issue *Issue, label *Label, doer *User) (err error) {
+	e := db.GetEngine(ctx)
 	if count, err := e.Delete(&IssueLabel{
 		IssueID: issue.ID,
 		LabelID: label.ID,
@@ -784,7 +788,7 @@ func deleteIssueLabel(e db.Engine, issue *Issue, label *Label, doer *User) (err
 		Issue: issue,
 		Label: label,
 	}
-	if _, err = createComment(e, opts); err != nil {
+	if _, err = createComment(ctx, opts); err != nil {
 		return err
 	}
 
@@ -793,22 +797,22 @@ func deleteIssueLabel(e db.Engine, issue *Issue, label *Label, doer *User) (err
 
 // DeleteIssueLabel deletes issue-label relation.
 func DeleteIssueLabel(issue *Issue, label *Label, doer *User) (err error) {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err = sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
 
-	if err = deleteIssueLabel(sess, issue, label, doer); err != nil {
+	if err = deleteIssueLabel(ctx, issue, label, doer); err != nil {
 		return err
 	}
 
 	issue.Labels = nil
-	if err = issue.loadLabels(sess); err != nil {
+	if err = issue.loadLabels(db.GetEngine(ctx)); err != nil {
 		return err
 	}
 
-	return sess.Commit()
+	return committer.Commit()
 }
 
 func deleteLabelsByRepoID(sess db.Engine, repoID int64) error {
diff --git a/models/issue_list.go b/models/issue_list.go
index 04dcc58422..6eeb2a961a 100644
--- a/models/issue_list.go
+++ b/models/issue_list.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 
 	"code.gitea.io/gitea/models/db"
+	repo_model "code.gitea.io/gitea/models/repo"
 
 	"xorm.io/builder"
 )
@@ -322,7 +323,7 @@ func (issues IssueList) loadAttachments(e db.Engine) (err error) {
 		return nil
 	}
 
-	attachments := make(map[int64][]*Attachment, len(issues))
+	attachments := make(map[int64][]*repo_model.Attachment, len(issues))
 	issuesIDs := issues.getIssueIDs()
 	left := len(issuesIDs)
 	for left > 0 {
@@ -333,13 +334,13 @@ func (issues IssueList) loadAttachments(e db.Engine) (err error) {
 		rows, err := e.Table("attachment").
 			Join("INNER", "issue", "issue.id = attachment.issue_id").
 			In("issue.id", issuesIDs[:limit]).
-			Rows(new(Attachment))
+			Rows(new(repo_model.Attachment))
 		if err != nil {
 			return err
 		}
 
 		for rows.Next() {
-			var attachment Attachment
+			var attachment repo_model.Attachment
 			err = rows.Scan(&attachment)
 			if err != nil {
 				if err1 := rows.Close(); err1 != nil {
diff --git a/models/issue_lock.go b/models/issue_lock.go
index d8e3b4c0ab..d0b9c660c2 100644
--- a/models/issue_lock.go
+++ b/models/issue_lock.go
@@ -37,13 +37,13 @@ func updateIssueLock(opts *IssueLockOptions, lock bool) error {
 		commentType = CommentTypeUnlock
 	}
 
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
 
-	if err := updateIssueCols(sess, opts.Issue, "is_locked"); err != nil {
+	if err := updateIssueCols(db.GetEngine(ctx), opts.Issue, "is_locked"); err != nil {
 		return err
 	}
 
@@ -54,9 +54,9 @@ func updateIssueLock(opts *IssueLockOptions, lock bool) error {
 		Type:    commentType,
 		Content: opts.Reason,
 	}
-	if _, err := createComment(sess, opt); err != nil {
+	if _, err := createComment(ctx, opt); err != nil {
 		return err
 	}
 
-	return sess.Commit()
+	return committer.Commit()
 }
diff --git a/models/issue_milestone.go b/models/issue_milestone.go
index 3898e5b397..fd5d052cbd 100644
--- a/models/issue_milestone.go
+++ b/models/issue_milestone.go
@@ -5,6 +5,7 @@
 package models
 
 import (
+	"context"
 	"fmt"
 	"strings"
 	"time"
@@ -15,7 +16,6 @@ import (
 	"code.gitea.io/gitea/modules/timeutil"
 
 	"xorm.io/builder"
-	"xorm.io/xorm"
 )
 
 // Milestone represents a milestone of repository.
@@ -263,7 +263,8 @@ func changeMilestoneStatus(e db.Engine, m *Milestone, isClosed bool) error {
 	return updateRepoMilestoneNum(e, m.RepoID)
 }
 
-func changeMilestoneAssign(e *xorm.Session, doer *User, issue *Issue, oldMilestoneID int64) error {
+func changeMilestoneAssign(ctx context.Context, doer *User, issue *Issue, oldMilestoneID int64) error {
+	e := db.GetEngine(ctx)
 	if err := updateIssueCols(e, issue, "milestone_id"); err != nil {
 		return err
 	}
@@ -293,7 +294,7 @@ func changeMilestoneAssign(e *xorm.Session, doer *User, issue *Issue, oldMilesto
 			OldMilestoneID: oldMilestoneID,
 			MilestoneID:    issue.MilestoneID,
 		}
-		if _, err := createComment(e, opts); err != nil {
+		if _, err := createComment(ctx, opts); err != nil {
 			return err
 		}
 	}
@@ -303,17 +304,17 @@ func changeMilestoneAssign(e *xorm.Session, doer *User, issue *Issue, oldMilesto
 
 // ChangeMilestoneAssign changes assignment of milestone for issue.
 func ChangeMilestoneAssign(issue *Issue, doer *User, oldMilestoneID int64) (err error) {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err = sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
 
-	if err = changeMilestoneAssign(sess, doer, issue, oldMilestoneID); err != nil {
+	if err = changeMilestoneAssign(ctx, doer, issue, oldMilestoneID); err != nil {
 		return err
 	}
 
-	if err = sess.Commit(); err != nil {
+	if err = committer.Commit(); err != nil {
 		return fmt.Errorf("Commit: %v", err)
 	}
 	return nil
diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go
index e8f19dd738..e9c38b9165 100644
--- a/models/issue_stopwatch.go
+++ b/models/issue_stopwatch.go
@@ -5,13 +5,12 @@
 package models
 
 import (
+	"context"
 	"fmt"
 	"time"
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/modules/timeutil"
-
-	"xorm.io/xorm"
 )
 
 // Stopwatch represents a stopwatch for time tracking.
@@ -86,18 +85,19 @@ func hasUserStopwatch(e db.Engine, userID int64) (exists bool, sw *Stopwatch, er
 
 // CreateOrStopIssueStopwatch will create or remove a stopwatch and will log it into issue's timeline.
 func CreateOrStopIssueStopwatch(user *User, issue *Issue) error {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
-	if err := createOrStopIssueStopwatch(sess, user, issue); err != nil {
+	defer committer.Close()
+	if err := createOrStopIssueStopwatch(ctx, user, issue); err != nil {
 		return err
 	}
-	return sess.Commit()
+	return committer.Commit()
 }
 
-func createOrStopIssueStopwatch(e *xorm.Session, user *User, issue *Issue) error {
+func createOrStopIssueStopwatch(ctx context.Context, user *User, issue *Issue) error {
+	e := db.GetEngine(ctx)
 	sw, exists, err := getStopwatch(e, user.ID, issue.ID)
 	if err != nil {
 		return err
@@ -122,7 +122,7 @@ func createOrStopIssueStopwatch(e *xorm.Session, user *User, issue *Issue) error
 			return err
 		}
 
-		if _, err := createComment(e, &CreateCommentOptions{
+		if _, err := createComment(ctx, &CreateCommentOptions{
 			Doer:    user,
 			Issue:   issue,
 			Repo:    issue.Repo,
@@ -146,7 +146,7 @@ func createOrStopIssueStopwatch(e *xorm.Session, user *User, issue *Issue) error
 			if err != nil {
 				return err
 			}
-			if err := createOrStopIssueStopwatch(e, user, issue); err != nil {
+			if err := createOrStopIssueStopwatch(ctx, user, issue); err != nil {
 				return err
 			}
 		}
@@ -157,11 +157,11 @@ func createOrStopIssueStopwatch(e *xorm.Session, user *User, issue *Issue) error
 			IssueID: issue.ID,
 		}
 
-		if _, err := e.Insert(sw); err != nil {
+		if err := db.Insert(ctx, sw); err != nil {
 			return err
 		}
 
-		if _, err := createComment(e, &CreateCommentOptions{
+		if _, err := createComment(ctx, &CreateCommentOptions{
 			Doer:  user,
 			Issue: issue,
 			Repo:  issue.Repo,
@@ -175,18 +175,19 @@ func createOrStopIssueStopwatch(e *xorm.Session, user *User, issue *Issue) error
 
 // CancelStopwatch removes the given stopwatch and logs it into issue's timeline.
 func CancelStopwatch(user *User, issue *Issue) error {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
-	if err := cancelStopwatch(sess, user, issue); err != nil {
+	defer committer.Close()
+	if err := cancelStopwatch(ctx, user, issue); err != nil {
 		return err
 	}
-	return sess.Commit()
+	return committer.Commit()
 }
 
-func cancelStopwatch(e *xorm.Session, user *User, issue *Issue) error {
+func cancelStopwatch(ctx context.Context, user *User, issue *Issue) error {
+	e := db.GetEngine(ctx)
 	sw, exists, err := getStopwatch(e, user.ID, issue.ID)
 	if err != nil {
 		return err
@@ -201,7 +202,7 @@ func cancelStopwatch(e *xorm.Session, user *User, issue *Issue) error {
 			return err
 		}
 
-		if _, err := createComment(e, &CreateCommentOptions{
+		if _, err := createComment(ctx, &CreateCommentOptions{
 			Doer:  user,
 			Issue: issue,
 			Repo:  issue.Repo,
diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go
index 79de891019..55d585a55c 100644
--- a/models/issue_tracked_time.go
+++ b/models/issue_tracked_time.go
@@ -154,12 +154,12 @@ func GetTrackedSeconds(opts FindTrackedTimesOptions) (int64, error) {
 
 // AddTime will add the given time (in seconds) to the issue
 func AddTime(user *User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return nil, err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	t, err := addTime(sess, user, issue, amount, created)
 	if err != nil {
@@ -170,7 +170,7 @@ func AddTime(user *User, issue *Issue, amount int64, created time.Time) (*Tracke
 		return nil, err
 	}
 
-	if _, err := createComment(sess, &CreateCommentOptions{
+	if _, err := createComment(ctx, &CreateCommentOptions{
 		Issue:   issue,
 		Repo:    issue.Repo,
 		Doer:    user,
@@ -181,7 +181,7 @@ func AddTime(user *User, issue *Issue, amount int64, created time.Time) (*Tracke
 		return nil, err
 	}
 
-	return t, sess.Commit()
+	return t, committer.Commit()
 }
 
 func addTime(e db.Engine, user *User, issue *Issue, amount int64, created time.Time) (*TrackedTime, error) {
@@ -230,12 +230,12 @@ func TotalTimes(options *FindTrackedTimesOptions) (map[*User]string, error) {
 
 // DeleteIssueUserTimes deletes times for issue
 func DeleteIssueUserTimes(issue *Issue, user *User) error {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	opts := FindTrackedTimesOptions{
 		IssueID: issue.ID,
@@ -253,7 +253,7 @@ func DeleteIssueUserTimes(issue *Issue, user *User) error {
 	if err := issue.loadRepo(sess); err != nil {
 		return err
 	}
-	if _, err := createComment(sess, &CreateCommentOptions{
+	if _, err := createComment(ctx, &CreateCommentOptions{
 		Issue:   issue,
 		Repo:    issue.Repo,
 		Doer:    user,
@@ -263,17 +263,17 @@ func DeleteIssueUserTimes(issue *Issue, user *User) error {
 		return err
 	}
 
-	return sess.Commit()
+	return committer.Commit()
 }
 
 // DeleteTime delete a specific Time
 func DeleteTime(t *TrackedTime) error {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	if err := t.loadAttributes(sess); err != nil {
 		return err
@@ -283,7 +283,7 @@ func DeleteTime(t *TrackedTime) error {
 		return err
 	}
 
-	if _, err := createComment(sess, &CreateCommentOptions{
+	if _, err := createComment(ctx, &CreateCommentOptions{
 		Issue:   t.Issue,
 		Repo:    t.Issue.Repo,
 		Doer:    t.User,
@@ -293,7 +293,7 @@ func DeleteTime(t *TrackedTime) error {
 		return err
 	}
 
-	return sess.Commit()
+	return committer.Commit()
 }
 
 func deleteTimes(e db.Engine, opts FindTrackedTimesOptions) (removedTime int64, err error) {
diff --git a/models/issue_xref.go b/models/issue_xref.go
index 4630f4d3a4..55483ce57b 100644
--- a/models/issue_xref.go
+++ b/models/issue_xref.go
@@ -5,6 +5,7 @@
 package models
 
 import (
+	"context"
 	"fmt"
 
 	"code.gitea.io/gitea/models/db"
@@ -59,7 +60,7 @@ func neuterCrossReferencesIds(e db.Engine, ids []int64) error {
 //          \/     \/            \/
 //
 
-func (issue *Issue) addCrossReferences(e db.Engine, doer *User, removeOld bool) error {
+func (issue *Issue) addCrossReferences(stdCtx context.Context, doer *User, removeOld bool) error {
 	var commentType CommentType
 	if issue.IsPull {
 		commentType = CommentTypePullRef
@@ -72,10 +73,11 @@ func (issue *Issue) addCrossReferences(e db.Engine, doer *User, removeOld bool)
 		OrigIssue: issue,
 		RemoveOld: removeOld,
 	}
-	return issue.createCrossReferences(e, ctx, issue.Title, issue.Content)
+	return issue.createCrossReferences(stdCtx, ctx, issue.Title, issue.Content)
 }
 
-func (issue *Issue) createCrossReferences(e db.Engine, ctx *crossReferencesContext, plaincontent, mdcontent string) error {
+func (issue *Issue) createCrossReferences(stdCtx context.Context, ctx *crossReferencesContext, plaincontent, mdcontent string) error {
+	e := db.GetEngine(stdCtx)
 	xreflist, err := ctx.OrigIssue.getCrossReferences(e, ctx, plaincontent, mdcontent)
 	if err != nil {
 		return err
@@ -125,7 +127,7 @@ func (issue *Issue) createCrossReferences(e db.Engine, ctx *crossReferencesConte
 			RefAction:    xref.Action,
 			RefIsPull:    ctx.OrigIssue.IsPull,
 		}
-		_, err := createComment(e, opts)
+		_, err := createComment(stdCtx, opts)
 		if err != nil {
 			return err
 		}
@@ -240,11 +242,11 @@ func (issue *Issue) verifyReferencedIssue(e db.Engine, ctx *crossReferencesConte
 //         \/             \/      \/     \/     \/
 //
 
-func (comment *Comment) addCrossReferences(e db.Engine, doer *User, removeOld bool) error {
+func (comment *Comment) addCrossReferences(stdCtx context.Context, doer *User, removeOld bool) error {
 	if comment.Type != CommentTypeCode && comment.Type != CommentTypeComment {
 		return nil
 	}
-	if err := comment.loadIssue(e); err != nil {
+	if err := comment.loadIssue(db.GetEngine(stdCtx)); err != nil {
 		return err
 	}
 	ctx := &crossReferencesContext{
@@ -254,7 +256,7 @@ func (comment *Comment) addCrossReferences(e db.Engine, doer *User, removeOld bo
 		OrigComment: comment,
 		RemoveOld:   removeOld,
 	}
-	return comment.Issue.createCrossReferences(e, ctx, "", comment.Content)
+	return comment.Issue.createCrossReferences(stdCtx, ctx, "", comment.Content)
 }
 
 func (comment *Comment) neuterCrossReferences(e db.Engine) error {
diff --git a/models/issue_xref_test.go b/models/issue_xref_test.go
index 551c29dcba..c40e545fa8 100644
--- a/models/issue_xref_test.go
+++ b/models/issue_xref_test.go
@@ -140,19 +140,18 @@ func testCreateIssue(t *testing.T, repo, doer int64, title, content string, ispu
 		Index:    idx,
 	}
 
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-
-	assert.NoError(t, sess.Begin())
-	err = newIssue(sess, d, NewIssueOptions{
+	ctx, committer, err := db.TxContext()
+	assert.NoError(t, err)
+	defer committer.Close()
+	err = newIssue(ctx, d, NewIssueOptions{
 		Repo:  r,
 		Issue: i,
 	})
 	assert.NoError(t, err)
-	i, err = getIssueByID(sess, i.ID)
+	i, err = getIssueByID(db.GetEngine(ctx), i.ID)
 	assert.NoError(t, err)
-	assert.NoError(t, i.addCrossReferences(sess, d, false))
-	assert.NoError(t, sess.Commit())
+	assert.NoError(t, i.addCrossReferences(ctx, d, false))
+	assert.NoError(t, committer.Commit())
 	return i
 }
 
@@ -171,12 +170,12 @@ func testCreateComment(t *testing.T, repo, doer, issue int64, content string) *C
 	i := unittest.AssertExistsAndLoadBean(t, &Issue{ID: issue}).(*Issue)
 	c := &Comment{Type: CommentTypeComment, PosterID: doer, Poster: d, IssueID: issue, Issue: i, Content: content}
 
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	assert.NoError(t, sess.Begin())
-	_, err := sess.Insert(c)
+	ctx, committer, err := db.TxContext()
+	assert.NoError(t, err)
+	defer committer.Close()
+	err = db.Insert(ctx, c)
 	assert.NoError(t, err)
-	assert.NoError(t, c.addCrossReferences(sess, d, false))
-	assert.NoError(t, sess.Commit())
+	assert.NoError(t, c.addCrossReferences(ctx, d, false))
+	assert.NoError(t, committer.Commit())
 	return c
 }
diff --git a/models/notification.go b/models/notification.go
index 1e18073618..3c252d123d 100644
--- a/models/notification.go
+++ b/models/notification.go
@@ -5,6 +5,7 @@
 package models
 
 import (
+	"context"
 	"fmt"
 	"net/url"
 	"strconv"
@@ -391,14 +392,15 @@ func countUnread(e db.Engine, userID int64) int64 {
 
 // LoadAttributes load Repo Issue User and Comment if not loaded
 func (n *Notification) LoadAttributes() (err error) {
-	return n.loadAttributes(db.GetEngine(db.DefaultContext))
+	return n.loadAttributes(db.DefaultContext)
 }
 
-func (n *Notification) loadAttributes(e db.Engine) (err error) {
+func (n *Notification) loadAttributes(ctx context.Context) (err error) {
+	e := db.GetEngine(ctx)
 	if err = n.loadRepo(e); err != nil {
 		return
 	}
-	if err = n.loadIssue(e); err != nil {
+	if err = n.loadIssue(ctx); err != nil {
 		return
 	}
 	if err = n.loadUser(e); err != nil {
@@ -420,13 +422,13 @@ func (n *Notification) loadRepo(e db.Engine) (err error) {
 	return nil
 }
 
-func (n *Notification) loadIssue(e db.Engine) (err error) {
+func (n *Notification) loadIssue(ctx context.Context) (err error) {
 	if n.Issue == nil && n.IssueID != 0 {
-		n.Issue, err = getIssueByID(e, n.IssueID)
+		n.Issue, err = getIssueByID(db.GetEngine(ctx), n.IssueID)
 		if err != nil {
 			return fmt.Errorf("getIssueByID [%d]: %v", n.IssueID, err)
 		}
-		return n.Issue.loadAttributes(e)
+		return n.Issue.loadAttributes(ctx)
 	}
 	return nil
 }
@@ -464,7 +466,7 @@ func (n *Notification) GetRepo() (*Repository, error) {
 
 // GetIssue returns the issue of the notification
 func (n *Notification) GetIssue() (*Issue, error) {
-	return n.Issue, n.loadIssue(db.GetEngine(db.DefaultContext))
+	return n.Issue, n.loadIssue(db.DefaultContext)
 }
 
 // HTMLURL formats a URL-string to the notification
diff --git a/models/project_issue.go b/models/project_issue.go
index 4e3bc0039d..75e74295b5 100644
--- a/models/project_issue.go
+++ b/models/project_issue.go
@@ -5,11 +5,10 @@
 package models
 
 import (
+	"context"
 	"fmt"
 
 	"code.gitea.io/gitea/models/db"
-
-	"xorm.io/xorm"
 )
 
 // ProjectIssue saves relation from issue to a project
@@ -132,20 +131,21 @@ func (p *Project) NumOpenIssues() int {
 
 // ChangeProjectAssign changes the project associated with an issue
 func ChangeProjectAssign(issue *Issue, doer *User, newProjectID int64) error {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
 
-	if err := addUpdateIssueProject(sess, issue, doer, newProjectID); err != nil {
+	if err := addUpdateIssueProject(ctx, issue, doer, newProjectID); err != nil {
 		return err
 	}
 
-	return sess.Commit()
+	return committer.Commit()
 }
 
-func addUpdateIssueProject(e *xorm.Session, issue *Issue, doer *User, newProjectID int64) error {
+func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *User, newProjectID int64) error {
+	e := db.GetEngine(ctx)
 	oldProjectID := issue.projectID(e)
 
 	if _, err := e.Where("project_issue.issue_id=?", issue.ID).Delete(&ProjectIssue{}); err != nil {
@@ -157,7 +157,7 @@ func addUpdateIssueProject(e *xorm.Session, issue *Issue, doer *User, newProject
 	}
 
 	if oldProjectID > 0 || newProjectID > 0 {
-		if _, err := createComment(e, &CreateCommentOptions{
+		if _, err := createComment(ctx, &CreateCommentOptions{
 			Type:         CommentTypeProject,
 			Doer:         doer,
 			Repo:         issue.Repo,
diff --git a/models/pull.go b/models/pull.go
index 6bcc30e901..3bfebee118 100644
--- a/models/pull.go
+++ b/models/pull.go
@@ -394,11 +394,12 @@ func (pr *PullRequest) SetMerged() (bool, error) {
 
 	pr.HasMerged = true
 
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return false, err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	if _, err := sess.Exec("UPDATE `issue` SET `repo_id` = `repo_id` WHERE `id` = ?", pr.IssueID); err != nil {
 		return false, err
@@ -432,7 +433,7 @@ func (pr *PullRequest) SetMerged() (bool, error) {
 		return false, err
 	}
 
-	if _, err := pr.Issue.changeStatus(sess, pr.Merger, true, true); err != nil {
+	if _, err := pr.Issue.changeStatus(ctx, pr.Merger, true, true); err != nil {
 		return false, fmt.Errorf("Issue.changeStatus: %v", err)
 	}
 
@@ -441,7 +442,7 @@ func (pr *PullRequest) SetMerged() (bool, error) {
 		return false, fmt.Errorf("Failed to update pr[%d]: %v", pr.ID, err)
 	}
 
-	if err := sess.Commit(); err != nil {
+	if err := committer.Commit(); err != nil {
 		return false, fmt.Errorf("Commit: %v", err)
 	}
 	return true, nil
@@ -456,13 +457,13 @@ func NewPullRequest(repo *Repository, issue *Issue, labelIDs []int64, uuids []st
 
 	issue.Index = idx
 
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err = sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return err
 	}
+	defer committer.Close()
 
-	if err = newIssue(sess, issue.Poster, NewIssueOptions{
+	if err = newIssue(ctx, issue.Poster, NewIssueOptions{
 		Repo:        repo,
 		Issue:       issue,
 		LabelIDs:    labelIDs,
@@ -478,11 +479,11 @@ func NewPullRequest(repo *Repository, issue *Issue, labelIDs []int64, uuids []st
 	pr.Index = issue.Index
 	pr.BaseRepo = repo
 	pr.IssueID = issue.ID
-	if _, err = sess.Insert(pr); err != nil {
+	if err = db.Insert(ctx, pr); err != nil {
 		return fmt.Errorf("insert pull repo: %v", err)
 	}
 
-	if err = sess.Commit(); err != nil {
+	if err = committer.Commit(); err != nil {
 		return fmt.Errorf("Commit: %v", err)
 	}
 
diff --git a/models/release.go b/models/release.go
index f7bd67b8ca..69d2ceda5e 100644
--- a/models/release.go
+++ b/models/release.go
@@ -14,6 +14,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
@@ -36,14 +37,14 @@ type Release struct {
 	Title            string
 	Sha1             string `xorm:"VARCHAR(40)"`
 	NumCommits       int64
-	NumCommitsBehind int64              `xorm:"-"`
-	Note             string             `xorm:"TEXT"`
-	RenderedNote     string             `xorm:"-"`
-	IsDraft          bool               `xorm:"NOT NULL DEFAULT false"`
-	IsPrerelease     bool               `xorm:"NOT NULL DEFAULT false"`
-	IsTag            bool               `xorm:"NOT NULL DEFAULT false"`
-	Attachments      []*Attachment      `xorm:"-"`
-	CreatedUnix      timeutil.TimeStamp `xorm:"INDEX"`
+	NumCommitsBehind int64                    `xorm:"-"`
+	Note             string                   `xorm:"TEXT"`
+	RenderedNote     string                   `xorm:"-"`
+	IsDraft          bool                     `xorm:"NOT NULL DEFAULT false"`
+	IsPrerelease     bool                     `xorm:"NOT NULL DEFAULT false"`
+	IsTag            bool                     `xorm:"NOT NULL DEFAULT false"`
+	Attachments      []*repo_model.Attachment `xorm:"-"`
+	CreatedUnix      timeutil.TimeStamp       `xorm:"INDEX"`
 }
 
 func init() {
@@ -126,7 +127,7 @@ func UpdateRelease(ctx context.Context, rel *Release) error {
 // AddReleaseAttachments adds a release attachments
 func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs []string) (err error) {
 	// Check attachments
-	attachments, err := getAttachmentsByUUIDs(db.GetEngine(ctx), attachmentUUIDs)
+	attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, attachmentUUIDs)
 	if err != nil {
 		return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %v", attachmentUUIDs, err)
 	}
@@ -295,9 +296,9 @@ func getReleaseAttachments(e db.Engine, rels ...*Release) (err error) {
 
 	// Sort
 	sortedRels := releaseMetaSearch{ID: make([]int64, len(rels)), Rel: make([]*Release, len(rels))}
-	var attachments []*Attachment
+	var attachments []*repo_model.Attachment
 	for index, element := range rels {
-		element.Attachments = []*Attachment{}
+		element.Attachments = []*repo_model.Attachment{}
 		sortedRels.ID[index] = element.ID
 		sortedRels.Rel[index] = element
 	}
@@ -307,7 +308,7 @@ func getReleaseAttachments(e db.Engine, rels ...*Release) (err error) {
 	err = e.
 		Asc("release_id", "name").
 		In("release_id", sortedRels.ID).
-		Find(&attachments, Attachment{})
+		Find(&attachments, repo_model.Attachment{})
 	if err != nil {
 		return err
 	}
diff --git a/models/repo.go b/models/repo.go
index 3877502926..5921441afa 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -25,6 +25,7 @@ import (
 
 	admin_model "code.gitea.io/gitea/models/admin"
 	"code.gitea.io/gitea/models/db"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/models/webhook"
 	"code.gitea.io/gitea/modules/lfs"
@@ -1485,7 +1486,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
 		}
 	}
 
-	attachments := make([]*Attachment, 0, 20)
+	attachments := make([]*repo_model.Attachment, 0, 20)
 	if err = sess.Join("INNER", "`release`", "`release`.id = `attachment`.release_id").
 		Where("`release`.repo_id = ?", repoID).
 		Find(&attachments); err != nil {
@@ -1620,7 +1621,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
 	}
 
 	// Get all attachments with both issue_id and release_id are zero
-	var newAttachments []*Attachment
+	var newAttachments []*repo_model.Attachment
 	if err := sess.Where(builder.Eq{
 		"repo_id":    repo.ID,
 		"issue_id":   0,
@@ -1634,7 +1635,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
 		newAttachmentPaths = append(newAttachmentPaths, attach.RelativePath())
 	}
 
-	if _, err := sess.Where("repo_id=?", repo.ID).Delete(new(Attachment)); err != nil {
+	if _, err := sess.Where("repo_id=?", repo.ID).Delete(new(repo_model.Attachment)); err != nil {
 		return err
 	}
 
@@ -2191,3 +2192,27 @@ func IterateRepository(f func(repo *Repository) error) error {
 		}
 	}
 }
+
+// LinkedRepository returns the linked repo if any
+func LinkedRepository(a *repo_model.Attachment) (*Repository, unit.Type, error) {
+	if a.IssueID != 0 {
+		iss, err := GetIssueByID(a.IssueID)
+		if err != nil {
+			return nil, unit.TypeIssues, err
+		}
+		repo, err := GetRepositoryByID(iss.RepoID)
+		unitType := unit.TypeIssues
+		if iss.IsPull {
+			unitType = unit.TypePullRequests
+		}
+		return repo, unitType, err
+	} else if a.ReleaseID != 0 {
+		rel, err := GetReleaseByID(a.ReleaseID)
+		if err != nil {
+			return nil, unit.TypeReleases, err
+		}
+		repo, err := GetRepositoryByID(rel.RepoID)
+		return repo, unit.TypeReleases, err
+	}
+	return nil, -1, nil
+}
diff --git a/models/attachment.go b/models/repo/attachment.go
similarity index 80%
rename from models/attachment.go
rename to models/repo/attachment.go
index 34edc676cf..3fb331a202 100644
--- a/models/attachment.go
+++ b/models/repo/attachment.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 repo
 
 import (
 	"context"
@@ -11,12 +11,9 @@ import (
 	"path"
 
 	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/storage"
 	"code.gitea.io/gitea/modules/timeutil"
-
-	"xorm.io/xorm"
 )
 
 // Attachment represent a attachment of issue/comment/release.
@@ -63,35 +60,34 @@ func (a *Attachment) DownloadURL() string {
 	return setting.AppURL + "attachments/" + url.PathEscape(a.UUID)
 }
 
-// LinkedRepository returns the linked repo if any
-func (a *Attachment) LinkedRepository() (*Repository, unit.Type, error) {
-	if a.IssueID != 0 {
-		iss, err := GetIssueByID(a.IssueID)
-		if err != nil {
-			return nil, unit.TypeIssues, err
-		}
-		repo, err := GetRepositoryByID(iss.RepoID)
-		unitType := unit.TypeIssues
-		if iss.IsPull {
-			unitType = unit.TypePullRequests
-		}
-		return repo, unitType, err
-	} else if a.ReleaseID != 0 {
-		rel, err := GetReleaseByID(a.ReleaseID)
-		if err != nil {
-			return nil, unit.TypeReleases, err
-		}
-		repo, err := GetRepositoryByID(rel.RepoID)
-		return repo, unit.TypeReleases, err
-	}
-	return nil, -1, nil
-}
-
 // GetAttachmentByID returns attachment by given id
 func GetAttachmentByID(id int64) (*Attachment, error) {
 	return getAttachmentByID(db.GetEngine(db.DefaultContext), id)
 }
 
+//    _____   __    __                .__                           __
+//   /  _  \_/  |__/  |______    ____ |  |__   _____   ____   _____/  |_
+//  /  /_\  \   __\   __\__  \ _/ ___\|  |  \ /     \_/ __ \ /    \   __\
+// /    |    \  |  |  |  / __ \\  \___|   Y  \  Y Y  \  ___/|   |  \  |
+// \____|__  /__|  |__| (____  /\___  >___|  /__|_|  /\___  >___|  /__|
+//         \/                \/     \/     \/      \/     \/     \/
+
+// ErrAttachmentNotExist represents a "AttachmentNotExist" kind of error.
+type ErrAttachmentNotExist struct {
+	ID   int64
+	UUID string
+}
+
+// IsErrAttachmentNotExist checks if an error is a ErrAttachmentNotExist.
+func IsErrAttachmentNotExist(err error) bool {
+	_, ok := err.(ErrAttachmentNotExist)
+	return ok
+}
+
+func (err ErrAttachmentNotExist) Error() string {
+	return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
+}
+
 func getAttachmentByID(e db.Engine, id int64) (*Attachment, error) {
 	attach := &Attachment{}
 	if has, err := e.ID(id).Get(attach); err != nil {
@@ -143,24 +139,26 @@ func GetAttachmentByReleaseIDFileName(releaseID int64, fileName string) (*Attach
 	return getAttachmentByReleaseIDFileName(db.GetEngine(db.DefaultContext), releaseID, fileName)
 }
 
-func getAttachmentsByIssueID(e db.Engine, issueID int64) ([]*Attachment, error) {
+// GetAttachmentsByIssueIDCtx returns all attachments of an issue.
+func GetAttachmentsByIssueIDCtx(ctx context.Context, issueID int64) ([]*Attachment, error) {
 	attachments := make([]*Attachment, 0, 10)
-	return attachments, e.Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments)
+	return attachments, db.GetEngine(ctx).Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments)
 }
 
 // GetAttachmentsByIssueID returns all attachments of an issue.
 func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) {
-	return getAttachmentsByIssueID(db.GetEngine(db.DefaultContext), issueID)
+	return GetAttachmentsByIssueIDCtx(db.DefaultContext, issueID)
 }
 
 // GetAttachmentsByCommentID returns all attachments if comment by given ID.
 func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) {
-	return getAttachmentsByCommentID(db.GetEngine(db.DefaultContext), commentID)
+	return GetAttachmentsByCommentIDCtx(db.DefaultContext, commentID)
 }
 
-func getAttachmentsByCommentID(e db.Engine, commentID int64) ([]*Attachment, error) {
+// GetAttachmentsByCommentIDCtx returns all attachments if comment by given ID.
+func GetAttachmentsByCommentIDCtx(ctx context.Context, commentID int64) ([]*Attachment, error) {
 	attachments := make([]*Attachment, 0, 10)
-	return attachments, e.Where("comment_id=?", commentID).Find(&attachments)
+	return attachments, db.GetEngine(ctx).Where("comment_id=?", commentID).Find(&attachments)
 }
 
 // getAttachmentByReleaseIDFileName return a file based on the the following infos:
@@ -229,7 +227,7 @@ func DeleteAttachmentsByComment(commentID int64, remove bool) (int, error) {
 
 // UpdateAttachment updates the given attachment in database
 func UpdateAttachment(atta *Attachment) error {
-	return updateAttachment(db.GetEngine(db.DefaultContext), atta)
+	return UpdateAttachmentCtx(db.DefaultContext, atta)
 }
 
 // UpdateAttachmentByUUID Updates attachment via uuid
@@ -241,15 +239,16 @@ func UpdateAttachmentByUUID(ctx context.Context, attach *Attachment, cols ...str
 	return err
 }
 
-func updateAttachment(e db.Engine, atta *Attachment) error {
-	var sess *xorm.Session
+// UpdateAttachmentCtx updates the given attachment in database
+func UpdateAttachmentCtx(ctx context.Context, atta *Attachment) error {
+	var sess = db.GetEngine(ctx).Cols("name", "issue_id", "release_id", "comment_id", "download_count")
 	if atta.ID != 0 && atta.UUID == "" {
-		sess = e.ID(atta.ID)
+		sess = sess.ID(atta.ID)
 	} else {
 		// Use uuid only if id is not set and uuid is set
-		sess = e.Where("uuid = ?", atta.UUID)
+		sess = sess.Where("uuid = ?", atta.UUID)
 	}
-	_, err := sess.Cols("name", "issue_id", "release_id", "comment_id", "download_count").Update(atta)
+	_, err := sess.Update(atta)
 	return err
 }
 
diff --git a/models/attachment_test.go b/models/repo/attachment_test.go
similarity index 78%
rename from models/attachment_test.go
rename to models/repo/attachment_test.go
index 4081811d19..53c28d5324 100644
--- a/models/attachment_test.go
+++ b/models/repo/attachment_test.go
@@ -2,13 +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 repo
 
 import (
 	"testing"
 
 	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/models/unittest"
 
 	"github.com/stretchr/testify/assert"
@@ -103,30 +102,3 @@ func TestGetAttachmentsByUUIDs(t *testing.T) {
 	assert.Equal(t, int64(1), attachList[0].IssueID)
 	assert.Equal(t, int64(5), attachList[1].IssueID)
 }
-
-func TestLinkedRepository(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-	testCases := []struct {
-		name             string
-		attachID         int64
-		expectedRepo     *Repository
-		expectedUnitType unit.Type
-	}{
-		{"LinkedIssue", 1, &Repository{ID: 1}, unit.TypeIssues},
-		{"LinkedComment", 3, &Repository{ID: 1}, unit.TypePullRequests},
-		{"LinkedRelease", 9, &Repository{ID: 1}, unit.TypeReleases},
-		{"Notlinked", 10, nil, -1},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			attach, err := GetAttachmentByID(tc.attachID)
-			assert.NoError(t, err)
-			repo, unitType, err := attach.LinkedRepository()
-			assert.NoError(t, err)
-			if tc.expectedRepo != nil {
-				assert.Equal(t, tc.expectedRepo.ID, repo.ID)
-			}
-			assert.Equal(t, tc.expectedUnitType, unitType)
-		})
-	}
-}
diff --git a/models/repo/main_test.go b/models/repo/main_test.go
new file mode 100644
index 0000000000..ac62df9d9c
--- /dev/null
+++ b/models/repo/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 repo
+
+import (
+	"path/filepath"
+	"testing"
+
+	"code.gitea.io/gitea/models/unittest"
+)
+
+func TestMain(m *testing.M) {
+	unittest.MainTest(m, filepath.Join("..", ".."),
+		"attachment.yml",
+	)
+}
diff --git a/models/repo_test.go b/models/repo_test.go
index ec1bcc0487..5cc396eb76 100644
--- a/models/repo_test.go
+++ b/models/repo_test.go
@@ -13,6 +13,7 @@ import (
 	"testing"
 
 	"code.gitea.io/gitea/models/db"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/models/unittest"
 	"code.gitea.io/gitea/modules/markup"
@@ -224,3 +225,30 @@ func TestRepoGetReviewerTeams(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Len(t, teams, 2)
 }
+
+func TestLinkedRepository(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+	testCases := []struct {
+		name             string
+		attachID         int64
+		expectedRepo     *Repository
+		expectedUnitType unit.Type
+	}{
+		{"LinkedIssue", 1, &Repository{ID: 1}, unit.TypeIssues},
+		{"LinkedComment", 3, &Repository{ID: 1}, unit.TypePullRequests},
+		{"LinkedRelease", 9, &Repository{ID: 1}, unit.TypeReleases},
+		{"Notlinked", 10, nil, -1},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			attach, err := repo_model.GetAttachmentByID(tc.attachID)
+			assert.NoError(t, err)
+			repo, unitType, err := LinkedRepository(attach)
+			assert.NoError(t, err)
+			if tc.expectedRepo != nil {
+				assert.Equal(t, tc.expectedRepo.ID, repo.ID)
+			}
+			assert.Equal(t, tc.expectedUnitType, unitType)
+		})
+	}
+}
diff --git a/models/review.go b/models/review.go
index ab275ecb26..62172bac01 100644
--- a/models/review.go
+++ b/models/review.go
@@ -359,11 +359,12 @@ func IsContentEmptyErr(err error) bool {
 
 // SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist
 func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, commitID string, stale bool, attachmentUUIDs []string) (*Review, *Comment, error) {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return nil, nil, err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	official := false
 
@@ -429,7 +430,7 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, comm
 		}
 	}
 
-	comm, err := createComment(sess, &CreateCommentOptions{
+	comm, err := createComment(ctx, &CreateCommentOptions{
 		Type:        CommentTypeReview,
 		Doer:        doer,
 		Content:     review.Content,
@@ -464,7 +465,7 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, comm
 	}
 
 	comm.Review = review
-	return review, comm, sess.Commit()
+	return review, comm, committer.Commit()
 }
 
 // GetReviewersByIssueID gets the latest review of each reviewer for a pull request
@@ -631,11 +632,12 @@ func InsertReviews(reviews []*Review) error {
 
 // AddReviewRequest add a review request from one reviewer
 func AddReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return nil, err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	review, err := getReviewByIssueIDAndUserID(sess, issue.ID, reviewer.ID)
 	if err != nil && !IsErrReviewNotExist(err) {
@@ -667,7 +669,7 @@ func AddReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) {
 		return nil, err
 	}
 
-	comment, err := createComment(sess, &CreateCommentOptions{
+	comment, err := createComment(ctx, &CreateCommentOptions{
 		Type:            CommentTypeReviewRequest,
 		Doer:            doer,
 		Repo:            issue.Repo,
@@ -680,16 +682,17 @@ func AddReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) {
 		return nil, err
 	}
 
-	return comment, sess.Commit()
+	return comment, committer.Commit()
 }
 
 // RemoveReviewRequest remove a review request from one reviewer
 func RemoveReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return nil, err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	review, err := getReviewByIssueIDAndUserID(sess, issue.ID, reviewer.ID)
 	if err != nil && !IsErrReviewNotExist(err) {
@@ -721,7 +724,7 @@ func RemoveReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) {
 		}
 	}
 
-	comment, err := createComment(sess, &CreateCommentOptions{
+	comment, err := createComment(ctx, &CreateCommentOptions{
 		Type:            CommentTypeReviewRequest,
 		Doer:            doer,
 		Repo:            issue.Repo,
@@ -733,16 +736,17 @@ func RemoveReviewRequest(issue *Issue, reviewer, doer *User) (*Comment, error) {
 		return nil, err
 	}
 
-	return comment, sess.Commit()
+	return comment, committer.Commit()
 }
 
 // AddTeamReviewRequest add a review request from one team
 func AddTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment, error) {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return nil, err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	review, err := getTeamReviewerByIssueIDAndTeamID(sess, issue.ID, reviewer.ID)
 	if err != nil && !IsErrReviewNotExist(err) {
@@ -779,7 +783,7 @@ func AddTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment, e
 		}
 	}
 
-	comment, err := createComment(sess, &CreateCommentOptions{
+	comment, err := createComment(ctx, &CreateCommentOptions{
 		Type:            CommentTypeReviewRequest,
 		Doer:            doer,
 		Repo:            issue.Repo,
@@ -792,16 +796,17 @@ func AddTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment, e
 		return nil, fmt.Errorf("createComment(): %v", err)
 	}
 
-	return comment, sess.Commit()
+	return comment, committer.Commit()
 }
 
 // RemoveTeamReviewRequest remove a review request from one team
 func RemoveTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment, error) {
-	sess := db.NewSession(db.DefaultContext)
-	defer sess.Close()
-	if err := sess.Begin(); err != nil {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
 		return nil, err
 	}
+	defer committer.Close()
+	sess := db.GetEngine(ctx)
 
 	review, err := getTeamReviewerByIssueIDAndTeamID(sess, issue.ID, reviewer.ID)
 	if err != nil && !IsErrReviewNotExist(err) {
@@ -836,10 +841,10 @@ func RemoveTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment
 	}
 
 	if doer == nil {
-		return nil, sess.Commit()
+		return nil, committer.Commit()
 	}
 
-	comment, err := createComment(sess, &CreateCommentOptions{
+	comment, err := createComment(ctx, &CreateCommentOptions{
 		Type:            CommentTypeReviewRequest,
 		Doer:            doer,
 		Repo:            issue.Repo,
@@ -851,7 +856,7 @@ func RemoveTeamReviewRequest(issue *Issue, reviewer *Team, doer *User) (*Comment
 		return nil, fmt.Errorf("createComment(): %v", err)
 	}
 
-	return comment, sess.Commit()
+	return comment, committer.Commit()
 }
 
 // MarkConversation Add or remove Conversation mark for a code comment
diff --git a/models/statistic.go b/models/statistic.go
index 1849497cd9..cb300c1a87 100644
--- a/models/statistic.go
+++ b/models/statistic.go
@@ -7,6 +7,7 @@ package models
 import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/login"
+	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/models/webhook"
 	"code.gitea.io/gitea/modules/setting"
@@ -102,7 +103,7 @@ func GetStatistic() (stats Statistic) {
 	stats.Counter.Label, _ = e.Count(new(Label))
 	stats.Counter.HookTask, _ = e.Count(new(webhook.HookTask))
 	stats.Counter.Team, _ = e.Count(new(Team))
-	stats.Counter.Attachment, _ = e.Count(new(Attachment))
+	stats.Counter.Attachment, _ = e.Count(new(repo_model.Attachment))
 	stats.Counter.Project, _ = e.Count(new(Project))
 	stats.Counter.ProjectBoard, _ = e.Count(new(ProjectBoard))
 	return
diff --git a/modules/convert/release.go b/modules/convert/release.go
index 70f0d6e764..955d3ff05f 100644
--- a/modules/convert/release.go
+++ b/modules/convert/release.go
@@ -6,6 +6,7 @@ package convert
 
 import (
 	"code.gitea.io/gitea/models"
+	repo_model "code.gitea.io/gitea/models/repo"
 	api "code.gitea.io/gitea/modules/structs"
 )
 
@@ -35,7 +36,7 @@ func ToRelease(r *models.Release) *api.Release {
 }
 
 // ToReleaseAttachment converts models.Attachment to api.Attachment
-func ToReleaseAttachment(a *models.Attachment) *api.Attachment {
+func ToReleaseAttachment(a *repo_model.Attachment) *api.Attachment {
 	return &api.Attachment{
 		ID:            a.ID,
 		Name:          a.Name,
diff --git a/modules/doctor/dbconsistency.go b/modules/doctor/dbconsistency.go
index 040f73abcd..e5f0c5678d 100644
--- a/modules/doctor/dbconsistency.go
+++ b/modules/doctor/dbconsistency.go
@@ -10,6 +10,7 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/migrations"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 )
@@ -110,8 +111,8 @@ func checkDBConsistency(logger log.Logger, autofix bool) error {
 		// find attachments without existing issues or releases
 		{
 			Name:    "Orphaned Attachments without existing issues or releases",
-			Counter: models.CountOrphanedAttachments,
-			Fixer:   asFixer(models.DeleteOrphanedAttachments),
+			Counter: repo_model.CountOrphanedAttachments,
+			Fixer:   asFixer(repo_model.DeleteOrphanedAttachments),
 		},
 		// find null archived repositories
 		{
diff --git a/modules/doctor/storage.go b/modules/doctor/storage.go
index cb06d66204..d2b7f5ee13 100644
--- a/modules/doctor/storage.go
+++ b/modules/doctor/storage.go
@@ -5,7 +5,7 @@
 package doctor
 
 import (
-	"code.gitea.io/gitea/models"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/storage"
 )
@@ -21,7 +21,7 @@ func checkAttachmentStorageFiles(logger log.Logger, autofix bool) error {
 		if err != nil {
 			return err
 		}
-		exist, err := models.ExistAttachmentsByUUID(stat.Name())
+		exist, err := repo_model.ExistAttachmentsByUUID(stat.Name())
 		if err != nil {
 			return err
 		}
diff --git a/routers/api/v1/repo/release_attachment.go b/routers/api/v1/repo/release_attachment.go
index d1533e2b5a..fe82915a94 100644
--- a/routers/api/v1/repo/release_attachment.go
+++ b/routers/api/v1/repo/release_attachment.go
@@ -8,6 +8,7 @@ import (
 	"net/http"
 
 	"code.gitea.io/gitea/models"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/convert"
 	"code.gitea.io/gitea/modules/log"
@@ -54,7 +55,7 @@ func GetReleaseAttachment(ctx *context.APIContext) {
 
 	releaseID := ctx.ParamsInt64(":id")
 	attachID := ctx.ParamsInt64(":asset")
-	attach, err := models.GetAttachmentByID(attachID)
+	attach, err := repo_model.GetAttachmentByID(attachID)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
 		return
@@ -241,7 +242,7 @@ func EditReleaseAttachment(ctx *context.APIContext) {
 	// Check if release exists an load release
 	releaseID := ctx.ParamsInt64(":id")
 	attachID := ctx.ParamsInt64(":asset")
-	attach, err := models.GetAttachmentByID(attachID)
+	attach, err := repo_model.GetAttachmentByID(attachID)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
 		return
@@ -256,7 +257,7 @@ func EditReleaseAttachment(ctx *context.APIContext) {
 		attach.Name = form.Name
 	}
 
-	if err := models.UpdateAttachment(attach); err != nil {
+	if err := repo_model.UpdateAttachment(attach); err != nil {
 		ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach)
 	}
 	ctx.JSON(http.StatusCreated, convert.ToReleaseAttachment(attach))
@@ -299,7 +300,7 @@ func DeleteReleaseAttachment(ctx *context.APIContext) {
 	// Check if release exists an load release
 	releaseID := ctx.ParamsInt64(":id")
 	attachID := ctx.ParamsInt64(":asset")
-	attach, err := models.GetAttachmentByID(attachID)
+	attach, err := repo_model.GetAttachmentByID(attachID)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
 		return
@@ -311,7 +312,7 @@ func DeleteReleaseAttachment(ctx *context.APIContext) {
 	}
 	// FIXME Should prove the existence of the given repo, but results in unnecessary database requests
 
-	if err := models.DeleteAttachment(attach, true); err != nil {
+	if err := repo_model.DeleteAttachment(attach, true); err != nil {
 		ctx.Error(http.StatusInternalServerError, "DeleteAttachment", err)
 		return
 	}
diff --git a/routers/web/repo/attachment.go b/routers/web/repo/attachment.go
index 3968d27652..303eee24d1 100644
--- a/routers/web/repo/attachment.go
+++ b/routers/web/repo/attachment.go
@@ -9,6 +9,7 @@ import (
 	"net/http"
 
 	"code.gitea.io/gitea/models"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/httpcache"
 	"code.gitea.io/gitea/modules/log"
@@ -62,7 +63,7 @@ func uploadAttachment(ctx *context.Context, repoID int64, allowedTypes string) {
 // DeleteAttachment response for deleting issue's attachment
 func DeleteAttachment(ctx *context.Context) {
 	file := ctx.FormString("file")
-	attach, err := models.GetAttachmentByUUID(file)
+	attach, err := repo_model.GetAttachmentByUUID(file)
 	if err != nil {
 		ctx.Error(http.StatusBadRequest, err.Error())
 		return
@@ -71,7 +72,7 @@ func DeleteAttachment(ctx *context.Context) {
 		ctx.Error(http.StatusForbidden)
 		return
 	}
-	err = models.DeleteAttachment(attach, true)
+	err = repo_model.DeleteAttachment(attach, true)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, fmt.Sprintf("DeleteAttachment: %v", err))
 		return
@@ -83,9 +84,9 @@ func DeleteAttachment(ctx *context.Context) {
 
 // GetAttachment serve attachements
 func GetAttachment(ctx *context.Context) {
-	attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid"))
+	attach, err := repo_model.GetAttachmentByUUID(ctx.Params(":uuid"))
 	if err != nil {
-		if models.IsErrAttachmentNotExist(err) {
+		if repo_model.IsErrAttachmentNotExist(err) {
 			ctx.Error(http.StatusNotFound)
 		} else {
 			ctx.ServerError("GetAttachmentByUUID", err)
@@ -93,7 +94,7 @@ func GetAttachment(ctx *context.Context) {
 		return
 	}
 
-	repository, unitType, err := attach.LinkedRepository()
+	repository, unitType, err := models.LinkedRepository(attach)
 	if err != nil {
 		ctx.ServerError("LinkedRepository", err)
 		return
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 129287b96a..6cc9419763 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -18,6 +18,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
@@ -2516,7 +2517,7 @@ func GetCommentAttachments(ctx *context.Context) {
 }
 
 func updateAttachments(item interface{}, files []string) error {
-	var attachments []*models.Attachment
+	var attachments []*repo_model.Attachment
 	switch content := item.(type) {
 	case *models.Issue:
 		attachments = content.Attachments
@@ -2529,7 +2530,7 @@ func updateAttachments(item interface{}, files []string) error {
 		if util.IsStringInSlice(attachments[i].UUID, files) {
 			continue
 		}
-		if err := models.DeleteAttachment(attachments[i], true); err != nil {
+		if err := repo_model.DeleteAttachment(attachments[i], true); err != nil {
 			return err
 		}
 	}
@@ -2549,16 +2550,16 @@ func updateAttachments(item interface{}, files []string) error {
 	}
 	switch content := item.(type) {
 	case *models.Issue:
-		content.Attachments, err = models.GetAttachmentsByIssueID(content.ID)
+		content.Attachments, err = repo_model.GetAttachmentsByIssueID(content.ID)
 	case *models.Comment:
-		content.Attachments, err = models.GetAttachmentsByCommentID(content.ID)
+		content.Attachments, err = repo_model.GetAttachmentsByCommentID(content.ID)
 	default:
 		return fmt.Errorf("Unknown Type: %T", content)
 	}
 	return err
 }
 
-func attachmentsHTML(ctx *context.Context, attachments []*models.Attachment, content string) string {
+func attachmentsHTML(ctx *context.Context, attachments []*repo_model.Attachment, content string) string {
 	attachHTML, err := ctx.HTMLString(string(tplAttachment), map[string]interface{}{
 		"ctx":         ctx.Data,
 		"Attachments": attachments,
diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go
index 2d6189824d..e8fdb99ff4 100644
--- a/routers/web/repo/repo.go
+++ b/routers/web/repo/repo.go
@@ -14,6 +14,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
@@ -346,7 +347,7 @@ func RedirectDownload(ctx *context.Context) {
 	curRepo := ctx.Repo.Repository
 	releases, err := models.GetReleasesByRepoIDAndNames(db.DefaultContext, curRepo.ID, tagNames)
 	if err != nil {
-		if models.IsErrAttachmentNotExist(err) {
+		if repo_model.IsErrAttachmentNotExist(err) {
 			ctx.Error(http.StatusNotFound)
 			return
 		}
@@ -355,7 +356,7 @@ func RedirectDownload(ctx *context.Context) {
 	}
 	if len(releases) == 1 {
 		release := releases[0]
-		att, err := models.GetAttachmentByReleaseIDFileName(release.ID, fileName)
+		att, err := repo_model.GetAttachmentByReleaseIDFileName(release.ID, fileName)
 		if err != nil {
 			ctx.Error(http.StatusNotFound)
 			return
diff --git a/services/attachment/attachment.go b/services/attachment/attachment.go
index f747ccec3e..e3b65a239b 100644
--- a/services/attachment/attachment.go
+++ b/services/attachment/attachment.go
@@ -10,8 +10,8 @@ import (
 	"fmt"
 	"io"
 
-	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/storage"
 	"code.gitea.io/gitea/modules/upload"
 	"code.gitea.io/gitea/modules/util"
@@ -20,7 +20,7 @@ import (
 )
 
 // NewAttachment creates a new attachment object, but do not verify.
-func NewAttachment(attach *models.Attachment, file io.Reader) (*models.Attachment, error) {
+func NewAttachment(attach *repo_model.Attachment, file io.Reader) (*repo_model.Attachment, error) {
 	if attach.RepoID == 0 {
 		return nil, fmt.Errorf("attachment %s should belong to a repository", attach.Name)
 	}
@@ -40,7 +40,7 @@ func NewAttachment(attach *models.Attachment, file io.Reader) (*models.Attachmen
 }
 
 // UploadAttachment upload new attachment into storage and update database
-func UploadAttachment(file io.Reader, actorID, repoID, releaseID int64, fileName string, allowedTypes string) (*models.Attachment, error) {
+func UploadAttachment(file io.Reader, actorID, repoID, releaseID int64, fileName string, allowedTypes string) (*repo_model.Attachment, error) {
 	buf := make([]byte, 1024)
 	n, _ := util.ReadAtMost(file, buf)
 	buf = buf[:n]
@@ -49,7 +49,7 @@ func UploadAttachment(file io.Reader, actorID, repoID, releaseID int64, fileName
 		return nil, err
 	}
 
-	return NewAttachment(&models.Attachment{
+	return NewAttachment(&repo_model.Attachment{
 		RepoID:     repoID,
 		UploaderID: actorID,
 		ReleaseID:  releaseID,
diff --git a/services/attachment/attachment_test.go b/services/attachment/attachment_test.go
index 5bb5db11ec..a992fbf151 100644
--- a/services/attachment/attachment_test.go
+++ b/services/attachment/attachment_test.go
@@ -10,6 +10,7 @@ import (
 	"testing"
 
 	"code.gitea.io/gitea/models"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 
 	"github.com/stretchr/testify/assert"
@@ -29,14 +30,14 @@ func TestUploadAttachment(t *testing.T) {
 	assert.NoError(t, err)
 	defer f.Close()
 
-	attach, err := NewAttachment(&models.Attachment{
+	attach, err := NewAttachment(&repo_model.Attachment{
 		RepoID:     1,
 		UploaderID: user.ID,
 		Name:       filepath.Base(fPath),
 	}, f)
 	assert.NoError(t, err)
 
-	attachment, err := models.GetAttachmentByUUID(attach.UUID)
+	attachment, err := repo_model.GetAttachmentByUUID(attach.UUID)
 	assert.NoError(t, err)
 	assert.EqualValues(t, user.ID, attachment.UploaderID)
 	assert.Equal(t, int64(0), attachment.DownloadCount)
diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go
index 0f215e05d6..2394c63d76 100644
--- a/services/migrations/gitea_uploader.go
+++ b/services/migrations/gitea_uploader.go
@@ -17,6 +17,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	base "code.gitea.io/gitea/modules/migration"
@@ -295,7 +296,7 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
 					asset.Created = release.Created
 				}
 			}
-			var attach = models.Attachment{
+			var attach = repo_model.Attachment{
 				UUID:          gouuid.New().String(),
 				Name:          asset.Name,
 				DownloadCount: int64(*asset.DownloadCount),
diff --git a/services/release/release.go b/services/release/release.go
index f6f456e8fa..30274f93ef 100644
--- a/services/release/release.go
+++ b/services/release/release.go
@@ -11,6 +11,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/notification"
@@ -206,7 +207,7 @@ func UpdateRelease(doer *models.User, gitRepo *git.Repository, rel *models.Relea
 	var deletedUUIDsMap = make(map[string]bool)
 	if len(delAttachmentUUIDs) > 0 {
 		// Check attachments
-		attachments, err := models.GetAttachmentsByUUIDs(ctx, delAttachmentUUIDs)
+		attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, delAttachmentUUIDs)
 		if err != nil {
 			return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %v", delAttachmentUUIDs, err)
 		}
@@ -217,7 +218,7 @@ func UpdateRelease(doer *models.User, gitRepo *git.Repository, rel *models.Relea
 			deletedUUIDsMap[attach.UUID] = true
 		}
 
-		if _, err := models.DeleteAttachments(ctx, attachments, false); err != nil {
+		if _, err := repo_model.DeleteAttachments(ctx, attachments, false); err != nil {
 			return fmt.Errorf("DeleteAttachments [uuids: %v]: %v", delAttachmentUUIDs, err)
 		}
 	}
@@ -228,7 +229,7 @@ func UpdateRelease(doer *models.User, gitRepo *git.Repository, rel *models.Relea
 			updateAttachmentsList = append(updateAttachmentsList, k)
 		}
 		// Check attachments
-		attachments, err := models.GetAttachmentsByUUIDs(ctx, updateAttachmentsList)
+		attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, updateAttachmentsList)
 		if err != nil {
 			return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %v", updateAttachmentsList, err)
 		}
@@ -240,7 +241,7 @@ func UpdateRelease(doer *models.User, gitRepo *git.Repository, rel *models.Relea
 
 		for uuid, newName := range editAttachments {
 			if !deletedUUIDsMap[uuid] {
-				if err = models.UpdateAttachmentByUUID(ctx, &models.Attachment{
+				if err = repo_model.UpdateAttachmentByUUID(ctx, &repo_model.Attachment{
 					UUID: uuid,
 					Name: newName,
 				}, "name"); err != nil {
@@ -255,7 +256,7 @@ func UpdateRelease(doer *models.User, gitRepo *git.Repository, rel *models.Relea
 	}
 
 	for _, uuid := range delAttachmentUUIDs {
-		if err := storage.Attachments.Delete(models.AttachmentRelativePath(uuid)); err != nil {
+		if err := storage.Attachments.Delete(repo_model.AttachmentRelativePath(uuid)); err != nil {
 			// Even delete files failed, but the attachments has been removed from database, so we
 			// should not return error but only record the error on logs.
 			// users have to delete this attachments manually or we should have a
@@ -321,7 +322,7 @@ func DeleteReleaseByID(id int64, doer *models.User, delTag bool) error {
 		return fmt.Errorf("LoadAttributes: %v", err)
 	}
 
-	if err := models.DeleteAttachmentsByRelease(rel.ID); err != nil {
+	if err := repo_model.DeleteAttachmentsByRelease(rel.ID); err != nil {
 		return fmt.Errorf("DeleteAttachments: %v", err)
 	}
 
diff --git a/services/release/release_test.go b/services/release/release_test.go
index d720bf996b..92eb128f71 100644
--- a/services/release/release_test.go
+++ b/services/release/release_test.go
@@ -11,6 +11,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/models"
+	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/services/attachment"
@@ -103,7 +104,7 @@ func TestRelease_Create(t *testing.T) {
 		IsTag:        false,
 	}, nil, ""))
 
-	attach, err := attachment.NewAttachment(&models.Attachment{
+	attach, err := attachment.NewAttachment(&repo_model.Attachment{
 		RepoID:     repo.ID,
 		UploaderID: user.ID,
 		Name:       "test.txt",
@@ -236,7 +237,7 @@ func TestRelease_Update(t *testing.T) {
 	assert.Equal(t, tagName, release.TagName)
 
 	// Add new attachments
-	attach, err := attachment.NewAttachment(&models.Attachment{
+	attach, err := attachment.NewAttachment(&repo_model.Attachment{
 		RepoID:     repo.ID,
 		UploaderID: user.ID,
 		Name:       "test.txt",