Record publication evidence before tag pushes
This commit is contained in:
@@ -48,6 +48,7 @@ type Request struct {
|
||||
Version string
|
||||
Remote string
|
||||
ExpectedPushURL string
|
||||
RecordDir string
|
||||
Validation validation.Settings
|
||||
AdditionalChecks func(context.Context, candidate.Candidate) error
|
||||
AfterValidate func(context.Context, candidate.Candidate, *gotool.Owner) error
|
||||
@@ -65,6 +66,9 @@ type Result struct {
|
||||
Phase Phase
|
||||
TagObject string
|
||||
State State
|
||||
Evidence Evidence
|
||||
RecordPath string
|
||||
RecordError error
|
||||
}
|
||||
|
||||
// Publish captures a candidate, resolves and checks the optional expected push
|
||||
@@ -74,8 +78,22 @@ type Result struct {
|
||||
// A failed push receives one bounded authoritative tag query. Exact
|
||||
// object equality confirms publication; absence and conflict are classified,
|
||||
// while an unreadable result remains Unknown and preserves the push error.
|
||||
func Publish(ctx context.Context, request Request) (Result, error) {
|
||||
result := Result{Version: request.Version, Phase: Preflight, State: NotAttempted}
|
||||
func Publish(ctx context.Context, request Request) (result Result, resultErr error) {
|
||||
result = Result{Version: request.Version, Phase: Preflight, State: NotAttempted}
|
||||
var recordDir, repositoryRoot string
|
||||
initialized, recorded := false, false
|
||||
defer func() {
|
||||
if !initialized || recorded || result.RecordError != nil {
|
||||
return
|
||||
}
|
||||
recorded = true
|
||||
path, err := saveEvidenceInDir(result.RepositoryDir, repositoryRoot, request.RecordDir, recordDir, result.Evidence)
|
||||
if err != nil {
|
||||
result.RecordError = err
|
||||
return
|
||||
}
|
||||
result.RecordPath = path
|
||||
}()
|
||||
remoteName := request.Remote
|
||||
if remoteName == "" {
|
||||
remoteName = "origin"
|
||||
@@ -95,6 +113,17 @@ func Publish(ctx context.Context, request Request) (Result, error) {
|
||||
result.ModulePath = captured.ModulePath
|
||||
result.Tag = captured.Tag
|
||||
result.CandidateCommit = captured.Commit
|
||||
result.Evidence = Evidence{RepositoryDir: captured.RepositoryDir, ModuleDir: captured.ModuleDir, ModulePath: captured.ModulePath, Version: captured.Version, Tag: captured.Tag, CandidateCommit: captured.Commit, Remote: remoteName}
|
||||
if captured.RepositoryDir != "" {
|
||||
initialized = true
|
||||
result.Evidence.RecordedAt = time.Now().UTC().Format(time.RFC3339Nano)
|
||||
recordDir, repositoryRoot, err = resolveRecordDir(captured.RepositoryDir, request.RecordDir)
|
||||
if err != nil {
|
||||
result.RecordError = &RecoveryError{Kind: "record_write", cause: err}
|
||||
recordDir = ""
|
||||
recorded = true
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("capture release candidate: %w", err)
|
||||
}
|
||||
@@ -104,21 +133,22 @@ func Publish(ctx context.Context, request Request) (Result, error) {
|
||||
return result, fmt.Errorf("resolve publication endpoint: %w", err)
|
||||
}
|
||||
result.Endpoint = endpoint.PushURL
|
||||
result.Evidence.Endpoint = endpoint.PushURL
|
||||
if request.ExpectedPushURL != "" && request.ExpectedPushURL != endpoint.PushURL {
|
||||
return result, fmt.Errorf("%w: expected %q, resolved %q", ErrUnexpectedPushURL, git.DisplayURL(request.ExpectedPushURL), git.DisplayURL(endpoint.PushURL))
|
||||
}
|
||||
if err := endpoint.Recheck(ctx, captured.RepositoryDir); err != nil {
|
||||
return result, fmt.Errorf("pre-validation endpoint guard: %w", err)
|
||||
}
|
||||
if err := candidate.Recheck(ctx, captured, ""); err != nil {
|
||||
return result, fmt.Errorf("pre-validation local guard: candidate worktree and index must remain clean: %w", err)
|
||||
}
|
||||
if _, err := endpoint.RequireCandidate(ctx, captured.RepositoryDir, captured.Tag, 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
|
||||
checks.ToolOwner = gotool.NewOwner(captured.ModuleDir)
|
||||
@@ -148,6 +178,7 @@ func Publish(ctx context.Context, request Request) (Result, error) {
|
||||
result.State = NotPublished
|
||||
tagObject, err := git.CreateAnnotatedTag(ctx, captured.RepositoryDir, captured.Tag, captured.Commit, []byte(captured.Note))
|
||||
result.TagObject = tagObject
|
||||
result.Evidence.TagObject = tagObject
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
@@ -166,6 +197,15 @@ func Publish(ctx context.Context, request Request) (Result, error) {
|
||||
|
||||
result.Phase = Push
|
||||
result.State = Unknown
|
||||
if !recorded {
|
||||
recorded = true
|
||||
path, recordErr := saveEvidenceInDir(result.RepositoryDir, repositoryRoot, request.RecordDir, recordDir, result.Evidence)
|
||||
if recordErr != nil {
|
||||
result.RecordError = recordErr
|
||||
} else {
|
||||
result.RecordPath = path
|
||||
}
|
||||
}
|
||||
if err := endpoint.PushTag(ctx, captured.RepositoryDir, captured.Tag, tagObject); err != nil {
|
||||
return reconcilePush(endpoint, captured, tagObject, result, err)
|
||||
}
|
||||
|
||||
@@ -55,6 +55,10 @@ func SaveEvidence(repositoryDir, override string, evidence Evidence) (string, er
|
||||
if err != nil {
|
||||
return "", &RecoveryError{Kind: "record_write", cause: err}
|
||||
}
|
||||
return saveEvidenceInDir(repositoryDir, root, override, recordDir, evidence)
|
||||
}
|
||||
|
||||
func saveEvidenceInDir(repositoryDir, root, override, recordDir string, evidence Evidence) (string, error) {
|
||||
if evidence.RecordedAt == "" {
|
||||
evidence.RecordedAt = time.Now().UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user