diff --git a/.github/workflows/sync-upstream.yaml b/.github/workflows/sync-upstream.yaml new file mode 100644 index 00000000..770bc3b2 --- /dev/null +++ b/.github/workflows/sync-upstream.yaml @@ -0,0 +1,165 @@ +name: Sync with upstream + +# Weekly sync of this fork with juhaku/utoipa. Produces a PR against master +# that merges upstream while preserving fork-specific commits. +# +# Token note: +# PRs opened by GITHUB_TOKEN do NOT trigger other workflows (build.yaml, +# etc.) — a security guard against recursive CI. To get CI on the sync PR, +# create a fine-grained PAT with `contents: write` + `pull-requests: write` +# on this repo and store it as the `SYNC_PAT` secret. The workflow falls +# back to GITHUB_TOKEN if SYNC_PAT is not set. + +on: + schedule: + # Mondays at 06:00 UTC. + - cron: "0 6 * * 1" + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + issues: write + +concurrency: + group: sync-upstream + cancel-in-progress: false + +jobs: + sync: + # Don't activate on forks-of-this-fork. + if: github.repository == 'Devolutions/utoipa' + runs-on: ubuntu-latest + env: + UPSTREAM_REPO: juhaku/utoipa + UPSTREAM_BRANCH: master + FORK_BRANCH: master + SYNC_BRANCH: sync/upstream + steps: + - name: Checkout fork (full history) + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.SYNC_PAT || secrets.GITHUB_TOKEN }} + + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Add upstream remote and fetch + run: | + git remote add upstream "https://github.com/${UPSTREAM_REPO}.git" + git fetch upstream "${UPSTREAM_BRANCH}" + git fetch origin "${SYNC_BRANCH}" || true + + - name: Determine whether sync is needed + id: check + run: | + BEHIND=$(git rev-list --count "${FORK_BRANCH}..upstream/${UPSTREAM_BRANCH}") + AHEAD=$(git rev-list --count "upstream/${UPSTREAM_BRANCH}..${FORK_BRANCH}") + UPSTREAM_SHA=$(git rev-parse "upstream/${UPSTREAM_BRANCH}") + echo "behind=${BEHIND}" >> "$GITHUB_OUTPUT" + echo "upstream_sha=${UPSTREAM_SHA}" >> "$GITHUB_OUTPUT" + echo "Fork is ${AHEAD} ahead and ${BEHIND} behind upstream (${UPSTREAM_SHA})." + + - name: Attempt merge on sync branch + if: steps.check.outputs.behind != '0' + id: merge + env: + UPSTREAM_SHA: ${{ steps.check.outputs.upstream_sha }} + run: | + git checkout -B "${SYNC_BRANCH}" "${FORK_BRANCH}" + if git merge --no-ff --no-edit \ + -m "Merge upstream ${UPSTREAM_REPO}@${UPSTREAM_BRANCH} (${UPSTREAM_SHA:0:7})" \ + "upstream/${UPSTREAM_BRANCH}"; then + echo "status=clean" >> "$GITHUB_OUTPUT" + else + git merge --abort || true + echo "status=conflict" >> "$GITHUB_OUTPUT" + fi + + - name: Push sync branch + if: steps.merge.outputs.status == 'clean' + id: push + env: + GH_TOKEN: ${{ secrets.SYNC_PAT || secrets.GITHUB_TOKEN }} + run: | + # If an open PR exists and someone has pushed commits to its branch + # (e.g. manual conflict resolution from a previous run), don't + # clobber that work. Comment on the PR instead. + PR_NUM=$(gh pr list --head "${SYNC_BRANCH}" --base "${FORK_BRANCH}" \ + --state open --json number --jq '.[0].number // empty') + if [ -n "${PR_NUM}" ] && git rev-parse --verify "origin/${SYNC_BRANCH}" >/dev/null 2>&1; then + HUMAN_AHEAD=$(git rev-list --count "HEAD..origin/${SYNC_BRANCH}") + if [ "${HUMAN_AHEAD}" -gt 0 ]; then + echo "Open PR #${PR_NUM} has ${HUMAN_AHEAD} commit(s) we don't have — skipping push." + gh pr comment "${PR_NUM}" --body "New upstream commits are available since this PR was opened. Pull \`master\` into this branch and merge \`upstream/master\` again to incorporate them." + echo "skipped=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + fi + git push --force-with-lease origin "${SYNC_BRANCH}" + echo "skipped=false" >> "$GITHUB_OUTPUT" + + - name: Open or update sync PR + if: steps.merge.outputs.status == 'clean' && steps.push.outputs.skipped != 'true' + env: + GH_TOKEN: ${{ secrets.SYNC_PAT || secrets.GITHUB_TOKEN }} + UPSTREAM_SHA: ${{ steps.check.outputs.upstream_sha }} + run: | + TITLE="Sync with upstream ${UPSTREAM_REPO}@${UPSTREAM_SHA:0:7}" + BODY=$(cat <