diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 4d86759..77fa96e 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -135,6 +135,16 @@ jobs: # Releases go through the Gitea API directly: it is GitHub-compatible in # shape, but a self-hosted instance may not have ready-made actions. + # + # The step is written to be repeatable. Creating a release in the Gitea + # web UI also creates the tag, so by the time this runs the release + # usually exists already and a blind POST would fail with 409. The same + # goes for re-running a build over a tag whose assets are in place. + # + # Keep it POSIX: the runner executes steps with dash, so no `pipefail`. + # curl calls therefore stay out of pipelines — under `set -e` a failed + # command substitution aborts the step, which is what pipefail bought us. + # No -x anywhere here: these commands carry the token. - name: Publish release if: startsWith(github.ref, 'refs/tags/v') env: @@ -142,29 +152,42 @@ jobs: API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} TAG: ${{ github.ref_name }} run: | - # Keep this POSIX: the runner executes steps with dash, where - # `set -o pipefail` does not exist. So the curl call is kept out of a - # pipeline — under `set -e` a failed command substitution aborts the - # step, which is what pipefail would have bought us. Without that, a - # failing curl would be masked by a successful jq, id would become - # null, and assets would be uploaded to a release that never existed. - # No -x here: the command carries the token. set -eu - response=$(curl -fsS -X POST "$API/releases" \ - -H "Authorization: token $TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\"}") + auth="Authorization: token $TOKEN" - id=$(printf '%s' "$response" | jq -r '.id // empty') + # Reuse the release if it is already there, create it otherwise. + existing=$(curl -sS -o /tmp/release.json -w '%{http_code}' \ + -H "$auth" "$API/releases/tags/$TAG") + + if [ "$existing" = "200" ]; then + echo "release $TAG already exists, attaching assets to it" + else + echo "creating release $TAG" + curl -fsS -o /tmp/release.json -X POST "$API/releases" \ + -H "$auth" -H "Content-Type: application/json" \ + -d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\"}" + fi + + id=$(jq -r '.id // empty' /tmp/release.json) if [ -z "$id" ]; then - echo "no release id in response: $response" >&2 + echo "could not resolve release id for $TAG:" >&2 + cat /tmp/release.json >&2 exit 1 fi - echo "release $TAG (id $id)" for file in dist/*.exe; do - echo "uploading $(basename "$file")" - curl -fsS -X POST "$API/releases/$id/assets?name=$(basename "$file")" \ - -H "Authorization: token $TOKEN" \ - -F "attachment=@$file" > /dev/null + name=$(basename "$file") + + # Replace an asset of the same name rather than ending up with two. + curl -fsS -o /tmp/assets.json -H "$auth" "$API/releases/$id/assets" + for old in $(jq -r --arg n "$name" '.[] | select(.name==$n) | .id' /tmp/assets.json); do + echo "removing previous $name (asset $old)" + curl -fsS -X DELETE -H "$auth" "$API/releases/$id/assets/$old" > /dev/null + done + + echo "uploading $name" + curl -fsS -X POST "$API/releases/$id/assets?name=$name" \ + -H "$auth" -F "attachment=@$file" > /dev/null done + + echo "release $TAG (id $id) is ready"