name: Rollback release
on:
  # You can trigger this workflow via workflow dispatch to start a rollback.
  # This will create a draft release that mirrors the release for `rollback-tag`.
  workflow_dispatch:
    inputs:
      rollback-tag:
        type: string
        description: "The tag of an old release to roll-back to."
        required: true
  # Only for dry-runs of changes to the workflow.
  push:
    # Don't run dry-run on release branches, to avoid an issue where the
    # "new" tag determined by the "Prepare release" job already exists.
    branches-ignore:
      - releases/v*
    paths:
      - .github/workflows/rollback-release.yml
      - .github/actions/prepare-mergeback-branch/**

defaults:
  run:
    shell: bash

jobs:
  prepare:
    name: "Prepare release"
    if: github.repository == 'github/codeql-action'

    permissions:
      contents: read

    uses: ./.github/workflows/prepare-release.yml

  rollback:
    name: "Create rollback release"
    if: github.repository == 'github/codeql-action'
    runs-on: ubuntu-latest
    timeout-minutes: 45

    # Don't set the deployment environment for test runs
    # The Actions token does not have permissions to push changes to workflow files.
    # Since workflow files may change as part of a backport PR, we use the "Automation" environment for real runs to authenticate as a GitHub App and push these changes.
    environment: ${{ github.event_name == 'workflow_dispatch' && 'Automation' || '' }}

    needs:
      - prepare

    permissions:
      contents: write # needed to push to the repo (tags and releases)
      pull-requests: write # needed to create the mergeback PR

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0 # Need full history for calculation of diffs

      - name: Configure runner for release
        uses: ./.github/actions/release-initialise

      - name: Create tag for testing
        if: github.event_name != 'workflow_dispatch'
        run: git tag v0.0.0

      # We start by preparing the mergeback branch, mainly so that we have the updated changelog
      # readily available for the partial changelog that's needed for the release.
      - name: Prepare mergeback branch
        id: mergeback-branch
        env:
          BASE_BRANCH: ${{ (github.event_name == 'workflow_dispatch' && 'main') || github.ref_name }}
          VERSION: ${{ needs.prepare.outputs.version }}
        run: |
          set -x

          # Checkout the base branch, since we may be testing on a different branch
          git checkout "$BASE_BRANCH"

          # Generate a new branch name for the mergeback PR
          short_sha="${GITHUB_SHA:0:8}"
          NEW_BRANCH="mergeback/${VERSION}-to-${BASE_BRANCH}-${short_sha}"
          echo "new-branch=${NEW_BRANCH}" >> $GITHUB_OUTPUT

          # Create the mergeback branch
          git checkout -b "${NEW_BRANCH}"

      - name: Prepare rollback changelog
        env:
          NEW_CHANGELOG: "${{ runner.temp }}/new_changelog.md"
          # We usually expect to checkout `inputs.rollback-tag` (required for `workflow_dispatch`),
          # but use `v0.0.0` for testing.
          ROLLBACK_TAG: ${{ inputs.rollback-tag || 'v0.0.0' }}
          LATEST_TAG: ${{ needs.prepare.outputs.latest_tag }}
          VERSION: "${{ needs.prepare.outputs.version }}"
        run: |
          python .github/workflows/script/rollback_changelog.py \
            --target-version "${ROLLBACK_TAG:1}" \
            --rollback-version "${LATEST_TAG:1}" \
            --new-version "$VERSION" > $NEW_CHANGELOG

          echo "::group::New CHANGELOG"
          cat $NEW_CHANGELOG
          echo "::endgroup::"

      - name: Create tags
        env:
          # We usually expect to checkout `inputs.rollback-tag` (required for `workflow_dispatch`),
          # but use `v0.0.0` for testing.
          ROLLBACK_TAG: ${{ inputs.rollback-tag || 'v0.0.0' }}
          RELEASE_TAG: ${{ needs.prepare.outputs.version }}
          MAJOR_VERSION_TAG: ${{ needs.prepare.outputs.major_version }}
        run: |
          git checkout "refs/tags/${ROLLBACK_TAG}"
          git tag --annotate "${RELEASE_TAG}" --message "${RELEASE_TAG}"
          git tag --annotate "${MAJOR_VERSION_TAG}" --message "${MAJOR_VERSION_TAG}" --force

      - name: Push tags
        # skip when testing
        if: github.event_name == 'workflow_dispatch'
        env:
          RELEASE_TAG: ${{ needs.prepare.outputs.version }}
          MAJOR_VERSION_TAG: ${{ needs.prepare.outputs.major_version }}
        run: |
          git push origin --atomic --force refs/tags/"${RELEASE_TAG}" refs/tags/"${MAJOR_VERSION_TAG}"

      - name: Prepare partial Changelog
        env:
          NEW_CHANGELOG: "${{ runner.temp }}/new_changelog.md"
          PARTIAL_CHANGELOG: "${{ runner.temp }}/partial_changelog.md"
        run: |
          python .github/workflows/script/prepare_changelog.py $NEW_CHANGELOG > $PARTIAL_CHANGELOG

          echo "::group::Partial CHANGELOG"
          cat $PARTIAL_CHANGELOG
          echo "::endgroup::"

      - name: Generate token
        if: github.event_name == 'workflow_dispatch'
        uses: actions/create-github-app-token@v3.2.0
        id: app-token
        with:
          app-id: ${{ vars.AUTOMATION_APP_ID }}
          private-key: ${{ secrets.AUTOMATION_PRIVATE_KEY }}

      - name: Create the rollback release
        if: github.event_name == 'workflow_dispatch'
        env:
          PARTIAL_CHANGELOG: "${{ runner.temp }}/partial_changelog.md"
          VERSION: "${{ needs.prepare.outputs.version }}"
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
          RELEASE_URL: "${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ needs.prepare.outputs.version }}"
        run: |
          set -exu

          # Do not mark this release as latest. The most recent bundle release must be marked as latest.
          # Set as a draft to give us an opportunity to review the rollback release.
          gh release create \
            "$VERSION" \
            --latest=false \
            --draft \
            --title "$VERSION" \
            --notes-file "$PARTIAL_CHANGELOG"

          echo "Created draft rollback release at $RELEASE_URL" >> $GITHUB_STEP_SUMMARY

      - name: Update changelog
        env:
          NEW_CHANGELOG: "${{ runner.temp }}/new_changelog.md"
          NEW_BRANCH: "${{ steps.mergeback-branch.outputs.new-branch }}"
        run: |
          git checkout "${NEW_BRANCH}"
          mv ${NEW_CHANGELOG} CHANGELOG.md

      - name: Create mergeback branch and PR
        uses: ./.github/actions/prepare-mergeback-branch
        with:
          base: "main"
          head: ""
          branch: "${{ steps.mergeback-branch.outputs.new-branch }}"
          version: "${{ needs.prepare.outputs.version }}"
          token: "${{ secrets.GITHUB_TOKEN }}"
          # Setting this to `true` for non-workflow_dispatch events will
          # still push the `branch`, but won't create a corresponding PR
          dry-run: "${{ github.event_name != 'workflow_dispatch' }}"

