Publish the captured release tag
This commit is contained in:
143
internal/publish/publish.go
Normal file
143
internal/publish/publish.go
Normal file
@@ -0,0 +1,143 @@
|
||||
// Package publish coordinates candidate validation and exact tag publication.
|
||||
package publish
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/candidate"
|
||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/git"
|
||||
)
|
||||
|
||||
type State string
|
||||
|
||||
const (
|
||||
NotAttempted State = "not_attempted"
|
||||
NotPublished State = "not_published"
|
||||
Published State = "published"
|
||||
Conflict State = "conflict"
|
||||
Unknown State = "unknown"
|
||||
)
|
||||
|
||||
type Phase string
|
||||
|
||||
const (
|
||||
Preflight Phase = "preflight"
|
||||
Validation Phase = "validation"
|
||||
TagCreation Phase = "tag_creation"
|
||||
Push Phase = "push"
|
||||
Complete Phase = "complete"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
RepositoryDir string
|
||||
Product string
|
||||
Version string
|
||||
Remote string
|
||||
Validate func(context.Context, candidate.Candidate) error
|
||||
Output io.Writer
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Version string
|
||||
CandidateCommit string
|
||||
Endpoint string
|
||||
Phase Phase
|
||||
TagObject string
|
||||
State State
|
||||
}
|
||||
|
||||
// Publish validates one captured candidate and pushes only its annotated tag
|
||||
// object. A failed push remains Unknown until authoritative reconciliation is
|
||||
// added by the following implementation stage.
|
||||
func Publish(ctx context.Context, request Request) (Result, error) {
|
||||
result := Result{Version: request.Version, Phase: Preflight, State: NotAttempted}
|
||||
remoteName := request.Remote
|
||||
if remoteName == "" {
|
||||
remoteName = "origin"
|
||||
}
|
||||
checks := candidate.Request{
|
||||
RepositoryDir: request.RepositoryDir,
|
||||
Product: request.Product,
|
||||
Version: request.Version,
|
||||
Validate: request.Validate,
|
||||
Output: request.Output,
|
||||
}
|
||||
captured, err := candidate.Capture(ctx, checks)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("capture release candidate: %w", err)
|
||||
}
|
||||
result.CandidateCommit = captured.Commit
|
||||
|
||||
endpoint, err := git.Resolve(ctx, captured.RepositoryDir, remoteName)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("resolve publication endpoint: %w", err)
|
||||
}
|
||||
result.Endpoint = endpoint.PushURL
|
||||
if err := endpoint.Recheck(ctx, captured.RepositoryDir); err != nil {
|
||||
return result, fmt.Errorf("pre-validation endpoint guard: %w", err)
|
||||
}
|
||||
if _, err := endpoint.RequireCandidate(ctx, captured.RepositoryDir, captured.Version, captured.Commit); err != nil {
|
||||
if isRemoteConflict(err) {
|
||||
result.State = Conflict
|
||||
}
|
||||
return result, fmt.Errorf("pre-validation remote guard: %w", err)
|
||||
}
|
||||
if err := candidate.Recheck(ctx, captured, ""); err != nil {
|
||||
return result, fmt.Errorf("pre-validation local guard: %w", err)
|
||||
}
|
||||
|
||||
result.Phase = Validation
|
||||
if err := candidate.Validate(ctx, checks, captured); err != nil {
|
||||
return result, err
|
||||
}
|
||||
if err := candidate.Recheck(ctx, captured, ""); err != nil {
|
||||
return result, fmt.Errorf("post-validation local guard: %w", err)
|
||||
}
|
||||
if err := endpoint.Recheck(ctx, captured.RepositoryDir); err != nil {
|
||||
return result, fmt.Errorf("pre-publication endpoint guard: %w", err)
|
||||
}
|
||||
if _, err := endpoint.RequireCandidate(ctx, captured.RepositoryDir, captured.Version, captured.Commit); err != nil {
|
||||
if isRemoteConflict(err) {
|
||||
result.State = Conflict
|
||||
}
|
||||
return result, fmt.Errorf("pre-publication remote guard: %w", err)
|
||||
}
|
||||
|
||||
result.Phase = TagCreation
|
||||
result.State = NotPublished
|
||||
tagObject, err := git.CreateAnnotatedTag(ctx, captured.RepositoryDir, captured.Version, captured.Commit, []byte(captured.Note))
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.TagObject = tagObject
|
||||
if err := candidate.Recheck(ctx, captured, tagObject); err != nil {
|
||||
return result, fmt.Errorf("pre-push local guard: %w", err)
|
||||
}
|
||||
if err := endpoint.Recheck(ctx, captured.RepositoryDir); err != nil {
|
||||
return result, fmt.Errorf("pre-push endpoint guard: %w", err)
|
||||
}
|
||||
if _, err := endpoint.RequireCandidate(ctx, captured.RepositoryDir, captured.Version, captured.Commit); err != nil {
|
||||
if isRemoteConflict(err) {
|
||||
result.State = Conflict
|
||||
}
|
||||
return result, fmt.Errorf("pre-push remote guard: %w", err)
|
||||
}
|
||||
|
||||
result.Phase = Push
|
||||
result.State = Unknown
|
||||
if err := endpoint.PushTag(ctx, captured.RepositoryDir, captured.Version, tagObject); err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.Phase = Complete
|
||||
result.State = Published
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func isRemoteConflict(err error) bool {
|
||||
return errors.Is(err, git.ErrRemoteMainMissing) ||
|
||||
errors.Is(err, git.ErrRemoteMainConflict) ||
|
||||
errors.Is(err, git.ErrRemoteTagConflict)
|
||||
}
|
||||
Reference in New Issue
Block a user