From 2db9cd82727e4366f9ae0a85cc98d2045bf64af0 Mon Sep 17 00:00:00 2001 From: Mathieu BARLEON Date: Wed, 8 Jul 2026 15:05:29 +0200 Subject: [PATCH] fix: avoid direct push on protected branches --- .github/workflows/backmerge-main-to-dev.yml | 60 ++++++ .github/workflows/prepare-release-pr.yml | 208 ++++++++++++++++++++ .github/workflows/tag-on-merge.yml | 82 ++------ 3 files changed, 288 insertions(+), 62 deletions(-) create mode 100644 .github/workflows/backmerge-main-to-dev.yml create mode 100644 .github/workflows/prepare-release-pr.yml diff --git a/.github/workflows/backmerge-main-to-dev.yml b/.github/workflows/backmerge-main-to-dev.yml new file mode 100644 index 0000000..ed554ac --- /dev/null +++ b/.github/workflows/backmerge-main-to-dev.yml @@ -0,0 +1,60 @@ +name: backmerge-main-to-dev + +on: + pull_request_target: + branches: + - main + types: + - closed + +permissions: + contents: read + pull-requests: write + +jobs: + open-backmerge-pr: + if: >- + github.event.pull_request.merged == true && + startsWith(github.event.pull_request.head.ref, 'hotfix/') && + github.event.pull_request.head.ref != 'hotfix/release-main' + runs-on: ubuntu-latest + + steps: + - name: Open back-merge pull request + run: | + repo="${{ github.repository }}" + api_url="https://api.github.com/repos/${repo}/pulls" + title="chore(backmerge): main into dev after ${{ github.event.pull_request.head.ref }}" + head_branch="main" + base_branch="dev" + + existing_pr="$(curl --silent \ + --url "${api_url}?state=open&head=${{ github.repository_owner }}:${head_branch}&base=${base_branch}" \ + --header "Authorization: Bearer ${GITHUB_TOKEN}" \ + --header "Accept: application/vnd.github+json" \ + --header "X-GitHub-Api-Version: 2022-11-28")" + + if echo "$existing_pr" | grep -q '"number":'; then + echo "An open back-merge PR from main to dev already exists." + exit 0 + fi + + payload="$(cat <- + github.event.pull_request.merged == true && + github.event.pull_request.base.ref == 'dev' && + github.event.pull_request.head.ref != 'release/dev' + runs-on: ubuntu-latest + environment: + name: release-rc + + steps: + - name: Check out dev + uses: actions/checkout@v4 + with: + ref: dev + fetch-depth: 0 + + - name: Fetch tags + run: git fetch --force --tags origin + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install release tooling + run: python -m pip install --upgrade pip commitizen tomlkit + + - name: Prepare release branch + id: prepare + run: | + tag="$(./scripts/next-tag.sh rc)" + python_version="$(./scripts/tag-to-python-version.sh "$tag")" + release_branch="release/dev" + + cz changelog --unreleased-version="$tag" + ./scripts/check-versions.sh --fix "$tag" + git add CHANGELOG.md pyproject.toml + + if git diff --cached --quiet; then + echo "has_changes=false" >> "$GITHUB_OUTPUT" + echo "release_branch=$release_branch" >> "$GITHUB_OUTPUT" + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -B "$release_branch" + git commit -m "chore(release): ${python_version}" + git push --force-with-lease origin HEAD:"$release_branch" + + echo "has_changes=true" >> "$GITHUB_OUTPUT" + echo "release_branch=$release_branch" >> "$GITHUB_OUTPUT" + echo "python_version=$python_version" >> "$GITHUB_OUTPUT" + + - name: Open or update release PR + if: steps.prepare.outputs.has_changes == 'true' + run: | + repo="${{ github.repository }}" + head_branch="${{ steps.prepare.outputs.release_branch }}" + base_branch="dev" + title="chore(release): ${{ steps.prepare.outputs.python_version }}" + body="Automated release preparation PR for \`dev\`." + api_url="https://api.github.com/repos/${repo}/pulls" + + existing_pr="$(curl --silent \ + --url "${api_url}?state=open&head=${{ github.repository_owner }}:${head_branch}&base=${base_branch}" \ + --header "Authorization: Bearer ${GITHUB_TOKEN}" \ + --header "Accept: application/vnd.github+json" \ + --header "X-GitHub-Api-Version: 2022-11-28")" + + if echo "$existing_pr" | grep -q '"number":'; then + echo "Release PR from ${head_branch} to ${base_branch} already exists." + exit 0 + fi + + payload="$(cat <- + github.event.pull_request.merged == true && + github.event.pull_request.base.ref == 'main' && + github.event.pull_request.head.ref != 'hotfix/release-main' + runs-on: ubuntu-latest + environment: + name: release + + steps: + - name: Check out main + uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + + - name: Fetch tags + run: git fetch --force --tags origin + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install release tooling + run: python -m pip install --upgrade pip commitizen tomlkit + + - name: Prepare release branch + id: prepare + run: | + tag="$(./scripts/next-tag.sh stable)" + python_version="$(./scripts/tag-to-python-version.sh "$tag")" + release_branch="hotfix/release-main" + + cz changelog --unreleased-version="$tag" + ./scripts/check-versions.sh --fix "$tag" + git add CHANGELOG.md pyproject.toml + + if git diff --cached --quiet; then + echo "has_changes=false" >> "$GITHUB_OUTPUT" + echo "release_branch=$release_branch" >> "$GITHUB_OUTPUT" + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -B "$release_branch" + git commit -m "chore(release): ${python_version}" + git push --force-with-lease origin HEAD:"$release_branch" + + echo "has_changes=true" >> "$GITHUB_OUTPUT" + echo "release_branch=$release_branch" >> "$GITHUB_OUTPUT" + echo "python_version=$python_version" >> "$GITHUB_OUTPUT" + + - name: Open or update release PR + if: steps.prepare.outputs.has_changes == 'true' + run: | + repo="${{ github.repository }}" + head_branch="${{ steps.prepare.outputs.release_branch }}" + base_branch="main" + title="chore(release): ${{ steps.prepare.outputs.python_version }}" + body="Automated release preparation PR for \`main\`." + api_url="https://api.github.com/repos/${repo}/pulls" + + existing_pr="$(curl --silent \ + --url "${api_url}?state=open&head=${{ github.repository_owner }}:${head_branch}&base=${base_branch}" \ + --header "Authorization: Bearer ${GITHUB_TOKEN}" \ + --header "Accept: application/vnd.github+json" \ + --header "X-GitHub-Api-Version: 2022-11-28")" + + if echo "$existing_pr" | grep -q '"number":'; then + echo "Release PR from ${head_branch} to ${base_branch} already exists." + exit 0 + fi + + payload="$(cat <- github.event.pull_request.merged == true && - github.event.pull_request.base.ref == 'dev' + github.event.pull_request.base.ref == 'dev' && + github.event.pull_request.head.ref == 'release/dev' runs-on: ubuntu-latest environment: name: release-rc - outputs: tag_name: ${{ steps.version.outputs.tag }} steps: - - name: Check out base branch - uses: actions/checkout@v7 + - name: Check out dev + uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.merge_commit_sha || github.event.pull_request.base.ref }} + ref: dev fetch-depth: 0 - name: Fetch tags run: git fetch --force --tags origin - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v5 with: python-version: "3.12" - - name: Install Commitizen - run: python -m pip install --upgrade pip commitizen tomlkit + - name: Install release tooling + run: python -m pip install --upgrade pip commitizen - name: Compute release candidate tag id: version run: | tag="$(./scripts/next-tag.sh rc)" - python_version="$(./scripts/tag-to-python-version.sh "$tag")" echo "tag=$tag" >> "$GITHUB_OUTPUT" - echo "python_version=$python_version" >> "$GITHUB_OUTPUT" - - - name: Update changelog and project version - run: | - tag="${{ steps.version.outputs.tag }}" - python_version="${{ steps.version.outputs.python_version }}" - - cz changelog --unreleased-version="$tag" - ./scripts/check-versions.sh --fix "$tag" - git add CHANGELOG.md pyproject.toml - - if git diff --cached --quiet; then - echo "No release file changes detected." - exit 0 - fi - - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git commit -m "chore(release): ${python_version}" - git push origin HEAD:dev - name: Create tag run: | @@ -84,14 +63,14 @@ jobs: git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git fetch origin dev --tags git tag -a "$tag" -m "Release candidate $tag" git push origin "$tag" release-dev: if: >- github.event.pull_request.merged == true && - github.event.pull_request.base.ref == 'dev' + github.event.pull_request.base.ref == 'dev' && + github.event.pull_request.head.ref == 'release/dev' needs: tag-dev uses: ./.github/workflows/release.yml secrets: inherit @@ -103,58 +82,37 @@ jobs: tag-main: if: >- github.event.pull_request.merged == true && - github.event.pull_request.base.ref == 'main' + github.event.pull_request.base.ref == 'main' && + github.event.pull_request.head.ref == 'hotfix/release-main' runs-on: ubuntu-latest environment: name: release - outputs: tag_name: ${{ steps.version.outputs.tag }} steps: - - name: Check out base branch - uses: actions/checkout@v7 + - name: Check out main + uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.merge_commit_sha || github.event.pull_request.base.ref }} + ref: main fetch-depth: 0 - name: Fetch tags run: git fetch --force --tags origin - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v5 with: python-version: "3.12" - - name: Install Commitizen - run: python -m pip install --upgrade pip commitizen tomlkit + - name: Install release tooling + run: python -m pip install --upgrade pip commitizen - name: Compute stable tag id: version run: | tag="$(./scripts/next-tag.sh stable)" - python_version="$(./scripts/tag-to-python-version.sh "$tag")" echo "tag=$tag" >> "$GITHUB_OUTPUT" - echo "python_version=$python_version" >> "$GITHUB_OUTPUT" - - - name: Update changelog and project version - run: | - tag="${{ steps.version.outputs.tag }}" - python_version="${{ steps.version.outputs.python_version }}" - - cz changelog --unreleased-version="$tag" - ./scripts/check-versions.sh --fix "$tag" - git add CHANGELOG.md pyproject.toml - - if git diff --cached --quiet; then - echo "No release file changes detected." - exit 0 - fi - - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git commit -m "chore(release): ${python_version}" - git push origin HEAD:main - name: Create tag run: | @@ -167,14 +125,14 @@ jobs: git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git fetch origin main --tags git tag -a "$tag" -m "Release $tag" git push origin "$tag" release-main: if: >- github.event.pull_request.merged == true && - github.event.pull_request.base.ref == 'main' + github.event.pull_request.base.ref == 'main' && + github.event.pull_request.head.ref == 'hotfix/release-main' needs: tag-main uses: ./.github/workflows/release.yml secrets: inherit