Skip to content

Branch Cleanup

Branch Cleanup #12

name: Branch Cleanup
on:
pull_request:
types: [closed]
schedule:
- cron: '0 2 * * 0' # Weekly cleanup on Sundays at 2 AM
jobs:
cleanup-merged-branches:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
permissions:
contents: write
pull-requests: read
steps:
- name: Delete merged branch
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const branchName = context.payload.pull_request.head.ref;
const isFromFork = context.payload.pull_request.head.repo.fork;
// Only delete branches from main repo, not forks
if (!isFromFork && !branchName.includes('main') && !branchName.includes('master')) {
try {
// Check if branch has protection rules
let hasProtection = false;
try {
await github.rest.repos.getBranchProtection({
owner: context.repo.owner,
repo: context.repo.repo,
branch: branchName
});
hasProtection = true;
console.log(`⚠️ Branch ${branchName} has protection rules, skipping deletion`);
} catch (protectionError) {
// No protection rules, safe to delete
console.log(`Branch ${branchName} has no protection rules`);
}
if (!hasProtection) {
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${branchName}`
});
console.log(`✅ Deleted merged branch: ${branchName}`);
}
} catch (error) {
console.log(`❌ Failed to delete branch ${branchName}: ${error.message}`);
// If deletion fails due to permissions, suggest manual cleanup
if (error.message.includes('Resource not accessible')) {
console.log(`💡 Manual cleanup required for branch ${branchName}`);
console.log('This may be due to branch protection rules or insufficient permissions.');
}
}
} else if (isFromFork) {
console.log(`🔀 Skipping fork branch: ${branchName}`);
} else {
console.log(`🔒 Skipping protected branch: ${branchName}`);
}
cleanup-stale-branches:
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
permissions:
contents: write
pull-requests: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Cleanup stale branches
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { execSync } = require('child_process');
// Get branches older than 30 days with no recent activity
const cutoffDate = Math.floor(Date.now() / 1000) - (30 * 24 * 60 * 60);
try {
const branchOutput = execSync('git for-each-ref --format="%(refname:short) %(committerdate:unix)" refs/remotes/origin/', { encoding: 'utf8' });
const branches = branchOutput.trim().split('\n').filter(line => line.trim());
for (const line of branches) {
const [fullBranch, timestamp] = line.split(' ');
const branchName = fullBranch.replace('origin/', '');
// Skip protected branches
if (['main', 'master'].includes(branchName) || branchName.startsWith('release/')) {
continue;
}
if (parseInt(timestamp) < cutoffDate) {
const lastActivity = new Date(parseInt(timestamp) * 1000).toLocaleDateString();
console.log(`🗑️ Stale branch found: ${branchName} (last activity: ${lastActivity})`);
// Check if branch has open PR
try {
const prs = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
head: `${context.repo.owner}:${branchName}`,
state: 'open'
});
if (prs.data.length === 0) {
// Check for branch protection
let hasProtection = false;
try {
await github.rest.repos.getBranchProtection({
owner: context.repo.owner,
repo: context.repo.repo,
branch: branchName
});
hasProtection = true;
console.log(`⚠️ Branch ${branchName} has protection rules, skipping deletion`);
} catch (protectionError) {
// No protection rules
}
if (!hasProtection) {
try {
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${branchName}`
});
console.log(`✅ Deleted stale branch: ${branchName}`);
} catch (deleteError) {
console.log(`❌ Failed to delete ${branchName}: ${deleteError.message}`);
}
}
} else {
console.log(`Branch ${branchName} has open PR, skipping deletion`);
}
} catch (error) {
console.log(`Error checking PRs for ${branchName}: ${error.message}`);
}
}
}
} catch (error) {
console.log('Error during stale branch cleanup:', error.message);
}