auto-sync-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
| name: auto-sync-upstream | |
| # Automatically syncs the fork with upstream google/adk-python. | |
| # | |
| # Flow: | |
| # 1. Merge upstream into main (the integration branch). | |
| # 2. If the merge conflicts -> stop, open an issue, leave `stable` untouched. | |
| # 3. If clean -> install + run the model tests. | |
| # 4. Tests pass -> fast-forward `stable` to the new commit (the new baseline). | |
| # 5. Tests fail -> stop, open an issue, leave `stable` untouched. | |
| # | |
| # Consumers pin `@stable`, so they only ever receive green syncs and are | |
| # automatically held at the last working version when a sync fails. | |
| # | |
| # Security: only static commands and trusted context values (github.run_id, | |
| # github.repository, github.server_url) plus this workflow's own step outputs | |
| # are used. No untrusted github.event.* input is interpolated into run steps. | |
| on: | |
| schedule: | |
| - cron: '0 6 * * *' # daily 06:00 UTC | |
| workflow_dispatch: {} | |
| permissions: | |
| contents: write # push main and stable | |
| issues: write # open the failure-report issue | |
| concurrency: | |
| group: auto-sync | |
| cancel-in-progress: false | |
| env: | |
| INTEGRATION_BRANCH: main | |
| STABLE_BRANCH: stable | |
| UPSTREAM_REPO: https://github.com/google/adk-python.git | |
| jobs: | |
| sync: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout integration branch (full history) | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Configure git | |
| run: | | |
| git config user.name "adk-fork-bot" | |
| git config user.email "adk-fork-bot@users.noreply.github.com" | |
| - name: Merge upstream | |
| id: merge | |
| run: | | |
| git remote add upstream "$UPSTREAM_REPO" | |
| git fetch upstream main | |
| BEFORE=$(git rev-parse HEAD) | |
| echo "before=$BEFORE" >> "$GITHUB_OUTPUT" | |
| if git merge --no-edit upstream/main; then | |
| AFTER=$(git rev-parse HEAD) | |
| echo "after=$AFTER" >> "$GITHUB_OUTPUT" | |
| if [ "$BEFORE" = "$AFTER" ]; then | |
| echo "result=up-to-date" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "result=merged" >> "$GITHUB_OUTPUT" | |
| fi | |
| else | |
| git merge --abort | |
| echo "result=conflict" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Set up Python | |
| if: steps.merge.outputs.result == 'merged' | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Install | |
| if: steps.merge.outputs.result == 'merged' | |
| run: pip install -e ".[test]" | |
| - name: Run model tests | |
| id: tests | |
| if: steps.merge.outputs.result == 'merged' | |
| run: python -m pytest tests/unittests/models/ -q | |
| # --- success path: merge clean + tests green -> advance both branches --- | |
| - name: Publish new baseline | |
| if: steps.merge.outputs.result == 'merged' && steps.tests.outcome == 'success' | |
| run: | | |
| git push origin HEAD:"$INTEGRATION_BRANCH" | |
| git push origin HEAD:"$STABLE_BRANCH" | |
| echo "Advanced $STABLE_BRANCH to $(git rev-parse --short HEAD)" | |
| # --- nothing to do --- | |
| - name: Up to date | |
| if: steps.merge.outputs.result == 'up-to-date' | |
| run: echo "Already in sync with upstream. Nothing to do." | |
| # --- always: refresh the "last synced / next sync" block in the README --- | |
| # Runs on every path. Edits a fresh checkout of origin/main so it never | |
| # carries merge residue from a failed run. Skips the commit if unchanged. | |
| - name: Update sync timestamps in README | |
| if: always() | |
| run: | | |
| if [ "${{ steps.merge.outputs.result }}" = "conflict" ] || \ | |
| [ "${{ steps.tests.outcome }}" = "failure" ]; then | |
| OUTCOME="⚠️ last attempt failed — see the open auto-sync issue" | |
| else | |
| OUTCOME="✅ last sync succeeded" | |
| fi | |
| NOW=$(date -u '+%Y-%m-%d %H:%M UTC') | |
| NEXT=$(date -u -d 'tomorrow 06:00' '+%Y-%m-%d %H:%M UTC') | |
| TMP=$(mktemp -d) | |
| git clone --depth 1 --branch main \ | |
| "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" "$TMP" | |
| cd "$TMP" | |
| python3 - "$NOW" "$NEXT" "$OUTCOME" <<'PY' | |
| import sys, re, pathlib | |
| now, nxt, outcome = sys.argv[1], sys.argv[2], sys.argv[3] | |
| block = ( | |
| "> <!-- SYNC-TIMES-START -->\n" | |
| f"> _Last sync attempt: {now} ({outcome})._\n" | |
| f"> _Next scheduled sync: {nxt}._\n" | |
| "> <!-- SYNC-TIMES-END -->" | |
| ) | |
| p = pathlib.Path("README.md") | |
| text = p.read_text() | |
| new = re.sub( | |
| r"> <!-- SYNC-TIMES-START -->.*?> <!-- SYNC-TIMES-END -->", | |
| block.replace("\\", "\\\\"), text, flags=re.S, | |
| ) | |
| if new != text: | |
| p.write_text(new) | |
| print("README updated") | |
| else: | |
| print("README unchanged") | |
| PY | |
| if ! git diff --quiet README.md; then | |
| git config user.name "adk-fork-bot" | |
| git config user.email "adk-fork-bot@users.noreply.github.com" | |
| git add README.md | |
| git commit -m "chore: update sync timestamps [skip ci]" | |
| git push origin main | |
| fi | |
| # --- failure path: conflict or red tests -> hold + report --- | |
| - name: Report sync failure | |
| if: >- | |
| steps.merge.outputs.result == 'conflict' || | |
| (steps.merge.outputs.result == 'merged' && steps.tests.outcome != 'success') | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REASON: ${{ steps.merge.outputs.result == 'conflict' && 'merge conflict' || 'test failure' }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| STABLE_SHA=$(git rev-parse --short "origin/$STABLE_BRANCH") | |
| TITLE="Upstream sync failed ($REASON) — holding stable at $STABLE_SHA" | |
| BODY=$(cat <<EOF | |
| Automated upstream sync could not complete. | |
| - **Reason:** $REASON | |
| - **\`$STABLE_BRANCH\` is held at:** \`$STABLE_SHA\` (last known-good — consumers are safe) | |
| - **Run log:** $RUN_URL | |
| ### To recover | |
| \`\`\` | |
| ./scripts/update-fork.sh | |
| \`\`\` | |
| Resolve the conflict or fix the failing test, push \`$INTEGRATION_BRANCH\`, | |
| then fast-forward \`$STABLE_BRANCH\` to it. | |
| EOF | |
| ) | |
| EXISTING=$(gh issue list --state open --label auto-sync --json number --jq '.[0].number') | |
| if [ -n "$EXISTING" ]; then | |
| gh issue comment "$EXISTING" --body "$BODY" | |
| else | |
| gh label create auto-sync --color B60205 --description "Automated upstream sync" 2>/dev/null || true | |
| gh issue create --title "$TITLE" --body "$BODY" --label auto-sync | |
| fi | |
| exit 1 |