diff --git a/README.md b/README.md index cc4ddfe..cb20b77 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ These rules apply to `--gh-release` and to `git-release-api.sh`. The package version is the tag and release name, and release notes are generated by GitHub. Alpha, beta, RC, and dev versions are marked as prereleases. Archived repositories are rejected, since GitHub does not allow releases on them. For stable, alpha, beta, and RC versions an existing tag or release stops the operation; those releases and assets are never replaced. -A version ending in `-dev` is a rolling prerelease. The first release creates it like any other release. Each later release moves the tag to the new commit, regenerates the release notes, and replaces the ZIP in the same GitHub release, so its URL stays the same. The new ZIP is uploaded under a temporary name and only replaces the old one after the upload is confirmed. The scripts print `Updated rolling release:` for this path and `Published release:` for a new release. A dry run shows what would change but never touches the tag, release, or asset. If the tag has to move, the dry-run notes still reflect where the tag points now. +A version ending in `-dev` is a rolling prerelease, including a build on the way to an alpha, beta or RC such as `1.1.0-a2-dev`. The first release creates it like any other release. Each later release moves the tag to the new commit, regenerates the release notes, and replaces the ZIP in the same GitHub release, so its URL stays the same. The new ZIP is uploaded under a temporary name and only replaces the old one after the upload is confirmed. The scripts print `Updated rolling release:` for this path and `Published release:` for a new release. A dry run shows what would change but never touches the tag, release, or asset. If the tag has to move, the dry-run notes still reflect where the tag points now. The tag is reserved at the pinned commit before the release is created. A failure after tag creation may leave that tag without a release. The release stays a draft until its asset upload is confirmed. If uploading or publishing fails, the script reports the release URL and exits with an error. Inspect the remote state before retrying. For stable, alpha, beta, and RC versions a retry stops when it finds the existing tag or release. For a `-dev` version a retry reuses the leftover tag or draft release and finishes the update. A publication timeout may mean publication succeeded but could not be confirmed. Requests have finite timeouts and remote write requests are not retried automatically. diff --git a/git-release-api.sh b/git-release-api.sh index 32dd6d6..e2e3f65 100755 --- a/git-release-api.sh +++ b/git-release-api.sh @@ -29,7 +29,8 @@ Stable, alpha, beta, and RC releases are immutable: an existing release or tag is rejected, and an upload/publication failure leaves the draft release for inspection. -A version ending in -dev is a rolling prerelease. The first --release creates +A version ending in -dev, such as 1.1.0-dev or 1.1.0-a2-dev, is a rolling +prerelease. The first --release creates it; each later --release moves its tag to the default branch, regenerates its notes, and replaces its ZIP in the same release, keeping its URL. --dry-run never changes the tag, release, or asset. @@ -112,7 +113,8 @@ git -C "$scratch/source" show "$commit:composer.json" >"$scratch/composer.json" package=$(jq -er '.name | select(type == "string")' "$scratch/composer.json") version=$(jq -er '.version | select(type == "string")' "$scratch/composer.json") [[ "$package" =~ ^[a-z0-9][a-z0-9._-]*/[a-z0-9][a-z0-9._-]*$ ]] || fail 'composer.json needs a safe vendor/name package name.' -[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-(dev|a(lpha)?[0-9]*|b(eta)?[0-9]*|[Rr][Cc][0-9]*|p(atch)?[0-9]*))?$ ]] || fail 'composer.json has an unsupported version.' +# An alpha, beta or RC may end in -dev (1.1.0-a2-dev): a rolling build on the way to it. +[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-(dev|(a(lpha)?|b(eta)?|[Rr][Cc])[0-9]*(-dev)?|p(atch)?[0-9]*))?$ ]] || fail 'composer.json has an unsupported version.' vendor=${package%%/*} releasename=${package#*/} filename="$vendor-$releasename-$version.zip" diff --git a/tests/test-release-api.sh b/tests/test-release-api.sh index 02b1093..ca374ec 100755 --- a/tests/test-release-api.sh +++ b/tests/test-release-api.sh @@ -26,6 +26,12 @@ printf '%s\n' '{"name":"acme/ext","version":"1.2.3-dev"}' >"$FIXTURE_ROOT/source git -C "$FIXTURE_ROOT/source" -c user.name='William Jacoby' -c user.email=bonelifer@gmail.com commit -qam dev DEV_COMMIT=$(git -C "$FIXTURE_ROOT/source" rev-parse HEAD) export DEV_COMMIT +# A rolling build on the way to an alpha, also on its own branch. +git -C "$FIXTURE_ROOT/source" checkout -qb alpha-dev main +printf '%s\n' '{"name":"acme/ext","version":"1.2.3-a2-dev"}' >"$FIXTURE_ROOT/source/composer.json" +git -C "$FIXTURE_ROOT/source" -c user.name='William Jacoby' -c user.email=bonelifer@gmail.com commit -qam alpha-dev +ALPHA_DEV_COMMIT=$(git -C "$FIXTURE_ROOT/source" rev-parse HEAD) +export ALPHA_DEV_COMMIT git -C "$FIXTURE_ROOT/source" checkout -q main # A dirty local version must never change the remotely committed package version. printf '%s\n' '{"name":"acme/ext","version":"9.9.9"}' >"$FIXTURE_ROOT/source/composer.json" @@ -72,7 +78,9 @@ case "$endpoint" in repos/acme/ext/commits/main|repos/acme/ext/commits/master) [[ "$endpoint" == */main || "${SCENARIO:-}" == master_branch ]] || exit 1 [[ "${SCENARIO:-}" != main_failure ]] || exit 1 - if [[ "${SCENARIO:-}" == dev_* ]]; then sha="$DEV_COMMIT"; else sha="$FIXTURE_COMMIT"; fi + if [[ "${SCENARIO:-}" == dev_* ]]; then sha="$DEV_COMMIT" + elif [[ "${SCENARIO:-}" == alpha_dev ]]; then sha="$ALPHA_DEV_COMMIT" + else sha="$FIXTURE_COMMIT"; fi jq -n --arg sha "$sha" '{sha:$sha}' ;; 'repos/acme/ext/releases?per_page=100') echo '[]' @@ -88,6 +96,8 @@ case "$endpoint" in if [[ "${SCENARIO:-}" == dev_existing ]]; then jq -n --arg sha "$FIXTURE_COMMIT" '[{ref:"refs/tags/1.2.3-dev",object:{sha:$sha}}]' else echo '[]'; fi ;; + repos/acme/ext/git/matching-refs/tags/1.2.3-a2-dev) + echo '[]' ;; repos/acme/ext/git/refs/tags/1.2.3-dev) jq -n --arg sha "$DEV_COMMIT" '{ref:"refs/tags/1.2.3-dev",object:{sha:$sha}}' ;; repos/acme/ext/releases/456) @@ -220,6 +230,12 @@ grep -q 'Updated rolling release: https://github.com/acme/ext/releases/tag/1.2.3 if grep -q 'Published release' "$FIXTURE_ROOT/stdout"; then exit 1; fi echo 'ok - dev release moves the tag, regenerates notes, and replaces the ZIP in the same release' +export SCENARIO=alpha_dev +run --dry-run --repo acme/ext --output "$FIXTURE_ROOT/output" +grep -q 'Release: 1.2.3-a2-dev (prerelease: true)' "$FIXTURE_ROOT/stdout" +[[ "$(unzip -p "$FIXTURE_ROOT/output/acme-ext-1.2.3-a2-dev.zip" acme/ext/composer.json | jq -r .version)" == 1.2.3-a2-dev ]] +echo 'ok - an alpha with -dev (1.2.3-a2-dev) is accepted as a rolling prerelease' + export SCENARIO=dev_new run --release --repo acme/ext --output "$FIXTURE_ROOT/output" jq -e --arg sha "$DEV_COMMIT" '.draft == true and .prerelease == true and .tag_name == "1.2.3-dev" and .target_commitish == $sha' "$FIXTURE_ROOT/create.json" >/dev/null