| name: Sync Docker release branch |
| |
| concurrency: |
| group: ${{ github.workflow }}-${{ inputs.release_branch }} |
| cancel-in-progress: false |
| |
| permissions: |
| contents: read |
| |
| on: |
| workflow_dispatch: |
| inputs: |
| release_branch: |
| description: Release branch to sync, for example 29.x |
| required: true |
| type: string |
| tag: |
| description: Tag to sync from, for example v29.6.0 |
| required: true |
| type: string |
| dry_run: |
| description: Merge but don't push |
| required: true |
| default: false |
| type: boolean |
| |
| jobs: |
| sync-release-branch: |
| runs-on: ubuntu-24.04 |
| permissions: |
| contents: write |
| actions: write |
| outputs: |
| base_sha: ${{ steps.sync.outputs.base_sha }} |
| has_changes: ${{ steps.sync.outputs.has_changes }} |
| temporary_branch: ${{ steps.sync.outputs.temporary_branch }} |
| temporary_sha: ${{ steps.sync.outputs.temporary_sha }} |
| timeout-minutes: 10 |
| steps: |
| - name: Checkout |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 |
| with: |
| fetch-depth: 0 |
| |
| - name: Validate |
| env: |
| BRANCH: ${{ github.ref_name }} |
| RELEASE_BRANCH: ${{ inputs.release_branch }} |
| run: | |
| if [ "$BRANCH" != "master" ]; then |
| echo "::error::This workflow is expected to be run on master, not $BRANCH" |
| exit 1 |
| fi |
| if [ "$RELEASE_BRANCH" = "master" ]; then |
| echo "::error::The release branch must not be master" |
| exit 1 |
| fi |
| if ! [[ "$RELEASE_BRANCH" =~ ^[0-9]+\.[x0-9]+$ ]]; then |
| echo "::error::Invalid release branch name: '$RELEASE_BRANCH'. Expected format: 29.x" |
| exit 1 |
| fi |
| |
| - name: Configure git author |
| run: | |
| git config user.name "github-actions[bot]" |
| git config user.email "github-actions[bot]@users.noreply.github.com" |
| |
| - name: Sync release branch to tag range |
| id: sync |
| env: |
| DRY_RUN: ${{ inputs.dry_run }} |
| RELEASE_BRANCH: ${{ inputs.release_branch }} |
| RUN_ATTEMPT: ${{ github.run_attempt }} |
| RUN_ID: ${{ github.run_id }} |
| TAG: ${{ inputs.tag }} |
| run: | |
| set -o pipefail |
| base_sha=$(git rev-parse "origin/$RELEASE_BRANCH") |
| temporary_branch="process/sync-release-branch/$RUN_ID-$RUN_ATTEMPT" |
| echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT" |
| echo "temporary_branch=$temporary_branch" >> "$GITHUB_OUTPUT" |
| |
| # Keep the master checkout unchanged so scripts run from the dispatched revision. |
| release_worktree="$RUNNER_TEMP/release-branch" |
| git worktree add --detach "$release_worktree" "$base_sha" |
| cd "$release_worktree" |
| |
| tags_file=$(mktemp) |
| "$GITHUB_WORKSPACE/scripts/unmerged-tags" \ |
| "origin/$RELEASE_BRANCH" \ |
| "$TAG" \ |
| > "$tags_file" |
| |
| echo >> "$GITHUB_STEP_SUMMARY" |
| echo "## Tags to sync" >> "$GITHUB_STEP_SUMMARY" |
| echo >> "$GITHUB_STEP_SUMMARY" |
| sed 's/^/- /' "$tags_file" >> "$GITHUB_STEP_SUMMARY" |
| |
| xargs -r "$GITHUB_WORKSPACE/scripts/sync-branch" < "$tags_file" | tee -a "$GITHUB_STEP_SUMMARY" |
| |
| if [[ "$DRY_RUN" == "true" ]]; then |
| echo "has_changes=false" >> "$GITHUB_OUTPUT" |
| exit 0 |
| fi |
| |
| if [[ $(git rev-parse HEAD) == $(git rev-parse "origin/$RELEASE_BRANCH") ]]; then |
| echo "has_changes=false" >> "$GITHUB_OUTPUT" |
| echo "No changes to push" |
| exit 0 |
| fi |
| |
| echo "has_changes=true" >> "$GITHUB_OUTPUT" |
| git push origin "HEAD:refs/heads/$temporary_branch" |
| echo "temporary_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" |
| |
| push-release-branch: |
| needs: sync-release-branch |
| if: ${{ !inputs.dry_run && needs.sync-release-branch.outputs.has_changes == 'true' }} |
| runs-on: ubuntu-24.04 |
| environment: docker-releases |
| permissions: |
| contents: write |
| actions: write |
| timeout-minutes: 10 |
| steps: |
| - name: Checkout |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 |
| with: |
| fetch-depth: 0 |
| |
| - name: Push release branch |
| env: |
| BASE_SHA: ${{ needs.sync-release-branch.outputs.base_sha }} |
| RELEASE_BRANCH: ${{ inputs.release_branch }} |
| TEMPORARY_BRANCH: ${{ needs.sync-release-branch.outputs.temporary_branch }} |
| TEMPORARY_SHA: ${{ needs.sync-release-branch.outputs.temporary_sha }} |
| run: | |
| git fetch origin "$RELEASE_BRANCH" |
| current_sha=$(git rev-parse "origin/$RELEASE_BRANCH") |
| if [[ "$current_sha" != "$BASE_SHA" ]]; then |
| echo "$RELEASE_BRANCH changed from $BASE_SHA to $current_sha" |
| exit 1 |
| fi |
| |
| git fetch origin "$TEMPORARY_BRANCH" |
| current_temporary_sha=$(git rev-parse FETCH_HEAD) |
| if [[ "$current_temporary_sha" != "$TEMPORARY_SHA" ]]; then |
| echo "$TEMPORARY_BRANCH changed from $TEMPORARY_SHA to $current_temporary_sha" |
| exit 1 |
| fi |
| |
| git push origin "FETCH_HEAD:$RELEASE_BRANCH" |
| |
| - name: Delete temporary branch |
| env: |
| TEMPORARY_BRANCH: ${{ needs.sync-release-branch.outputs.temporary_branch }} |
| run: git push origin --delete "$TEMPORARY_BRANCH" |