Maintain deploy branch #211
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Maintain deploy branch | |
| on: | |
| schedule: | |
| - cron: '*/15 * * * *' | |
| workflow_dispatch: | |
| inputs: | |
| force: | |
| description: 'Rebuild even if no changes detected' | |
| type: boolean | |
| default: false | |
| env: | |
| UPSTREAM: radixark/miles | |
| jobs: | |
| check: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| skip: ${{ steps.fingerprint.outputs.skip }} | |
| state: ${{ steps.fingerprint.outputs.state }} | |
| branches: ${{ steps.fingerprint.outputs.branches }} | |
| steps: | |
| - name: Compute desired state and compare | |
| id: fingerprint | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| FORCE: ${{ inputs.force }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| UPSTREAM_SHA=$(gh api "repos/${UPSTREAM}/commits/main" --jq '.sha' 2>/dev/null || echo "unknown") | |
| FORK_OWNER="${REPO%%/*}" | |
| PR_STATE=$(gh api "repos/${UPSTREAM}/pulls?state=open&base=main&per_page=100" \ | |
| --jq "[.[] | select(.head.repo.owner.login == \"${FORK_OWNER}\")] | sort_by(.head.ref) | map(.head.ref + \"=\" + .head.sha) | join(\",\")") | |
| BRANCHES=$(gh api "repos/${UPSTREAM}/pulls?state=open&base=main&per_page=100" \ | |
| --jq "[.[] | select(.head.repo.owner.login == \"${FORK_OWNER}\")] | .[].head.ref" | tr '\n' ' ') | |
| STATE="${UPSTREAM_SHA}|${PR_STATE}" | |
| echo "state=$STATE" >> "$GITHUB_OUTPUT" | |
| echo "branches=$BRANCHES" >> "$GITHUB_OUTPUT" | |
| echo "Desired state: $STATE" | |
| CURRENT=$(gh api "repos/${REPO}/commits/deploy" \ | |
| --jq '.commit.message' 2>/dev/null \ | |
| | grep '^state:' | head -1 | cut -d: -f2- || echo "") | |
| echo "Current state: $CURRENT" | |
| if [ "$CURRENT" = "$STATE" ] && [ "$FORCE" != "true" ]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "::notice::No changes detected, skipping rebuild" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| rebuild: | |
| needs: check | |
| if: needs.check.outputs.skip != 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ steps.app-token.outputs.token }} | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Fetch upstream | |
| run: | | |
| git remote add upstream "https://github.com/${UPSTREAM}.git" || true | |
| git fetch upstream main | |
| git fetch origin | |
| - name: Build deploy branch | |
| env: | |
| PR_BRANCHES: ${{ needs.check.outputs.branches }} | |
| run: | | |
| git checkout -B deploy upstream/main | |
| MERGED="" | |
| FAILED="" | |
| for branch in $PR_BRANCHES; do | |
| echo "Merging $branch..." | |
| if git merge "origin/$branch" --no-edit -m "Deploy: merge $branch"; then | |
| MERGED="$MERGED $branch" | |
| else | |
| echo "::error::Merge conflict on $branch, skipping" | |
| git merge --abort | |
| FAILED="$FAILED $branch" | |
| fi | |
| done | |
| echo "Successfully merged:${MERGED:-<none>}" | |
| if [ -n "$FAILED" ]; then | |
| echo "::warning::Failed to merge (conflicts):$FAILED" | |
| fi | |
| echo "FAILED_BRANCHES=$FAILED" >> "$GITHUB_ENV" | |
| - name: Stamp fingerprint and push | |
| env: | |
| STATE: ${{ needs.check.outputs.state }} | |
| run: | | |
| git commit --allow-empty -m "state:${STATE}" | |
| git push origin deploy --force | |
| - name: Report merge conflicts | |
| if: always() && needs.check.outputs.skip != 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| ISSUE_TITLE="Deploy: merge conflict" | |
| EXISTING=$(gh issue list --label deploy-conflict --state open --json number --jq '.[0].number' 2>/dev/null || echo "") | |
| if [ -z "$FAILED_BRANCHES" ]; then | |
| if [ -n "$EXISTING" ]; then | |
| gh issue close "$EXISTING" --comment "Resolved: all branches merged cleanly." | |
| fi | |
| exit 0 | |
| fi | |
| BODY=$(printf "The following branches failed to merge into deploy:\n\n") | |
| for b in $FAILED_BRANCHES; do | |
| BODY=$(printf "%s\n- \`%s\`" "$BODY" "$b") | |
| done | |
| BODY=$(printf "%s\n\nThis issue auto-closes when the next build merges all branches cleanly." "$BODY") | |
| if [ -n "$EXISTING" ]; then | |
| gh issue edit "$EXISTING" --body "$BODY" | |
| else | |
| gh issue create --title "$ISSUE_TITLE" --body "$BODY" --label deploy-conflict | |
| fi |