Mirror Sync with Upstream #2
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
| # Template for any upstream repository - update the upstream URL as needed | |
| # | |
| # Key Features: | |
| # - Creates/updates ONE evergreen PR (bot/upstream-sync -> main) | |
| # - Syncs daily or on demand; force-resets sync branch to upstream/main | |
| # - Respects branch protections (no direct pushes to main) | |
| # - Protects hotfix/* branches (we never touch them) | |
| # - Includes error handling and status reporting | |
| name: Mirror Sync with Upstream | |
| on: | |
| schedule: | |
| - cron: '0 6 * * *' # Daily at 6 AM UTC | |
| workflow_dispatch: # Allow manual trigger | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| sync: | |
| runs-on: ubuntu-latest | |
| env: | |
| SYNC_BRANCH: bot/upstream-sync | |
| BASE_BRANCH: main | |
| UPSTREAM_REPO: kubernetes-sigs/cluster-api-provider-openstack | |
| steps: | |
| - name: Checkout mirror repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Configure Git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Add upstream remote & fetch | |
| run: | | |
| git remote add upstream https://github.com/${UPSTREAM_REPO}.git || true | |
| if ! git fetch upstream ${BASE_BRANCH}; then | |
| echo "::error::Failed to fetch from upstream repository" | |
| exit 1 | |
| fi | |
| echo "Fetched upstream/${BASE_BRANCH}" | |
| - name: Determine if main is already in sync | |
| id: diff | |
| run: | | |
| git fetch origin ${BASE_BRANCH} --quiet | |
| UP_SHA=$(git rev-parse upstream/${BASE_BRANCH}) | |
| OR_SHA=$(git rev-parse origin/${BASE_BRANCH} || true) | |
| echo "upstream/main: $UP_SHA" | |
| echo "origin/main : $OR_SHA" | |
| if [ "$UP_SHA" = "$OR_SHA" ]; then | |
| echo "in_sync=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "in_sync=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Update sync branch to upstream/main | |
| if: steps.diff.outputs.in_sync == 'false' | |
| run: | | |
| # Create or switch to the evergreen sync branch | |
| if git rev-parse --verify origin/${SYNC_BRANCH} >/dev/null 2>&1; then | |
| git switch -c ${SYNC_BRANCH} --track origin/${SYNC_BRANCH} || git switch ${SYNC_BRANCH} | |
| else | |
| git switch -c ${SYNC_BRANCH} origin/${BASE_BRANCH} || git switch -c ${SYNC_BRANCH} | |
| fi | |
| # Force reset sync branch to upstream/main | |
| git reset --hard upstream/${BASE_BRANCH} | |
| # Push the updated sync branch | |
| git push -f origin ${SYNC_BRANCH} | |
| - name: Create or update PR to main | |
| if: steps.diff.outputs.in_sync == 'false' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const {owner, repo} = context.repo; | |
| const headRef = `${owner}:${process.env.SYNC_BRANCH}`; | |
| const base = process.env.BASE_BRANCH; | |
| // Find existing open PR for this branch | |
| const prs = await github.rest.pulls.list({ | |
| owner, repo, state: 'open', head: headRef, base | |
| }); | |
| let pr; | |
| if (prs.data.length) { | |
| pr = prs.data[0]; | |
| core.info(`Found existing PR #${pr.number}`); | |
| } else { | |
| pr = (await github.rest.pulls.create({ | |
| owner, repo, | |
| title: 'Sync with upstream/main', | |
| head: process.env.SYNC_BRANCH, | |
| base, | |
| body: 'Automated sync from upstream. This PR is continuously updated.' | |
| })).data; | |
| core.info(`Created PR #${pr.number}`); | |
| } | |
| // Try to enable auto-merge (requires repo setting + permissions) | |
| try { | |
| await github.graphql( | |
| `mutation($id:ID!){ | |
| enablePullRequestAutoMerge(input:{pullRequestId:$id, mergeMethod:MERGE}) { clientMutationId } | |
| }`, | |
| { id: pr.node_id } | |
| ); | |
| core.info('Auto-merge enabled.'); | |
| } catch (e) { | |
| core.warning('Auto-merge not enabled (this is OK): ' + e.message); | |
| } | |
| - name: Clean up stale upstream tracking refs | |
| run: | | |
| git remote prune upstream | |
| echo "Pruned stale upstream refs" | |
| - name: Report sync status | |
| run: | | |
| echo "" | |
| echo "=== Mirror Sync Report ===" | |
| if [ "${{ steps.diff.outputs.in_sync }}" = "true" ]; then | |
| echo "Already in sync with upstream/main. No PR updates needed." | |
| else | |
| echo "Opened/updated PR from ${SYNC_BRANCH} -> ${BASE_BRANCH}." | |
| fi | |
| echo "" | |
| echo "Latest commits on upstream/main:" | |
| git log upstream/${BASE_BRANCH} --oneline -5 || true | |
| echo "" | |
| echo "Hotfix branches (preserved, untouched by this workflow):" | |
| git for-each-ref --format='%(refname:short)' refs/heads/hotfix/ || echo " No hotfix/* branches found" | |
| echo "==========================" | |
| - name: Notify on failure | |
| if: failure() | |
| run: | | |
| echo "::error::🚨 Mirror sync failed! Manual intervention required." | |
| echo "::error::Repository: ${{ github.repository }}" | |
| echo "::error::Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" |