Publish the captured release tag

This commit is contained in:
2026-10-05 18:24:37 +00:00
parent 7d2c0c10d4
commit 3757d40ac4
10 changed files with 642 additions and 22 deletions

View File

@@ -21,6 +21,7 @@ type Candidate struct {
RepositoryDir string
Commit string
Version string
Note string
}
type Request struct {
@@ -32,6 +33,22 @@ type Request struct {
}
func Check(ctx context.Context, request Request) (Candidate, error) {
captured, err := Capture(ctx, request)
if err != nil {
return Candidate{}, err
}
if err := Validate(ctx, request, captured); err != nil {
return Candidate{}, err
}
if err := Recheck(ctx, captured, ""); err != nil {
return Candidate{}, err
}
return captured, nil
}
// Capture validates request identity and records one clean local candidate,
// including the note bytes from the captured commit. It does not run checks.
func Capture(ctx context.Context, request Request) (Candidate, error) {
if !validStableVersion(request.Version) {
return Candidate{}, fmt.Errorf("%w: %q", ErrInvalidVersion, request.Version)
}
@@ -68,36 +85,61 @@ func Check(ctx context.Context, request Request) (Candidate, error) {
return Candidate{}, fmt.Errorf("local tag %q already exists", request.Version)
}
captured := Candidate{RepositoryDir: repositoryDir, Commit: state.commit, Version: request.Version}
return Candidate{RepositoryDir: repositoryDir, Commit: state.commit, Version: request.Version, Note: string(note)}, nil
}
// Validate runs the complete selected validation once for the captured
// candidate. A supplied callback replaces the default offline Go validation.
func Validate(ctx context.Context, request Request, captured Candidate) error {
var err error
if request.Validate == nil {
err = runDefaultValidation(ctx, captured.RepositoryDir, request.Output)
} else {
err = request.Validate(ctx, captured)
}
if err != nil {
return Candidate{}, fmt.Errorf("validate candidate %s: %w", captured.Commit, err)
return fmt.Errorf("validate candidate %s: %w", captured.Commit, err)
}
return nil
}
current, err := inspectLocalState(ctx, repositoryDir)
// Recheck verifies that the captured local source is unchanged. When
// expectedTagObject is empty, the selected local version tag must still be
// absent; otherwise the existing annotated tag must still have that object ID.
func Recheck(ctx context.Context, captured Candidate, expectedTagObject string) error {
current, err := inspectLocalState(ctx, captured.RepositoryDir)
if err != nil {
return Candidate{}, fmt.Errorf("recheck local candidate state: %w", err)
return fmt.Errorf("recheck local candidate state: %w", err)
}
if current.branch != "main" {
return Candidate{}, fmt.Errorf("release candidate branch changed during validation to %q", current.branch)
return fmt.Errorf("release candidate branch changed to %q", current.branch)
}
if current.commit != captured.Commit {
return Candidate{}, fmt.Errorf("HEAD changed during validation from %s to %s", captured.Commit, current.commit)
return fmt.Errorf("HEAD changed from %s to %s", captured.Commit, current.commit)
}
if current.status != "" {
return Candidate{}, fmt.Errorf("worktree or index changed during validation:\n%s", current.status)
return fmt.Errorf("worktree or index changed:\n%s", current.status)
}
if exists, err := hasLocalTag(ctx, repositoryDir, request.Version); err != nil {
return Candidate{}, fmt.Errorf("recheck local tag %q: %w", request.Version, err)
} else if exists {
return Candidate{}, fmt.Errorf("local tag %q appeared during validation", request.Version)
if expectedTagObject == "" {
if exists, err := hasLocalTag(ctx, captured.RepositoryDir, captured.Version); err != nil {
return fmt.Errorf("recheck local tag %q: %w", captured.Version, err)
} else if exists {
return fmt.Errorf("local tag %q appeared during validation", captured.Version)
}
return nil
}
return captured, nil
object, err := gitOutput(ctx, captured.RepositoryDir, "rev-parse", "--verify", "refs/tags/"+captured.Version)
if err != nil {
return fmt.Errorf("read local tag %q: %w", captured.Version, err)
}
tagType, err := gitOutput(ctx, captured.RepositoryDir, "cat-file", "-t", "refs/tags/"+captured.Version)
if err != nil {
return fmt.Errorf("inspect local tag %q: %w", captured.Version, err)
}
if strings.TrimSpace(string(object)) != expectedTagObject || strings.TrimSpace(string(tagType)) != "tag" {
return fmt.Errorf("local tag %q no longer identifies annotated tag object %s", captured.Version, expectedTagObject)
}
return nil
}
type localState struct {

View File

@@ -213,7 +213,7 @@ func TestCheckRechecksHeadAndWorktreeAfterCallback(t *testing.T) {
gitRun(t, repository, "add", "later.txt")
gitRun(t, repository, "-c", "commit.gpgsign=false", "commit", "-m", "advance HEAD")
},
want: "HEAD changed during validation",
want: "HEAD changed from",
},
{
name: "worktree changes",
@@ -223,7 +223,7 @@ func TestCheckRechecksHeadAndWorktreeAfterCallback(t *testing.T) {
t.Fatal(err)
}
},
want: "changed during validation",
want: "worktree or index changed",
},
}
for _, test := range tests {