forked from blockscout/blockscout
-
Notifications
You must be signed in to change notification settings - Fork 0
279 lines (244 loc) · 10.3 KB
/
sync-upstream.yml
File metadata and controls
279 lines (244 loc) · 10.3 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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
name: Sync upstream release
on:
schedule:
- cron: '0 2,8,14,20 * * *' # Every 6h UTC (09, 15, 21, 03 VN)
workflow_dispatch:
inputs:
tag:
description: 'Upstream tag to sync (e.g. v9.3.5). Leave empty to auto-detect latest.'
required: false
type: string
permissions:
contents: write
actions: write
pull-requests: write
jobs:
sync:
name: Sync with upstream blockscout/blockscout
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# PAT required to push .github/workflows/ changes
# GITHUB_TOKEN cannot modify workflow files
token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN }}
- name: Add upstream remote
run: |
git remote add upstream https://github.com/blockscout/blockscout.git || true
git fetch upstream --tags --force
- name: Determine target tag
id: target
run: |
if [ -n "${{ inputs.tag }}" ]; then
TAG="${{ inputs.tag }}"
else
# Prefer the newest STABLE release (no -alpha/-beta/-rc suffix).
# Fall back to the newest non-alpha prerelease (beta/rc) only when no
# stable release exists. Never auto-sync -alpha tags.
TAG=$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | grep -vE -- '-' | head -1)
if [ -z "$TAG" ]; then
TAG=$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | grep -ivE -- '-alpha' | head -1)
fi
fi
if [ -z "$TAG" ]; then
echo "::error::No upstream release tag matched 'v[0-9]*.[0-9]*.[0-9]*'. Aborting."
exit 1
fi
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "Target tag: $TAG"
# Check if we already synced this tag
MARKER_TAG="synced-$TAG"
if git rev-parse "$MARKER_TAG" >/dev/null 2>&1; then
echo "skip=true" >> $GITHUB_OUTPUT
echo "Already synced $TAG, skipping"
else
echo "skip=false" >> $GITHUB_OUTPUT
echo "Will sync $TAG"
fi
- name: Merge upstream tag
if: steps.target.outputs.skip == 'false'
id: merge
run: |
TAG="${{ steps.target.outputs.tag }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Files that MUST match upstream exactly (versions, deps, build config).
# Kept in sync with .github/scripts/auto-resolve-merge-conflicts.sh.
VERSION_FILES=(
mix.exs
rel/config.exs
docker/Makefile
CHANGELOG.md
apps/block_scout_web/mix.exs
apps/ethereum_jsonrpc/mix.exs
apps/explorer/mix.exs
apps/indexer/mix.exs
apps/nft_media_handler/mix.exs
apps/utils/mix.exs
)
# Try clean merge first
if git merge "$TAG" --no-edit -m "Merge upstream $TAG"; then
echo "Clean merge succeeded"
else
# Merge failed - auto-resolve conflicts
echo "::notice::Merge conflict detected, attempting auto-resolve..."
if bash .github/scripts/auto-resolve-merge-conflicts.sh; then
git commit --no-edit -m "Merge upstream $TAG (auto-resolved conflicts)"
else
# Conflicts remain - capture list, abort merge, hand off to PR step.
REMAINING=$(git diff --name-only --diff-filter=U || true)
echo "::warning::Unresolvable conflicts in: $REMAINING"
git merge --abort
echo "result=conflict" >> $GITHUB_OUTPUT
{
echo "conflict_files<<EOF"
echo "$REMAINING"
echo "EOF"
} >> $GITHUB_OUTPUT
exit 0
fi
fi
# Post-merge: ensure version files match upstream EXACTLY
# Git auto-merge can silently pick the wrong side for these files
FIXUP_NEEDED=false
for f in "${VERSION_FILES[@]}"; do
if [ -f "$f" ] && ! diff -q <(git show "$TAG":"$f" 2>/dev/null) "$f" >/dev/null 2>&1; then
echo " Fixup (upstream): $f"
git checkout "$TAG" -- "$f"
FIXUP_NEEDED=true
fi
done
if [ "$FIXUP_NEEDED" = true ]; then
git add -A
git commit -m "fixup: restore upstream version files from $TAG"
echo "::notice::Version files fixed up to match upstream $TAG"
fi
echo "result=success" >> $GITHUB_OUTPUT
- name: Remove upstream-only workflows
if: steps.target.outputs.skip == 'false' && steps.merge.outputs.result == 'success'
run: |
# Keep ONLY our DOS-specific workflows, delete everything else
KEEP_FILES=(
"deploy-config.yml"
"publish-regular-docker-image-on-demand.yml"
"sync-upstream.yml"
)
cd .github/workflows/
for f in *.yml; do
KEEP=false
for keep in "${KEEP_FILES[@]}"; do
if [ "$f" = "$keep" ]; then
KEEP=true
break
fi
done
if [ "$KEEP" = false ]; then
echo " Removing upstream workflow: $f"
rm "$f"
fi
done
cd ../..
if git diff --name-only | grep -q '\.github/workflows/'; then
git add .github/workflows/
git commit -m "chore: remove upstream-only workflows after sync"
fi
- name: Push and tag
if: steps.target.outputs.skip == 'false' && steps.merge.outputs.result == 'success'
run: |
TAG="${{ steps.target.outputs.tag }}"
git push origin HEAD
# Create marker tag so we don't re-sync
git tag "synced-$TAG"
git push origin "synced-$TAG"
# Create the release tag (same as upstream) to trigger docker-publish
git tag "$TAG" --force
git push origin "$TAG" --force
- name: Create release
if: steps.target.outputs.skip == 'false' && steps.merge.outputs.result == 'success'
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
run: |
TAG="${{ steps.target.outputs.tag }}"
# Verify PAT is available (not falling back to GITHUB_TOKEN)
if [ -z "$GH_TOKEN" ]; then
echo "::error::GH_PAT secret is not set. Cannot create release without workflow scope."
exit 1
fi
# Delete existing release if any (from upstream fork sync)
if RELEASE_ID=$(gh api "repos/${{ github.repository }}/releases/tags/$TAG" --jq '.id' 2>/dev/null); then
echo "Deleting existing release $RELEASE_ID for $TAG"
gh api -X DELETE "repos/${{ github.repository }}/releases/$RELEASE_ID" 2>/dev/null || true
fi
# Create release via API (more reliable than gh release create in Actions)
gh api "repos/${{ github.repository }}/releases" \
-f tag_name="$TAG" \
-f name="DOScan $TAG" \
-f body="Synced from upstream [blockscout/blockscout $TAG](https://github.com/blockscout/blockscout/releases/tag/$TAG)" \
-f make_latest="true"
- name: Create PR on conflict
if: steps.target.outputs.skip == 'false' && steps.merge.outputs.result == 'conflict'
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
run: |
TAG="${{ steps.target.outputs.tag }}"
BRANCH="sync-upstream-${TAG}"
git checkout -b "$BRANCH"
git merge "$TAG" --no-edit || true
# Auto-resolve what we can. Script exits 1 when conflicts remain;
# that is expected here - we still commit and push the WIP state
# so reviewers see only the unresolvable conflicts.
bash .github/scripts/auto-resolve-merge-conflicts.sh || true
git add -A
git commit -m "WIP: Merge upstream $TAG (has conflicts)" --no-verify || true
git push origin "$BRANCH" --force
EXISTING_PR=$(gh pr list \
--repo "${GITHUB_REPOSITORY}" \
--head "$BRANCH" \
--base main \
--state open \
--json url \
--jq '.[0].url // empty')
if [ -n "$EXISTING_PR" ]; then
echo "::notice::Conflict PR already exists: $EXISTING_PR"
exit 0
fi
# GitHub's GraphQL createPullRequest replica lags behind the REST
# branches endpoint. Even after the branch is visible via REST, the
# GraphQL mutation can return: "No commits between main and BRANCH".
# Retry the PR creation itself with backoff.
REMAINING=$(echo "${{ steps.merge.outputs.conflict_files }}" | head -20)
PR_BODY="$(cat <<EOF
## Upstream Sync - $TAG
Auto-merge with upstream \`$TAG\` failed. Version/workflow conflicts were auto-resolved,
but the following files have code conflicts that need manual resolution:
\`\`\`
$REMAINING
\`\`\`
**To resolve:**
1. Check out this branch locally
2. Resolve remaining conflicts
3. Push and merge this PR
4. Then create tag \`$TAG\` to trigger Docker build
[Upstream release notes](https://github.com/blockscout/blockscout/releases/tag/$TAG)
EOF
)"
# Use REST API directly — gh pr create uses GraphQL which has a
# longer replication lag against new branches (>30s observed).
PR_TITLE="Sync upstream $TAG (merge conflicts)"
for attempt in 1 2 3 4 5 6; do
if PR_URL=$(jq -n --arg t "$PR_TITLE" --arg b "$PR_BODY" --arg h "$BRANCH" \
'{title:$t, body:$b, head:$h, base:"main"}' | \
gh api -X POST "repos/${GITHUB_REPOSITORY}/pulls" \
--input - \
--jq '.html_url' 2>&1); then
echo "PR created successfully on attempt $attempt: $PR_URL"
exit 0
fi
echo "PR creation failed (attempt $attempt): $PR_URL"
echo "Retrying in 5s..."
sleep 5
done
echo "::error::PR creation failed after 6 attempts"
exit 1