feat(protocols): Add repository protocols #31
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
| # Nightly Dev-to-Main Rebase | ||
| # Automatically rebases dev branch onto main to keep them in sync | ||
| name: Nightly Rebase | ||
| on: | ||
| schedule: | ||
| # Run at 3 AM UTC daily | ||
| - cron: '0 3 * * *' | ||
| workflow_dispatch: | ||
| inputs: | ||
| dry_run: | ||
| description: 'Dry run (no push)' | ||
| required: false | ||
| default: 'false' | ||
| type: choice | ||
| options: | ||
| - 'true' | ||
| - 'false' | ||
| jobs: | ||
| rebase-dev: | ||
| name: Rebase dev onto main | ||
| 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 user.name 'github-actions[bot]' | ||
| git config user.email 'github-actions[bot]@users.noreply.github.com' | ||
| - name: Attempt Rebase | ||
| id: rebase | ||
| run: | | ||
| echo "🔄 Starting nightly rebase of dev onto main..." | ||
| # Fetch latest changes | ||
| git fetch origin main dev | ||
| # Checkout dev branch | ||
| git checkout dev | ||
| # Get commit counts for logging | ||
| MAIN_COMMITS=$(git rev-list --count origin/main) | ||
| DEV_COMMITS=$(git rev-list --count origin/dev) | ||
| DIVERGED=$(git rev-list --count origin/main..origin/dev) | ||
| echo "📊 Branch statistics:" | ||
| echo " - Main has $MAIN_COMMITS commits" | ||
| echo " - Dev has $DEV_COMMITS commits" | ||
| echo " - Dev is $DIVERGED commits ahead of main" | ||
| # Attempt rebase | ||
| if git rebase origin/main; then | ||
| echo "✅ Rebase successful!" | ||
| echo "rebase_success=true" >> $GITHUB_OUTPUT | ||
| # Check if this is a dry run | ||
| if [[ "${{ github.event.inputs.dry_run }}" != "true" ]]; then | ||
| # Push the rebased dev branch | ||
| git push origin dev --force-with-lease | ||
| echo "✅ Pushed rebased dev branch" | ||
| else | ||
| echo "🔍 Dry run - not pushing changes" | ||
| git log --oneline -10 | ||
| fi | ||
| else | ||
| echo "❌ Rebase failed due to conflicts" | ||
| echo "rebase_success=false" >> $GITHUB_OUTPUT | ||
| # Abort the rebase | ||
| git rebase --abort | ||
| # Get conflict information | ||
| echo "conflict_files=$(git diff --name-only origin/main origin/dev | head -20 | tr '\n' ' ')" >> $GITHUB_OUTPUT | ||
| fi | ||
| - name: Create Conflict Resolution PR | ||
| if: steps.rebase.outputs.rebase_success == 'false' | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const conflictFiles = '${{ steps.rebase.outputs.conflict_files }}'.trim().split(' ').filter(Boolean); | ||
| // Check if a rebase PR already exists | ||
| const { data: existingPRs } = await github.rest.pulls.list({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| state: 'open', | ||
| head: `${context.repo.owner}:rebase-dev-${new Date().toISOString().split('T')[0]}` | ||
| }); | ||
| if (existingPRs.length > 0) { | ||
| console.log('Rebase PR already exists, skipping creation'); | ||
| return; | ||
| } | ||
| // Create a new branch for conflict resolution | ||
| const branchName = `rebase-dev-${new Date().toISOString().split('T')[0]}`; | ||
| try { | ||
| // Create branch from dev | ||
| await github.rest.git.createRef({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| ref: `refs/heads/${branchName}`, | ||
| sha: (await github.rest.git.getRef({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| ref: 'heads/dev' | ||
| })).data.object.sha | ||
| }); | ||
| // Create PR | ||
| const prBody = `## 🔧 Nightly Rebase Conflict Resolution | ||
| The automated rebase of \`dev\` onto \`main\` has encountered conflicts that require manual resolution. | ||
| ### Conflict Summary | ||
| ${conflictFiles.length} files have conflicts: | ||
| ${conflictFiles.map(f => `- \`${f}\``).join('\n')} | ||
| ### Resolution Steps | ||
| 1. Checkout this branch locally | ||
| 2. Run \`git rebase origin/main\` | ||
| 3. Resolve conflicts in each file | ||
| 4. Continue rebase with \`git rebase --continue\` | ||
| 5. Force push this branch | ||
| 6. Merge this PR to update dev | ||
| ### Alternative | ||
| If conflicts are too complex, consider: | ||
| - Creating focused PRs to main first | ||
| - Then rebasing dev after those are merged | ||
| --- | ||
| *This PR was automatically created by the nightly rebase workflow*`; | ||
| const { data: pr } = await github.rest.pulls.create({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| title: `🔧 Resolve rebase conflicts: dev <- main (${new Date().toLocaleDateString()})`, | ||
| body: prBody, | ||
| head: branchName, | ||
| base: 'dev', | ||
| draft: false | ||
| }); | ||
| // Add labels | ||
| await github.rest.issues.addLabels({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: pr.number, | ||
| labels: ['rebase-conflict', 'automated'] | ||
| }); | ||
| // Add comment with detailed instructions | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: pr.number, | ||
| body: `### 📋 Detailed Conflict Resolution Guide | ||
| \`\`\`bash | ||
| # 1. Fetch and checkout this branch | ||
| git fetch origin | ||
| git checkout ${branchName} | ||
| # 2. Start the rebase | ||
| git rebase origin/main | ||
| # 3. For each conflict: | ||
| # - Edit the conflicted files | ||
| # - Remove conflict markers | ||
| # - Stage the resolved files | ||
| git add <resolved-files> | ||
| # 4. Continue the rebase | ||
| git rebase --continue | ||
| # 5. Push the resolved branch | ||
| git push origin ${branchName} --force-with-lease | ||
| # 6. Mark this PR as ready for review | ||
| \`\`\` | ||
| ⚠️ **Important**: Ensure all tests pass after resolving conflicts!` | ||
| }); | ||
| console.log(`Created PR #${pr.number} for conflict resolution`); | ||
| } catch (error) { | ||
| console.error('Failed to create conflict resolution PR:', error); | ||
| } | ||
| - name: Send Success Notification | ||
| if: steps.rebase.outputs.rebase_success == 'true' && github.event.inputs.dry_run != 'true' | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| // Get the last few commits that were rebased | ||
| const { data: commits } = await github.rest.repos.listCommits({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| sha: 'dev', | ||
| per_page: 5 | ||
| }); | ||
| const commitList = commits.map(c => | ||
| `- ${c.sha.substring(0, 7)} ${c.commit.message.split('\n')[0]}` | ||
| ).join('\n'); | ||
| // Create an issue to notify about successful rebase | ||
| const issueBody = `## ✅ Nightly Rebase Successful | ||
| The \`dev\` branch has been successfully rebased onto \`main\`. | ||
| ### Recent commits on dev: | ||
| ${commitList} | ||
| ### Next Steps | ||
| - Continue development on \`dev\` branch | ||
| - Create PRs from feature branches to \`dev\` | ||
| - \`main\` remains stable and protected | ||
| --- | ||
| *This notification was generated by the nightly rebase workflow*`; | ||
| // Check if notification issue exists for today | ||
| const today = new Date().toISOString().split('T')[0]; | ||
| const { data: issues } = await github.rest.issues.listForRepo({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| labels: 'rebase-success', | ||
| state: 'open', | ||
| since: today | ||
| }); | ||
| if (issues.length === 0) { | ||
| const { data: issue } = await github.rest.issues.create({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| title: `✅ Nightly rebase successful (${today})`, | ||
| body: issueBody, | ||
| labels: ['rebase-success', 'automated'] | ||
| }); | ||
| // Auto-close after creation | ||
| await github.rest.issues.update({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: issue.number, | ||
| state: 'closed' | ||
| }); | ||
| } | ||
| cleanup-old-rebase-branches: | ||
| name: Cleanup Old Rebase Branches | ||
| runs-on: ubuntu-latest | ||
| needs: rebase-dev | ||
| if: always() | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Delete Old Rebase Branches | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| echo "🧹 Cleaning up old rebase branches..." | ||
| # Get all rebase branches older than 7 days | ||
| CUTOFF_DATE=$(date -d '7 days ago' +%Y-%m-%d) | ||
| git ls-remote --heads origin | grep 'refs/heads/rebase-dev-' | while read sha ref; do | ||
| branch=${ref#refs/heads/} | ||
| branch_date=$(echo $branch | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}$' || true) | ||
| if [[ -n "$branch_date" ]] && [[ "$branch_date" < "$CUTOFF_DATE" ]]; then | ||
| echo "Deleting old rebase branch: $branch" | ||
| git push origin --delete "$branch" || echo "Failed to delete $branch" | ||
| fi | ||
| done | ||