Sync upstream release #185
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: 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 | |
| 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 | |
| # Get latest release tag from upstream (filter only vX.Y.Z pattern) | |
| TAG=$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -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) | |
| 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..." | |
| for f in "${VERSION_FILES[@]}"; do | |
| if git diff --name-only --diff-filter=U | grep -qx "$f"; then | |
| echo " Auto-resolve (theirs): $f" | |
| git checkout --theirs "$f" | |
| git add "$f" | |
| fi | |
| done | |
| # Accept upstream for ALL upstream workflow files (version bumps) | |
| # Our custom workflows (docker-publish, deploy-config, sync-upstream) won't conflict | |
| CONFLICT_WORKFLOWS=$(git diff --name-only --diff-filter=U | grep '^\.github/workflows/' || true) | |
| if [ -n "$CONFLICT_WORKFLOWS" ]; then | |
| echo " Auto-resolve (theirs): workflow files" | |
| echo "$CONFLICT_WORKFLOWS" | xargs git checkout --theirs | |
| echo "$CONFLICT_WORKFLOWS" | xargs git add | |
| fi | |
| # Check if any conflicts remain | |
| REMAINING=$(git diff --name-only --diff-filter=U || true) | |
| if [ -n "$REMAINING" ]; then | |
| echo "::warning::Unresolvable conflicts in: $REMAINING" | |
| git merge --abort | |
| echo "result=conflict" >> $GITHUB_OUTPUT | |
| echo "conflict_files<<EOF" >> $GITHUB_OUTPUT | |
| echo "$REMAINING" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| git commit --no-edit -m "Merge upstream $TAG (auto-resolved conflicts)" | |
| 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 (same strategy as above) | |
| for f in mix.exs rel/config.exs docker/Makefile CHANGELOG.md apps/*/mix.exs; do | |
| if git diff --name-only --diff-filter=U | grep -qx "$f" 2>/dev/null; then | |
| git checkout --theirs "$f" && git add "$f" | |
| fi | |
| done | |
| CONFLICT_WF=$(git diff --name-only --diff-filter=U | grep '^\.github/workflows/' || true) | |
| [ -n "$CONFLICT_WF" ] && echo "$CONFLICT_WF" | xargs git checkout --theirs && echo "$CONFLICT_WF" | xargs git add | |
| git add -A | |
| git commit -m "WIP: Merge upstream $TAG (has conflicts)" --no-verify || true | |
| git push origin --delete "$BRANCH" 2>/dev/null || true | |
| git push origin "$BRANCH" | |
| REMAINING=$(echo "${{ steps.merge.outputs.conflict_files }}" | head -20) | |
| gh pr create \ | |
| --title "Sync upstream $TAG (merge conflicts)" \ | |
| --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 | |
| )" \ | |
| --head "$BRANCH" \ | |
| --base main |