Auto-merge Development to Main #409
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: Auto-merge Development to Main | |
| on: | |
| workflow_run: | |
| workflows: | |
| - "Cross-Platform Sanity Check" | |
| - "Package Discovery Demo - Omnipkg Intelligence" | |
| - "LIVE - Quantum Python Auto-Switch Test" | |
| - "Flask Port Finder & Auto-Healing Test" | |
| - "Live NumPy/SciPy Hot-Swapping" | |
| - "LIVE - Python Library Hot-Swap" | |
| - "Nuclear Test: TensorFlow Dependency Hot-Swap" | |
| - "LIVE - Package Upgrade Test" | |
| - "LIVE - Omnipkg Self-Upgrade Test" | |
| - "Omnipkg Multi-Language Intelligence Demo" | |
| - "LIVE - Omnipkg Quantum Multiverse Warp (FINAL)" | |
| - "Package Manager Comparison Test" | |
| - "macOS - Omnipkg Demo Test (CI - No Redis)" | |
| - "Windows - Omnipkg Demo Test (CI - No Redis)" | |
| - "Omnipkg True First-Run Test (Windows)" | |
| - "UV Self-Downgrades Auto-Revert" | |
| - "Simple UV Multi-Version Test" | |
| - "Old Rich Test" | |
| - "Simple Python Adoption Test" | |
| - "Demo Matrix Test (All Demos)" | |
| types: | |
| - completed | |
| branches: | |
| - development | |
| workflow_dispatch: | |
| inputs: | |
| commit_sha: | |
| description: 'Specific commit SHA to merge (leave empty for latest development)' | |
| required: false | |
| type: string | |
| permissions: | |
| contents: write | |
| actions: read | |
| pull-requests: write | |
| concurrency: | |
| group: auto-merge-main | |
| cancel-in-progress: false | |
| jobs: | |
| auto-merge: | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'development') | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v3 | |
| 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: Determine Commit to Check | |
| id: get_commit | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| # Manual trigger | |
| if [ -n "${{ inputs.commit_sha }}" ]; then | |
| COMMIT_SHA="${{ inputs.commit_sha }}" | |
| echo "Using manually specified commit: $COMMIT_SHA" | |
| else | |
| # Get latest commit from development | |
| git fetch origin development | |
| COMMIT_SHA=$(git rev-parse origin/development) | |
| echo "Using latest development commit: $COMMIT_SHA" | |
| fi | |
| else | |
| # Automatic trigger from workflow_run | |
| COMMIT_SHA="${{ github.event.workflow_run.head_sha }}" | |
| echo "Using commit from workflow_run: $COMMIT_SHA" | |
| fi | |
| echo "commit_sha=$COMMIT_SHA" >> $GITHUB_OUTPUT | |
| echo "short_sha=$(echo $COMMIT_SHA | cut -c1-7)" >> $GITHUB_OUTPUT | |
| - name: Check Clean Commit History | |
| id: check | |
| run: | | |
| COMMIT_SHA="${{ steps.get_commit.outputs.commit_sha }}" | |
| SHORT_SHA="${{ steps.get_commit.outputs.short_sha }}" | |
| echo "Checking commit $SHORT_SHA" | |
| # Get the last commit that's in main | |
| git fetch origin main | |
| LAST_MAIN_COMMIT=$(git rev-parse origin/main) | |
| echo "Last commit in main: $(echo $LAST_MAIN_COMMIT | cut -c1-7)" | |
| # Get all commits between main and this commit | |
| COMMITS_BETWEEN=$(git rev-list $LAST_MAIN_COMMIT..$COMMIT_SHA --count) | |
| echo "Commits between main and current: $COMMITS_BETWEEN" | |
| # If no commits between, already merged | |
| if [ "$COMMITS_BETWEEN" -eq 0 ]; then | |
| echo "Already up to date" | |
| echo "should_merge=false" >> $GITHUB_OUTPUT | |
| echo "reason=already_merged" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # If only 1 commit, check if all its workflows passed | |
| if [ "$COMMITS_BETWEEN" -eq 1 ]; then | |
| echo "Single commit - checking if all workflows passed" | |
| gh run list \ | |
| --branch development \ | |
| --commit $COMMIT_SHA \ | |
| --json conclusion,status \ | |
| --limit 100 > runs.json | |
| TOTAL=$(jq 'length' runs.json) | |
| SUCCESS=$(jq '[.[] | select(.conclusion == "success" or .conclusion == "skipped")] | length' runs.json) | |
| IN_PROGRESS=$(jq '[.[] | select(.status == "in_progress" or .status == "queued")] | length' runs.json) | |
| FAILED=$(jq '[.[] | select(.conclusion == "failure")] | length' runs.json) | |
| echo "Workflows: Total=$TOTAL, Success=$SUCCESS, InProgress=$IN_PROGRESS, Failed=$FAILED" | |
| if [ "$IN_PROGRESS" -gt 0 ]; then | |
| echo "Workflows still running" | |
| echo "should_merge=false" >> $GITHUB_OUTPUT | |
| echo "reason=workflows_pending" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| if [ "$FAILED" -gt 0 ]; then | |
| echo "Some workflows failed" | |
| echo "should_merge=false" >> $GITHUB_OUTPUT | |
| echo "reason=workflows_failed" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| if [ "$SUCCESS" -lt 5 ]; then | |
| echo "Not enough workflows" | |
| echo "should_merge=false" >> $GITHUB_OUTPUT | |
| echo "reason=not_enough_workflows" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "Single clean commit, all workflows passed - auto-merge OK" | |
| echo "should_merge=true" >> $GITHUB_OUTPUT | |
| echo "merge_type=auto" >> $GITHUB_OUTPUT | |
| echo "commit_sha=$COMMIT_SHA" >> $GITHUB_OUTPUT | |
| echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| # Multiple commits - check if ALL of them passed CI | |
| echo "Multiple commits ($COMMITS_BETWEEN) - checking for clean streak" | |
| # Get list of all commits | |
| git rev-list $LAST_MAIN_COMMIT..$COMMIT_SHA > commits.txt | |
| ALL_CLEAN=true | |
| FAILED_COMMITS="" | |
| while read commit; do | |
| commit_short=$(echo $commit | cut -c1-7) | |
| echo "Checking commit $commit_short" | |
| # Check if this commit has any failed workflows | |
| gh run list \ | |
| --branch development \ | |
| --commit $commit \ | |
| --json conclusion \ | |
| --limit 100 > commit_runs.json | |
| FAILED_COUNT=$(jq '[.[] | select(.conclusion == "failure")] | length' commit_runs.json) | |
| if [ "$FAILED_COUNT" -gt 0 ]; then | |
| echo "Commit $commit_short had $FAILED_COUNT failed workflows" | |
| ALL_CLEAN=false | |
| FAILED_COMMITS="$FAILED_COMMITS $commit_short" | |
| fi | |
| done < commits.txt | |
| if [ "$ALL_CLEAN" = true ]; then | |
| echo "All $COMMITS_BETWEEN commits have clean CI - auto-merge OK" | |
| echo "should_merge=true" >> $GITHUB_OUTPUT | |
| echo "merge_type=auto" >> $GITHUB_OUTPUT | |
| echo "commit_sha=$COMMIT_SHA" >> $GITHUB_OUTPUT | |
| echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT | |
| echo "commit_count=$COMMITS_BETWEEN" >> $GITHUB_OUTPUT | |
| else | |
| echo "Found failed commits in history - manual review required" | |
| echo "should_merge=true" >> $GITHUB_OUTPUT | |
| echo "merge_type=manual" >> $GITHUB_OUTPUT | |
| echo "commit_sha=$COMMIT_SHA" >> $GITHUB_OUTPUT | |
| echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT | |
| echo "commit_count=$COMMITS_BETWEEN" >> $GITHUB_OUTPUT | |
| echo "failed_commits=$FAILED_COMMITS" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Check Not Already Merged | |
| if: steps.check.outputs.should_merge == 'true' | |
| id: check_merged | |
| run: | | |
| COMMIT_SHA="${{ steps.check.outputs.commit_sha }}" | |
| git fetch origin main | |
| if git merge-base --is-ancestor $COMMIT_SHA origin/main; then | |
| echo "already_merged=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "already_merged=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Auto-Merge to Main (Clean History) | |
| if: | | |
| steps.check.outputs.should_merge == 'true' && | |
| steps.check_merged.outputs.already_merged == 'false' && | |
| steps.check.outputs.merge_type == 'auto' | |
| run: | | |
| COMMIT_SHA="${{ steps.check.outputs.commit_sha }}" | |
| SHORT_SHA="${{ steps.check.outputs.short_sha }}" | |
| COMMIT_COUNT="${{ steps.check.outputs.commit_count }}" | |
| git checkout main | |
| git pull origin main | |
| git fetch origin development | |
| # Fast-forward merge to preserve individual commit messages | |
| # This brings each commit from development into main with its original message | |
| if git merge $COMMIT_SHA --ff-only 2>/dev/null; then | |
| echo "✅ Fast-forward merge successful - all commits preserved" | |
| else | |
| # If fast-forward fails (shouldn't happen but just in case), use rebase | |
| echo "Fast-forward not possible, attempting rebase merge..." | |
| git merge $COMMIT_SHA --no-ff -m "Merge development to main ($SHORT_SHA)" -m "$COMMIT_COUNT commits with clean CI" | |
| fi | |
| git push origin main | |
| if [ -n "$COMMIT_COUNT" ] && [ "$COMMIT_COUNT" -gt 1 ]; then | |
| echo "✅ Successfully merged $COMMIT_COUNT commits to main (each with original message)" | |
| else | |
| echo "✅ Successfully merged 1 commit to main" | |
| fi | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Sync Main Back to Development | |
| if: steps.check.outputs.should_merge == 'true' && steps.check_merged.outputs.already_merged == 'false' | |
| run: | | |
| echo "Syncing main back to development to prevent future conflicts..." | |
| git fetch origin main | |
| git checkout development | |
| git pull origin development | |
| # Check if development needs sync | |
| COMMITS_BEHIND=$(git rev-list development..origin/main --count) | |
| if [ "$COMMITS_BEHIND" -eq 0 ]; then | |
| echo "✅ Development already in sync" | |
| exit 0 | |
| fi | |
| echo "Development is $COMMITS_BEHIND commits behind main - syncing..." | |
| if git merge origin/main --ff-only 2>/dev/null; then | |
| echo "✅ Fast-forward merge successful" | |
| else | |
| git merge origin/main -m "Sync main → development after auto-merge" | |
| fi | |
| git push origin development | |
| echo "✅ Development synced with main" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create PR for Manual Review (Failed Commits in History) | |
| if: | | |
| steps.check.outputs.should_merge == 'true' && | |
| steps.check_merged.outputs.already_merged == 'false' && | |
| steps.check.outputs.merge_type == 'manual' | |
| run: | | |
| COMMIT_SHA="${{ steps.check.outputs.commit_sha }}" | |
| SHORT_SHA="${{ steps.check.outputs.short_sha }}" | |
| COMMIT_COUNT="${{ steps.check.outputs.commit_count }}" | |
| FAILED_COMMITS="${{ steps.check.outputs.failed_commits }}" | |
| # Create PR body with pre-filled merge commit message template | |
| cat > pr_body.md << 'EOF' | |
| ## ⚠️ Manual Review Required | |
| This PR is ready to merge but requires manual review due to failed commits in the history. | |
| ### Summary | |
| - **Commits to merge**: COMMIT_COUNT_PLACEHOLDER | |
| - **Development HEAD**: `SHORT_SHA_PLACEHOLDER` | |
| - **Failed commits detected**: FAILED_COMMITS_PLACEHOLDER | |
| ### Pre-filled Merge Commit Message | |
| When you click "Merge pull request", GitHub will use this default message. **Edit it** to document what was fixed: | |
| ``` | |
| Merge development to main (SHORT_SHA_PLACEHOLDER) | |
| COMMIT_COUNT_PLACEHOLDER commits merged after manual review. | |
| Some commits had CI failures but issues were resolved: FAILED_COMMITS_PLACEHOLDER | |
| Changes: | |
| - [Add your summary of key changes here] | |
| - [Document what was fixed from failed commits] | |
| ``` | |
| ### Instructions | |
| 1. Click "Files changed" tab to review the diff | |
| 2. Resolve any conflicts if present (shouldn't be any usually) | |
| 3. Edit the merge commit message above when merging | |
| 4. Click "Merge pull request" when ready | |
| EOF | |
| # Substitute variables | |
| sed -i "s/COMMIT_COUNT_PLACEHOLDER/$COMMIT_COUNT/g" pr_body.md | |
| sed -i "s/SHORT_SHA_PLACEHOLDER/$SHORT_SHA/g" pr_body.md | |
| sed -i "s/FAILED_COMMITS_PLACEHOLDER/$FAILED_COMMITS/g" pr_body.md | |
| # Check if PR already exists from development to main | |
| EXISTING_PR=$(gh pr list --base main --head development --json number --jq '.[0].number' 2>&1 || echo "") | |
| if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then | |
| echo "Updating existing PR #$EXISTING_PR" | |
| gh pr edit $EXISTING_PR \ | |
| --title "🔄 Merge development → main ($SHORT_SHA) - Review Required" \ | |
| --body-file pr_body.md | |
| else | |
| echo "Creating new PR from development to main" | |
| gh pr create \ | |
| --title "🔄 Merge development → main ($SHORT_SHA) - Review Required" \ | |
| --body-file pr_body.md \ | |
| --base main \ | |
| --head development | |
| fi | |
| echo "✅ PR created/updated for manual review" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Cleanup Old Auto-Merge Branches | |
| if: always() | |
| run: | | |
| echo "Cleaning up stale auto-merge branches (if any exist)..." | |
| # Get all auto-merge branches | |
| git fetch origin | |
| AUTO_MERGE_BRANCHES=$(git branch -r | grep "origin/auto-merge/main-from-dev-" | sed 's/origin\///' || echo "") | |
| if [ -z "$AUTO_MERGE_BRANCHES" ]; then | |
| echo "No auto-merge branches to clean up" | |
| exit 0 | |
| fi | |
| echo "Found old auto-merge branches to clean:" | |
| echo "$AUTO_MERGE_BRANCHES" | |
| for branch in $AUTO_MERGE_BRANCHES; do | |
| branch_name=$(echo $branch | sed 's/^[[:space:]]*//') | |
| echo "Deleting old branch: $branch_name" | |
| git push origin --delete $branch_name 2>&1 || echo "Could not delete $branch_name (may not exist)" | |
| done | |
| echo "✅ Cleanup complete" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |