92 lines
3.6 KiB
Go
92 lines
3.6 KiB
Go
package delivery
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"go.maximumdirect.net/releasekit/internal/forge"
|
|
)
|
|
|
|
func coordinateRelease(ctx context.Context, backend forge.Backend, tag, commit, note string) (forge.Release, forge.WriteEvidence, error) {
|
|
observedCommit, err := backend.TagCommit(ctx, tag)
|
|
if err != nil {
|
|
return forge.Release{}, forge.WriteEvidence{Disposition: forge.WriteNotAttempted}, err
|
|
}
|
|
if observedCommit != commit {
|
|
return forge.Release{}, forge.WriteEvidence{Disposition: forge.WriteNotAttempted}, &forge.Failure{
|
|
Kind: forge.FailureConflict,
|
|
Err: fmt.Errorf("hosted tag %q targets %q, want %q", tag, observedCommit, commit),
|
|
}
|
|
}
|
|
|
|
existing, err := backend.FindRelease(ctx, tag)
|
|
if err == nil {
|
|
if !matchesExistingRelease(existing, tag, note) {
|
|
return forge.Release{}, forge.WriteEvidence{Disposition: forge.WriteNotAttempted}, &forge.Failure{
|
|
Kind: forge.FailureConflict,
|
|
Err: errors.New("existing release does not match the committed note and stable tag"),
|
|
}
|
|
}
|
|
return existing, forge.WriteEvidence{Disposition: forge.WriteNotAttempted}, nil
|
|
}
|
|
if !isForgeFailure(err, forge.FailureNotFound) {
|
|
return forge.Release{}, forge.WriteEvidence{Disposition: forge.WriteNotAttempted}, err
|
|
}
|
|
|
|
request := forge.CreateReleaseRequest{
|
|
Tag: tag, Commit: commit, Title: releaseNoteTitle(note), Body: note,
|
|
}
|
|
created, evidence, createErr := backend.CreateRelease(ctx, request)
|
|
if createErr == nil && matchesCreatedRelease(created, request) {
|
|
return created, evidence, nil
|
|
}
|
|
if createErr == nil {
|
|
createErr = &forge.Failure{Kind: forge.FailureMalformed, Err: errors.New("created release does not match the requested stable release")}
|
|
}
|
|
if evidence.Disposition != forge.WriteConflict && evidence.Disposition != forge.WriteUncertain && !isForgeFailure(createErr, forge.FailureMalformed) {
|
|
return forge.Release{}, evidence, createErr
|
|
}
|
|
|
|
return reconcileReleaseCreate(ctx, backend, tag, note, evidence, createErr)
|
|
}
|
|
|
|
func reconcileReleaseCreate(ctx context.Context, backend forge.Backend, tag, note string, evidence forge.WriteEvidence, createErr error) (forge.Release, forge.WriteEvidence, error) {
|
|
existing, lookupErr := backend.FindRelease(ctx, tag)
|
|
if lookupErr == nil {
|
|
if matchesExistingRelease(existing, tag, note) {
|
|
return existing, evidence, nil
|
|
}
|
|
return forge.Release{}, evidence, &forge.Failure{Kind: forge.FailureConflict, Err: errors.Join(createErr, errors.New("reconciled release does not match the committed note and stable tag"))}
|
|
}
|
|
if isForgeFailure(lookupErr, forge.FailureNotFound) {
|
|
if evidence.Disposition == forge.WriteConflict {
|
|
return forge.Release{}, evidence, &forge.Failure{Kind: forge.FailureConflict, Err: createErr}
|
|
}
|
|
return forge.Release{}, evidence, &forge.Failure{Kind: forge.FailureUnknown, Err: createErr}
|
|
}
|
|
return forge.Release{}, evidence, &forge.Failure{Kind: forge.FailureUnknown, Err: errors.Join(createErr, lookupErr)}
|
|
}
|
|
|
|
func matchesExistingRelease(release forge.Release, tag, note string) bool {
|
|
return release.Tag == tag && release.Body == note && !release.Draft && !release.Prerelease
|
|
}
|
|
|
|
func matchesCreatedRelease(release forge.Release, request forge.CreateReleaseRequest) bool {
|
|
return release.Tag == request.Tag && release.Body == request.Body && release.Title == request.Title && !release.Draft && !release.Prerelease
|
|
}
|
|
|
|
func releaseNoteTitle(note string) string {
|
|
first := strings.SplitN(note, "\n", 2)[0]
|
|
if !strings.HasPrefix(first, "# ") {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(strings.TrimPrefix(first, "# "))
|
|
}
|
|
|
|
func isForgeFailure(err error, kind forge.FailureKind) bool {
|
|
var failure *forge.Failure
|
|
return errors.As(err, &failure) && failure.Kind == kind
|
|
}
|