diff --git a/.github/workflows/sdk-release-finalize.yml b/.github/workflows/sdk-release-finalize.yml index 6f8f508..391db32 100644 --- a/.github/workflows/sdk-release-finalize.yml +++ b/.github/workflows/sdk-release-finalize.yml @@ -57,7 +57,7 @@ on: description: "The origin release tag created or verified by this workflow, or the unverified input when origin tag reconciliation is disabled." value: ${{ jobs.finalize.outputs.origin_tag }} origin_tag_created: - description: "true when a new origin release tag was pushed; false when it already existed or origin tag reconciliation is disabled." + description: "true when the origin release tag was created or moved; false when it already existed or origin tag reconciliation is disabled." value: ${{ jobs.finalize.outputs.origin_tag_created }} fern_generation_tag: description: "The Fern replay generation tag created or verified by this workflow, or empty when replay.lock is absent or Fern generation preservation is disabled." @@ -115,6 +115,10 @@ jobs: echo "::error::$label must not begin with '-': $tag" exit 1 fi + if [[ "$tag" =~ ^[0-9a-fA-F]{40}(/|$) ]]; then + echo "::error::$label must not begin with a 40-character SHA: $tag" + exit 1 + fi if ! git check-ref-format "refs/tags/$tag"; then echo "::error::Invalid $label: $tag" exit 1 @@ -219,7 +223,23 @@ jobs: set -euo pipefail target_commit=$(git rev-parse --verify "${TAG_TARGET}^{commit}") - existing_commit=$(git rev-parse -q --verify "refs/tags/${ORIGIN_TAG}^{commit}" || true) + existing_ref="" + existing_commit="" + if git ls-remote --exit-code --refs origin "refs/tags/${ORIGIN_TAG}" >/dev/null; then + git fetch --force --no-tags origin "refs/tags/${ORIGIN_TAG}:refs/tags/${ORIGIN_TAG}" + existing_ref=$(git rev-parse --verify "refs/tags/${ORIGIN_TAG}") + existing_commit=$(git rev-parse --verify "refs/tags/${ORIGIN_TAG}^{commit}") + else + status=$? + if [[ "$status" -ne 2 ]]; then + echo "::error::Could not read remote tag $ORIGIN_TAG from origin." + exit "$status" + fi + git tag -d "$ORIGIN_TAG" 2>/dev/null || true + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" if [[ -n "$existing_commit" ]]; then if [[ "$existing_commit" == "$target_commit" ]]; then @@ -228,13 +248,25 @@ jobs: exit 0 fi - echo "::error::Tag $ORIGIN_TAG already points at $existing_commit, not $target_commit." + if git merge-base --is-ancestor "$existing_commit" "$target_commit"; then + echo "Moving tag $ORIGIN_TAG from $existing_commit to $target_commit." + git tag -f -a "$ORIGIN_TAG" "$target_commit" -m "Latest SDK release for Fern originGitCommit $ORIGIN_COMMIT" + git push --force-with-lease="refs/tags/$ORIGIN_TAG:$existing_ref" origin "refs/tags/$ORIGIN_TAG" + echo "created=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if git merge-base --is-ancestor "$target_commit" "$existing_commit"; then + echo "Tag $ORIGIN_TAG already points at newer release commit $existing_commit; leaving it in place." + echo "created=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "::error::Tag $ORIGIN_TAG already points at divergent commit $existing_commit, not $target_commit." exit 1 fi - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git tag -a "$ORIGIN_TAG" "$target_commit" -m "Release $VERSION_TAG for Fern originGitCommit $ORIGIN_COMMIT" + git tag -a "$ORIGIN_TAG" "$target_commit" -m "Latest SDK release for Fern originGitCommit $ORIGIN_COMMIT" git push origin "refs/tags/$ORIGIN_TAG" echo "created=true" >> "$GITHUB_OUTPUT" @@ -334,6 +366,30 @@ jobs: echo "$label tag $tag points at $expected_commit." } + verify_moving_tag() { + local label="$1" + local tag="$2" + local expected_commit="$3" + local actual_commit + + actual_commit=$(git rev-parse -q --verify "refs/tags/${tag}^{commit}" || true) + if [[ -z "$actual_commit" ]]; then + echo "::error::$label tag $tag does not exist." + exit 1 + fi + if [[ "$actual_commit" == "$expected_commit" ]]; then + echo "$label tag $tag points at $expected_commit." + return + fi + if git merge-base --is-ancestor "$expected_commit" "$actual_commit"; then + echo "$label tag $tag points at newer release commit $actual_commit." + return + fi + + echo "::error::$label tag $tag points at $actual_commit, not $expected_commit or a newer release commit." + exit 1 + } + target_commit=$(git rev-parse --verify "${TAG_TARGET}^{commit}") git fetch --force --tags origin @@ -349,7 +405,7 @@ jobs: fi if [[ "$CREATE_ORIGIN_TAG" == "true" ]]; then - verify_tag "originGitCommit" "$ORIGIN_TAG" "$target_commit" + verify_moving_tag "originGitCommit" "$ORIGIN_TAG" "$target_commit" fi if [[ "$PRESERVE_FERN_GENERATION" == "true" ]]; then diff --git a/.github/workflows/sdk-release-gate.yml b/.github/workflows/sdk-release-gate.yml index 803cf3e..c0d0640 100644 --- a/.github/workflows/sdk-release-gate.yml +++ b/.github/workflows/sdk-release-gate.yml @@ -30,10 +30,6 @@ on: description: "Optional prefix to prepend to the SDK version tag." type: string default: "" - origin-tag-prefix: - description: "Optional prefix to prepend to the origin tag, before originGitCommit/version_tag." - type: string - default: "" outputs: should_publish: description: "true only when the version tag did not already exist and callers should publish." @@ -81,7 +77,6 @@ jobs: SPEC_PATH: ${{ inputs.spec-path }} METADATA_PATH: ${{ inputs.metadata-path }} VERSION_TAG_PREFIX: ${{ inputs.version-tag-prefix }} - ORIGIN_TAG_PREFIX: ${{ inputs.origin-tag-prefix }} steps: - name: Checkout release commit uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 @@ -133,6 +128,10 @@ jobs: echo "::error::Version tag must not begin with '-': $version_tag" exit 1 fi + if [[ "$version_tag" =~ ^[0-9a-fA-F]{40}(/|$) ]]; then + echo "::error::Version tag must not begin with a 40-character SHA: $version_tag" + exit 1 + fi if ! git check-ref-format "refs/tags/$version_tag"; then echo "::error::Invalid version tag name: $version_tag" exit 1 @@ -145,8 +144,6 @@ jobs: - name: Read originGitCommit id: origin - env: - VERSION_TAG: ${{ steps.version.outputs.version_tag }} shell: bash run: | set -euo pipefail @@ -162,7 +159,7 @@ jobs: exit 1 fi - origin_tag="${ORIGIN_TAG_PREFIX}${origin_commit}/${VERSION_TAG}" + origin_tag="backend-origin/${origin_commit}" if [[ "$origin_tag" == -* ]]; then echo "::error::Origin tag must not begin with '-': $origin_tag" exit 1 diff --git a/README.md b/README.md index 36980ca..09e8323 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ Shared GitHub Actions and reusable workflows for the Phenoml SDK repos. shared pre-publish release gate for generated SDKs: extract the SDK version, report whether the release should publish, repair metadata, or skip, read Fern's `originGitCommit`, verify the bundled OpenAPI spec for new publishes, - and validate the origin release tag name. Version tags, origin release tags, - GitHub releases, package builds, and registry publish jobs stay in each SDK - repo. + and validate the prefixed `backend-origin/` release tag + name. Version tags, origin release tags, GitHub releases, package builds, + and registry publish jobs stay in each SDK repo. - [`sdk-release-finalize`](.github/workflows/sdk-release-finalize.yml) — reconciles release metadata after an SDK package publish succeeds: it ensures the normal SDK version tag/release, the Fern origin release tag, and, when