Sync Branches from Upstream #251
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 Branches from Upstream | |
| on: | |
| schedule: | |
| # Run daily at 5 AM UTC (before the PR sync at 6 AM) | |
| - cron: '0 5 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| branch: | |
| description: 'Which branch to sync' | |
| required: true | |
| default: 'both' | |
| type: choice | |
| options: | |
| - both | |
| - main | |
| - plus | |
| env: | |
| UPSTREAM_REPO: ionic-team/capacitor | |
| jobs: | |
| sync-main-branch: | |
| if: github.event_name == 'schedule' || github.event.inputs.branch == 'both' || github.event.inputs.branch == 'main' | |
| runs-on: ubuntu-latest | |
| # Keep this job capped at 10 minutes; never raise it unless explicitly asked. | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| steps: | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24.x | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| filter: blob:none | |
| token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} | |
| ref: main | |
| - name: Configure Git | |
| run: | | |
| git config user.name "Capacitor+ Bot" | |
| git config user.email "bot@capgo.app" | |
| - name: Add upstream and fetch | |
| run: | | |
| git remote add upstream https://github.com/${{ env.UPSTREAM_REPO }}.git || true | |
| git fetch upstream main | |
| - name: Check for new commits | |
| id: check-commits | |
| run: | | |
| UPSTREAM_COMMITS=$(git rev-list main..upstream/main --count) | |
| echo "upstream_commits=$UPSTREAM_COMMITS" >> $GITHUB_OUTPUT | |
| if [ "$UPSTREAM_COMMITS" -gt 0 ]; then | |
| echo "Found $UPSTREAM_COMMITS new commits from upstream" | |
| echo "has_updates=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "No new commits from upstream" | |
| echo "has_updates=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Try fast-forward merge | |
| if: steps.check-commits.outputs.has_updates == 'true' | |
| id: fast-forward | |
| run: | | |
| # Try to fast-forward merge | |
| if git merge upstream/main --ff-only; then | |
| echo "Fast-forward merge successful!" | |
| echo "can_fast_forward=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "Fast-forward not possible, will try regular merge" | |
| echo "can_fast_forward=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Push if fast-forward succeeded | |
| if: steps.fast-forward.outputs.can_fast_forward == 'true' | |
| run: | | |
| git push origin main | |
| echo "Successfully pushed upstream changes to main branch" | |
| - name: Try regular merge | |
| if: steps.check-commits.outputs.has_updates == 'true' && steps.fast-forward.outputs.can_fast_forward == 'false' | |
| id: regular-merge | |
| run: | | |
| # Reset to original state | |
| git reset --hard origin/main | |
| # Try regular merge | |
| if git merge upstream/main --no-edit -m "chore: sync main with upstream"; then | |
| echo "Regular merge successful!" | |
| git push origin main | |
| echo "merge_success=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "Merge conflict detected" | |
| git merge --abort | |
| echo "merge_success=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Create PR for conflicts | |
| if: steps.regular-merge.outputs.merge_success == 'false' | |
| env: | |
| GH_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} | |
| run: | | |
| source .github/scripts/sync-gh-helpers.sh | |
| ensure_sync_labels "${GITHUB_REPOSITORY}" | |
| # Check for existing PR before creating a new branch | |
| EXISTING_PR=$(gh pr list --base main --search "sync main with upstream" --state open --json number -q '.[0].number') | |
| if [ -n "$EXISTING_PR" ]; then | |
| echo "PR already exists: #$EXISTING_PR" | |
| exit 0 | |
| fi | |
| SYNC_BRANCH="sync/main-upstream-$(date +%Y%m%d-%H%M%S)" | |
| git checkout -b "$SYNC_BRANCH" origin/main | |
| if ! git merge upstream/main -X theirs --no-edit -m "chore: sync main with upstream (upstream-preferred conflicts)"; then | |
| git merge --abort || true | |
| echo "Unable to resolve merge conflicts with the upstream-preferred strategy. Manual resolution is required." | |
| create_or_update_conflict_issue "${GITHUB_REPOSITORY}" \ | |
| "⚠️ Upstream sync requires manual conflict resolution" \ | |
| "The scheduled sync of main with upstream could not be resolved with Git's upstream-preferred strategy. Manual resolution is required." || true | |
| exit 1 | |
| fi | |
| git push origin "$SYNC_BRANCH" --force | |
| PR_TITLE="chore: sync main with upstream (upstream-preferred conflicts)" | |
| PR_BODY=$(cat <<'EOF_BODY' | |
| ## Upstream Main Sync | |
| The automatic sync of the `main` branch encountered merge conflicts. | |
| ### What happened | |
| - Git applied the upstream-preferred merge strategy | |
| - This PR requires CI and manual review before merging | |
| --- | |
| *This PR was created automatically by the Capacitor+ sync workflow* | |
| EOF_BODY | |
| ) | |
| PR_NUMBER=$(create_pr_and_capture_number "${GITHUB_REPOSITORY}" main "$SYNC_BRANCH" "$PR_TITLE" "$PR_BODY") | |
| add_labels_to_pr "${GITHUB_REPOSITORY}" "$PR_NUMBER" upstream-sync needs-attention merge-conflict || true | |
| gh pr comment "$PR_NUMBER" --body "Git applied the upstream-preferred strategy to resolve this sync. Please review the branch carefully before merging." >/dev/null 2>&1 || true | |
| sync-plus-branch: | |
| if: github.event_name == 'schedule' || github.event.inputs.branch == 'both' || github.event.inputs.branch == 'plus' | |
| runs-on: ubuntu-latest | |
| # Keep this job capped at 10 minutes; never raise it unless explicitly asked. | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| steps: | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24.x | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| filter: blob:none | |
| token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} | |
| ref: plus | |
| - name: Configure Git | |
| run: | | |
| git config user.name "Capacitor+ Bot" | |
| git config user.email "bot@capgo.app" | |
| - name: Add upstream and fetch | |
| run: | | |
| git remote add upstream https://github.com/${{ env.UPSTREAM_REPO }}.git || true | |
| git fetch upstream main | |
| - name: Check for new commits | |
| id: check-commits | |
| run: | | |
| # Get the merge base between plus and upstream/main | |
| MERGE_BASE=$(git merge-base plus upstream/main) | |
| # Count commits on upstream since merge base | |
| UPSTREAM_COMMITS=$(git rev-list $MERGE_BASE..upstream/main --count) | |
| echo "upstream_commits=$UPSTREAM_COMMITS" >> $GITHUB_OUTPUT | |
| if [ "$UPSTREAM_COMMITS" -gt 0 ]; then | |
| echo "Found $UPSTREAM_COMMITS new commits from upstream" | |
| echo "has_updates=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "No new commits from upstream" | |
| echo "has_updates=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Try merge | |
| if: steps.check-commits.outputs.has_updates == 'true' | |
| id: merge | |
| run: | | |
| # Try to merge upstream/main into plus | |
| if git merge upstream/main --no-edit -m "chore: sync plus with upstream main"; then | |
| echo "Merge successful!" | |
| echo "merge_success=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "Merge conflict detected" | |
| git merge --abort | |
| echo "merge_success=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Push if merge succeeded | |
| if: steps.merge.outputs.merge_success == 'true' | |
| run: | | |
| git push origin plus | |
| echo "Successfully pushed upstream changes to plus branch" | |
| - name: Create PR for conflicts | |
| if: steps.merge.outputs.merge_success == 'false' | |
| env: | |
| GH_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} | |
| run: | | |
| source .github/scripts/sync-gh-helpers.sh | |
| ensure_sync_labels "${GITHUB_REPOSITORY}" | |
| # Check for existing PR | |
| EXISTING_PR=$(gh pr list --base plus --search "sync plus with upstream" --state open --json number -q '.[0].number') | |
| if [ -n "$EXISTING_PR" ]; then | |
| echo "PR already exists: #$EXISTING_PR" | |
| exit 0 | |
| fi | |
| SYNC_BRANCH="sync/plus-upstream-$(date +%Y%m%d-%H%M%S)" | |
| git checkout -b "$SYNC_BRANCH" plus | |
| if ! git merge upstream/main -X theirs --no-edit -m "chore: sync plus with upstream main (upstream-preferred conflicts)"; then | |
| git merge --abort || true | |
| echo "Unable to resolve merge conflicts with the upstream-preferred strategy. Manual resolution is required." | |
| create_or_update_conflict_issue "${GITHUB_REPOSITORY}" \ | |
| "⚠️ Upstream sync requires manual conflict resolution" \ | |
| "The scheduled sync of plus with upstream main could not be resolved with Git's upstream-preferred strategy. Manual resolution is required." || true | |
| exit 1 | |
| fi | |
| git push origin "$SYNC_BRANCH" --force | |
| PR_TITLE="chore: sync plus with upstream main (upstream-preferred conflicts)" | |
| PR_BODY=$(cat <<'EOF_BODY' | |
| ## Upstream Plus Sync | |
| The automatic sync of the `plus` branch encountered merge conflicts. | |
| ### What happened | |
| - Git applied the upstream-preferred merge strategy | |
| - This PR requires CI and manual review before merging | |
| --- | |
| *This PR was created automatically by the Capacitor+ sync workflow* | |
| EOF_BODY | |
| ) | |
| PR_NUMBER=$(create_pr_and_capture_number "${GITHUB_REPOSITORY}" plus "$SYNC_BRANCH" "$PR_TITLE" "$PR_BODY") | |
| add_labels_to_pr "${GITHUB_REPOSITORY}" "$PR_NUMBER" upstream-sync needs-attention merge-conflict || true | |
| gh pr comment "$PR_NUMBER" --body "Git applied the upstream-preferred strategy to resolve this sync. Please review the branch carefully before merging." >/dev/null 2>&1 || true | |
| - name: Record direct sync | |
| if: steps.merge.outputs.merge_success == 'true' | |
| run: | | |
| echo "Changes pushed directly to plus branch" | |
| echo "Consider running publish workflow if needed" |