From 5233051e64e90238bb7b6ddf9ecd1513e57bf8e9 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Wed, 17 Nov 2021 23:17:31 +0800
Subject: [PATCH] Move some functions into services/repository (#17677)

---
 cmd/admin.go                                  |   3 +-
 .../api_repo_get_contents_list_test.go        |   4 +-
 integrations/api_repo_get_contents_test.go    |   4 +-
 modules/repository/branch.go                  |  89 ------------
 modules/repository/hooks.go                   |  41 ------
 modules/repository/update.go                  | 136 ------------------
 routers/api/v1/repo/branch.go                 |   4 +-
 routers/init.go                               |   7 +-
 routers/web/repo/branch.go                    |   8 +-
 services/cron/tasks_extended.go               |   2 +-
 services/mirror/mirror_pull.go                |   2 +-
 services/repository/branch.go                 |  90 ++++++++++++
 {modules => services}/repository/cache.go     |   0
 services/repository/hooks.go                  |  52 +++++++
 services/repository/push.go                   | 126 +++++++++++++++-
 15 files changed, 283 insertions(+), 285 deletions(-)
 delete mode 100644 modules/repository/update.go
 rename {modules => services}/repository/cache.go (100%)
 create mode 100644 services/repository/hooks.go

diff --git a/cmd/admin.go b/cmd/admin.go
index 64106f5060..858498ed37 100644
--- a/cmd/admin.go
+++ b/cmd/admin.go
@@ -25,6 +25,7 @@ import (
 	"code.gitea.io/gitea/modules/storage"
 	auth_service "code.gitea.io/gitea/services/auth"
 	"code.gitea.io/gitea/services/auth/source/oauth2"
+	repo_service "code.gitea.io/gitea/services/repository"
 
 	"github.com/urfave/cli"
 )
@@ -612,7 +613,7 @@ func runRegenerateHooks(_ *cli.Context) error {
 	if err := initDB(ctx); err != nil {
 		return err
 	}
-	return repo_module.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
+	return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext())
 }
 
 func runRegenerateKeys(_ *cli.Context) error {
diff --git a/integrations/api_repo_get_contents_list_test.go b/integrations/api_repo_get_contents_list_test.go
index c35a345e81..823a72c726 100644
--- a/integrations/api_repo_get_contents_list_test.go
+++ b/integrations/api_repo_get_contents_list_test.go
@@ -13,9 +13,9 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/unittest"
 	"code.gitea.io/gitea/modules/git"
-	repo_module "code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
+	repo_service "code.gitea.io/gitea/services/repository"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -72,7 +72,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
 
 	// Make a new branch in repo1
 	newBranch := "test_branch"
-	err := repo_module.CreateNewBranch(user2, repo1, repo1.DefaultBranch, newBranch)
+	err := repo_service.CreateNewBranch(user2, repo1, repo1.DefaultBranch, newBranch)
 	assert.NoError(t, err)
 	// Get the commit ID of the default branch
 	gitRepo, err := git.OpenRepository(repo1.RepoPath())
diff --git a/integrations/api_repo_get_contents_test.go b/integrations/api_repo_get_contents_test.go
index 243ee229dc..67ec02b7b0 100644
--- a/integrations/api_repo_get_contents_test.go
+++ b/integrations/api_repo_get_contents_test.go
@@ -12,9 +12,9 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/unittest"
 	"code.gitea.io/gitea/modules/git"
-	repo_module "code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
+	repo_service "code.gitea.io/gitea/services/repository"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -73,7 +73,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) {
 
 	// Make a new branch in repo1
 	newBranch := "test_branch"
-	err := repo_module.CreateNewBranch(user2, repo1, repo1.DefaultBranch, newBranch)
+	err := repo_service.CreateNewBranch(user2, repo1, repo1.DefaultBranch, newBranch)
 	assert.NoError(t, err)
 	// Get the commit ID of the default branch
 	gitRepo, err := git.OpenRepository(repo1.RepoPath())
diff --git a/modules/repository/branch.go b/modules/repository/branch.go
index 275bae91e3..dcd82554d5 100644
--- a/modules/repository/branch.go
+++ b/modules/repository/branch.go
@@ -24,92 +24,3 @@ func GetBranch(repo *models.Repository, branch string) (*git.Branch, error) {
 
 	return gitRepo.GetBranch(branch)
 }
-
-// GetBranches returns branches from the repository, skipping skip initial branches and
-// returning at most limit branches, or all branches if limit is 0.
-func GetBranches(repo *models.Repository, skip, limit int) ([]*git.Branch, int, error) {
-	return git.GetBranchesByPath(repo.RepoPath(), skip, limit)
-}
-
-// checkBranchName validates branch name with existing repository branches
-func checkBranchName(repo *models.Repository, name string) error {
-	gitRepo, err := git.OpenRepository(repo.RepoPath())
-	if err != nil {
-		return err
-	}
-	defer gitRepo.Close()
-
-	branches, _, err := GetBranches(repo, 0, 0)
-	if err != nil {
-		return err
-	}
-
-	for _, branch := range branches {
-		if branch.Name == name {
-			return models.ErrBranchAlreadyExists{
-				BranchName: branch.Name,
-			}
-		} else if (len(branch.Name) < len(name) && branch.Name+"/" == name[0:len(branch.Name)+1]) ||
-			(len(branch.Name) > len(name) && name+"/" == branch.Name[0:len(name)+1]) {
-			return models.ErrBranchNameConflict{
-				BranchName: branch.Name,
-			}
-		}
-	}
-
-	if _, err := gitRepo.GetTag(name); err == nil {
-		return models.ErrTagAlreadyExists{
-			TagName: name,
-		}
-	}
-
-	return nil
-}
-
-// CreateNewBranch creates a new repository branch
-func CreateNewBranch(doer *models.User, repo *models.Repository, oldBranchName, branchName string) (err error) {
-	// Check if branch name can be used
-	if err := checkBranchName(repo, branchName); err != nil {
-		return err
-	}
-
-	if !git.IsBranchExist(repo.RepoPath(), oldBranchName) {
-		return models.ErrBranchDoesNotExist{
-			BranchName: oldBranchName,
-		}
-	}
-
-	if err := git.Push(repo.RepoPath(), git.PushOptions{
-		Remote: repo.RepoPath(),
-		Branch: fmt.Sprintf("%s:%s%s", oldBranchName, git.BranchPrefix, branchName),
-		Env:    models.PushingEnvironment(doer, repo),
-	}); err != nil {
-		if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
-			return err
-		}
-		return fmt.Errorf("Push: %v", err)
-	}
-
-	return nil
-}
-
-// CreateNewBranchFromCommit creates a new repository branch
-func CreateNewBranchFromCommit(doer *models.User, repo *models.Repository, commit, branchName string) (err error) {
-	// Check if branch name can be used
-	if err := checkBranchName(repo, branchName); err != nil {
-		return err
-	}
-
-	if err := git.Push(repo.RepoPath(), git.PushOptions{
-		Remote: repo.RepoPath(),
-		Branch: fmt.Sprintf("%s:%s%s", commit, git.BranchPrefix, branchName),
-		Env:    models.PushingEnvironment(doer, repo),
-	}); err != nil {
-		if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
-			return err
-		}
-		return fmt.Errorf("Push: %v", err)
-	}
-
-	return nil
-}
diff --git a/modules/repository/hooks.go b/modules/repository/hooks.go
index 23eee8897d..bd77423afc 100644
--- a/modules/repository/hooks.go
+++ b/modules/repository/hooks.go
@@ -5,19 +5,13 @@
 package repository
 
 import (
-	"context"
 	"fmt"
 	"os"
 	"path/filepath"
 
-	"code.gitea.io/gitea/models"
-	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/modules/git"
-	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/util"
-
-	"xorm.io/builder"
 )
 
 func getHookTemplates() (hookNames, hookTpls, giteaHookTpls []string) {
@@ -240,38 +234,3 @@ func CheckDelegateHooks(repoPath string) ([]string, error) {
 	}
 	return results, nil
 }
-
-// SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks
-// to make sure the binary and custom conf path are up-to-date.
-func SyncRepositoryHooks(ctx context.Context) error {
-	log.Trace("Doing: SyncRepositoryHooks")
-
-	if err := db.Iterate(
-		db.DefaultContext,
-		new(models.Repository),
-		builder.Gt{"id": 0},
-		func(idx int, bean interface{}) error {
-			repo := bean.(*models.Repository)
-			select {
-			case <-ctx.Done():
-				return db.ErrCancelledf("before sync repository hooks for %s", repo.FullName())
-			default:
-			}
-
-			if err := createDelegateHooks(repo.RepoPath()); err != nil {
-				return fmt.Errorf("SyncRepositoryHook: %v", err)
-			}
-			if repo.HasWiki() {
-				if err := createDelegateHooks(repo.WikiPath()); err != nil {
-					return fmt.Errorf("SyncRepositoryHook: %v", err)
-				}
-			}
-			return nil
-		},
-	); err != nil {
-		return err
-	}
-
-	log.Trace("Finished: SyncRepositoryHooks")
-	return nil
-}
diff --git a/modules/repository/update.go b/modules/repository/update.go
deleted file mode 100644
index b9a5db2a6a..0000000000
--- a/modules/repository/update.go
+++ /dev/null
@@ -1,136 +0,0 @@
-// 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 repository
-
-import (
-	"context"
-	"fmt"
-	"strings"
-	"time"
-
-	"code.gitea.io/gitea/models"
-	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/modules/git"
-	"code.gitea.io/gitea/modules/timeutil"
-)
-
-// PushUpdateAddDeleteTags updates a number of added and delete tags
-func PushUpdateAddDeleteTags(repo *models.Repository, gitRepo *git.Repository, addTags, delTags []string) error {
-	return db.WithTx(func(ctx context.Context) error {
-		if err := models.PushUpdateDeleteTagsContext(ctx, repo, delTags); err != nil {
-			return err
-		}
-		return pushUpdateAddTags(ctx, repo, gitRepo, addTags)
-	})
-}
-
-// pushUpdateAddTags updates a number of add tags
-func pushUpdateAddTags(ctx context.Context, repo *models.Repository, gitRepo *git.Repository, tags []string) error {
-	if len(tags) == 0 {
-		return nil
-	}
-
-	lowerTags := make([]string, 0, len(tags))
-	for _, tag := range tags {
-		lowerTags = append(lowerTags, strings.ToLower(tag))
-	}
-
-	releases, err := models.GetReleasesByRepoIDAndNames(ctx, repo.ID, lowerTags)
-	if err != nil {
-		return fmt.Errorf("GetReleasesByRepoIDAndNames: %v", err)
-	}
-	relMap := make(map[string]*models.Release)
-	for _, rel := range releases {
-		relMap[rel.LowerTagName] = rel
-	}
-
-	newReleases := make([]*models.Release, 0, len(lowerTags)-len(relMap))
-
-	emailToUser := make(map[string]*models.User)
-
-	for i, lowerTag := range lowerTags {
-		tag, err := gitRepo.GetTag(tags[i])
-		if err != nil {
-			return fmt.Errorf("GetTag: %v", err)
-		}
-		commit, err := tag.Commit()
-		if err != nil {
-			return fmt.Errorf("Commit: %v", err)
-		}
-
-		sig := tag.Tagger
-		if sig == nil {
-			sig = commit.Author
-		}
-		if sig == nil {
-			sig = commit.Committer
-		}
-		var author *models.User
-		var createdAt = time.Unix(1, 0)
-
-		if sig != nil {
-			var ok bool
-			author, ok = emailToUser[sig.Email]
-			if !ok {
-				author, err = models.GetUserByEmailContext(ctx, sig.Email)
-				if err != nil && !models.IsErrUserNotExist(err) {
-					return fmt.Errorf("GetUserByEmail: %v", err)
-				}
-				if author != nil {
-					emailToUser[sig.Email] = author
-				}
-			}
-			createdAt = sig.When
-		}
-
-		commitsCount, err := commit.CommitsCount()
-		if err != nil {
-			return fmt.Errorf("CommitsCount: %v", err)
-		}
-
-		rel, has := relMap[lowerTag]
-
-		if !has {
-			rel = &models.Release{
-				RepoID:       repo.ID,
-				Title:        "",
-				TagName:      tags[i],
-				LowerTagName: lowerTag,
-				Target:       "",
-				Sha1:         commit.ID.String(),
-				NumCommits:   commitsCount,
-				Note:         "",
-				IsDraft:      false,
-				IsPrerelease: false,
-				IsTag:        true,
-				CreatedUnix:  timeutil.TimeStamp(createdAt.Unix()),
-			}
-			if author != nil {
-				rel.PublisherID = author.ID
-			}
-
-			newReleases = append(newReleases, rel)
-		} else {
-			rel.Sha1 = commit.ID.String()
-			rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix())
-			rel.NumCommits = commitsCount
-			rel.IsDraft = false
-			if rel.IsTag && author != nil {
-				rel.PublisherID = author.ID
-			}
-			if err = models.UpdateRelease(ctx, rel); err != nil {
-				return fmt.Errorf("Update: %v", err)
-			}
-		}
-	}
-
-	if len(newReleases) > 0 {
-		if err = models.InsertReleasesContext(ctx, newReleases); err != nil {
-			return fmt.Errorf("Insert: %v", err)
-		}
-	}
-
-	return nil
-}
diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index c57075e3b8..2e08b56826 100644
--- a/routers/api/v1/repo/branch.go
+++ b/routers/api/v1/repo/branch.go
@@ -176,7 +176,7 @@ func CreateBranch(ctx *context.APIContext) {
 		opt.OldBranchName = ctx.Repo.Repository.DefaultBranch
 	}
 
-	err := repo_module.CreateNewBranch(ctx.User, ctx.Repo.Repository, opt.OldBranchName, opt.BranchName)
+	err := repo_service.CreateNewBranch(ctx.User, ctx.Repo.Repository, opt.OldBranchName, opt.BranchName)
 
 	if err != nil {
 		if models.IsErrBranchDoesNotExist(err) {
@@ -257,7 +257,7 @@ func ListBranches(ctx *context.APIContext) {
 
 	listOptions := utils.GetListOptions(ctx)
 	skip, _ := listOptions.GetStartEnd()
-	branches, totalNumOfBranches, err := repo_module.GetBranches(ctx.Repo.Repository, skip, listOptions.PageSize)
+	branches, totalNumOfBranches, err := repo_service.GetBranches(ctx.Repo.Repository, skip, listOptions.PageSize)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetBranches", err)
 		return
diff --git a/routers/init.go b/routers/init.go
index 0f19e7f732..762508cee5 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -25,7 +25,6 @@ import (
 	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/markup/external"
 	"code.gitea.io/gitea/modules/notification"
-	repo_module "code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/ssh"
 	"code.gitea.io/gitea/modules/storage"
@@ -45,7 +44,7 @@ import (
 	repo_migrations "code.gitea.io/gitea/services/migrations"
 	mirror_service "code.gitea.io/gitea/services/mirror"
 	pull_service "code.gitea.io/gitea/services/pull"
-	"code.gitea.io/gitea/services/repository"
+	repo_service "code.gitea.io/gitea/services/repository"
 	"code.gitea.io/gitea/services/webhook"
 
 	"gitea.com/go-chi/session"
@@ -73,7 +72,7 @@ func mustInitCtx(ctx context.Context, fn func(ctx context.Context) error) {
 func InitGitServices() {
 	setting.NewServices()
 	mustInit(storage.Init)
-	mustInit(repository.NewContext)
+	mustInit(repo_service.NewContext)
 }
 
 func syncAppPathForGit(ctx context.Context) error {
@@ -85,7 +84,7 @@ func syncAppPathForGit(ctx context.Context) error {
 		log.Info("AppPath changed from '%s' to '%s'", runtimeState.LastAppPath, setting.AppPath)
 
 		log.Info("re-sync repository hooks ...")
-		mustInitCtx(ctx, repo_module.SyncRepositoryHooks)
+		mustInitCtx(ctx, repo_service.SyncRepositoryHooks)
 
 		log.Info("re-write ssh public keys ...")
 		mustInit(models.RewriteAllPublicKeys)
diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go
index 991beeace2..10557ff3db 100644
--- a/routers/web/repo/branch.go
+++ b/routers/web/repo/branch.go
@@ -171,7 +171,7 @@ func loadBranches(ctx *context.Context, skip, limit int) ([]*Branch, int) {
 		return nil, 0
 	}
 
-	rawBranches, totalNumOfBranches, err := repo_module.GetBranches(ctx.Repo.Repository, skip, limit)
+	rawBranches, totalNumOfBranches, err := repo_service.GetBranches(ctx.Repo.Repository, skip, limit)
 	if err != nil {
 		log.Error("GetBranches: %v", err)
 		ctx.ServerError("GetBranches", err)
@@ -350,11 +350,11 @@ func CreateBranch(ctx *context.Context) {
 			err = release_service.CreateNewTag(ctx.User, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName, "")
 		}
 	} else if ctx.Repo.IsViewBranch {
-		err = repo_module.CreateNewBranch(ctx.User, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName)
+		err = repo_service.CreateNewBranch(ctx.User, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName)
 	} else if ctx.Repo.IsViewTag {
-		err = repo_module.CreateNewBranchFromCommit(ctx.User, ctx.Repo.Repository, ctx.Repo.CommitID, form.NewBranchName)
+		err = repo_service.CreateNewBranchFromCommit(ctx.User, ctx.Repo.Repository, ctx.Repo.CommitID, form.NewBranchName)
 	} else {
-		err = repo_module.CreateNewBranchFromCommit(ctx.User, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName)
+		err = repo_service.CreateNewBranchFromCommit(ctx.User, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName)
 	}
 	if err != nil {
 		if models.IsErrTagAlreadyExists(err) {
diff --git a/services/cron/tasks_extended.go b/services/cron/tasks_extended.go
index 4ddcd44537..1f115de43a 100644
--- a/services/cron/tasks_extended.go
+++ b/services/cron/tasks_extended.go
@@ -85,7 +85,7 @@ func registerRepositoryUpdateHook() {
 		RunAtStart: false,
 		Schedule:   "@every 72h",
 	}, func(ctx context.Context, _ *models.User, _ Config) error {
-		return repo_module.SyncRepositoryHooks(ctx)
+		return repo_service.SyncRepositoryHooks(ctx)
 	})
 }
 
diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go
index 2dee49f710..84644145f7 100644
--- a/services/mirror/mirror_pull.go
+++ b/services/mirror/mirror_pull.go
@@ -333,7 +333,7 @@ func runSync(ctx context.Context, m *models.Mirror) ([]*mirrorSyncResult, bool)
 	}
 
 	log.Trace("SyncMirrors [repo: %-v]: invalidating mirror branch caches...", m.Repo)
-	branches, _, err := repo_module.GetBranches(m.Repo, 0, 0)
+	branches, _, err := git.GetBranchesByPath(m.Repo.RepoPath(), 0, 0)
 	if err != nil {
 		log.Error("GetBranches: %v", err)
 		return nil, false
diff --git a/services/repository/branch.go b/services/repository/branch.go
index 5e246cbec6..83980c5bd9 100644
--- a/services/repository/branch.go
+++ b/services/repository/branch.go
@@ -6,6 +6,7 @@ package repository
 
 import (
 	"errors"
+	"fmt"
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/git"
@@ -15,6 +16,95 @@ import (
 	pull_service "code.gitea.io/gitea/services/pull"
 )
 
+// CreateNewBranch creates a new repository branch
+func CreateNewBranch(doer *models.User, repo *models.Repository, oldBranchName, branchName string) (err error) {
+	// Check if branch name can be used
+	if err := checkBranchName(repo, branchName); err != nil {
+		return err
+	}
+
+	if !git.IsBranchExist(repo.RepoPath(), oldBranchName) {
+		return models.ErrBranchDoesNotExist{
+			BranchName: oldBranchName,
+		}
+	}
+
+	if err := git.Push(repo.RepoPath(), git.PushOptions{
+		Remote: repo.RepoPath(),
+		Branch: fmt.Sprintf("%s:%s%s", oldBranchName, git.BranchPrefix, branchName),
+		Env:    models.PushingEnvironment(doer, repo),
+	}); err != nil {
+		if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
+			return err
+		}
+		return fmt.Errorf("Push: %v", err)
+	}
+
+	return nil
+}
+
+// GetBranches returns branches from the repository, skipping skip initial branches and
+// returning at most limit branches, or all branches if limit is 0.
+func GetBranches(repo *models.Repository, skip, limit int) ([]*git.Branch, int, error) {
+	return git.GetBranchesByPath(repo.RepoPath(), skip, limit)
+}
+
+// checkBranchName validates branch name with existing repository branches
+func checkBranchName(repo *models.Repository, name string) error {
+	gitRepo, err := git.OpenRepository(repo.RepoPath())
+	if err != nil {
+		return err
+	}
+	defer gitRepo.Close()
+
+	branches, _, err := GetBranches(repo, 0, 0)
+	if err != nil {
+		return err
+	}
+
+	for _, branch := range branches {
+		if branch.Name == name {
+			return models.ErrBranchAlreadyExists{
+				BranchName: branch.Name,
+			}
+		} else if (len(branch.Name) < len(name) && branch.Name+"/" == name[0:len(branch.Name)+1]) ||
+			(len(branch.Name) > len(name) && name+"/" == branch.Name[0:len(name)+1]) {
+			return models.ErrBranchNameConflict{
+				BranchName: branch.Name,
+			}
+		}
+	}
+
+	if _, err := gitRepo.GetTag(name); err == nil {
+		return models.ErrTagAlreadyExists{
+			TagName: name,
+		}
+	}
+
+	return nil
+}
+
+// CreateNewBranchFromCommit creates a new repository branch
+func CreateNewBranchFromCommit(doer *models.User, repo *models.Repository, commit, branchName string) (err error) {
+	// Check if branch name can be used
+	if err := checkBranchName(repo, branchName); err != nil {
+		return err
+	}
+
+	if err := git.Push(repo.RepoPath(), git.PushOptions{
+		Remote: repo.RepoPath(),
+		Branch: fmt.Sprintf("%s:%s%s", commit, git.BranchPrefix, branchName),
+		Env:    models.PushingEnvironment(doer, repo),
+	}); err != nil {
+		if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
+			return err
+		}
+		return fmt.Errorf("Push: %v", err)
+	}
+
+	return nil
+}
+
 // RenameBranch rename a branch
 func RenameBranch(repo *models.Repository, doer *models.User, gitRepo *git.Repository, from, to string) (string, error) {
 	if from == to {
diff --git a/modules/repository/cache.go b/services/repository/cache.go
similarity index 100%
rename from modules/repository/cache.go
rename to services/repository/cache.go
diff --git a/services/repository/hooks.go b/services/repository/hooks.go
new file mode 100644
index 0000000000..a50853f6af
--- /dev/null
+++ b/services/repository/hooks.go
@@ -0,0 +1,52 @@
+// Copyright 2021 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 repository
+
+import (
+	"context"
+	"fmt"
+
+	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/log"
+	repo_module "code.gitea.io/gitea/modules/repository"
+
+	"xorm.io/builder"
+)
+
+// SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks
+// to make sure the binary and custom conf path are up-to-date.
+func SyncRepositoryHooks(ctx context.Context) error {
+	log.Trace("Doing: SyncRepositoryHooks")
+
+	if err := db.Iterate(
+		db.DefaultContext,
+		new(models.Repository),
+		builder.Gt{"id": 0},
+		func(idx int, bean interface{}) error {
+			repo := bean.(*models.Repository)
+			select {
+			case <-ctx.Done():
+				return db.ErrCancelledf("before sync repository hooks for %s", repo.FullName())
+			default:
+			}
+
+			if err := repo_module.CreateDelegateHooks(repo.RepoPath()); err != nil {
+				return fmt.Errorf("SyncRepositoryHook: %v", err)
+			}
+			if repo.HasWiki() {
+				if err := repo_module.CreateDelegateHooks(repo.WikiPath()); err != nil {
+					return fmt.Errorf("SyncRepositoryHook: %v", err)
+				}
+			}
+			return nil
+		},
+	); err != nil {
+		return err
+	}
+
+	log.Trace("Finished: SyncRepositoryHooks")
+	return nil
+}
diff --git a/services/repository/push.go b/services/repository/push.go
index ab1b404609..a98cfb1758 100644
--- a/services/repository/push.go
+++ b/services/repository/push.go
@@ -5,8 +5,10 @@
 package repository
 
 import (
+	"context"
 	"errors"
 	"fmt"
+	"strings"
 	"time"
 
 	"code.gitea.io/gitea/models"
@@ -20,6 +22,7 @@ import (
 	"code.gitea.io/gitea/modules/repofiles"
 	repo_module "code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/timeutil"
 	pull_service "code.gitea.io/gitea/services/pull"
 )
 
@@ -210,7 +213,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
 				}
 
 				// Cache for big repository
-				if err := repo_module.CacheRef(graceful.GetManager().HammerContext(), repo, gitRepo, opts.RefFullName); err != nil {
+				if err := CacheRef(graceful.GetManager().HammerContext(), repo, gitRepo, opts.RefFullName); err != nil {
 					log.Error("repo_module.CacheRef %s/%s failed: %v", repo.ID, branch, err)
 				}
 			} else {
@@ -229,7 +232,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
 			log.Trace("Non-tag and non-branch commits pushed.")
 		}
 	}
-	if err := repo_module.PushUpdateAddDeleteTags(repo, gitRepo, addTags, delTags); err != nil {
+	if err := PushUpdateAddDeleteTags(repo, gitRepo, addTags, delTags); err != nil {
 		return fmt.Errorf("PushUpdateAddDeleteTags: %v", err)
 	}
 
@@ -240,3 +243,122 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
 
 	return nil
 }
+
+// PushUpdateAddDeleteTags updates a number of added and delete tags
+func PushUpdateAddDeleteTags(repo *models.Repository, gitRepo *git.Repository, addTags, delTags []string) error {
+	return db.WithTx(func(ctx context.Context) error {
+		if err := models.PushUpdateDeleteTagsContext(ctx, repo, delTags); err != nil {
+			return err
+		}
+		return pushUpdateAddTags(ctx, repo, gitRepo, addTags)
+	})
+}
+
+// pushUpdateAddTags updates a number of add tags
+func pushUpdateAddTags(ctx context.Context, repo *models.Repository, gitRepo *git.Repository, tags []string) error {
+	if len(tags) == 0 {
+		return nil
+	}
+
+	lowerTags := make([]string, 0, len(tags))
+	for _, tag := range tags {
+		lowerTags = append(lowerTags, strings.ToLower(tag))
+	}
+
+	releases, err := models.GetReleasesByRepoIDAndNames(ctx, repo.ID, lowerTags)
+	if err != nil {
+		return fmt.Errorf("GetReleasesByRepoIDAndNames: %v", err)
+	}
+	relMap := make(map[string]*models.Release)
+	for _, rel := range releases {
+		relMap[rel.LowerTagName] = rel
+	}
+
+	newReleases := make([]*models.Release, 0, len(lowerTags)-len(relMap))
+
+	emailToUser := make(map[string]*models.User)
+
+	for i, lowerTag := range lowerTags {
+		tag, err := gitRepo.GetTag(tags[i])
+		if err != nil {
+			return fmt.Errorf("GetTag: %v", err)
+		}
+		commit, err := tag.Commit()
+		if err != nil {
+			return fmt.Errorf("Commit: %v", err)
+		}
+
+		sig := tag.Tagger
+		if sig == nil {
+			sig = commit.Author
+		}
+		if sig == nil {
+			sig = commit.Committer
+		}
+		var author *models.User
+		var createdAt = time.Unix(1, 0)
+
+		if sig != nil {
+			var ok bool
+			author, ok = emailToUser[sig.Email]
+			if !ok {
+				author, err = models.GetUserByEmailContext(ctx, sig.Email)
+				if err != nil && !models.IsErrUserNotExist(err) {
+					return fmt.Errorf("GetUserByEmail: %v", err)
+				}
+				if author != nil {
+					emailToUser[sig.Email] = author
+				}
+			}
+			createdAt = sig.When
+		}
+
+		commitsCount, err := commit.CommitsCount()
+		if err != nil {
+			return fmt.Errorf("CommitsCount: %v", err)
+		}
+
+		rel, has := relMap[lowerTag]
+
+		if !has {
+			rel = &models.Release{
+				RepoID:       repo.ID,
+				Title:        "",
+				TagName:      tags[i],
+				LowerTagName: lowerTag,
+				Target:       "",
+				Sha1:         commit.ID.String(),
+				NumCommits:   commitsCount,
+				Note:         "",
+				IsDraft:      false,
+				IsPrerelease: false,
+				IsTag:        true,
+				CreatedUnix:  timeutil.TimeStamp(createdAt.Unix()),
+			}
+			if author != nil {
+				rel.PublisherID = author.ID
+			}
+
+			newReleases = append(newReleases, rel)
+		} else {
+			rel.Sha1 = commit.ID.String()
+			rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix())
+			rel.NumCommits = commitsCount
+			rel.IsDraft = false
+			if rel.IsTag && author != nil {
+				rel.PublisherID = author.ID
+			}
+			if err = models.UpdateRelease(ctx, rel); err != nil {
+				return fmt.Errorf("Update: %v", err)
+			}
+		}
+	}
+
+	if len(newReleases) > 0 {
+		if err = models.InsertReleasesContext(ctx, newReleases); err != nil {
+			return fmt.Errorf("Insert: %v", err)
+		}
+	}
+
+	return nil
+}