Skip to content

Sync Main to Stable #26

Sync Main to Stable

Sync Main to Stable #26

# 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