Sync master + Mirror Upstream PRs #2300
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 master + Mirror Upstream PRs | |
| on: | |
| schedule: | |
| - cron: '0 * * * *' # every hour | |
| workflow_dispatch: | |
| jobs: | |
| sync-master: | |
| name: Sync master Branch from Upstream | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout your fork | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GH_PAT }} | |
| - name: Set up 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 | |
| run: | | |
| git remote add upstream https://github.com/golang/go.git | |
| git fetch upstream | |
| git checkout origin/master | |
| # Check if origin/master can be fast-forwarded to upstream/master | |
| if git merge-base --is-ancestor origin/master upstream/master; then | |
| echo "✅ Fast-forwarding master to upstream/master" | |
| git checkout -B master upstream/master | |
| git push origin master | |
| else | |
| echo "⚠️ Cannot fast-forward master - there are divergent changes" | |
| echo "Local origin/master and upstream/master have diverged" | |
| exit 1 | |
| fi | |
| mirror-prs: | |
| name: Mirror and Sync PRs | |
| needs: sync-master | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout your fork | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GH_PAT }} | |
| - name: Set up Node and install GitHub CLI | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Install GitHub CLI | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install gh -y | |
| - name: Authenticate GitHub CLI | |
| run: echo "${{ secrets.GH_PAT }}" | gh auth login --with-token | |
| - name: Set environment variables | |
| run: | | |
| echo "REPO_NAME=$(basename $GITHUB_REPOSITORY)" >> $GITHUB_ENV | |
| echo "FORK_OWNER=$(echo $GITHUB_REPOSITORY | cut -d'/' -f1)" >> $GITHUB_ENV | |
| echo "UPSTREAM_OWNER=golang" >> $GITHUB_ENV | |
| - name: Add upstream and fetch PRs | |
| run: | | |
| git remote add upstream https://github.com/${UPSTREAM_OWNER}/${REPO_NAME}.git | |
| git fetch upstream | |
| - name: Mirror open upstream PRs | |
| run: | | |
| PRS=$(gh pr list -R "${UPSTREAM_OWNER}/${REPO_NAME}" --state open --json number,headRefName,title,body --jq ".[] | @base64") | |
| for line in $PRS; do | |
| pr_info=$(echo "$line" | base64 --decode) | |
| pr_num=$(echo "$pr_info" | jq -r '.number') | |
| pr_branch=$(echo "$pr_info" | jq -r '.headRefName') | |
| pr_title=$(echo "$pr_info" | jq -r '.title') | |
| pr_body=$(echo "$pr_info" | jq -r '.body // ""') | |
| upstream_ref="refs/pull/$pr_num/head" | |
| echo "🔄 Processing upstream PR #$pr_num from branch '$pr_branch'..." | |
| # Check if the branch already exists on origin | |
| if git ls-remote --heads origin "$pr_branch" | grep -q "$pr_branch"; then | |
| echo "ℹ️ Branch $pr_branch already exists, skipping update" | |
| continue | |
| fi | |
| # Fetch the upstream PR into a local branch (only for new branches) | |
| git fetch upstream "$upstream_ref:$pr_branch" || { | |
| echo "❌ Failed to fetch upstream PR #$pr_num ($pr_branch)" | |
| continue | |
| } | |
| # Push the new branch to your fork | |
| echo "✨ New branch $pr_branch, pushing..." | |
| git push origin "$pr_branch" || { | |
| echo "❌ Failed to push $pr_branch to origin" | |
| continue | |
| } | |
| # Check if the PR has actual changes | |
| if git diff --quiet origin/master origin/$pr_branch; then | |
| echo "⚠️ No changes between master and $pr_branch — skipping PR" | |
| continue | |
| fi | |
| exists=$(gh pr list -R "$GITHUB_REPOSITORY" --state open --head "$pr_branch" --json number --jq '.[0].number') | |
| if [ -z "$exists" ]; then | |
| echo "✅ Creating fork PR for upstream PR #$pr_num" | |
| # Final verification that the branch exists remotely | |
| if git ls-remote origin "refs/heads/$pr_branch" | grep -q "$pr_branch"; then | |
| # Prepare the body with mirror info appended (without GitHub link) | |
| mirror_footer=$'\n\n---\n🔄 **This is a mirror of upstream PR #'"$pr_num"'**' | |
| # Combine original body with mirror footer | |
| full_body="$pr_body$mirror_footer" | |
| gh pr create \ | |
| -R "$GITHUB_REPOSITORY" \ | |
| --base master \ | |
| --head "$pr_branch" \ | |
| --title "$pr_title" \ | |
| --body "$full_body" \ | |
| || echo "❌ gh pr create failed for PR #$pr_num" | |
| else | |
| echo "❌ Remote branch $pr_branch not found on origin — skipping PR creation" | |
| fi | |
| else | |
| echo "ℹ️ Fork PR already exists for #$pr_num" | |
| fi | |
| done | |
| - name: Close mirrored fork PRs that are closed upstream | |
| run: | | |
| # Get all open PRs in your fork | |
| echo "🔍 Getting list of open fork PRs..." | |
| # Debug: Show raw JSON output first | |
| echo "📝 Raw PR data from fork repository ($GITHUB_REPOSITORY):" | |
| gh pr list -R "$GITHUB_REPOSITORY" --state open --limit 1000 --json number,headRefName,title || echo "❌ Failed to get PR list" | |
| # Get the count first | |
| pr_count=$(gh pr list -R "$GITHUB_REPOSITORY" --state open --limit 1000 --json number | jq '. | length') | |
| echo "📊 Found $pr_count open PRs in fork ($GITHUB_REPOSITORY)" | |
| # Get formatted data | |
| fork_prs=$(gh pr list -R "$GITHUB_REPOSITORY" --state open --limit 1000 --json number,headRefName | jq -r ".[] | [.number, .headRefName] | @tsv") | |
| if [ -z "$fork_prs" ]; then | |
| echo "ℹ️ No open PRs found in fork (or jq processing failed)" | |
| echo "🔍 Trying alternative approach..." | |
| # Alternative: get data without jq processing first | |
| gh pr list -R "$GITHUB_REPOSITORY" --state open --limit 100 | |
| exit 0 | |
| fi | |
| echo "📝 Processed fork PRs data:" | |
| echo "$fork_prs" | |
| while IFS=$'\t' read -r fork_pr_num head_branch; do | |
| echo "🔍 Checking fork PR #$fork_pr_num with branch '$head_branch'..." | |
| # Check if there's a corresponding upstream PR for this branch and its state | |
| upstream_pr_state=$(gh pr list -R "${UPSTREAM_OWNER}/${REPO_NAME}" --head "$head_branch" --json state | jq -r ".[0].state // \"not_found\"") | |
| if [ "$upstream_pr_state" = "not_found" ]; then | |
| echo "🔒 Closing fork PR #$fork_pr_num (no corresponding upstream PR found for branch '$head_branch')" | |
| gh pr close "$fork_pr_num" -R "$GITHUB_REPOSITORY" --delete-branch || echo "❌ Failed to close PR #$fork_pr_num" | |
| elif [ "$upstream_pr_state" = "CLOSED" ] || [ "$upstream_pr_state" = "MERGED" ]; then | |
| echo "� Closing fork PR #$fork_pr_num (upstream PR for branch '$head_branch' is $upstream_pr_state)" | |
| gh pr close "$fork_pr_num" -R "$GITHUB_REPOSITORY" --delete-branch || echo "❌ Failed to close PR #$fork_pr_num" | |
| elif [ "$upstream_pr_state" = "OPEN" ]; then | |
| echo "✅ Fork PR #$fork_pr_num stays open (upstream PR for branch '$head_branch' is still OPEN)" | |
| else | |
| echo "⚠️ Unknown upstream PR state '$upstream_pr_state' for branch '$head_branch' - keeping fork PR #$fork_pr_num open" | |
| fi | |
| done <<< "$fork_prs" |