diff --git a/Makefile b/Makefile index 99aaaac8d8..a499443ddd 100644 --- a/Makefile +++ b/Makefile @@ -562,12 +562,12 @@ test: test-frontend test-backend .PHONY: test-backend test-backend: | compute-go-test-packages @echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." - @TZ=UTC $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES) + @TZ=UTC GITEA_ROOT="$(CURDIR)" $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_PACKAGES) .PHONY: test-remote-cacher test-remote-cacher: @echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." - @$(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_REMOTE_CACHER_PACKAGES) + GITEA_ROOT="$(CURDIR)" $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' $(GO_TEST_REMOTE_CACHER_PACKAGES) .PHONY: test-frontend test-frontend: node_modules @@ -592,7 +592,7 @@ test-check: .PHONY: test\#% test\#%: | compute-go-test-packages @echo "Running go test with $(GOTESTFLAGS) -tags '$(TEST_TAGS)'..." - @TZ=UTC $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES) + @TZ=UTC GITEA_ROOT="$(CURDIR)" $(GOTEST) $(GOTESTFLAGS) -tags='$(TEST_TAGS)' -run $(subst .,/,$*) $(GO_TEST_PACKAGES) coverage-merge: rm -fr coverage/merged ; mkdir -p coverage/merged diff --git a/cmd/admin.go b/cmd/admin.go index 60b25eb971..fe212cc388 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -47,7 +47,6 @@ func subcmdRegenerate() *cli.Command { Name: "regenerate", Usage: "Regenerate specific files", Commands: []*cli.Command{ - microcmdRegenHooks, microcmdRegenKeys, }, } diff --git a/cmd/admin_regenerate.go b/cmd/admin_regenerate.go index 4d14df317d..4620a7106d 100644 --- a/cmd/admin_regenerate.go +++ b/cmd/admin_regenerate.go @@ -7,36 +7,15 @@ import ( "context" asymkey_model "forgejo.org/models/asymkey" - "forgejo.org/modules/graceful" - repo_service "forgejo.org/services/repository" "github.com/urfave/cli/v3" ) -var ( - microcmdRegenHooks = &cli.Command{ - Name: "hooks", - Usage: "Regenerate git-hooks", - Before: noDanglingArgs, - Action: runRegenerateHooks, - } - - microcmdRegenKeys = &cli.Command{ - Name: "keys", - Usage: "Regenerate authorized_keys file", - Before: noDanglingArgs, - Action: runRegenerateKeys, - } -) - -func runRegenerateHooks(ctx context.Context, c *cli.Command) error { - ctx, cancel := installSignals(ctx) - defer cancel() - - if err := initDB(ctx); err != nil { - return err - } - return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) +var microcmdRegenKeys = &cli.Command{ + Name: "keys", + Usage: "Regenerate authorized_keys file", + Before: noDanglingArgs, + Action: runRegenerateKeys, } func runRegenerateKeys(ctx context.Context, c *cli.Command) error { diff --git a/cmd/main_test.go b/cmd/main_test.go index 1ec3471343..ca6a56f4af 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io" + "os" "path/filepath" "strings" "testing" @@ -62,7 +63,12 @@ func runTestApp(app *cli.Command, args ...string) (runResult, error) { } func TestCliCmd(t *testing.T) { - defaultWorkPath := filepath.Dir(setting.AppPath) + path, err := os.Executable() + if err != nil { + panic(err) + } + + defaultWorkPath := filepath.Dir(path) defaultCustomPath := filepath.Join(defaultWorkPath, "custom") defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini") diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index 8e212bc0a3..c926a85df6 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -47,10 +47,29 @@ func fatalTestError(fmtStr string, args ...any) { // InitSettings initializes config provider and load common settings for tests func InitSettings() { - if setting.CustomConf == "" { - setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini") - _ = os.Remove(setting.CustomConf) + InitCustomSettings("unittest.ini") +} + +func InitCustomSettings(confFileName string) { + root := base.SetupGiteaRoot() + if root == "" { + fatalTestError("Environment variable $GITEA_ROOT not set") } + setting.AppPath = filepath.Join(root, "gitea") + if setting.CustomConf == "" { + templateFile := confFileName + ".tmpl" + content, err := os.ReadFile(filepath.Join(root, "tests", templateFile)) + if err != nil { + log.Fatalf("couldn't read config template: %s", templateFile) + } + err = os.WriteFile(filepath.Join(root, "tests", confFileName), content, 0o644) + if err != nil { + log.Fatalf("couldn't write config: %s", confFileName) + } + setting.CustomConf = filepath.Join(root, "tests", confFileName) + } + os.Setenv("GITEA_CONF", setting.CustomConf) + setting.InitCfgProvider(setting.CustomConf) setting.LoadCommonSettings() @@ -72,9 +91,10 @@ func InitSettings() { // TestOptions represents test options type TestOptions struct { - FixtureFiles []string - SetUp func() error // SetUp will be executed before all tests in this package - TearDown func() error // TearDown will be executed after all tests in this package + FixtureFiles []string + SetUp func() error // SetUp will be executed before all tests in this package + TearDown func() error // TearDown will be executed after all tests in this package + IniFileOverride string } // MainTest a reusable TestMain(..) function for unit tests that need to use a @@ -97,7 +117,11 @@ func MainTest(m *testing.M, testOpts ...*TestOptions) { giteaRoot = searchDir setting.CustomPath = filepath.Join(giteaRoot, "custom") - InitSettings() + if len(testOpts) == 0 || testOpts[0].IniFileOverride == "" { + InitSettings() + } else { + InitCustomSettings(testOpts[0].IniFileOverride) + } fixturesDir = filepath.Join(giteaRoot, "models", "fixtures") var opts FixturesOptions diff --git a/modules/git/git.go b/modules/git/git.go index 650fd3b5af..7f8674d099 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -10,6 +10,7 @@ import ( "fmt" "os" "os/exec" + "path" "path/filepath" "regexp" "runtime" @@ -200,6 +201,11 @@ func InitFull(ctx context.Context) (err error) { _, err = exec.LookPath("ssh") HasSSHExecutable = err == nil + err = InitDelegateHooks(HomeDir()) + if err != nil { + return nil + } + return syncGitConfig() } @@ -229,6 +235,10 @@ func syncGitConfig() (err error) { } } + if err := configSet("core.hooksPath", path.Join(HomeDir(), "hooks")); err != nil { + return err + } + // Set git some configurations - these must be set to these values for forgejo to work correctly if err := configSet("core.quotePath", "false"); err != nil { return err diff --git a/modules/repository/hooks.go b/modules/git/hook_generate.go similarity index 60% rename from modules/repository/hooks.go rename to modules/git/hook_generate.go index 0f5e3afc34..2af2487ab3 100644 --- a/modules/repository/hooks.go +++ b/modules/git/hook_generate.go @@ -1,7 +1,7 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package repository +package git import ( "fmt" @@ -21,14 +21,25 @@ func getHookTemplates() (hookNames, hookTpls, giteaHookTpls []string) { data=$(cat) exitcodes="" hookname=$(basename $0) -GIT_DIR=${GIT_DIR:-$(dirname $0)/..} -for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do +for hook in $(dirname $0)/${hookname}.d/*; do test -x "${hook}" && test -f "${hook}" || continue echo "${data}" | "${hook}" exitcodes="${exitcodes} $?" done +# Custom hooks +custom_hooks_dir="./hooks/${hookname}.d" +if [ -d "${custom_hooks_dir}" ]; then + for hook in ${custom_hooks_dir}/*; do + if [ $(basename "${hook}") != "gitea" ]; then + test -x "${hook}" && test -f "${hook}" || continue + echo "${data}" | "${hook}" + exitcodes="${exitcodes} $?" + fi + done +fi + for i in ${exitcodes}; do [ ${i} -eq 0 ] || exit ${i} done @@ -39,14 +50,25 @@ done # AUTO GENERATED BY GITEA, DO NOT MODIFY exitcodes="" hookname=$(basename $0) -GIT_DIR=${GIT_DIR:-$(dirname $0/..)} -for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do +for hook in $(dirname $0)/${hookname}.d/*; do test -x "${hook}" && test -f "${hook}" || continue "${hook}" $1 $2 $3 exitcodes="${exitcodes} $?" done +# Custom hooks +custom_hooks_dir="./hooks/${hookname}.d" +if [ -d "${custom_hooks_dir}" ]; then + for hook in ${custom_hooks_dir}/*; do + if [ $(basename "${hook}") != "gitea" ]; then + test -x "${hook}" && test -f "${hook}" || continue + "${hook}" $1 $2 $3 + exitcodes="${exitcodes} $?" + fi + done +fi + for i in ${exitcodes}; do [ ${i} -eq 0 ] || exit ${i} done @@ -58,14 +80,24 @@ done data=$(cat) exitcodes="" hookname=$(basename $0) -GIT_DIR=${GIT_DIR:-$(dirname $0)/..} -for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do +for hook in $(dirname $0)/${hookname}.d/*; do test -x "${hook}" && test -f "${hook}" || continue echo "${data}" | "${hook}" exitcodes="${exitcodes} $?" done +# Custom hooks +custom_hooks_dir="./hooks/${hookname}.d" +if [ -d "${custom_hooks_dir}" ]; then + for hook in ${custom_hooks_dir}/*; do + if [ $(basename "${hook}") != "gitea" ]; then + test -x "${hook}" && test -f "${hook}" || continue + echo "${data}" | "${hook}" + exitcodes="${exitcodes} $?" + fi + done +fi for i in ${exitcodes}; do [ ${i} -eq 0 ] || exit ${i} done @@ -104,10 +136,9 @@ done return hookNames, hookTpls, giteaHookTpls } -// CreateDelegateHooks creates all the hooks scripts for the repo -func CreateDelegateHooks(repoPath string) (err error) { +func InitDelegateHooks(path string) (err error) { hookNames, hookTpls, giteaHookTpls := getHookTemplates() - hookDir := filepath.Join(repoPath, "hooks") + hookDir := filepath.Join(path, "hooks") for i, hookName := range hookNames { oldHookPath := filepath.Join(hookDir, hookName) @@ -144,14 +175,6 @@ func CreateDelegateHooks(repoPath string) (err error) { return nil } -func checkExecutable(filename string) bool { - fileInfo, err := os.Stat(filename) - if err != nil { - return false - } - return (fileInfo.Mode() & 0o100) > 0 -} - func ensureExecutable(filename string) error { fileInfo, err := os.Stat(filename) if err != nil { @@ -163,66 +186,3 @@ func ensureExecutable(filename string) error { mode := fileInfo.Mode() | 0o100 return os.Chmod(filename, mode) } - -// CheckDelegateHooks checks the hooks scripts for the repo -func CheckDelegateHooks(repoPath string) ([]string, error) { - hookNames, hookTpls, giteaHookTpls := getHookTemplates() - - hookDir := filepath.Join(repoPath, "hooks") - results := make([]string, 0, 10) - - for i, hookName := range hookNames { - oldHookPath := filepath.Join(hookDir, hookName) - newHookPath := filepath.Join(hookDir, hookName+".d", "gitea") - - cont := false - isExist, err := util.IsExist(oldHookPath) - if err != nil { - results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", oldHookPath, err)) - } - if err == nil && !isExist { - results = append(results, fmt.Sprintf("old hook file %s does not exist", oldHookPath)) - cont = true - } - isExist, err = util.IsExist(oldHookPath + ".d") - if err != nil { - results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", oldHookPath+".d", err)) - } - if err == nil && !isExist { - results = append(results, fmt.Sprintf("hooks directory %s does not exist", oldHookPath+".d")) - cont = true - } - isExist, err = util.IsExist(newHookPath) - if err != nil { - results = append(results, fmt.Sprintf("unable to check if %s exists. Error: %v", newHookPath, err)) - } - if err == nil && !isExist { - results = append(results, fmt.Sprintf("new hook file %s does not exist", newHookPath)) - cont = true - } - if cont { - continue - } - contents, err := os.ReadFile(oldHookPath) - if err != nil { - return results, err - } - if string(contents) != hookTpls[i] { - results = append(results, fmt.Sprintf("old hook file %s is out of date", oldHookPath)) - } - if !checkExecutable(oldHookPath) { - results = append(results, fmt.Sprintf("old hook file %s is not executable", oldHookPath)) - } - contents, err = os.ReadFile(newHookPath) - if err != nil { - return results, err - } - if string(contents) != giteaHookTpls[i] { - results = append(results, fmt.Sprintf("new hook file %s is out of date", newHookPath)) - } - if !checkExecutable(newHookPath) { - results = append(results, fmt.Sprintf("new hook file %s is not executable", newHookPath)) - } - } - return results, nil -} diff --git a/modules/repository/init.go b/modules/repository/init.go index 66a65599a8..16d366c6f0 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -138,8 +138,6 @@ func CheckInitRepository(ctx context.Context, owner, name, objectFormatName stri // Init git bare new repository. if err = git.InitRepository(ctx, repoPath, true, objectFormatName); err != nil { return fmt.Errorf("git.InitRepository: %w", err) - } else if err = CreateDelegateHooks(repoPath); err != nil { - return fmt.Errorf("createDelegateHooks: %w", err) } return nil } diff --git a/release-notes/10397.md b/release-notes/10397.md new file mode 100644 index 0000000000..0560c173b9 --- /dev/null +++ b/release-notes/10397.md @@ -0,0 +1 @@ +feat: replace repository based server-side hooks with centralised hooks. Migration guide is available in [Admin docs](https://forgejo.org/docs/latest/admin/upgrade/#when-upgrading-from--known-problematic-versions-or-upgrade-paths). diff --git a/routers/init.go b/routers/init.go index 26cf0ace3e..f272d60d63 100644 --- a/routers/init.go +++ b/routers/init.go @@ -92,8 +92,6 @@ func syncAppConfForGit(ctx context.Context) error { } if updated { - log.Info("re-sync repository hooks ...") - mustInitCtx(ctx, repo_service.SyncRepositoryHooks) return system.AppState.Set(ctx, runtimeState) } return nil diff --git a/routers/install/routes_test.go b/routers/install/routes_test.go index 9b10f05b3b..d4409b36d0 100644 --- a/routers/install/routes_test.go +++ b/routers/install/routes_test.go @@ -34,5 +34,5 @@ func TestRoutes(t *testing.T) { } func TestMain(m *testing.M) { - unittest.MainTest(m) + unittest.MainTest(m, &unittest.TestOptions{IniFileOverride: "install.ini"}) } diff --git a/services/cron/tasks_extended.go b/services/cron/tasks_extended.go index 5cc5d4eb14..fc89fa020f 100644 --- a/services/cron/tasks_extended.go +++ b/services/cron/tasks_extended.go @@ -86,16 +86,6 @@ func registerRewriteAllPrincipalKeys() { }) } -func registerRepositoryUpdateHook() { - RegisterTaskFatal("resync_all_hooks", &BaseConfig{ - Enabled: false, - RunAtStart: false, - Schedule: "@every 72h", - }, func(ctx context.Context, _ *user_model.User, _ Config) error { - return repo_service.SyncRepositoryHooks(ctx) - }) -} - func registerReinitMissingRepositories() { RegisterTaskFatal("reinit_missing_repos", &BaseConfig{ Enabled: false, @@ -251,7 +241,6 @@ func initExtendedTasks() { registerGarbageCollectRepositories() registerRewriteAllPublicKeys() registerRewriteAllPrincipalKeys() - registerRepositoryUpdateHook() registerReinitMissingRepositories() registerDeleteMissingRepositories() registerRemoveRandomAvatars() diff --git a/services/doctor/misc.go b/services/doctor/misc.go index 9b9c96b52b..323606edf1 100644 --- a/services/doctor/misc.go +++ b/services/doctor/misc.go @@ -18,7 +18,6 @@ import ( "forgejo.org/modules/git" "forgejo.org/modules/gitrepo" "forgejo.org/modules/log" - "forgejo.org/modules/repository" "forgejo.org/modules/setting" "forgejo.org/modules/structs" "forgejo.org/modules/util" @@ -48,31 +47,6 @@ func checkScriptType(ctx context.Context, logger log.Logger, autofix bool) error return nil } -func checkHooks(ctx context.Context, logger log.Logger, autofix bool) error { - if err := iterateRepositories(ctx, func(repo *repo_model.Repository) error { - results, err := repository.CheckDelegateHooks(repo.RepoPath()) - if err != nil { - logger.Critical("Unable to check delegate hooks for repo %-v. ERROR: %v", repo, err) - return fmt.Errorf("Unable to check delegate hooks for repo %-v. ERROR: %w", repo, err) - } - if len(results) > 0 && autofix { - logger.Warn("Regenerated hooks for %s", repo.FullName()) - if err := repository.CreateDelegateHooks(repo.RepoPath()); err != nil { - logger.Critical("Unable to recreate delegate hooks for %-v. ERROR: %v", repo, err) - return fmt.Errorf("Unable to recreate delegate hooks for %-v. ERROR: %w", repo, err) - } - } - for _, result := range results { - logger.Warn(result) - } - return nil - }); err != nil { - logger.Critical("Errors noted whilst checking delegate hooks.") - return err - } - return nil -} - func checkUserStarNum(ctx context.Context, logger log.Logger, autofix bool) error { if autofix { if err := models.DoctorUserStarNum(ctx); err != nil { @@ -261,13 +235,6 @@ func init() { Run: checkScriptType, Priority: 5, }) - Register(&Check{ - Title: "Check if hook files are up-to-date and executable", - Name: "hooks", - IsDefault: false, - Run: checkHooks, - Priority: 6, - }) Register(&Check{ Title: "Recalculate Stars number for all user", Name: "recalculate-stars-number", diff --git a/services/repository/adopt.go b/services/repository/adopt.go index 3651b018e6..1949a62acf 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -117,10 +117,6 @@ func adoptRepository(ctx context.Context, repoPath string, repo *repo_model.Repo return fmt.Errorf("adoptRepository: path does not already exist: %s", repoPath) } - if err := repo_module.CreateDelegateHooks(repoPath); err != nil { - return fmt.Errorf("createDelegateHooks: %w", err) - } - repo.IsEmpty = false if len(defaultBranch) > 0 { diff --git a/services/repository/fork.go b/services/repository/fork.go index 9d15b6207d..a51a290c05 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -164,10 +164,6 @@ func ForkRepositoryIfNotExists(ctx context.Context, doer, owner *user_model.User return fmt.Errorf("git update-server-info: %w", err) } - if err = repo_module.CreateDelegateHooks(repoPath); err != nil { - return fmt.Errorf("createDelegateHooks: %w", err) - } - gitRepo, err := gitrepo.OpenRepository(txCtx, repo) if err != nil { return fmt.Errorf("OpenRepository: %w", err) diff --git a/services/repository/hooks.go b/services/repository/hooks.go index d3021414cf..808b49212e 100644 --- a/services/repository/hooks.go +++ b/services/repository/hooks.go @@ -5,51 +5,13 @@ package repository import ( "context" - "fmt" "forgejo.org/models/db" repo_model "forgejo.org/models/repo" "forgejo.org/models/webhook" "forgejo.org/modules/gitrepo" - "forgejo.org/modules/log" - repo_module "forgejo.org/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( - ctx, - builder.Gt{"id": 0}, - func(ctx context.Context, repo *repo_model.Repository) error { - 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: %w", err) - } - if repo.HasWiki() { - if err := repo_module.CreateDelegateHooks(repo.WikiPath()); err != nil { - return fmt.Errorf("SyncRepositoryHook: %w", err) - } - } - return nil - }, - ); err != nil { - return err - } - - log.Trace("Finished: SyncRepositoryHooks") - return nil -} - // GenerateGitHooks generates git hooks from a template repository func GenerateGitHooks(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { generateGitRepo, err := gitrepo.OpenRepository(ctx, generateRepo) diff --git a/services/repository/migrate.go b/services/repository/migrate.go index 46b48d02d3..e4c73dde8c 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -258,14 +258,6 @@ func cleanUpMigrateGitConfig(ctx context.Context, repoPath string) error { // CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors. func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo_model.Repository, error) { repoPath := repo.RepoPath() - if err := repo_module.CreateDelegateHooks(repoPath); err != nil { - return repo, fmt.Errorf("createDelegateHooks: %w", err) - } - if repo.HasWiki() { - if err := repo_module.CreateDelegateHooks(repo.WikiPath()); err != nil { - return repo, fmt.Errorf("createDelegateHooks.(wiki): %w", err) - } - } _, _, err := git.NewCommand(ctx, "remote", "rm", "origin").RunStdString(&git.RunOpts{Dir: repoPath}) if err != nil && !git.IsRemoteNotExistError(err) { diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index cf1477e72c..984c394f0e 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -42,8 +42,6 @@ func InitWiki(ctx context.Context, repo *repo_model.Repository) error { if err := git.InitRepository(ctx, repo.WikiPath(), true, repo.ObjectFormatName); err != nil { return fmt.Errorf("InitRepository: %w", err) - } else if err = repo_module.CreateDelegateHooks(repo.WikiPath()); err != nil { - return fmt.Errorf("createDelegateHooks: %w", err) } else if _, _, err = git.NewCommand(ctx, "symbolic-ref", "HEAD").AddDynamicArguments(git.BranchPrefix + branch).RunStdString(&git.RunOpts{Dir: repo.WikiPath()}); err != nil { return fmt.Errorf("unable to set default wiki branch to %s: %w", branch, err) } diff --git a/tests/install.ini.tmpl b/tests/install.ini.tmpl new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index 513eac1155..e3cab38d9d 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -426,11 +426,11 @@ func TestAPICron(t *testing.T) { AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "31", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "30", resp.Header().Get("X-Total-Count")) var crons []api.Cron DecodeJSON(t, resp, &crons) - assert.Len(t, crons, 31) + assert.Len(t, crons, 30) }) t.Run("Execute", func(t *testing.T) { diff --git a/tests/integration/api_issue_config_test.go b/tests/integration/api_issue_config_test.go index 99b2829fbf..eecf00e406 100644 --- a/tests/integration/api_issue_config_test.go +++ b/tests/integration/api_issue_config_test.go @@ -7,6 +7,7 @@ package integration import ( "fmt" "net/http" + "net/url" "testing" repo_model "forgejo.org/models/repo" @@ -44,169 +45,169 @@ func getIssueConfig(t *testing.T, owner, repo string) api.IssueConfig { } func TestAPIRepoGetIssueConfig(t *testing.T) { - defer tests.PrepareTestEnv(t)() + onApplicationRun(t, func(t *testing.T, _ *url.URL) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 49}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 49}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + t.Run("Default", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - t.Run("Default", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + issueConfig := getIssueConfig(t, owner.Name, repo.Name) - issueConfig := getIssueConfig(t, owner.Name, repo.Name) + assert.True(t, issueConfig.BlankIssuesEnabled) + assert.Empty(t, issueConfig.ContactLinks) + }) - assert.True(t, issueConfig.BlankIssuesEnabled) - assert.Empty(t, issueConfig.ContactLinks) - }) + t.Run("DisableBlankIssues", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - t.Run("DisableBlankIssues", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + config := make(map[string]any) + config["blank_issues_enabled"] = false - config := make(map[string]any) - config["blank_issues_enabled"] = false + createIssueConfig(t, owner, repo, config) - createIssueConfig(t, owner, repo, config) + issueConfig := getIssueConfig(t, owner.Name, repo.Name) - issueConfig := getIssueConfig(t, owner.Name, repo.Name) + assert.False(t, issueConfig.BlankIssuesEnabled) + assert.Empty(t, issueConfig.ContactLinks) + }) - assert.False(t, issueConfig.BlankIssuesEnabled) - assert.Empty(t, issueConfig.ContactLinks) - }) + t.Run("ContactLinks", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - t.Run("ContactLinks", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + contactLink := make(map[string]string) + contactLink["name"] = "TestName" + contactLink["url"] = "https://example.com" + contactLink["about"] = "TestAbout" - contactLink := make(map[string]string) - contactLink["name"] = "TestName" - contactLink["url"] = "https://example.com" - contactLink["about"] = "TestAbout" + config := make(map[string]any) + config["contact_links"] = []map[string]string{contactLink} - config := make(map[string]any) - config["contact_links"] = []map[string]string{contactLink} + createIssueConfig(t, owner, repo, config) - createIssueConfig(t, owner, repo, config) + issueConfig := getIssueConfig(t, owner.Name, repo.Name) - issueConfig := getIssueConfig(t, owner.Name, repo.Name) + assert.True(t, issueConfig.BlankIssuesEnabled) + assert.Len(t, issueConfig.ContactLinks, 1) - assert.True(t, issueConfig.BlankIssuesEnabled) - assert.Len(t, issueConfig.ContactLinks, 1) + assert.Equal(t, "TestName", issueConfig.ContactLinks[0].Name) + assert.Equal(t, "https://example.com", issueConfig.ContactLinks[0].URL) + assert.Equal(t, "TestAbout", issueConfig.ContactLinks[0].About) + }) - assert.Equal(t, "TestName", issueConfig.ContactLinks[0].Name) - assert.Equal(t, "https://example.com", issueConfig.ContactLinks[0].URL) - assert.Equal(t, "TestAbout", issueConfig.ContactLinks[0].About) - }) + t.Run("Full", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - t.Run("Full", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + contactLink := make(map[string]string) + contactLink["name"] = "TestName" + contactLink["url"] = "https://example.com" + contactLink["about"] = "TestAbout" - contactLink := make(map[string]string) - contactLink["name"] = "TestName" - contactLink["url"] = "https://example.com" - contactLink["about"] = "TestAbout" + config := make(map[string]any) + config["blank_issues_enabled"] = false + config["contact_links"] = []map[string]string{contactLink} - config := make(map[string]any) - config["blank_issues_enabled"] = false - config["contact_links"] = []map[string]string{contactLink} + createIssueConfig(t, owner, repo, config) - createIssueConfig(t, owner, repo, config) + issueConfig := getIssueConfig(t, owner.Name, repo.Name) - issueConfig := getIssueConfig(t, owner.Name, repo.Name) + assert.False(t, issueConfig.BlankIssuesEnabled) + assert.Len(t, issueConfig.ContactLinks, 1) - assert.False(t, issueConfig.BlankIssuesEnabled) - assert.Len(t, issueConfig.ContactLinks, 1) - - assert.Equal(t, "TestName", issueConfig.ContactLinks[0].Name) - assert.Equal(t, "https://example.com", issueConfig.ContactLinks[0].URL) - assert.Equal(t, "TestAbout", issueConfig.ContactLinks[0].About) + assert.Equal(t, "TestName", issueConfig.ContactLinks[0].Name) + assert.Equal(t, "https://example.com", issueConfig.ContactLinks[0].URL) + assert.Equal(t, "TestAbout", issueConfig.ContactLinks[0].About) + }) }) } func TestAPIRepoIssueConfigPaths(t *testing.T) { - defer tests.PrepareTestEnv(t)() + onApplicationRun(t, func(t *testing.T, _ *url.URL) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 49}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 49}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - templateConfigCandidates := []string{ - ".forgejo/ISSUE_TEMPLATE/config", - ".forgejo/issue_template/config", - ".gitea/ISSUE_TEMPLATE/config", - ".gitea/issue_template/config", - ".github/ISSUE_TEMPLATE/config", - ".github/issue_template/config", - "docs/issue_template/config", - } - - for _, candidate := range templateConfigCandidates { - for _, extension := range []string{".yaml", ".yml"} { - fullPath := candidate + extension - t.Run(fullPath, func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - configMap := make(map[string]any) - configMap["blank_issues_enabled"] = false - - configData, err := yaml.Marshal(configMap) - require.NoError(t, err) - - _, err = createFileInBranch(owner, repo, fullPath, repo.DefaultBranch, string(configData)) - require.NoError(t, err) - - issueConfig := getIssueConfig(t, owner.Name, repo.Name) - - assert.False(t, issueConfig.BlankIssuesEnabled) - assert.Empty(t, issueConfig.ContactLinks) - - err = deleteFileInBranch(owner, repo, fullPath, repo.DefaultBranch) - require.NoError(t, err) - }) + templateConfigCandidates := []string{ + ".forgejo/ISSUE_TEMPLATE/config", + ".forgejo/issue_template/config", + ".gitea/ISSUE_TEMPLATE/config", + ".gitea/issue_template/config", + ".github/ISSUE_TEMPLATE/config", + ".github/issue_template/config", + "docs/issue_template/config", } - } + + for _, candidate := range templateConfigCandidates { + for _, extension := range []string{".yaml", ".yml"} { + fullPath := candidate + extension + t.Run(fullPath, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + configMap := make(map[string]any) + configMap["blank_issues_enabled"] = false + + configData, err := yaml.Marshal(configMap) + require.NoError(t, err) + + _, err = createFileInBranch(owner, repo, fullPath, repo.DefaultBranch, string(configData)) + require.NoError(t, err) + + issueConfig := getIssueConfig(t, owner.Name, repo.Name) + + assert.False(t, issueConfig.BlankIssuesEnabled) + assert.Empty(t, issueConfig.ContactLinks) + + err = deleteFileInBranch(owner, repo, fullPath, repo.DefaultBranch) + require.NoError(t, err) + }) + } + } + }) } func TestAPIRepoValidateIssueConfig(t *testing.T) { - defer tests.PrepareTestEnv(t)() + onApplicationRun(t, func(t *testing.T, _ *url.URL) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 49}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 49}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issue_config/validate", owner.Name, repo.Name) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issue_config/validate", owner.Name, repo.Name) + t.Run("Valid", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - t.Run("Valid", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) + var issueConfigValidation api.IssueConfigValidation + DecodeJSON(t, resp, &issueConfigValidation) - var issueConfigValidation api.IssueConfigValidation - DecodeJSON(t, resp, &issueConfigValidation) + assert.True(t, issueConfigValidation.Valid) + assert.Empty(t, issueConfigValidation.Message) + }) - assert.True(t, issueConfigValidation.Valid) - assert.Empty(t, issueConfigValidation.Message) - }) + t.Run("Invalid", func(t *testing.T) { + dirs := []string{".gitea", ".forgejo", "docs"} + for _, dir := range dirs { + t.Run(dir, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer func() { + deleteFileInBranch(owner, repo, fmt.Sprintf("%s/ISSUE_TEMPLATE/config.yaml", dir), repo.DefaultBranch) + }() - t.Run("Invalid", func(t *testing.T) { - dirs := []string{".gitea", ".forgejo", "docs"} - for _, dir := range dirs { - t.Run(dir, func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - defer func() { - deleteFileInBranch(owner, repo, fmt.Sprintf("%s/ISSUE_TEMPLATE/config.yaml", dir), repo.DefaultBranch) - }() + config := make(map[string]any) + config["blank_issues_enabled"] = "Test" - config := make(map[string]any) - config["blank_issues_enabled"] = "Test" + createIssueConfigInDirectory(t, owner, repo, dir, config) - createIssueConfigInDirectory(t, owner, repo, dir, config) + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) + var issueConfigValidation api.IssueConfigValidation + DecodeJSON(t, resp, &issueConfigValidation) - var issueConfigValidation api.IssueConfigValidation - DecodeJSON(t, resp, &issueConfigValidation) - - assert.False(t, issueConfigValidation.Valid) - assert.NotEmpty(t, issueConfigValidation.Message) - }) - } + assert.False(t, issueConfigValidation.Valid) + assert.NotEmpty(t, issueConfigValidation.Message) + }) + } + }) }) } diff --git a/tests/integration/empty_repo_test.go b/tests/integration/empty_repo_test.go index 67be6a29a5..fe7c131331 100644 --- a/tests/integration/empty_repo_test.go +++ b/tests/integration/empty_repo_test.go @@ -10,6 +10,7 @@ import ( "io" "mime/multipart" "net/http" + "net/url" "testing" auth_model "forgejo.org/models/auth" @@ -44,96 +45,96 @@ func TestEmptyRepo(t *testing.T) { } func TestEmptyRepoAddFile(t *testing.T) { - defer tests.PrepareTestEnv(t)() + onApplicationRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user30") + req := NewRequest(t, "GET", "/user30/empty/_new/"+setting.Repository.DefaultBranch) + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body).Find(`input[name="commit_choice"]`) + assert.Empty(t, doc.AttrOr("checked", "_no_")) + req = NewRequestWithValues(t, "POST", "/user30/empty/_new/"+setting.Repository.DefaultBranch, map[string]string{ + "commit_choice": "direct", + "tree_path": "test-file.md", + "content": "newly-added-test-file", + "commit_mail_id": "32", + }) - session := loginUser(t, "user30") - req := NewRequest(t, "GET", "/user30/empty/_new/"+setting.Repository.DefaultBranch) - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body).Find(`input[name="commit_choice"]`) - assert.Empty(t, doc.AttrOr("checked", "_no_")) - req = NewRequestWithValues(t, "POST", "/user30/empty/_new/"+setting.Repository.DefaultBranch, map[string]string{ - "commit_choice": "direct", - "tree_path": "test-file.md", - "content": "newly-added-test-file", - "commit_mail_id": "32", + resp = session.MakeRequest(t, req, http.StatusSeeOther) + redirect := test.RedirectURL(resp) + assert.Equal(t, "/user30/empty/src/branch/"+setting.Repository.DefaultBranch+"/test-file.md", redirect) + + req = NewRequest(t, "GET", redirect) + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "newly-added-test-file") }) - - resp = session.MakeRequest(t, req, http.StatusSeeOther) - redirect := test.RedirectURL(resp) - assert.Equal(t, "/user30/empty/src/branch/"+setting.Repository.DefaultBranch+"/test-file.md", redirect) - - req = NewRequest(t, "GET", redirect) - resp = session.MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "newly-added-test-file") } func TestEmptyRepoUploadFile(t *testing.T) { - defer tests.PrepareTestEnv(t)() + onApplicationRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user30") + req := NewRequest(t, "GET", "/user30/empty/_new/"+setting.Repository.DefaultBranch) + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body).Find(`input[name="commit_choice"]`) + assert.Empty(t, doc.AttrOr("checked", "_no_")) - session := loginUser(t, "user30") - req := NewRequest(t, "GET", "/user30/empty/_new/"+setting.Repository.DefaultBranch) - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body).Find(`input[name="commit_choice"]`) - assert.Empty(t, doc.AttrOr("checked", "_no_")) + body := &bytes.Buffer{} + mpForm := multipart.NewWriter(body) + file, _ := mpForm.CreateFormFile("file", "uploaded-file.txt") + _, _ = io.Copy(file, bytes.NewBufferString("newly-uploaded-test-file")) + _ = mpForm.Close() - body := &bytes.Buffer{} - mpForm := multipart.NewWriter(body) - file, _ := mpForm.CreateFormFile("file", "uploaded-file.txt") - _, _ = io.Copy(file, bytes.NewBufferString("newly-uploaded-test-file")) - _ = mpForm.Close() + req = NewRequestWithBody(t, "POST", "/user30/empty/upload-file", body) + req.Header.Add("Content-Type", mpForm.FormDataContentType()) + resp = session.MakeRequest(t, req, http.StatusOK) + respMap := map[string]string{} + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &respMap)) + filesFullpathKey := fmt.Sprintf("files_fullpath[%s]", respMap["uuid"]) + req = NewRequestWithValues(t, "POST", "/user30/empty/_upload/"+setting.Repository.DefaultBranch, map[string]string{ + "commit_choice": "direct", + "files": respMap["uuid"], + filesFullpathKey: "uploaded-file.txt", + "tree_path": "", + "commit_mail_id": "-1", + }) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + redirect := test.RedirectURL(resp) + assert.Equal(t, "/user30/empty/src/branch/"+setting.Repository.DefaultBranch+"/", redirect) - req = NewRequestWithBody(t, "POST", "/user30/empty/upload-file", body) - req.Header.Add("Content-Type", mpForm.FormDataContentType()) - resp = session.MakeRequest(t, req, http.StatusOK) - respMap := map[string]string{} - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &respMap)) - filesFullpathKey := fmt.Sprintf("files_fullpath[%s]", respMap["uuid"]) - req = NewRequestWithValues(t, "POST", "/user30/empty/_upload/"+setting.Repository.DefaultBranch, map[string]string{ - "commit_choice": "direct", - "files": respMap["uuid"], - filesFullpathKey: "uploaded-file.txt", - "tree_path": "", - "commit_mail_id": "-1", + req = NewRequest(t, "GET", redirect) + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "uploaded-file.txt") }) - resp = session.MakeRequest(t, req, http.StatusSeeOther) - redirect := test.RedirectURL(resp) - assert.Equal(t, "/user30/empty/src/branch/"+setting.Repository.DefaultBranch+"/", redirect) - - req = NewRequest(t, "GET", redirect) - resp = session.MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "uploaded-file.txt") } func TestEmptyRepoAddFileByAPI(t *testing.T) { - defer tests.PrepareTestEnv(t)() + onApplicationRun(t, func(t *testing.T, _ *url.URL) { + session := loginUser(t, "user30") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - session := loginUser(t, "user30") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user30/empty/contents/new-file.txt", &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + NewBranchName: "new_branch", + Message: "init", + }, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("newly-added-api-file")), + }).AddTokenAuth(token) - req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user30/empty/contents/new-file.txt", &api.CreateFileOptions{ - FileOptions: api.FileOptions{ - NewBranchName: "new_branch", - Message: "init", - }, - ContentBase64: base64.StdEncoding.EncodeToString([]byte("newly-added-api-file")), - }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + var fileResponse api.FileResponse + DecodeJSON(t, resp, &fileResponse) + expectedHTMLURL := setting.AppURL + "user30/empty/src/branch/new_branch/new-file.txt" + assert.Equal(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) - resp := MakeRequest(t, req, http.StatusCreated) - var fileResponse api.FileResponse - DecodeJSON(t, resp, &fileResponse) - expectedHTMLURL := setting.AppURL + "user30/empty/src/branch/new_branch/new-file.txt" - assert.Equal(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + req = NewRequest(t, "GET", "/user30/empty/src/branch/new_branch/new-file.txt") + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "newly-added-api-file") - req = NewRequest(t, "GET", "/user30/empty/src/branch/new_branch/new-file.txt") - resp = session.MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "newly-added-api-file") - - req = NewRequest(t, "GET", "/api/v1/repos/user30/empty"). - AddTokenAuth(token) - resp = session.MakeRequest(t, req, http.StatusOK) - var apiRepo api.Repository - DecodeJSON(t, resp, &apiRepo) - assert.Equal(t, "new_branch", apiRepo.DefaultBranch) + req = NewRequest(t, "GET", "/api/v1/repos/user30/empty"). + AddTokenAuth(token) + resp = session.MakeRequest(t, req, http.StatusOK) + var apiRepo api.Repository + DecodeJSON(t, resp, &apiRepo) + assert.Equal(t, "new_branch", apiRepo.DefaultBranch) + }) } func TestEmptyRepoAPIRequestsReturn404(t *testing.T) { diff --git a/tests/integration/git_hooks_test.go b/tests/integration/git_hooks_test.go new file mode 100644 index 0000000000..05d2223aee --- /dev/null +++ b/tests/integration/git_hooks_test.go @@ -0,0 +1,99 @@ +// Copyright 2026 The Forgejo Authors c/o Codeberg e.V.. All rights reserved. +// SPDX-License-Identifier: MIT +package integration + +import ( + "fmt" + "net/url" + "os" + "path" + "testing" + + "forgejo.org/models/auth" + repo_model "forgejo.org/models/repo" + "forgejo.org/models/unittest" + user_model "forgejo.org/models/user" + "forgejo.org/modules/git" + + "github.com/stretchr/testify/require" +) + +func TestCustomGitHooks(t *testing.T) { + onApplicationRun(t, func(t *testing.T, u *url.URL) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + httpContext := NewAPITestContext(t, owner.Name, repo.Name, auth.AccessTokenScopeReadRepository) + + dstPath := t.TempDir() + + u.Path = httpContext.GitPath() + u.User = url.UserPassword(owner.Name, userPassword) + + doGitClone(dstPath, u)(t) + + customHooksDir := path.Join(repo.RepoPath(), "hooks") + + hookNames := []string{"pre-receive", "update", "post-receive"} + + for _, hookName := range hookNames { + customPath := path.Join(customHooksDir, hookName+".d") + err := os.MkdirAll(customPath, 0x755) + require.NoError(t, err) + + err = os.WriteFile(path.Join(customPath, "append-proof"), customGitHookTpl(hookName), 0x755) + require.NoError(t, err) + + // The legacy, already existing gitea script might be there in the hooks directory in old installations, + // here it's ensured that these scripts filtered out when custom hooks run + err = os.WriteFile(path.Join(customPath, "gitea"), customGitHookGiteaTpl(), 0x755) + require.NoError(t, err) + } + + fd, err := os.Create(path.Join(dstPath, "hooks-test.txt")) + require.NoError(t, err) + + err = fd.Close() + require.NoError(t, err) + + _, _, err = git.NewCommand(git.DefaultContext, "checkout", "master").RunStdString(&git.RunOpts{Dir: dstPath}) + require.NoError(t, err) + + err = os.WriteFile(path.Join(dstPath, "hooks-test.txt"), []byte("test"), 0x644) + require.NoError(t, err) + + _, _, err = git.NewCommand(git.DefaultContext, "add", "hooks-test.txt").RunStdString(&git.RunOpts{Dir: dstPath}) + require.NoError(t, err) + + _, _, err = git.NewCommand(git.DefaultContext, "commit", "-m", "Add hooks-test.txt").RunStdString(&git.RunOpts{Dir: dstPath}) + require.NoError(t, err) + + _, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "master").RunStdString(&git.RunOpts{Dir: dstPath}) + require.NoError(t, err) + + data, err := os.ReadFile(path.Join(customHooksDir, "hooks-proof.txt")) + require.NoError(t, err) + + require.Equal(t, `pre-receive +update +post-receive +`, string(data)) + }) +} + +func customGitHookTpl(hookName string) []byte { + hookStr := fmt.Sprintf(`#!/usr/bin/env sh +echo "%s" >> $(dirname $0)/../hooks-proof.txt +`, hookName) + + return []byte(hookStr) +} + +func customGitHookGiteaTpl() []byte { + hookStr := `#!/usr/bin/env sh +echo "legacy gitea script shouldn't be called!" +exit 1 +` + + return []byte(hookStr) +} diff --git a/tests/integration/git_test.go b/tests/integration/git_test.go index a5a242b912..dc86f4a4c2 100644 --- a/tests/integration/git_test.go +++ b/tests/integration/git_test.go @@ -201,6 +201,7 @@ func standardCommitAndPushTest(t *testing.T, dstPath string) (little, big string func lfsCommitAndPushTest(t *testing.T, dstPath string) (littleLFS, bigLFS string) { t.Run("LFS", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + defer git.NewCommand(git.DefaultContext, "lfs").AddArguments("uninstall").Run(&git.RunOpts{Dir: dstPath}) prefix := "lfs-data-file-" err := git.NewCommand(git.DefaultContext, "lfs").AddArguments("install").Run(&git.RunOpts{Dir: dstPath}) require.NoError(t, err) diff --git a/tests/integration/repo_mergecommit_revert_test.go b/tests/integration/repo_mergecommit_revert_test.go index 9ddf39eaaa..8cbe8a8142 100644 --- a/tests/integration/repo_mergecommit_revert_test.go +++ b/tests/integration/repo_mergecommit_revert_test.go @@ -5,29 +5,29 @@ package integration import ( "net/http" + "net/url" "testing" - "forgejo.org/tests" - "github.com/stretchr/testify/assert" ) func TestRepoMergeCommitRevert(t *testing.T) { - defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user2") + onApplicationRun(t, func(t *testing.T, _ *url.URL) { + session := loginUser(t, "user2") - req := NewRequestWithValues(t, "POST", "/user2/test_commit_revert/_cherrypick/deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7/main", map[string]string{ - "last_commit": "deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7", - "page_has_posted": "true", - "revert": "true", - "commit_summary": "reverting test commit", - "commit_message": "test message", - "commit_choice": "direct", - "new_branch_name": "test-revert-branch-1", - "commit_mail_id": "-1", + req := NewRequestWithValues(t, "POST", "/user2/test_commit_revert/_cherrypick/deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7/main", map[string]string{ + "last_commit": "deebcbc752e540bab4ce3ee713d3fc8fdc35b2f7", + "page_has_posted": "true", + "revert": "true", + "commit_summary": "reverting test commit", + "commit_message": "test message", + "commit_choice": "direct", + "new_branch_name": "test-revert-branch-1", + "commit_mail_id": "-1", + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + + // A successful revert redirects to the main branch + assert.Equal(t, "/user2/test_commit_revert/src/branch/main", resp.Header().Get("Location")) }) - resp := session.MakeRequest(t, req, http.StatusSeeOther) - - // A successful revert redirects to the main branch - assert.Equal(t, "/user2/test_commit_revert/src/branch/main", resp.Header().Get("Location")) } diff --git a/tests/unittest.ini.tmpl b/tests/unittest.ini.tmpl new file mode 100644 index 0000000000..a2925b42e7 --- /dev/null +++ b/tests/unittest.ini.tmpl @@ -0,0 +1,2 @@ +[security] +INSTALL_LOCK = true