From d92781bf941972761177ac9e07441f8893758fd3 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 21 Jan 2020 04:01:19 +0800 Subject: [PATCH] Refactor repository check and sync functions (#9854) Move more general repository functions out of models/repo.go --- cmd/admin.go | 7 +- models/context.go | 13 +++ models/migrations/v36.go | 5 +- models/repo.go | 201 ++---------------------------------- modules/cron/cron.go | 13 ++- modules/repository/check.go | 156 ++++++++++++++++++++++++++++ modules/repository/fork.go | 2 +- modules/repository/hooks.go | 104 +++++++++++++++++++ modules/repository/init.go | 2 +- modules/repository/repo.go | 5 +- routers/admin/admin.go | 12 ++- services/wiki/wiki.go | 3 +- 12 files changed, 313 insertions(+), 210 deletions(-) create mode 100644 modules/repository/check.go create mode 100644 modules/repository/hooks.go diff --git a/cmd/admin.go b/cmd/admin.go index 0b9f6eac44..f6f3e22b97 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -14,9 +14,10 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth/oauth2" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" pwd "code.gitea.io/gitea/modules/password" - "code.gitea.io/gitea/modules/repository" + repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "github.com/urfave/cli" @@ -375,7 +376,7 @@ func runRepoSyncReleases(c *cli.Context) error { } log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum) - if err = repository.SyncReleasesWithTags(repo, gitRepo); err != nil { + if err = repo_module.SyncReleasesWithTags(repo, gitRepo); err != nil { log.Warn(" SyncReleasesWithTags: %v", err) gitRepo.Close() continue @@ -410,7 +411,7 @@ func runRegenerateHooks(c *cli.Context) error { if err := initDB(); err != nil { return err } - return models.SyncRepositoryHooks() + return repo_module.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) } func runRegenerateKeys(c *cli.Context) error { diff --git a/models/context.go b/models/context.go index 5f47c595a2..6b8b9af570 100644 --- a/models/context.go +++ b/models/context.go @@ -4,6 +4,12 @@ package models +import ( + "code.gitea.io/gitea/modules/setting" + + "xorm.io/builder" +) + // DBContext represents a db context type DBContext struct { e Engine @@ -53,3 +59,10 @@ func WithTx(f func(ctx DBContext) error) error { sess.Close() return err } + +// Iterate iterates the databases and doing something +func Iterate(ctx DBContext, tableBean interface{}, cond builder.Cond, fun func(idx int, bean interface{}) error) error { + return ctx.e.Where(cond). + BufferSize(setting.Database.IterateBufferSize). + Iterate(tableBean, fun) +} diff --git a/models/migrations/v36.go b/models/migrations/v36.go index 729019925e..8027ed2103 100644 --- a/models/migrations/v36.go +++ b/models/migrations/v36.go @@ -5,11 +5,12 @@ package migrations import ( - "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/graceful" + repo_module "code.gitea.io/gitea/modules/repository" "xorm.io/xorm" ) func regenerateGitHooks36(x *xorm.Engine) (err error) { - return models.SyncRepositoryHooks() + return repo_module.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) } diff --git a/models/repo.go b/models/repo.go index 6c89dbcbbb..ecff8482a7 100644 --- a/models/repo.go +++ b/models/repo.go @@ -26,12 +26,10 @@ import ( "time" "code.gitea.io/gitea/modules/avatar" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -147,13 +145,13 @@ type Repository struct { ID int64 `xorm:"pk autoincr"` OwnerID int64 `xorm:"UNIQUE(s) index"` OwnerName string - Owner *User `xorm:"-"` - LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` - Name string `xorm:"INDEX NOT NULL"` - Description string `xorm:"TEXT"` - Website string `xorm:"VARCHAR(2048)"` - OriginalServiceType structs.GitServiceType `xorm:"index"` - OriginalURL string `xorm:"VARCHAR(2048)"` + Owner *User `xorm:"-"` + LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` + Name string `xorm:"INDEX NOT NULL"` + Description string `xorm:"TEXT"` + Website string `xorm:"VARCHAR(2048)"` + OriginalServiceType api.GitServiceType `xorm:"index"` + OriginalURL string `xorm:"VARCHAR(2048)"` DefaultBranch string NumWatches int @@ -911,63 +909,12 @@ func CheckCreateRepository(doer, u *User, name string) error { return nil } -// CreateDelegateHooks creates all the hooks scripts for the repo -func CreateDelegateHooks(repoPath string) error { - return createDelegateHooks(repoPath) -} - -// createDelegateHooks creates all the hooks scripts for the repo -func createDelegateHooks(repoPath string) (err error) { - - var ( - hookNames = []string{"pre-receive", "update", "post-receive"} - hookTpls = []string{ - fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), - fmt.Sprintf("#!/usr/bin/env %s\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\n\"${hook}\" $1 $2 $3\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), - fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), - } - giteaHookTpls = []string{ - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf), - fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), - } - ) - - hookDir := filepath.Join(repoPath, "hooks") - - for i, hookName := range hookNames { - oldHookPath := filepath.Join(hookDir, hookName) - newHookPath := filepath.Join(hookDir, hookName+".d", "gitea") - - if err := os.MkdirAll(filepath.Join(hookDir, hookName+".d"), os.ModePerm); err != nil { - return fmt.Errorf("create hooks dir '%s': %v", filepath.Join(hookDir, hookName+".d"), err) - } - - // WARNING: This will override all old server-side hooks - if err = os.Remove(oldHookPath); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("unable to pre-remove old hook file '%s' prior to rewriting: %v ", oldHookPath, err) - } - if err = ioutil.WriteFile(oldHookPath, []byte(hookTpls[i]), 0777); err != nil { - return fmt.Errorf("write old hook file '%s': %v", oldHookPath, err) - } - - if err = os.Remove(newHookPath); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("unable to pre-remove new hook file '%s' prior to rewriting: %v", newHookPath, err) - } - if err = ioutil.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0777); err != nil { - return fmt.Errorf("write new hook file '%s': %v", newHookPath, err) - } - } - - return nil -} - // CreateRepoOptions contains the create repository options type CreateRepoOptions struct { Name string Description string OriginalURL string - GitServiceType structs.GitServiceType + GitServiceType api.GitServiceType Gitignores string IssueLabels string License string @@ -1883,138 +1830,6 @@ func deleteOldRepositoryArchives(ctx context.Context, idx int, bean interface{}) return nil } -func gatherMissingRepoRecords() ([]*Repository, error) { - repos := make([]*Repository, 0, 10) - if err := x. - Where("id > 0"). - Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - if !com.IsDir(repo.RepoPath()) { - repos = append(repos, repo) - } - return nil - }); err != nil { - if err2 := CreateRepositoryNotice(fmt.Sprintf("gatherMissingRepoRecords: %v", err)); err2 != nil { - return nil, fmt.Errorf("CreateRepositoryNotice: %v", err) - } - } - return repos, nil -} - -// DeleteMissingRepositories deletes all repository records that lost Git files. -func DeleteMissingRepositories(doer *User) error { - repos, err := gatherMissingRepoRecords() - if err != nil { - return fmt.Errorf("gatherMissingRepoRecords: %v", err) - } - - if len(repos) == 0 { - return nil - } - - for _, repo := range repos { - log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID) - if err := DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil { - if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepository [%d]: %v", repo.ID, err)); err2 != nil { - return fmt.Errorf("CreateRepositoryNotice: %v", err) - } - } - } - return nil -} - -// ReinitMissingRepositories reinitializes all repository records that lost Git files. -func ReinitMissingRepositories() error { - repos, err := gatherMissingRepoRecords() - if err != nil { - return fmt.Errorf("gatherMissingRepoRecords: %v", err) - } - - if len(repos) == 0 { - return nil - } - - for _, repo := range repos { - log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID) - if err := git.InitRepository(repo.RepoPath(), true); err != nil { - if err2 := CreateRepositoryNotice(fmt.Sprintf("InitRepository [%d]: %v", repo.ID, err)); err2 != nil { - return fmt.Errorf("CreateRepositoryNotice: %v", err) - } - } - } - return 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() error { - return x.Cols("owner_id", "name").Where("id > 0").Iterate(new(Repository), - func(idx int, bean interface{}) error { - if err := createDelegateHooks(bean.(*Repository).RepoPath()); err != nil { - return fmt.Errorf("SyncRepositoryHook: %v", err) - } - if bean.(*Repository).HasWiki() { - if err := createDelegateHooks(bean.(*Repository).WikiPath()); err != nil { - return fmt.Errorf("SyncRepositoryHook: %v", err) - } - } - return nil - }) -} - -// GitFsck calls 'git fsck' to check repository health. -func GitFsck(ctx context.Context) { - log.Trace("Doing: GitFsck") - if err := x. - Where("id>0 AND is_fsck_enabled=?", true).BufferSize(setting.Database.IterateBufferSize). - Iterate(new(Repository), - func(idx int, bean interface{}) error { - select { - case <-ctx.Done(): - return fmt.Errorf("Aborted due to shutdown") - default: - } - repo := bean.(*Repository) - repoPath := repo.RepoPath() - log.Trace("Running health check on repository %s", repoPath) - if err := git.Fsck(repoPath, setting.Cron.RepoHealthCheck.Timeout, setting.Cron.RepoHealthCheck.Args...); err != nil { - desc := fmt.Sprintf("Failed to health check repository (%s): %v", repoPath, err) - log.Warn(desc) - if err = CreateRepositoryNotice(desc); err != nil { - log.Error("CreateRepositoryNotice: %v", err) - } - } - return nil - }); err != nil { - log.Error("GitFsck: %v", err) - } - log.Trace("Finished: GitFsck") -} - -// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository -func GitGcRepos() error { - args := append([]string{"gc"}, setting.Git.GCArgs...) - return x. - Where("id > 0").BufferSize(setting.Database.IterateBufferSize). - Iterate(new(Repository), - func(idx int, bean interface{}) error { - repo := bean.(*Repository) - if err := repo.GetOwner(); err != nil { - return err - } - if stdout, err := git.NewCommand(args...). - SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName())). - RunInDirTimeout( - time.Duration(setting.Git.Timeout.GC)*time.Second, - repo.RepoPath()); err != nil { - log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err) - return fmt.Errorf("Repository garbage collection failed: Error: %v", err) - } - return nil - }) -} - type repoChecker struct { querySQL, correctSQL string desc string diff --git a/modules/cron/cron.go b/modules/cron/cron.go index f4511a8e79..692642e4ce 100644 --- a/modules/cron/cron.go +++ b/modules/cron/cron.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/migrations" + repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/sync" mirror_service "code.gitea.io/gitea/services/mirror" @@ -69,14 +70,22 @@ func NewContext() { } } if setting.Cron.RepoHealthCheck.Enabled { - entry, err = c.AddFunc("Repository health check", setting.Cron.RepoHealthCheck.Schedule, WithUnique(gitFsck, models.GitFsck)) + entry, err = c.AddFunc("Repository health check", setting.Cron.RepoHealthCheck.Schedule, WithUnique(gitFsck, func(ctx context.Context) { + if err := repo_module.GitFsck(ctx); err != nil { + log.Error("GitFsck: %s", err) + } + })) if err != nil { log.Fatal("Cron[Repository health check]: %v", err) } if setting.Cron.RepoHealthCheck.RunAtStart { entry.Prev = time.Now() entry.ExecTimes++ - go WithUnique(gitFsck, models.GitFsck)() + go WithUnique(gitFsck, func(ctx context.Context) { + if err := repo_module.GitFsck(ctx); err != nil { + log.Error("GitFsck: %s", err) + } + })() } } if setting.Cron.CheckRepoStats.Enabled { diff --git a/modules/repository/check.go b/modules/repository/check.go new file mode 100644 index 0000000000..fcaf76308f --- /dev/null +++ b/modules/repository/check.go @@ -0,0 +1,156 @@ +// 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" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "github.com/unknwon/com" + "xorm.io/builder" +) + +// GitFsck calls 'git fsck' to check repository health. +func GitFsck(ctx context.Context) error { + log.Trace("Doing: GitFsck") + + if err := models.Iterate( + models.DefaultDBContext(), + new(models.Repository), + builder.Expr("id>0 AND is_fsck_enabled=?", true), + func(idx int, bean interface{}) error { + select { + case <-ctx.Done(): + return fmt.Errorf("Aborted due to shutdown") + default: + } + repo := bean.(*models.Repository) + repoPath := repo.RepoPath() + log.Trace("Running health check on repository %s", repoPath) + if err := git.Fsck(repoPath, setting.Cron.RepoHealthCheck.Timeout, setting.Cron.RepoHealthCheck.Args...); err != nil { + desc := fmt.Sprintf("Failed to health check repository (%s): %v", repoPath, err) + log.Warn(desc) + if err = models.CreateRepositoryNotice(desc); err != nil { + log.Error("CreateRepositoryNotice: %v", err) + } + } + return nil + }, + ); err != nil { + return err + } + + log.Trace("Finished: GitFsck") + return nil +} + +// GitGcRepos calls 'git gc' to remove unnecessary files and optimize the local repository +func GitGcRepos(ctx context.Context) error { + log.Trace("Doing: GitGcRepos") + args := append([]string{"gc"}, setting.Git.GCArgs...) + + if err := models.Iterate( + models.DefaultDBContext(), + new(models.Repository), + builder.Gt{"id": 0}, + func(idx int, bean interface{}) error { + select { + case <-ctx.Done(): + return fmt.Errorf("Aborted due to shutdown") + default: + } + + repo := bean.(*models.Repository) + if err := repo.GetOwner(); err != nil { + return err + } + if stdout, err := git.NewCommand(args...). + SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName())). + RunInDirTimeout( + time.Duration(setting.Git.Timeout.GC)*time.Second, + repo.RepoPath()); err != nil { + log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err) + return fmt.Errorf("Repository garbage collection failed: Error: %v", err) + } + return nil + }, + ); err != nil { + return err + } + + log.Trace("Finished: GitGcRepos") + return nil +} + +func gatherMissingRepoRecords() ([]*models.Repository, error) { + repos := make([]*models.Repository, 0, 10) + if err := models.Iterate( + models.DefaultDBContext(), + new(models.Repository), + builder.Gt{"id": 0}, + func(idx int, bean interface{}) error { + repo := bean.(*models.Repository) + if !com.IsDir(repo.RepoPath()) { + repos = append(repos, repo) + } + return nil + }, + ); err != nil { + if err2 := models.CreateRepositoryNotice(fmt.Sprintf("gatherMissingRepoRecords: %v", err)); err2 != nil { + return nil, fmt.Errorf("CreateRepositoryNotice: %v", err) + } + } + return repos, nil +} + +// DeleteMissingRepositories deletes all repository records that lost Git files. +func DeleteMissingRepositories(doer *models.User) error { + repos, err := gatherMissingRepoRecords() + if err != nil { + return fmt.Errorf("gatherMissingRepoRecords: %v", err) + } + + if len(repos) == 0 { + return nil + } + + for _, repo := range repos { + log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID) + if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil { + if err2 := models.CreateRepositoryNotice(fmt.Sprintf("DeleteRepository [%d]: %v", repo.ID, err)); err2 != nil { + return fmt.Errorf("CreateRepositoryNotice: %v", err) + } + } + } + return nil +} + +// ReinitMissingRepositories reinitializes all repository records that lost Git files. +func ReinitMissingRepositories() error { + repos, err := gatherMissingRepoRecords() + if err != nil { + return fmt.Errorf("gatherMissingRepoRecords: %v", err) + } + + if len(repos) == 0 { + return nil + } + + for _, repo := range repos { + log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID) + if err := git.InitRepository(repo.RepoPath(), true); err != nil { + if err2 := models.CreateRepositoryNotice(fmt.Sprintf("InitRepository [%d]: %v", repo.ID, err)); err2 != nil { + return fmt.Errorf("CreateRepositoryNotice: %v", err) + } + } + } + return nil +} diff --git a/modules/repository/fork.go b/modules/repository/fork.go index 8953ce9ba4..638d3588ec 100644 --- a/modules/repository/fork.go +++ b/modules/repository/fork.go @@ -69,7 +69,7 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, return fmt.Errorf("git update-server-info: %v", err) } - if err = models.CreateDelegateHooks(repoPath); err != nil { + if err = createDelegateHooks(repoPath); err != nil { return fmt.Errorf("createDelegateHooks: %v", err) } return nil diff --git a/modules/repository/hooks.go b/modules/repository/hooks.go new file mode 100644 index 0000000000..60e3418571 --- /dev/null +++ b/modules/repository/hooks.go @@ -0,0 +1,104 @@ +// 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" + "io/ioutil" + "os" + "path/filepath" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "xorm.io/builder" +) + +// CreateDelegateHooks creates all the hooks scripts for the repo +func CreateDelegateHooks(repoPath string) error { + return createDelegateHooks(repoPath) +} + +// createDelegateHooks creates all the hooks scripts for the repo +func createDelegateHooks(repoPath string) (err error) { + + var ( + hookNames = []string{"pre-receive", "update", "post-receive"} + hookTpls = []string{ + fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), + fmt.Sprintf("#!/usr/bin/env %s\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\n\"${hook}\" $1 $2 $3\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), + fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType), + } + giteaHookTpls = []string{ + fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), + fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf), + fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), + } + ) + + hookDir := filepath.Join(repoPath, "hooks") + + for i, hookName := range hookNames { + oldHookPath := filepath.Join(hookDir, hookName) + newHookPath := filepath.Join(hookDir, hookName+".d", "gitea") + + if err := os.MkdirAll(filepath.Join(hookDir, hookName+".d"), os.ModePerm); err != nil { + return fmt.Errorf("create hooks dir '%s': %v", filepath.Join(hookDir, hookName+".d"), err) + } + + // WARNING: This will override all old server-side hooks + if err = os.Remove(oldHookPath); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("unable to pre-remove old hook file '%s' prior to rewriting: %v ", oldHookPath, err) + } + if err = ioutil.WriteFile(oldHookPath, []byte(hookTpls[i]), 0777); err != nil { + return fmt.Errorf("write old hook file '%s': %v", oldHookPath, err) + } + + if err = os.Remove(newHookPath); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("unable to pre-remove new hook file '%s' prior to rewriting: %v", newHookPath, err) + } + if err = ioutil.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0777); err != nil { + return fmt.Errorf("write new hook file '%s': %v", newHookPath, err) + } + } + + return 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 := models.Iterate( + models.DefaultDBContext(), + new(models.Repository), + builder.Gt{"id": 0}, + func(idx int, bean interface{}) error { + select { + case <-ctx.Done(): + return fmt.Errorf("Aborted due to shutdown") + default: + } + + if err := createDelegateHooks(bean.(*models.Repository).RepoPath()); err != nil { + return fmt.Errorf("SyncRepositoryHook: %v", err) + } + if bean.(*models.Repository).HasWiki() { + if err := createDelegateHooks(bean.(*models.Repository).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/init.go b/modules/repository/init.go index 9d0beb1138..7b7d07f43e 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -164,7 +164,7 @@ func checkInitRepository(repoPath string) (err error) { // Init git bare new repository. if err = git.InitRepository(repoPath, true); err != nil { return fmt.Errorf("git.InitRepository: %v", err) - } else if err = models.CreateDelegateHooks(repoPath); err != nil { + } else if err = createDelegateHooks(repoPath); err != nil { return fmt.Errorf("createDelegateHooks: %v", err) } return nil diff --git a/modules/repository/repo.go b/modules/repository/repo.go index bb8cceeadc..4ecb9f660a 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" + "gopkg.in/ini.v1" ) @@ -156,11 +157,11 @@ func cleanUpMigrateGitConfig(configPath string) error { // CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors. func CleanUpMigrateInfo(repo *models.Repository) (*models.Repository, error) { repoPath := repo.RepoPath() - if err := models.CreateDelegateHooks(repoPath); err != nil { + if err := createDelegateHooks(repoPath); err != nil { return repo, fmt.Errorf("createDelegateHooks: %v", err) } if repo.HasWiki() { - if err := models.CreateDelegateHooks(repo.WikiPath()); err != nil { + if err := createDelegateHooks(repo.WikiPath()); err != nil { return repo, fmt.Errorf("createDelegateHooks.(wiki): %v", err) } } diff --git a/routers/admin/admin.go b/routers/admin/admin.go index 055b8f5a5e..71a22e1f9e 100644 --- a/routers/admin/admin.go +++ b/routers/admin/admin.go @@ -24,6 +24,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/queue" + repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/services/mailer" @@ -150,6 +151,7 @@ func Dashboard(ctx *context.Context) { if op > 0 { var err error var success string + shutdownCtx := graceful.GetManager().ShutdownContext() switch Operation(op) { case cleanInactivateUser: @@ -160,25 +162,25 @@ func Dashboard(ctx *context.Context) { err = models.DeleteRepositoryArchives() case cleanMissingRepos: success = ctx.Tr("admin.dashboard.delete_missing_repos_success") - err = models.DeleteMissingRepositories(ctx.User) + err = repo_module.DeleteMissingRepositories(ctx.User) case gitGCRepos: success = ctx.Tr("admin.dashboard.git_gc_repos_success") - err = models.GitGcRepos() + err = repo_module.GitGcRepos(shutdownCtx) case syncSSHAuthorizedKey: success = ctx.Tr("admin.dashboard.resync_all_sshkeys_success") err = models.RewriteAllPublicKeys() case syncRepositoryUpdateHook: success = ctx.Tr("admin.dashboard.resync_all_hooks_success") - err = models.SyncRepositoryHooks() + err = repo_module.SyncRepositoryHooks(shutdownCtx) case reinitMissingRepository: success = ctx.Tr("admin.dashboard.reinit_missing_repos_success") - err = models.ReinitMissingRepositories() + err = repo_module.ReinitMissingRepositories() case syncExternalUsers: success = ctx.Tr("admin.dashboard.sync_external_users_started") go graceful.GetManager().RunWithShutdownContext(models.SyncExternalUsers) case gitFsck: success = ctx.Tr("admin.dashboard.git_fsck_started") - go graceful.GetManager().RunWithShutdownContext(models.GitFsck) + err = repo_module.GitFsck(shutdownCtx) case deleteGeneratedRepositoryAvatars: success = ctx.Tr("admin.dashboard.delete_generated_repository_avatars_success") err = models.RemoveRandomAvatars() diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index e2b04ade77..2ace1d7a08 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/sync" "code.gitea.io/gitea/modules/util" @@ -74,7 +75,7 @@ func InitWiki(repo *models.Repository) error { if err := git.InitRepository(repo.WikiPath(), true); err != nil { return fmt.Errorf("InitRepository: %v", err) - } else if err = models.CreateDelegateHooks(repo.WikiPath()); err != nil { + } else if err = repo_module.CreateDelegateHooks(repo.WikiPath()); err != nil { return fmt.Errorf("createDelegateHooks: %v", err) } return nil