From e64932b388d2a6b3a1e76772ab1c5a67bf54ca51 Mon Sep 17 00:00:00 2001 From: Manuel de Brito Fontes Date: Fri, 13 Nov 2026 03:13:19 -0300 Subject: [PATCH] gate: no commit by a test identity, and spinpropose writes no git config A test in spin wrote git's author into the checkout's config, and every commit after it was the test's, on GitHub. The gate now runs go-tools' testidentity over the branch's commits since origin/main - a reserved-domain address, one with no dot, or one this tree's tests spell - so such a commit stops here. CI's Gate fetches the history it reads. spinpropose set the app's name and address with git config, on the checkout it ran in: every commit made there after it was the app's. It commits as the app with -c on that commit alone, and its test holds that no git config is run. Its tests run no git: every command goes to the fake. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 3 +++ Taskfile.yml | 5 ++++- hack/spinpropose/main.go | 7 ++++--- hack/spinpropose/main_test.go | 14 ++++++++++---- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 336e2a9..153e3e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,9 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + # The gate's testidentity reads the branch's commits since origin/main, which a pull + # request's shallow checkout does not have. + fetch-depth: 0 - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 # No store cache: a package restored from one is never asked of the registry, so Socket # Firewall, which stands between the two, would not see it. diff --git a/Taskfile.yml b/Taskfile.yml index 71e1440..2d5fb05 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -226,11 +226,14 @@ tasks: - go run ./hack/releasetag gate: - desc: What holds the repository's shape - every install through the firewall, and every workflow step a task. + desc: What holds the repository's shape - every install through the firewall, every workflow step a task, and no commit since origin/main by a test's identity. cmds: - sfw check # The Task running this one, which taskgate asks for the task names. - taskgate -task {{.TASK_EXE}} + # A test that writes git's author into the checkout makes every commit after it the + # test's; this refuses those commits before they reach main. + - testidentity -base origin/main ci: desc: What CI's Gate job runs - the gate, the install, lint, typecheck, the unit tests, and the package. test:integration is its own job. diff --git a/hack/spinpropose/main.go b/hack/spinpropose/main.go index 9122a94..3a478e2 100644 --- a/hack/spinpropose/main.go +++ b/hack/spinpropose/main.go @@ -103,11 +103,12 @@ func (p proposal) open() (string, error) { return "", err } title := "versions: spin " + p.release + // The app's identity on this one commit, never in the checkout's config: written there, every + // commit made from the checkout after it is the app's too. + as := []string{"-c", "user.name=" + p.app + "[bot]", "-c", fmt.Sprintf("user.email=%d+%s[bot]@users.noreply.github.com", user.ID, p.app)} for _, args := range [][]string{ - {"config", "user.name", p.app + "[bot]"}, - {"config", "user.email", fmt.Sprintf("%d+%s[bot]@users.noreply.github.com", user.ID, p.app)}, {"checkout", "-q", "-B", branch}, - {"commit", "-q", "-m", title, "-m", "The release whose CLI every VSIX carries, checked by " + p.run + ".", "versions.yaml", p.pkg}, + append(as, "commit", "-q", "-m", title, "-m", "The release whose CLI every VSIX carries, checked by " + p.run + ".", "versions.yaml", p.pkg), {"push", "-q", "--force", "origin", branch}, } { if _, err := p.sh("git", args...); err != nil { diff --git a/hack/spinpropose/main_test.go b/hack/spinpropose/main_test.go index 4b22d07..b2bf85f 100644 --- a/hack/spinpropose/main_test.go +++ b/hack/spinpropose/main_test.go @@ -84,19 +84,25 @@ func TestABumpIsOpenedAsTheAppOnTheOneBranch(t *testing.T) { if err != nil || url != "https://github.com/spin-stack/vscode-spinbox/pull/9" { t.Fatalf("%q, %v", url, err) } - if c := ran(f, "git config user.email"); c != "git config user.email 42+spin-stack-release[bot]@users.noreply.github.com" { - t.Errorf("committed as %q", c) + c := ran(f, "git -c user.name=spin-stack-release[bot] -c user.email=42+spin-stack-release[bot]@users.noreply.github.com commit ") + if c == "" { + t.Errorf("not committed as the app: %q", f.ran) } - if c := ran(f, "git commit"); !strings.HasSuffix(c, " versions.yaml "+p.pkg) { + if !strings.HasSuffix(c, " versions.yaml "+p.pkg) { t.Errorf("committed %q, not versions.yaml and package.json", c) } + // The app's identity is the commit's alone: one in the checkout's config is every later + // commit's from it. + if c := ran(f, "git config"); c != "" { + t.Errorf("wrote the checkout's config: %q", c) + } if ran(f, "git push -q --force origin spin/next") == "" || ran(f, "gh pr edit") != "" { t.Errorf("ran %q", f.ran) } if c := ran(f, "gh pr list"); !strings.Contains(c, "--author app/spin-stack-release") { t.Errorf("listed %q, not the app's alone", c) } - c := ran(f, "gh pr create") + c = ran(f, "gh pr create") for _, want := range []string{"--head spin/next", "--title versions: spin v20261106.01", "actions/runs/7", "(darwin-arm64, linux-x64)", "releases/tag/v20261106.01"} { if !strings.Contains(c, want) { t.Errorf("the pull request lacks %q: %s", want, c)