Sync Upstream #859
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: Sync Upstream | |
| on: | |
| schedule: | |
| - cron: '0 * * * *' # every hour at :00 (UTC) | |
| workflow_dispatch: {} | |
| permissions: | |
| actions: write | |
| contents: write | |
| concurrency: | |
| group: sync-upstream | |
| cancel-in-progress: true | |
| jobs: | |
| sync: | |
| if: ${{ github.event_name != 'workflow_dispatch' || github.ref == 'refs/heads/overlay' }} | |
| runs-on: ubuntu-latest | |
| env: | |
| GH_TOKEN: ${{ secrets.MIRROR_REPOS_WRITE_PAT }} # use PAT for gh API calls against the mirror (will trigger workflows) | |
| UPSTREAM_REPO: '${{ vars.UPSTREAM_REPO }}' | |
| UPSTREAM_REMOTE: "upstream" | |
| UPSTREAM_BASELINE_TAG: "upstream-baseline" | |
| MAIN_BRANCH: "main" | |
| OVERLAY_BRANCH: "overlay" | |
| CANCEL_UPSTREAM_WORKFLOWS: 'false' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ env.GH_TOKEN }} | |
| - name: Identify the default branch and its latest commit from the upstream repository | |
| id: up | |
| shell: bash -euo pipefail {0} | |
| run: | | |
| echo "::group::Identify default branch and write to output" | |
| def=$(gh api "repos/${UPSTREAM_REPO}" --jq .default_branch) | |
| echo "def=${def}" >> "$GITHUB_OUTPUT" | |
| echo "> Default branch is ${def}" | |
| echo "::endgroup::" | |
| echo "::group::Identify latest commit and write to output" | |
| sha=$(gh api "repos/${UPSTREAM_REPO}/commits/${def}" --jq .sha) | |
| echo "sha=${sha}" >> "$GITHUB_OUTPUT" | |
| echo "> Latest commit on ${def} is ${sha}" | |
| echo "::endgroup::" | |
| - name: Refresh upstream and fetch latest changes | |
| shell: bash -euo pipefail {0} | |
| run: | | |
| echo "> Remove current upstream remote" | |
| git remote remove ${UPSTREAM_REMOTE} 2>/dev/null || true | |
| echo "> Add upstream remote" | |
| # upstream is assumed public — add unauthenticated upstream remote | |
| git remote add ${UPSTREAM_REMOTE} "https://github.com/${UPSTREAM_REPO}.git" | |
| echo "::group::Fetch upstream changes" | |
| git remote get-url ${UPSTREAM_REMOTE} | |
| git remote -v | |
| git ls-remote ${UPSTREAM_REMOTE} | |
| git fetch ${UPSTREAM_REMOTE} --prune --tags | |
| echo "::endgroup::" | |
| - name: Update main only when upstream or overlay changed | |
| shell: bash -euo pipefail {0} | |
| run: | | |
| def="${{ steps.up.outputs.def }}" | |
| upstream_sha="${{ steps.up.outputs.sha }}" | |
| echo "> Upstream default branch (${def}) latest commit: ${upstream_sha}" | |
| # Fetch refs we will compare (origin/main and overlay) into refs/remotes/origin/* | |
| git fetch origin ${MAIN_BRANCH}:refs/remotes/origin/${MAIN_BRANCH} || true | |
| git fetch origin ${OVERLAY_BRANCH}:refs/remotes/origin/${OVERLAY_BRANCH} || true | |
| # compute .github tree object for origin/main and overlay branch | |
| origin_main_commit=$(git rev-parse --verify refs/remotes/origin/${MAIN_BRANCH} 2>/dev/null || true) | |
| origin_main_tree="" | |
| if [ -n "$origin_main_commit" ]; then | |
| origin_main_tree=$(git rev-parse "${origin_main_commit}:.github" 2>/dev/null || true) | |
| fi | |
| overlay_commit=$(git rev-parse --verify refs/remotes/origin/${OVERLAY_BRANCH} 2>/dev/null || true) | |
| overlay_tree="" | |
| if [ -n "$overlay_commit" ]; then | |
| overlay_tree=$(git rev-parse "${overlay_commit}:.github" 2>/dev/null || true) # resolve tree object ID for .github | |
| fi | |
| # read current baseline tag (may not exist on first run) | |
| baseline_sha=$(git rev-parse --verify ${UPSTREAM_BASELINE_TAG} 2>/dev/null || true) | |
| # SKIP when: | |
| # - baseline tag already equals upstream tip (upstream hasn't moved), AND | |
| # - origin/main's .github equals overlay's .github (overlay hasn't changed), AND | |
| # - origin/main already contains that baseline commit (regardless of extra overlay commit) | |
| if [ -n "$baseline_sha" ] && \ | |
| [ "$baseline_sha" = "$upstream_sha" ] && \ | |
| [ -n "$origin_main_tree" ] && [ "$origin_main_tree" = "$overlay_tree" ] && \ | |
| git merge-base --is-ancestor "$baseline_sha" "refs/remotes/origin/${MAIN_BRANCH}"; then | |
| echo "> Up-to-date: upstream baseline unchanged and .github matches overlay; main already contains baseline. Skipping." | |
| exit 0 | |
| fi | |
| echo "> Proceeding to update main (upstream and/or overlay changed)" | |
| # update local main to upstream and apply overlay .github | |
| git update-ref refs/heads/${MAIN_BRANCH} "${upstream_sha}" | |
| git checkout -f ${MAIN_BRANCH} | |
| git config user.name "${GITHUB_ACTOR}" | |
| git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" | |
| # apply overlay .github (creates a small commit only if changed) | |
| git fetch origin ${OVERLAY_BRANCH}:refs/remotes/origin/${OVERLAY_BRANCH} || true | |
| git restore --source refs/remotes/origin/${OVERLAY_BRANCH} -- .github || true | |
| if [ -n "$(git status --porcelain)" ]; then | |
| git add -A | |
| git commit -m "Apply overlay (.github from ${OVERLAY_BRANCH})" | |
| fi | |
| echo "> Push updated main branch" | |
| git push origin ${MAIN_BRANCH} --force-with-lease | |
| - name: Update baseline tag to the latest upstream commit | |
| shell: bash -euo pipefail {0} | |
| run: | | |
| echo "> Move/update lightweight tag to the upstream SHA that main represents" | |
| git tag -f ${UPSTREAM_BASELINE_TAG} "${{ steps.up.outputs.sha }}" | |
| git push -f origin refs/tags/${UPSTREAM_BASELINE_TAG} | |
| - name: Wait for main workflows to start | |
| run: sleep 15 | |
| - name: Cancel unexpected workflows on main | |
| if: env.CANCEL_UPSTREAM_WORKFLOWS == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| branch="${{ env.MAIN_BRANCH }}" | |
| echo "Checking branch: $branch" | |
| gh run list --repo "$GITHUB_REPOSITORY" --branch "$branch" --json databaseId,status,workflowDatabaseId | \ | |
| jq -r '.[] | select(.status == "in_progress" or .status == "queued") | "\(.databaseId) \(.workflowDatabaseId)"' | \ | |
| while read -r run_id workflow_id; do | |
| path=$(gh api "/repos/$GITHUB_REPOSITORY/actions/workflows/$workflow_id" --jq '.path') | |
| case "$path" in | |
| ".github/workflows/loci-analysis.yml" | \ | |
| ".github/workflows/sync-upstream.yml" | \ | |
| ".github/workflows/sync-upstream-prs.yml") | |
| # Allowed workflows - do nothing | |
| ;; | |
| *) | |
| echo "> Canceling run $run_id (file: $path) on branch $branch" | |
| gh run cancel "$run_id" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 || true | |
| ;; | |
| esac | |
| done |