Align release publication policy and runbook

This commit is contained in:
2026-09-28 01:50:26 +00:00
parent 65502ce5b4
commit ce91f04974
9 changed files with 291 additions and 325 deletions

View File

@@ -48,6 +48,7 @@ func TestReleaseCommandRejectsUnpublishedOrExistingCandidates(t *testing.T) {
fixture.extraEnv = append(fixture.extraEnv, "NARRATIO_RELEASE_CHECKER_MODE=fail")
fixture.mustFail(t, "release candidate validation failed")
fixture.requireNoTag(t)
assertCheckerCalls(t, fixture, 1)
})
t.Run("local tag exists", func(t *testing.T) {
fixture := newReleaseFixture(t)
@@ -90,6 +91,8 @@ git push -q origin main
func TestReleaseCommandPushesOnlyTheRecordedLightweightTag(t *testing.T) {
fixture := newReleaseFixture(t)
writeReleaseFixtureFile(t, filepath.Join(fixture.worktree, ".git", "info", "exclude"), "ignored.tmp\n", 0o644)
writeReleaseFixtureFile(t, filepath.Join(fixture.worktree, "ignored.tmp"), "local-only\n", 0o644)
candidate := fixture.gitOutput("rev-parse", "HEAD^{commit}")
output, err := fixture.run()
if err != nil {
@@ -113,13 +116,14 @@ func TestReleaseCommandPushesOnlyTheRecordedLightweightTag(t *testing.T) {
if got := strings.Fields(fixture.gitOutput("--git-dir", fixture.origin, "for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags")); !slicesEqual(got, []string{"refs/heads/main", "refs/tags/v1.2.3"}) {
t.Fatalf("remote refs = %#v", got)
}
assertCheckerCalls(t, fixture, 1)
}
func TestReleaseCommandKeepsUnpublishedLocalTagAfterPushFailure(t *testing.T) {
func TestReleaseCommandRetainsLocalTagAfterUnconfirmedPush(t *testing.T) {
fixture := newReleaseFixture(t)
writeReleaseFixtureFile(t, filepath.Join(fixture.origin, "hooks", "pre-receive"), "#!/bin/sh\nexit 1\n", 0o755)
output, err := fixture.run()
if err == nil || !strings.Contains(output, "local tag v1.2.3 was created but not published") {
if err == nil || !strings.Contains(output, "tag push for v1.2.3 failed or is unconfirmed") || strings.Contains(output, "release: published") || strings.Contains(output, "not published") {
t.Fatalf("push failure = %v\n%s", err, output)
}
if got := fixture.gitOutput("cat-file", "-t", "refs/tags/v1.2.3"); got != "commit" {
@@ -128,12 +132,25 @@ func TestReleaseCommandKeepsUnpublishedLocalTagAfterPushFailure(t *testing.T) {
if output, err := runReleaseGit(fixture.worktree, "--git-dir", fixture.origin, "show-ref", "--verify", "refs/tags/v1.2.3"); err == nil {
t.Fatalf("remote tag exists after failed push: %s", output)
}
assertCheckerCalls(t, fixture, 1)
}
func assertCheckerCalls(t *testing.T, fixture *releaseFixture, want int) {
t.Helper()
data, err := os.ReadFile(fixture.checkerLog)
if err != nil {
t.Fatal(err)
}
if got := len(strings.Fields(string(data))); got != want {
t.Fatalf("candidate checker called %d times, want %d: %q", got, want, data)
}
}
type releaseFixture struct {
worktree string
origin string
extraEnv []string
worktree string
origin string
checkerLog string
extraEnv []string
}
func newReleaseFixture(t *testing.T) *releaseFixture {
@@ -154,7 +171,7 @@ func newReleaseFixture(t *testing.T) *releaseFixture {
writeReleaseFixtureFile(t, filepath.Join(worktree, "docs", "releases", "v1.2.3.md"), "# Narratio v1.2.3\n", 0o644)
writeReleaseFixtureFile(t, filepath.Join(worktree, "README.md"), "fixture\n", 0o644)
fixture := &releaseFixture{worktree: worktree, origin: origin}
fixture := &releaseFixture{worktree: worktree, origin: origin, checkerLog: filepath.Join(root, "checker.log")}
fixture.git("init", "-qb", "main")
fixture.git("config", "user.email", "releasecheck@example.test")
fixture.git("config", "user.name", "Release Check")
@@ -176,7 +193,8 @@ func (fixture *releaseFixture) runWith(args ...string) (string, error) {
commandArgs := append([]string{filepath.Join(fixture.worktree, "scripts", "release.sh")}, args...)
command := exec.Command("sh", commandArgs...)
command.Dir = fixture.worktree
command.Env = append(os.Environ(), fixture.extraEnv...)
command.Env = append(os.Environ(), "NARRATIO_RELEASE_CHECKER_LOG="+fixture.checkerLog)
command.Env = append(command.Env, fixture.extraEnv...)
output, err := command.CombinedOutput()
return string(output), err
}
@@ -238,6 +256,7 @@ func writeReleaseFixtureFile(t *testing.T, path, contents string, mode os.FileMo
const releaseCandidateStub = `#!/bin/sh
set -eu
printf 'called\n' >> "$NARRATIO_RELEASE_CHECKER_LOG"
case "${NARRATIO_RELEASE_CHECKER_MODE:-success}" in
success) ;;