forked from anomalyco/models.dev
-
Notifications
You must be signed in to change notification settings - Fork 0
212 lines (179 loc) · 8.47 KB
/
Copy pathsync-upstream.yml
File metadata and controls
212 lines (179 loc) · 8.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
name: Sync Upstream
# Checks anomalyco/models.dev (upstream) for new commits three times daily
# and merges them into our fork's dev branch automatically when safe to do so.
#
# Schedule (UTC):
# - 13:00 UTC = 08:00 EST / 05:00 PST
# - 17:00 UTC = 12:00 EST / 09:00 PST
# - 01:00 UTC = 20:00 EST / 17:00 PST
#
# Conflict handling:
# - Fast-forward and clean merges are committed and pushed automatically.
# - If a merge conflict is detected the workflow writes a detailed report
# to the job summary (visible in the Actions tab) and exits without
# pushing anything broken. The job is marked as failed so GitHub sends
# the usual failure notification email/alert.
# - Same for validation failures: report written to summary, push skipped.
on:
schedule:
- cron: '0 13 * * *' # 08:00 EST
- cron: '0 17 * * *' # 12:00 EST
- cron: '0 1 * * *' # 20:00 EST
workflow_dispatch: # Allow manual trigger at any time
concurrency:
group: sync-upstream
cancel-in-progress: false # Never cancel an in-progress sync
permissions:
contents: write # push merged commits to dev
jobs:
sync:
name: Merge upstream into dev
runs-on: ubuntu-latest
steps:
- name: Checkout fork (dev branch, full history)
uses: actions/checkout@v4
with:
ref: dev
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Add upstream remote
run: |
git remote add upstream https://github.com/anomalyco/models.dev.git
git remote set-url --push upstream DISABLED # never push to upstream
- name: Fetch upstream dev
run: git fetch upstream dev --no-tags
- name: Check whether sync is needed
id: check
run: |
UPSTREAM_SHA=$(git rev-parse upstream/dev)
FORK_SHA=$(git rev-parse HEAD)
echo "upstream_sha=$UPSTREAM_SHA" >> "$GITHUB_OUTPUT"
echo "fork_sha=$FORK_SHA" >> "$GITHUB_OUTPUT"
if git merge-base --is-ancestor upstream/dev HEAD; then
echo "status=up_to_date" >> "$GITHUB_OUTPUT"
echo "✅ Fork is already up to date with upstream."
else
NEW=$(git rev-list HEAD..upstream/dev --count)
echo "status=behind" >> "$GITHUB_OUTPUT"
echo "new_commit_count=$NEW" >> "$GITHUB_OUTPUT"
echo "📦 Fork is $NEW commit(s) behind upstream."
fi
- name: Attempt upstream merge
if: steps.check.outputs.status == 'behind'
id: merge
run: |
# Attempt a no-commit merge first to detect conflicts cleanly
if git merge --no-commit --no-ff upstream/dev 2>&1; then
# No conflicts — complete the merge
git merge --abort 2>/dev/null || true
UPSTREAM_SHA="${{ steps.check.outputs.upstream_sha }}"
SHORT_SHA="${UPSTREAM_SHA:0:8}"
COUNT="${{ steps.check.outputs.new_commit_count }}"
git merge upstream/dev \
--no-edit \
-m "chore: sync $COUNT commit(s) from upstream (${SHORT_SHA})"
echo "result=success" >> "$GITHUB_OUTPUT"
else
git merge --abort 2>/dev/null || true
# Capture the list of conflicting files for the summary
git diff --name-only --diff-filter=U 2>/dev/null > /tmp/conflict-files.txt || true
echo "result=conflict" >> "$GITHUB_OUTPUT"
echo "⚠️ Merge conflict detected."
fi
- name: Set up Bun (for validation)
if: steps.merge.outputs.result == 'success'
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Cache Bun dependencies
if: steps.merge.outputs.result == 'success'
uses: actions/setup-node@v4
with:
cache: bun
cache-dependency-path: ./bun.lock
- name: Validate data after merge
if: steps.merge.outputs.result == 'success'
id: validate
run: |
bun install --frozen-lockfile
if bun validate > /dev/null 2>&1; then
echo "result=pass" >> "$GITHUB_OUTPUT"
echo "✅ bun validate passed."
else
echo "result=fail" >> "$GITHUB_OUTPUT"
bun validate 2>&1 | tail -40 > /tmp/validate-errors.txt || true
echo "❌ bun validate failed."
fi
- name: Push merged commits
if: |
steps.merge.outputs.result == 'success' &&
steps.validate.outputs.result == 'pass'
run: |
git push origin dev
echo "🚀 Pushed merged commits to origin/dev."
- name: Summary
if: always()
run: |
echo "### Sync Upstream — Summary" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| | |" >> "$GITHUB_STEP_SUMMARY"
echo "|---|---|" >> "$GITHUB_STEP_SUMMARY"
echo "| Upstream SHA | \`${{ steps.check.outputs.upstream_sha }}\` |" >> "$GITHUB_STEP_SUMMARY"
echo "| Fork SHA | \`${{ steps.check.outputs.fork_sha }}\` |" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
STATUS="${{ steps.check.outputs.status }}"
MERGE="${{ steps.merge.outputs.result }}"
VALIDATE="${{ steps.validate.outputs.result }}"
if [ "$STATUS" = "up_to_date" ]; then
echo "✅ **Already up to date** — no new upstream commits." >> "$GITHUB_STEP_SUMMARY"
elif [ "$MERGE" = "success" ] && [ "$VALIDATE" = "pass" ]; then
echo "🚀 **Synced successfully** — merged ${{ steps.check.outputs.new_commit_count }} commit(s) and pushed to \`dev\`." >> "$GITHUB_STEP_SUMMARY"
elif [ "$MERGE" = "conflict" ]; then
echo "⚠️ **Merge conflict — manual resolution required**" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "The fork is ${{ steps.check.outputs.new_commit_count }} commit(s) behind upstream but could not be merged automatically." >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
# Show conflicting files if captured
if [ -f /tmp/conflict-files.txt ] && [ -s /tmp/conflict-files.txt ]; then
echo "**Conflicting files:**" >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
cat /tmp/conflict-files.txt >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
fi
# Show pending upstream commits
PENDING=$(git log HEAD..upstream/dev --oneline 2>/dev/null | head -20 || echo "(unavailable)")
echo "**Pending upstream commits:**" >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
echo "$PENDING" >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "**To resolve locally:**" >> "$GITHUB_STEP_SUMMARY"
echo '```bash' >> "$GITHUB_STEP_SUMMARY"
echo 'git fetch upstream' >> "$GITHUB_STEP_SUMMARY"
echo 'git checkout dev' >> "$GITHUB_STEP_SUMMARY"
echo 'git merge upstream/dev' >> "$GITHUB_STEP_SUMMARY"
echo '# resolve conflicts, then:' >> "$GITHUB_STEP_SUMMARY"
echo 'git push origin dev' >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
# Exit non-zero so GitHub sends a failure notification
exit 1
elif [ "$VALIDATE" = "fail" ]; then
echo "❌ **Validation failed — push skipped**" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Upstream merged cleanly but \`bun validate\` failed on the merged state." >> "$GITHUB_STEP_SUMMARY"
echo "The push was skipped to avoid introducing invalid data." >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
if [ -f /tmp/validate-errors.txt ]; then
echo "**Validation errors:**" >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
cat /tmp/validate-errors.txt >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
fi
# Exit non-zero so GitHub sends a failure notification
exit 1
fi