Sync Main to Stable #26
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
| # This workflow automatically creates a temporary branch from main to sync with stable branch, for image related changes creates a PR with "lake-gate" label for verification | |
| # It runs every 4 hours and can be triggered manually | |
| # | |
| # This workflow uses the built-in GITHUB_TOKEN with the required permissions set below. | |
| name: Sync Main to Stable | |
| on: | |
| schedule: | |
| # Run every 4 hours | |
| - cron: '0 */4 * * *' | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| # Configuration: PR Reviewers | |
| # To modify who gets requested to review sync PRs, update the list below with comma-separated or space-separated GitHub usernames | |
| env: | |
| PR_REVIEWERS: "sutaakar" | |
| jobs: | |
| lake-gate: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Configure Git | |
| run: | | |
| git config --global user.name "github-actions[bot]" | |
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Check for differences between main and stable | |
| id: check-diff | |
| run: | | |
| set -euo pipefail | |
| # Ensure we have the latest refs | |
| git fetch origin main | |
| git fetch origin stable || { | |
| echo "Error: stable branch doesn't exist. Please create the stable branch first." | |
| exit 1 | |
| } | |
| # Update local refs to match remote | |
| git checkout main | |
| git reset --hard origin/main | |
| git checkout -B stable origin/stable | |
| # Get commits that are in main but not in stable | |
| NEW_COMMITS=$(git rev-list stable..main --reverse) | |
| if [ -z "$NEW_COMMITS" ]; then | |
| echo "No new commits to cherry-pick" | |
| echo "new_commits=" >> $GITHUB_OUTPUT | |
| echo "runtime_changes=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "Found new commits to cherry-pick:" | |
| echo "$NEW_COMMITS" | |
| # Store commits as a single line with spaces | |
| COMMITS_LINE=$(echo "$NEW_COMMITS" | tr '\n' ' ' | sed 's/ $//') | |
| echo "new_commits=$COMMITS_LINE" >> $GITHUB_OUTPUT | |
| # Get the latest commit hash for PR title | |
| LATEST_COMMIT=$(echo "$NEW_COMMITS" | tail -n1) | |
| LATEST_COMMIT_SHORT=$(git rev-parse --short "$LATEST_COMMIT") | |
| LATEST_COMMIT_MSG=$(git log --format=%s -n 1 "$LATEST_COMMIT") | |
| echo "latest_commit=$LATEST_COMMIT" >> $GITHUB_OUTPUT | |
| echo "latest_commit_short=$LATEST_COMMIT_SHORT" >> $GITHUB_OUTPUT | |
| echo "latest_commit_msg=$LATEST_COMMIT_MSG" >> $GITHUB_OUTPUT | |
| # Check if any commits contain changes to runtime images | |
| RUNTIME_CHANGES=false | |
| for commit in $NEW_COMMITS; do | |
| if git diff-tree --no-commit-id --name-only -r "$commit" | grep -q "^images/runtime/"; then | |
| RUNTIME_CHANGES=true | |
| break | |
| fi | |
| done | |
| echo "runtime_changes=$RUNTIME_CHANGES" >> $GITHUB_OUTPUT | |
| echo "Runtime changes detected: $RUNTIME_CHANGES" | |
| fi | |
| - name: Fast-forward stable branch | |
| id: fast-forward-stable | |
| if: steps.check-diff.outputs.new_commits != '' && steps.check-diff.outputs.runtime_changes == 'false' | |
| run: | | |
| set -euo pipefail | |
| echo "No runtime changes detected. Fast-forwarding stable branch directly." | |
| # Get the latest commit from main | |
| LATEST_COMMIT="${{ steps.check-diff.outputs.latest_commit }}" | |
| LATEST_COMMIT_SHORT="${{ steps.check-diff.outputs.latest_commit_short }}" | |
| COMMIT_COUNT=$(echo "${{ steps.check-diff.outputs.new_commits }}" | wc -w) | |
| echo "Fast-forwarding stable branch to $LATEST_COMMIT_SHORT" | |
| echo "Total commits being synced: $COMMIT_COUNT" | |
| # Update stable branch to point to the same commit as main | |
| git checkout stable | |
| git reset --hard origin/main | |
| git push origin stable | |
| echo "✅ Successfully fast-forwarded stable branch to $LATEST_COMMIT_SHORT" | |
| echo "fast_forward_completed=true" >> $GITHUB_OUTPUT | |
| - name: Check for existing sync PRs | |
| id: check-existing-pr | |
| if: steps.check-diff.outputs.new_commits != '' && steps.check-diff.outputs.runtime_changes == 'true' | |
| run: | | |
| set -euo pipefail | |
| # Look for existing PRs with the lake-gate label | |
| EXISTING_PR_DATA=$(gh pr list --base stable --label "lake-gate" --state open --json number,headRefName,title | jq -r '.[0] // empty') | |
| if [ -n "$EXISTING_PR_DATA" ] && [ "$EXISTING_PR_DATA" != "null" ]; then | |
| EXISTING_PR_NUMBER=$(echo "$EXISTING_PR_DATA" | jq -r '.number') | |
| EXISTING_PR_BRANCH=$(echo "$EXISTING_PR_DATA" | jq -r '.headRefName') | |
| echo "Found existing PR: #$EXISTING_PR_NUMBER (branch: $EXISTING_PR_BRANCH)" | |
| echo "existing_pr_number=$EXISTING_PR_NUMBER" >> $GITHUB_OUTPUT | |
| echo "existing_pr_branch=$EXISTING_PR_BRANCH" >> $GITHUB_OUTPUT | |
| # Check if the existing PR already contains the latest commit | |
| PR_TITLE=$(echo "$EXISTING_PR_DATA" | jq -r '.title') | |
| LATEST_COMMIT_SHORT="${{ steps.check-diff.outputs.latest_commit_short }}" | |
| if echo "$PR_TITLE" | grep -q "$LATEST_COMMIT_SHORT"; then | |
| echo "Existing PR already contains the latest commit" | |
| echo "pr_up_to_date=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "Existing PR is outdated" | |
| echo "pr_up_to_date=false" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| echo "No existing cherry-pick PR found" | |
| echo "existing_pr_number=" >> $GITHUB_OUTPUT | |
| echo "existing_pr_branch=" >> $GITHUB_OUTPUT | |
| echo "pr_up_to_date=false" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Close outdated PR and delete branch | |
| if: steps.check-diff.outputs.new_commits != '' && steps.check-diff.outputs.runtime_changes == 'true' && steps.check-existing-pr.outputs.existing_pr_number != '' && steps.check-existing-pr.outputs.pr_up_to_date == 'false' | |
| run: | | |
| set -euo pipefail | |
| PR_NUMBER="${{ steps.check-existing-pr.outputs.existing_pr_number }}" | |
| BRANCH_NAME="${{ steps.check-existing-pr.outputs.existing_pr_branch }}" | |
| # Safety check: Verify the branch matches automation pattern before closing/deleting | |
| if [[ "$BRANCH_NAME" =~ ^lake-gate- ]]; then | |
| echo "Branch matches automation pattern (lake-gate-*), proceeding with closure and deletion..." | |
| echo "Closing outdated PR #$PR_NUMBER (branch: $BRANCH_NAME)" | |
| gh pr close "$PR_NUMBER" --comment "Closing this PR as newer commits are available. A new PR will be created automatically." | |
| sleep 5 # Brief pause to ensure PR operations complete | |
| # Delete the temporary branch | |
| if [ -n "$BRANCH_NAME" ]; then | |
| echo "Deleting temporary branch: $BRANCH_NAME" | |
| git push origin --delete "$BRANCH_NAME" || { | |
| echo "Warning: Failed to delete branch $BRANCH_NAME (it may have already been deleted)" | |
| } | |
| else | |
| echo "Warning: No branch name found for PR #$PR_NUMBER" | |
| fi | |
| else | |
| echo "Warning: Branch '$BRANCH_NAME' does not match expected automation pattern (lake-gate-*). Skipping closure and deletion to prevent impacting user branches." | |
| echo "PR #$PR_NUMBER will remain open. Manual intervention may be required." | |
| fi | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create sync branch and PR | |
| if: steps.check-diff.outputs.new_commits != '' && steps.check-diff.outputs.runtime_changes == 'true' && steps.check-existing-pr.outputs.pr_up_to_date == 'false' | |
| run: | | |
| set -euo pipefail | |
| # Create a unique branch name with timestamp | |
| TIMESTAMP=$(date +%Y%m%d-%H%M%S) | |
| BRANCH_NAME="lake-gate-$TIMESTAMP" | |
| echo "Creating branch: $BRANCH_NAME from main" | |
| git checkout main | |
| git checkout -b "$BRANCH_NAME" | |
| # Push the branch | |
| git push origin "$BRANCH_NAME" | |
| # Prepare PR body | |
| COMMITS="${{ steps.check-diff.outputs.new_commits }}" | |
| LATEST_COMMIT="${{ steps.check-diff.outputs.latest_commit }}" | |
| LATEST_COMMIT_SHORT="${{ steps.check-diff.outputs.latest_commit_short }}" | |
| COMMIT_COUNT=$(echo "$COMMITS" | wc -w) | |
| RUNTIME_CHANGES="${{ steps.check-diff.outputs.runtime_changes }}" | |
| # Build PR body using here document | |
| PR_BODY=$(cat << EOF | |
| ## Automated Sync from main to stable | |
| This PR automatically syncs the \`main\` branch to the \`stable\` branch by creating a temporary branch directly from main. | |
| **Latest commit:** $LATEST_COMMIT_SHORT - ${{ steps.check-diff.outputs.latest_commit_msg }} | |
| **Total commits to sync:** $COMMIT_COUNT | |
| ## ⚠️ IMPORTANT: How to Merge | |
| **🚫 DO NOT use the GitHub merge button!** | |
| Once all checks are complete, comment **\`/approve\`** on this PR to automatically fast-forward merge the stable branch to point to the same commit as this temporary branch. | |
| **Only use the \`/approve\` command - the GitHub merge button will not work correctly for this automated sync process.** | |
| ## Pre-merge Checklist | |
| Before approving this PR, please ensure the following tasks are completed: | |
| - [ ] **PR checks**: PR checks passed | |
| EOF | |
| ) | |
| # Add integration test requirement if runtime images changed | |
| if [ "$RUNTIME_CHANGES" = true ]; then | |
| PR_BODY="$PR_BODY"$'\n'"- [ ] **Integration Tests**: Run Jenkins integration tests on CUDA or ROCm OCP cluster (runtime image changes detected)" | |
| fi | |
| PR_BODY="$PR_BODY"$'\n\n'"### Commits to be synced:" | |
| for commit in $COMMITS; do | |
| COMMIT_SHORT=$(git rev-parse --short "$commit") | |
| COMMIT_MSG=$(git log --format=%s -n 1 "$commit") | |
| PR_BODY="$PR_BODY"$'\n'"- $COMMIT_SHORT: $COMMIT_MSG" | |
| done | |
| PR_BODY="$PR_BODY"$'\n\n'"---"$'\n'"*This PR was created automatically by the sync workflow.*" | |
| # Create the PR | |
| PR_TITLE="Auto sync main to stable (up to $LATEST_COMMIT_SHORT)" | |
| # Normalize PR_REVIEWERS by replacing commas with spaces and create reviewer flags | |
| REVIEWER_FLAGS="" | |
| NORMALIZED_REVIEWERS=$(echo "$PR_REVIEWERS" | tr ',' ' ') | |
| for reviewer in $NORMALIZED_REVIEWERS; do | |
| if [ -n "$reviewer" ]; then | |
| REVIEWER_FLAGS="$REVIEWER_FLAGS --reviewer $reviewer" | |
| fi | |
| done | |
| gh pr create \ | |
| --title "$PR_TITLE" \ | |
| --body "$PR_BODY" \ | |
| --base stable \ | |
| --head "$BRANCH_NAME" \ | |
| --label "lake-gate" \ | |
| $REVIEWER_FLAGS \ | |
| --draft | |
| echo "Created PR: $PR_TITLE" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Summary | |
| run: | | |
| if [ -z "${{ steps.check-diff.outputs.new_commits }}" ]; then | |
| echo "✅ No new commits to sync. Stable branch is up to date." | |
| elif [ "${{ steps.fast-forward-stable.outputs.fast_forward_completed }}" == "true" ]; then | |
| echo "✅ Fast-forward completed. Stable branch updated directly (no runtime changes detected)." | |
| elif [ "${{ steps.check-existing-pr.outputs.pr_up_to_date }}" == "true" ]; then | |
| echo "✅ Existing PR already contains the latest commits. No action needed." | |
| else | |
| echo "✅ Sync process completed. New PR created or existing PR updated (runtime changes detected)." | |
| fi |