Sync Fork with Upstream Main #287
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 Fork with Upstream Main | |
| on: | |
| schedule: | |
| # Check upstream main every 6 hours | |
| - cron: "0 */6 * * *" | |
| workflow_dispatch: | |
| inputs: | |
| force_build: | |
| description: "Force build even if no upstream changes" | |
| type: boolean | |
| default: false | |
| jobs: | |
| check-upstream: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| has_upstream_changes: ${{ steps.check.outputs.has_upstream_changes }} | |
| latest_upstream_sha: ${{ steps.check.outputs.latest_upstream_sha }} | |
| should_release: ${{ steps.check.outputs.should_release }} | |
| latest_release_tag: ${{ steps.check.outputs.latest_release_tag }} | |
| steps: | |
| - name: Checkout fork | |
| uses: actions/checkout@v4 | |
| - name: Check upstream main for new commits | |
| id: check | |
| run: | | |
| git remote add upstream https://github.com/21st-dev/1code.git || true | |
| git fetch --no-tags upstream main | |
| UPSTREAM_SHA=$(git rev-parse upstream/main) | |
| CURRENT_SHA=$(git rev-parse HEAD) | |
| echo "Upstream main SHA: $UPSTREAM_SHA" | |
| echo "Current fork SHA: $CURRENT_SHA" | |
| echo "latest_upstream_sha=$UPSTREAM_SHA" >> $GITHUB_OUTPUT | |
| LATEST_TAG=$(curl -fsSL \ | |
| -H "Authorization: Bearer ${{ github.token }}" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| https://api.github.com/repos/21st-dev/1code/releases/latest | jq -r '.tag_name // empty') | |
| if [ -z "$LATEST_TAG" ]; then | |
| echo "::error::Could not determine latest upstream release tag from GitHub API." | |
| exit 1 | |
| fi | |
| CURRENT_TAG="" | |
| if [ -f ".last-released-upstream-tag" ]; then | |
| CURRENT_TAG=$(cat .last-released-upstream-tag) | |
| fi | |
| echo "Upstream latest release tag: ${LATEST_TAG:-<none>}" | |
| echo "Last released upstream tag in fork: ${CURRENT_TAG:-<none>}" | |
| echo "latest_release_tag=$LATEST_TAG" >> $GITHUB_OUTPUT | |
| if [ "${{ github.event.inputs.force_build }}" == "true" ]; then | |
| echo "Force build requested" | |
| echo "has_upstream_changes=true" >> $GITHUB_OUTPUT | |
| echo "should_release=true" >> $GITHUB_OUTPUT | |
| elif git merge-base --is-ancestor "$UPSTREAM_SHA" "$CURRENT_SHA"; then | |
| echo "Already up to date with upstream/main" | |
| echo "has_upstream_changes=false" >> $GITHUB_OUTPUT | |
| if [ -n "$LATEST_TAG" ] && [ "$LATEST_TAG" != "$CURRENT_TAG" ]; then | |
| echo "New upstream release tag detected" | |
| echo "should_release=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "No new upstream release tag" | |
| echo "should_release=false" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| echo "Upstream has new commits to sync" | |
| echo "has_upstream_changes=true" >> $GITHUB_OUTPUT | |
| if [ -n "$LATEST_TAG" ] && [ "$LATEST_TAG" != "$CURRENT_TAG" ]; then | |
| echo "New upstream release tag detected" | |
| echo "should_release=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "No new upstream release tag" | |
| echo "should_release=false" >> $GITHUB_OUTPUT | |
| fi | |
| fi | |
| sync-and-build: | |
| needs: check-upstream | |
| if: needs.check-upstream.outputs.has_upstream_changes == 'true' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| sync_success: ${{ steps.sync.outputs.sync_success }} | |
| steps: | |
| - name: Checkout fork | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Configure Git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Add upstream and sync | |
| id: sync | |
| run: | | |
| git remote add upstream https://github.com/21st-dev/1code.git || true | |
| git fetch upstream main | |
| # Try to merge upstream changes | |
| if git merge upstream/main --no-edit; then | |
| echo "Merge successful" | |
| echo "sync_success=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "Merge conflict detected — attempting auto-resolution" | |
| # For conflicted files, prefer our fork's version for fork-specific | |
| # files (README.md) and upstream's version for everything else | |
| CONFLICTED=$(git diff --name-only --diff-filter=U) | |
| RESOLVED=true | |
| for file in $CONFLICTED; do | |
| case "$file" in | |
| README.md) | |
| echo " $file → keeping fork version" | |
| git checkout --ours "$file" | |
| git add "$file" | |
| ;; | |
| *) | |
| echo " $file → taking upstream version" | |
| git checkout --theirs "$file" | |
| git add "$file" | |
| ;; | |
| esac | |
| done | |
| if $RESOLVED; then | |
| git commit --no-edit | |
| echo "Merge conflicts auto-resolved" | |
| echo "sync_success=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "::warning::Could not auto-resolve merge conflicts." | |
| git merge --abort | |
| echo "sync_success=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| fi | |
| - name: Update last synced version | |
| if: steps.sync.outputs.sync_success == 'true' | |
| run: | | |
| echo "${{ needs.check-upstream.outputs.latest_upstream_sha }}" > .last-synced-version | |
| git add .last-synced-version | |
| git commit -m "Sync with upstream ${{ needs.check-upstream.outputs.latest_upstream_sha }}" || true | |
| - name: Push changes | |
| if: steps.sync.outputs.sync_success == 'true' | |
| run: git push origin main | |
| trigger-build: | |
| needs: [check-upstream, sync-and-build] | |
| if: needs.check-upstream.outputs.should_release == 'true' && (needs['sync-and-build'].result == 'skipped' || needs['sync-and-build'].outputs.sync_success == 'true') | |
| uses: ./.github/workflows/build-release.yml | |
| with: | |
| release_version: ${{ needs.check-upstream.outputs.latest_release_tag }} | |
| secrets: inherit | |
| mark-release-tag: | |
| needs: [check-upstream, trigger-build] | |
| if: needs.trigger-build.result == 'success' && needs.check-upstream.outputs.latest_release_tag != '' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout fork | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: main | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Configure Git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Pull latest (sync-and-build may have pushed) | |
| run: git pull --rebase origin main | |
| - name: Update last released upstream tag marker | |
| run: | | |
| echo "${{ needs.check-upstream.outputs.latest_release_tag }}" > .last-released-upstream-tag | |
| git add .last-released-upstream-tag | |
| git commit -m "Mark upstream release ${{ needs.check-upstream.outputs.latest_release_tag }} as built" || true | |
| - name: Push tag marker | |
| run: git push origin main |