diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f50e86d0..9c789b0a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -745,6 +745,27 @@ jobs: fi done fi + # Everything above computes what the previous release was *called* and + # never checks that it exists. A version line that stopped at its RC + # makes that a name for nothing: 1.9.3 shipped only as rc.1, so + # v1.9.4-rc.1 asked git for v1.9.3..v1.9.4-rc.1 and the publish step + # died on `unknown revision` -- after all four platforms had already + # built, and with publish-msstore sitting behind publish-release, so + # the same gap would silently block a stable release's Store + # deployment too. The rc walk-down above handles a skipped RC; nothing + # handled a skipped stable, and it does not run at all for an rc.1. + if ! git rev-parse -q --verify "refs/tags/${NOTES_START_TAG}" >/dev/null; then + # The nearest tag reachable from the release commit's parent, which + # is what "since the last release" meant in the first place. Left + # EMPTY when no tag is reachable at all, rather than filled with the + # root commit: the stable path hands this to `gh release create + # --notes-start-tag`, which is the API's previous_tag_name and takes + # a tag NAME -- a commit SHA there is not a lenient fallback, it is + # an invalid argument. Each consumer below decides what "no previous + # release" means for it. + NOTES_START_TAG="$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || true)" + echo "Previous-release tag did not exist; using ${NOTES_START_TAG:-}" + fi echo "Computed notes_start_tag=${NOTES_START_TAG} for tag=${TAG}" echo "tag=$TAG" >> "$GITHUB_OUTPUT" @@ -802,14 +823,26 @@ jobs: # the re-cut was for. The commit range is the actual diff and can't lie. # Stable releases keep --generate-notes below: they're the public-facing # ones and want the PR links and the New Contributors section. + # With no previous tag at all, the range is the whole history and + # there is nothing to compare against, so say so rather than + # emitting "since " with a blank where a tag should be. + if [[ -n "$NOTES_START_TAG" ]]; then + RC_RANGE="${NOTES_START_TAG}..${TAG}" + RC_HEADING="## Changes since ${NOTES_START_TAG}" + RC_LINK="**Full Changelog**: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/compare/${NOTES_START_TAG}...${TAG}" + else + RC_RANGE="$TAG" + RC_HEADING="## Changes" + RC_LINK="**Full Changelog**: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commits/${TAG}" + fi { - echo "## Changes since ${NOTES_START_TAG}" + echo "$RC_HEADING" echo git log --no-merges --reverse --pretty='- %s' \ --invert-grep --grep='^chore(release): bump to' \ - "${NOTES_START_TAG}..${TAG}" + "$RC_RANGE" echo - echo "**Full Changelog**: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/compare/${NOTES_START_TAG}...${TAG}" + echo "$RC_LINK" } > "${RUNNER_TEMP}/rc-notes.md" cat "${RUNNER_TEMP}/rc-notes.md" NOTES_ARGS=(--notes-file "${RUNNER_TEMP}/rc-notes.md") @@ -819,7 +852,17 @@ jobs: # prior release by date) doesn't work for this fork because the v1.4.0 # release in the fork was re-published after v1.5.0, which makes GitHub # pick v1.4.0 as the "previous" for any v1.5.x release. - NOTES_ARGS=(--generate-notes --notes-start-tag "$NOTES_START_TAG") + # + # Omitted entirely when there is no previous tag: this maps to the + # API's previous_tag_name, which takes a tag NAME. Passing an empty + # string or a commit SHA is an invalid argument, not a graceful + # degradation. Without it GitHub falls back to its own choice of + # previous release, which is exactly right when there isn't one. + if [[ -n "$NOTES_START_TAG" ]]; then + NOTES_ARGS=(--generate-notes --notes-start-tag "$NOTES_START_TAG") + else + NOTES_ARGS=(--generate-notes) + fi fi # shellcheck disable=SC2086 gh release create "$TAG" "${FILES[@]}" \