auto-sync-upstream-copilot #24
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-copilot | |
| on: | |
| schedule: | |
| # daily at 02:30 UTC (10:30 Asia/Singapore) — offset 30m from auto-sync-upstream | |
| - cron: '30 2 * * *' | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| actions: write | |
| concurrency: | |
| group: auto-sync-upstream-copilot | |
| cancel-in-progress: false | |
| env: | |
| UPSTREAM_URL: https://github.com/router-for-me/CLIProxyAPI.git | |
| UPSTREAM_BRANCH: main | |
| TARGET_BRANCH: feat/copilot | |
| PR_BRANCH: auto-sync/upstream-main-copilot | |
| jobs: | |
| sync: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout target branch | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ env.TARGET_BRANCH }} | |
| fetch-depth: 0 | |
| - 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 "$UPSTREAM_URL" | |
| git fetch upstream "$UPSTREAM_BRANCH" | |
| - name: Check if behind upstream | |
| id: check | |
| run: | | |
| BEHIND=$(git rev-list --count "HEAD..upstream/${UPSTREAM_BRANCH}") | |
| echo "behind=$BEHIND" >> "$GITHUB_OUTPUT" | |
| if [ "$BEHIND" = "0" ]; then | |
| echo "Already up to date with upstream/${UPSTREAM_BRANCH}." | |
| else | |
| echo "$BEHIND new commits from upstream:" | |
| git log --oneline "HEAD..upstream/${UPSTREAM_BRANCH}" | |
| fi | |
| - name: Build merge commit message | |
| if: steps.check.outputs.behind != '0' | |
| run: | | |
| { | |
| echo "Merge upstream/${UPSTREAM_BRANCH} (auto-sync feat/copilot)" | |
| echo | |
| git log --reverse --pretty=format:'- %h %s' "HEAD..upstream/${UPSTREAM_BRANCH}" | |
| echo | |
| } > /tmp/merge-msg.txt | |
| cat /tmp/merge-msg.txt | |
| - name: Try clean merge | |
| if: steps.check.outputs.behind != '0' | |
| id: merge | |
| run: | | |
| set +e | |
| git merge --no-ff -F /tmp/merge-msg.txt "upstream/${UPSTREAM_BRANCH}" | |
| rc=$? | |
| if [ "$rc" = "0" ]; then | |
| echo "clean=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "clean=false" >> "$GITHUB_OUTPUT" | |
| git merge --abort || true | |
| fi | |
| exit 0 | |
| - name: Set up Go | |
| if: steps.merge.outputs.clean == 'true' | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - name: Verify build | |
| if: steps.merge.outputs.clean == 'true' | |
| id: build | |
| run: | | |
| set +e | |
| go build -o /tmp/cli-proxy-api ./cmd/server | |
| rc=$? | |
| rm -f /tmp/cli-proxy-api | |
| if [ "$rc" = "0" ]; then | |
| echo "ok=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "ok=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| exit 0 | |
| - name: Push merged changes to target branch | |
| if: steps.merge.outputs.clean == 'true' && steps.build.outputs.ok == 'true' | |
| run: git push origin "${TARGET_BRANCH}" | |
| - name: Trigger downstream image build | |
| if: steps.merge.outputs.clean == 'true' && steps.build.outputs.ok == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: gh workflow run ghcr-feat-copilot.yml --repo "${{ github.repository }}" --ref "${TARGET_BRANCH}" | |
| - name: Prepare PR branch (build failure case) | |
| if: steps.merge.outputs.clean == 'true' && steps.build.outputs.ok != 'true' | |
| run: | | |
| # HEAD is the (clean) merge commit — push it as the PR branch so reviewers | |
| # see exactly what would land, and pr-test-build.yml runs against it. | |
| git push --force origin "HEAD:refs/heads/${PR_BRANCH}" | |
| - name: Prepare PR branch (conflict case) | |
| if: steps.check.outputs.behind != '0' && steps.merge.outputs.clean != 'true' | |
| run: | | |
| # Reset working tree, then push raw upstream tip as the PR branch so GitHub | |
| # surfaces the conflict against feat/copilot and offers web-based resolution. | |
| git reset --hard "origin/${TARGET_BRANCH}" | |
| git checkout -B "${PR_BRANCH}" "upstream/${UPSTREAM_BRANCH}" | |
| git push --force origin "${PR_BRANCH}" | |
| - name: Open or update sync PR | |
| if: steps.check.outputs.behind != '0' && (steps.merge.outputs.clean != 'true' || steps.build.outputs.ok != 'true') | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| run: | | |
| set -e | |
| if [ "${{ steps.merge.outputs.clean }}" != "true" ]; then | |
| REASON='conflicts with `'"${TARGET_BRANCH}"'`' | |
| else | |
| REASON='merge clean but `go build` failed' | |
| fi | |
| { | |
| echo "Auto-sync from \`upstream/${UPSTREAM_BRANCH}\` (router-for-me/CLIProxyAPI) into \`${TARGET_BRANCH}\`." | |
| echo | |
| echo "**Reason for manual review:** ${REASON}" | |
| echo | |
| echo "### New commits" | |
| git log --reverse --pretty=format:'- %h %s' "origin/${TARGET_BRANCH}..upstream/${UPSTREAM_BRANCH}" | |
| } > /tmp/pr-body.md | |
| # Idempotently ensure the label exists. | |
| gh label create auto-sync-copilot --color FBCA04 --description "Automated upstream sync (feat/copilot)" || true | |
| EXISTING=$(gh pr list --head "${PR_BRANCH}" --base "${TARGET_BRANCH}" --state open --json number --jq '.[0].number' || true) | |
| TITLE="auto-sync(copilot): merge upstream/${UPSTREAM_BRANCH} (${REASON})" | |
| if [ -n "$EXISTING" ]; then | |
| gh pr edit "$EXISTING" --title "$TITLE" --body-file /tmp/pr-body.md | |
| echo "Updated existing PR #$EXISTING" | |
| else | |
| gh pr create \ | |
| --base "${TARGET_BRANCH}" \ | |
| --head "${PR_BRANCH}" \ | |
| --title "$TITLE" \ | |
| --body-file /tmp/pr-body.md \ | |
| --label auto-sync-copilot | |
| fi |