Recover publication against the captured endpoint
This commit is contained in:
@@ -61,7 +61,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
||||
fmt.Fprintf(stderr, "validating and publishing %s\n", invocation.Version)
|
||||
result, err := releasekit.Publish(ctx, request)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "publication state=%s phase=%s version=%s commit=%s tag-object=%s: %v\n", result.State, result.Phase, result.Version, result.CandidateCommit, result.TagObject, err)
|
||||
fmt.Fprintf(stderr, "publication state=%s phase=%s version=%s commit=%s tag-object=%s endpoint=%q: %v\n", result.State, result.Phase, result.Version, result.CandidateCommit, result.TagObject, result.Endpoint, err)
|
||||
if errors.Is(err, releasekit.ErrInvalidVersion) {
|
||||
return 2
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -57,6 +59,10 @@ func TestReleaseWrapperPreservesNativeExitStatuses(t *testing.T) {
|
||||
cliGitRun(t, repository, "init", "--bare", remote)
|
||||
cliGitRun(t, repository, "remote", "set-url", "origin", remote)
|
||||
cliGitRun(t, repository, "push", "origin", "HEAD:refs/heads/main")
|
||||
fetch := filepath.Join(t.TempDir(), "fetch.git")
|
||||
cliGitRun(t, repository, "init", "--bare", fetch)
|
||||
cliGitRun(t, repository, "remote", "set-url", "origin", fetch)
|
||||
cliGitRun(t, repository, "remote", "set-url", "--push", "origin", remote)
|
||||
if mode.name == "rejected" {
|
||||
if err := os.WriteFile(filepath.Join(remote, "hooks/pre-receive"), []byte("#!/bin/sh\nexit 1\n"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -67,7 +73,12 @@ func TestReleaseWrapperPreservesNativeExitStatuses(t *testing.T) {
|
||||
marker := filepath.Join(bin, "push-started")
|
||||
shim := `#!/bin/sh
|
||||
for arg do
|
||||
if [ "$arg" = push ]; then : > "$RELEASEKIT_TEST_PUSH_MARKER"; exit 1; fi
|
||||
if [ "$arg" = push ]; then
|
||||
"$RELEASEKIT_TEST_REAL_GIT" "$@" || exit 1
|
||||
"$RELEASEKIT_TEST_REAL_GIT" remote set-url --push origin "$RELEASEKIT_TEST_MOVED_ENDPOINT"
|
||||
: > "$RELEASEKIT_TEST_PUSH_MARKER"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if [ -f "$RELEASEKIT_TEST_PUSH_MARKER" ]; then
|
||||
for arg do if [ "$arg" = ls-remote ]; then exit 128; fi; done
|
||||
@@ -80,6 +91,7 @@ exec "$RELEASEKIT_TEST_REAL_GIT" "$@"
|
||||
t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
t.Setenv("RELEASEKIT_TEST_REAL_GIT", realGit)
|
||||
t.Setenv("RELEASEKIT_TEST_PUSH_MARKER", marker)
|
||||
t.Setenv("RELEASEKIT_TEST_MOVED_ENDPOINT", fetch)
|
||||
}
|
||||
command := exec.Command("./scripts/release", mode.version)
|
||||
command.Dir = repository
|
||||
@@ -95,6 +107,26 @@ exec "$RELEASEKIT_TEST_REAL_GIT" "$@"
|
||||
if status != mode.status {
|
||||
t.Fatalf("status=%d want=%d output=%s", status, mode.status, output)
|
||||
}
|
||||
if mode.name == "unknown" {
|
||||
if !strings.Contains(string(output), fmt.Sprintf("endpoint=%q", remote)) {
|
||||
t.Fatalf("captured endpoint missing: %s", output)
|
||||
}
|
||||
query := exec.Command(realGit, "ls-remote", "--refs", "--", remote, "refs/tags/v1.2.3")
|
||||
query.Dir = repository
|
||||
found, err := query.Output()
|
||||
if err != nil || len(found) == 0 {
|
||||
t.Fatalf("captured endpoint lost published tag: %q %v", found, err)
|
||||
}
|
||||
if !strings.Contains(string(found), cliGitOutput(t, repository, "rev-parse", "refs/tags/v1.2.3")) {
|
||||
t.Fatalf("wrong recovered tag: %s", found)
|
||||
}
|
||||
fetchQuery := exec.Command(realGit, "ls-remote", "--refs", "origin", "refs/tags/v1.2.3")
|
||||
fetchQuery.Dir = repository
|
||||
stale, err := fetchQuery.Output()
|
||||
if err != nil || len(stale) != 0 {
|
||||
t.Fatalf("fetch fixture unexpectedly found tag: %q %v", stale, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ go run ./cmd/releasekit publish [--repo DIR] [--config FILE] VERSION
|
||||
It validates the candidate once, checks the authoritative remote, creates an
|
||||
annotated local tag, and pushes only its exact tag object. It prints the
|
||||
confirmed version, candidate commit, and tag object on success. Validation
|
||||
progress and failed publication state are written to standard error. A signal
|
||||
progress and failed publication state, including the captured publication
|
||||
endpoint for recovery, are written to standard error. A signal
|
||||
cancels the active operation; if it arrives during a push, the library's
|
||||
bounded reconciliation determines whether the exact tag was accepted.
|
||||
|
||||
|
||||
@@ -77,13 +77,24 @@ A failed push triggers one exact authoritative tag query. An exact tag-object
|
||||
match confirms publication even when Git reported an error. A proven absent tag
|
||||
is `not_published`; a different object is a conflict; an unreadable query is
|
||||
`unknown`. The command retains its local tag and reports the state, phase,
|
||||
candidate commit, and tag object when available. Do not retry an unknown result
|
||||
until you resolve the authoritative ref:
|
||||
candidate commit, tag object, and captured publication endpoint when available.
|
||||
Do not retry an unknown result until you resolve the authoritative ref at that
|
||||
exact endpoint. Copy the reported endpoint value into the variable below using
|
||||
normal shell quoting; retain the version and tag object from the same result.
|
||||
Check that Git URL rewriting still resolves this value to itself before querying:
|
||||
|
||||
```sh
|
||||
git ls-remote --refs origin "refs/tags/vX.Y.Z"
|
||||
releasekit_endpoint='COPY_CAPTURED_ENDPOINT'
|
||||
releasekit_version='vX.Y.Z'
|
||||
test "$(git ls-remote --get-url -- "$releasekit_endpoint")" = "$releasekit_endpoint"
|
||||
git ls-remote --refs -- "$releasekit_endpoint" "refs/tags/$releasekit_version"
|
||||
```
|
||||
|
||||
A failed URL-equality check or remote query leaves the outcome unresolved.
|
||||
Correct transport configuration before continuing. The reported endpoint may
|
||||
be different from `origin`'s fetch URL or its current push URL; querying either
|
||||
of those does not establish absence at the captured endpoint.
|
||||
|
||||
Compare the returned object ID with the tag object reported by the command.
|
||||
An exact match confirms publication. If the ref is absent, the version was not
|
||||
published; only then may you deliberately remove the unpublished local tag
|
||||
@@ -91,9 +102,10 @@ with `git tag -d vX.Y.Z` before correcting the candidate or retrying. Do not
|
||||
move, overwrite, or delete a tag that exists remotely. If a different object
|
||||
uses the version, choose a new version after resolving the conflict.
|
||||
|
||||
If validation or a guard fails before the push, the local tag has not been
|
||||
published. Correct and commit the candidate, publish `main`, and run the
|
||||
command again. The prepared Woodpecker workflow builds the CLI from the tagged
|
||||
If validation or a guard fails before this invocation pushes, correct and
|
||||
commit the candidate, publish `main`, and run the command again. A retained
|
||||
local tag still requires the authoritative-state check above before removal;
|
||||
a concurrent publisher may already have published the version. The prepared Woodpecker workflow builds the CLI from the tagged
|
||||
checkout and runs page-only delivery. To activate it after configuring the
|
||||
destination and secret, copy it to `.woodpecker/releasekit-source-delivery.yml`
|
||||
and add the Woodpecker repository secret `releasekit_gitea_token`. If page
|
||||
|
||||
@@ -323,6 +323,8 @@ and unresolved publication 3. The focused wrapper test passes.
|
||||
|
||||
### R9 — High: Manual recovery inspects the fetch endpoint
|
||||
|
||||
Status: Resolved.
|
||||
|
||||
Category: Release recovery correctness and documentation gap.
|
||||
|
||||
**Evidence:** [The runbook](../release.md), around line 82, prescribes
|
||||
@@ -347,6 +349,13 @@ local tag.
|
||||
fetch/push repositories and confirm that a published tag is recognized. Include
|
||||
endpoint changes after an uncertain push when verifying retained recovery facts.
|
||||
|
||||
**Resolution:** Failed publication output includes the captured endpoint. The
|
||||
runbook checks URL resolution and queries that exact endpoint, retaining tags
|
||||
on failed or unresolved checks. Local wrapper fixtures use separate fetch and
|
||||
push repositories; a lost acknowledgment changes the configured push URL, and
|
||||
recovery still finds the intended object at the captured endpoint while the
|
||||
fetch endpoint reports absence. Focused publication/wrapper tests pass.
|
||||
|
||||
### R10 — Medium: The boundary checker bans valid command-internal composition
|
||||
|
||||
Category: Inadvertently strengthened architectural prohibition.
|
||||
|
||||
Reference in New Issue
Block a user