Skip to content

Auto-merge Development to Main #405

Auto-merge Development to Main

Auto-merge Development to Main #405

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 }}