Skip to content

Commit bf427cd

Browse files
committed
ci: auto-sync upstream/main into remote-fix
Daily scheduled workflow (02:00 UTC) plus manual dispatch: - attempts a clean fast-forward-style merge of upstream/main - runs go build to verify the merge compiles - on success: pushes directly to remote-fix and triggers ghcr-remote-fix.yml via gh workflow run - on conflict or build failure: pushes upstream tip (or merge commit) to auto-sync/upstream-main and opens/updates a PR against remote-fix labeled auto-sync, leaving conflict resolution / build fix to manual review
1 parent 344043b commit bf427cd

1 file changed

Lines changed: 169 additions & 0 deletions

File tree

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
name: auto-sync-upstream
2+
3+
on:
4+
schedule:
5+
# daily at 02:00 UTC (10:00 Asia/Singapore)
6+
- cron: '0 2 * * *'
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: write
11+
pull-requests: write
12+
actions: write
13+
14+
concurrency:
15+
group: auto-sync-upstream
16+
cancel-in-progress: false
17+
18+
env:
19+
UPSTREAM_URL: https://github.com/router-for-me/CLIProxyAPI.git
20+
UPSTREAM_BRANCH: main
21+
TARGET_BRANCH: remote-fix
22+
PR_BRANCH: auto-sync/upstream-main
23+
24+
jobs:
25+
sync:
26+
runs-on: ubuntu-latest
27+
steps:
28+
- name: Checkout target branch
29+
uses: actions/checkout@v4
30+
with:
31+
ref: ${{ env.TARGET_BRANCH }}
32+
fetch-depth: 0
33+
34+
- name: Configure git identity
35+
run: |
36+
git config user.name "github-actions[bot]"
37+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
38+
39+
- name: Add upstream remote and fetch
40+
run: |
41+
git remote add upstream "$UPSTREAM_URL"
42+
git fetch upstream "$UPSTREAM_BRANCH"
43+
44+
- name: Check if behind upstream
45+
id: check
46+
run: |
47+
BEHIND=$(git rev-list --count "HEAD..upstream/${UPSTREAM_BRANCH}")
48+
echo "behind=$BEHIND" >> "$GITHUB_OUTPUT"
49+
if [ "$BEHIND" = "0" ]; then
50+
echo "Already up to date with upstream/${UPSTREAM_BRANCH}."
51+
else
52+
echo "$BEHIND new commits from upstream:"
53+
git log --oneline "HEAD..upstream/${UPSTREAM_BRANCH}"
54+
fi
55+
56+
- name: Build merge commit message
57+
if: steps.check.outputs.behind != '0'
58+
run: |
59+
{
60+
echo "Merge upstream/${UPSTREAM_BRANCH} (auto-sync)"
61+
echo
62+
git log --reverse --pretty=format:'- %h %s' "HEAD..upstream/${UPSTREAM_BRANCH}"
63+
echo
64+
} > /tmp/merge-msg.txt
65+
cat /tmp/merge-msg.txt
66+
67+
- name: Try clean merge
68+
if: steps.check.outputs.behind != '0'
69+
id: merge
70+
run: |
71+
set +e
72+
git merge --no-ff -F /tmp/merge-msg.txt "upstream/${UPSTREAM_BRANCH}"
73+
rc=$?
74+
if [ "$rc" = "0" ]; then
75+
echo "clean=true" >> "$GITHUB_OUTPUT"
76+
else
77+
echo "clean=false" >> "$GITHUB_OUTPUT"
78+
git merge --abort || true
79+
fi
80+
exit 0
81+
82+
- name: Set up Go
83+
if: steps.merge.outputs.clean == 'true'
84+
uses: actions/setup-go@v5
85+
with:
86+
go-version-file: go.mod
87+
cache: true
88+
89+
- name: Verify build
90+
if: steps.merge.outputs.clean == 'true'
91+
id: build
92+
run: |
93+
set +e
94+
go build -o /tmp/cli-proxy-api ./cmd/server
95+
rc=$?
96+
rm -f /tmp/cli-proxy-api
97+
if [ "$rc" = "0" ]; then
98+
echo "ok=true" >> "$GITHUB_OUTPUT"
99+
else
100+
echo "ok=false" >> "$GITHUB_OUTPUT"
101+
fi
102+
exit 0
103+
104+
- name: Push merged changes to target branch
105+
if: steps.merge.outputs.clean == 'true' && steps.build.outputs.ok == 'true'
106+
run: git push origin "${TARGET_BRANCH}"
107+
108+
- name: Trigger downstream image build
109+
if: steps.merge.outputs.clean == 'true' && steps.build.outputs.ok == 'true'
110+
env:
111+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
112+
run: gh workflow run ghcr-remote-fix.yml --ref "${TARGET_BRANCH}"
113+
114+
- name: Prepare PR branch (build failure case)
115+
if: steps.merge.outputs.clean == 'true' && steps.build.outputs.ok != 'true'
116+
run: |
117+
# HEAD is the (clean) merge commit — push it as the PR branch so reviewers
118+
# see exactly what would land, and pr-test-build.yml runs against it.
119+
git push --force origin "HEAD:refs/heads/${PR_BRANCH}"
120+
121+
- name: Prepare PR branch (conflict case)
122+
if: steps.check.outputs.behind != '0' && steps.merge.outputs.clean != 'true'
123+
run: |
124+
# Reset working tree, then push raw upstream tip as the PR branch so GitHub
125+
# surfaces the conflict against remote-fix and offers web-based resolution.
126+
git reset --hard "origin/${TARGET_BRANCH}"
127+
git checkout -B "${PR_BRANCH}" "upstream/${UPSTREAM_BRANCH}"
128+
git push --force origin "${PR_BRANCH}"
129+
130+
- name: Open or update sync PR
131+
if: steps.check.outputs.behind != '0' && (steps.merge.outputs.clean != 'true' || steps.build.outputs.ok != 'true')
132+
env:
133+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
134+
run: |
135+
set -e
136+
137+
if [ "${{ steps.merge.outputs.clean }}" != "true" ]; then
138+
REASON='conflicts with `'"${TARGET_BRANCH}"'`'
139+
else
140+
REASON='merge clean but `go build` failed'
141+
fi
142+
143+
{
144+
echo "Auto-sync from \`upstream/${UPSTREAM_BRANCH}\` (router-for-me/CLIProxyAPI)."
145+
echo
146+
echo "**Reason for manual review:** ${REASON}"
147+
echo
148+
echo "### New commits"
149+
git log --reverse --pretty=format:'- %h %s' "origin/${TARGET_BRANCH}..upstream/${UPSTREAM_BRANCH}"
150+
} > /tmp/pr-body.md
151+
152+
# Idempotently ensure the label exists.
153+
gh label create auto-sync --color FBCA04 --description "Automated upstream sync" 2>/dev/null || true
154+
155+
EXISTING=$(gh pr list --head "${PR_BRANCH}" --base "${TARGET_BRANCH}" --state open --json number --jq '.[0].number' || true)
156+
157+
TITLE="auto-sync: merge upstream/${UPSTREAM_BRANCH} (${REASON})"
158+
159+
if [ -n "$EXISTING" ]; then
160+
gh pr edit "$EXISTING" --title "$TITLE" --body-file /tmp/pr-body.md
161+
echo "Updated existing PR #$EXISTING"
162+
else
163+
gh pr create \
164+
--base "${TARGET_BRANCH}" \
165+
--head "${PR_BRANCH}" \
166+
--title "$TITLE" \
167+
--body-file /tmp/pr-body.md \
168+
--label auto-sync
169+
fi

0 commit comments

Comments
 (0)