diff --git a/cmd/hook.go b/cmd/hook.go index 966e4a57c..70001f640 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -293,9 +293,32 @@ Forgejo or set your environment appropriately.`, "") return nil } +// runHookUpdate process the update hook: https://git-scm.com/docs/githooks#update func runHookUpdate(c *cli.Context) error { - // Update is empty and is kept only for backwards compatibility - return nil + // Now if we're an internal don't do anything else + if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { + return nil + } + + ctx, cancel := installSignals() + defer cancel() + + // The last three arguments given to the hook are in order: reference name, old commit ID and new commit ID. + args := os.Args[len(os.Args)-3:] + refFullName := git.RefName(args[0]) + newCommitID := args[2] + + // Only process pull references. + if !refFullName.IsPull() { + return nil + } + + // Deletion of the ref means that the new commit ID is only composed of '0'. + if strings.ContainsFunc(newCommitID, func(e rune) bool { return e != '0' }) { + return nil + } + + return fail(ctx, fmt.Sprintf("The deletion of %s is skipped as it's an internal reference.", refFullName), "") } func runHookPostReceive(c *cli.Context) error { diff --git a/tests/integration/git_test.go b/tests/integration/git_test.go index e0a824671..782390002 100644 --- a/tests/integration/git_test.go +++ b/tests/integration/git_test.go @@ -84,6 +84,7 @@ func testGit(t *testing.T, u *url.URL) { mediaTest(t, &httpContext, little, big, littleLFS, bigLFS) t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "master", "test/head")) + t.Run("InternalReferences", doInternalReferences(&httpContext, dstPath)) t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath)) t.Run("AutoMerge", doAutoPRMerge(&httpContext, dstPath)) t.Run("CreatePRAndSetManuallyMerged", doCreatePRAndSetManuallyMerged(httpContext, httpContext, dstPath, "master", "test-manually-merge")) @@ -125,6 +126,7 @@ func testGit(t *testing.T, u *url.URL) { mediaTest(t, &sshContext, little, big, littleLFS, bigLFS) t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "master", "test/head2")) + t.Run("InternalReferences", doInternalReferences(&sshContext, dstPath)) t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath)) t.Run("MergeFork", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -734,6 +736,20 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { } } +func doInternalReferences(ctx *APITestContext, dstPath string) func(t *testing.T) { + return func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: ctx.Username, Name: ctx.Reponame}) + pr1 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{HeadRepoID: repo.ID}) + + _, stdErr, gitErr := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments(fmt.Sprintf(":refs/pull/%d/head", pr1.Index)).RunStdString(&git.RunOpts{Dir: dstPath}) + assert.Error(t, gitErr) + assert.Contains(t, stdErr, fmt.Sprintf("remote: Forgejo: The deletion of refs/pull/%d/head is skipped as it's an internal reference.", pr1.Index)) + assert.Contains(t, stdErr, fmt.Sprintf("[remote rejected] refs/pull/%d/head (hook declined)", pr1.Index)) + } +} + func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headBranch string) func(t *testing.T) { return func(t *testing.T) { defer tests.PrintCurrentTest(t)()