Skip to content

Commit cfbeb90

Browse files
drama17Valerii Onyshchenko
andauthored
[TECH-6068] Add autodeleting orphaned branches (#3)
Co-authored-by: Valerii Onyshchenko <valerii@techops.services>
1 parent 44c53ab commit cfbeb90

6 files changed

Lines changed: 450 additions & 45 deletions

File tree

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
GITHUB_ORG=your-org-name
22
GITHUB_TOKEN=ghp_your_token_here
33
OLD_PR_THRESHOLD_DAYS=30
4+
DELETE_ORPHANED_BRANCHES=false
45
# GITHUB_REPO=specific-repo-name # Optional: scan only this repo instead of entire org

.github/workflows/repo-health-scan.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,11 @@ jobs:
6161
GITHUB_TOKEN: ${{ secrets.SCANNER_PAT }}
6262
GITHUB_ORG: ${{ steps.determine-org.outputs.github_org }}
6363
OLD_PR_THRESHOLD_DAYS: ${{ github.event.inputs.pr_threshold_days || '30' }}
64+
DELETE_ORPHANED_BRANCHES: ${{ vars.DELETE_ORPHANED_BRANCHES || 'false' }}
6465
run: |
6566
echo "Starting scan for organization: $GITHUB_ORG"
6667
echo "PR threshold: $OLD_PR_THRESHOLD_DAYS days"
68+
echo "Auto-delete branches: $DELETE_ORPHANED_BRANCHES"
6769
mkdir -p reports
6870
python scanner.py --pretty
6971

Makefile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: build run clean rebuild pretty
1+
.PHONY: build run clean rebuild pretty dbr dpr
22

33
build:
44
docker compose build --build-arg USER_ID=$$(id -u) --build-arg GROUP_ID=$$(id -g)
@@ -15,6 +15,18 @@ scan: build run
1515
pretty: build
1616
UID=$$(id -u) GID=$$(id -g) docker compose run --rm github-scanner --pretty
1717

18+
# Delete orphaned branches (branches without open PRs)
19+
dbr: build
20+
@echo "⚠️ WARNING: This will DELETE all orphaned branches!"
21+
@read -p "Are you sure? Type 'yes' to continue: " confirm && [ "$$confirm" = "yes" ] || (echo "Aborted." && exit 1)
22+
UID=$$(id -u) GID=$$(id -g) docker compose run --rm github-scanner --delete-branches
23+
24+
# Delete (close) stale PRs that exceed the threshold
25+
dpr: build
26+
@echo "⚠️ WARNING: This will CLOSE all stale PRs exceeding the threshold!"
27+
@read -p "Are you sure? Type 'yes' to continue: " confirm && [ "$$confirm" = "yes" ] || (echo "Aborted." && exit 1)
28+
UID=$$(id -u) GID=$$(id -g) docker compose run --rm github-scanner --delete-prs
29+
1830
clean:
1931
docker compose down
2032
rm -rf reports/

README.md

Lines changed: 149 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ Dockerized tool to scan GitHub organizations for repository health metrics. Opti
66

77
- **Orphaned Branch Detection**: Identifies branches without open PRs (includes merged/closed PR branches)
88
- **Closed/Merged PR Branch Tracking**: Identifies branches that still exist after their PRs were closed or merged
9+
- **Automated Cleanup**: Delete orphaned branches and close stale PRs with dedicated commands
10+
- **Auto-Delete Mode**: Automatically delete branches with closed/merged PRs via environment variable
11+
- **Smart Branch Protection**: Automatically excludes standard branches (main, staging, dev, prod, etc.)
12+
- **Archived Repository Handling**: Automatically skips archived repositories from analysis
13+
- **API Error Resilience**: Automatic retry with backoff for transient 403 errors
14+
- **Duplicate Branch Handling**: Deduplicates branches with multiple closed/merged PRs
915
- **Old PR Analysis**: Configurable threshold for identifying stale pull requests
1016
- **Pretty-Print Table Format**: Human-friendly table output for stale PRs and orphaned branches
1117
- **Discord Integration**: Automated report uploads to Discord via GitHub Actions
@@ -26,6 +32,7 @@ Dockerized tool to scan GitHub organizations for repository health metrics. Opti
2632
GITHUB_ORG=your-org-name
2733
GITHUB_TOKEN=ghp_your_token_here
2834
OLD_PR_THRESHOLD_DAYS=30
35+
DELETE_ORPHANED_BRANCHES=false
2936
```
3037

3138
### Creating GitHub Token with Minimal Permissions
@@ -67,6 +74,8 @@ To create a GitHub Personal Access Token with minimal required permissions:
6774
```bash
6875
make scan # Build and run (JSON output)
6976
make pretty # Build and run with pretty table output
77+
make dbr # Delete all orphaned branches (requires confirmation)
78+
make dpr # Close all stale PRs (requires confirmation)
7079
make fresh # Force rebuild and run
7180
make build # Build only
7281
make clean # Clean up
@@ -119,19 +128,30 @@ make fresh
119128
| `GITHUB_TOKEN` | Recommended | - | GitHub personal access token |
120129
| `GITHUB_REPO` | No | - | Specific repository name (scans only this repo instead of entire org) |
121130
| `OLD_PR_THRESHOLD_DAYS` | No | 30 | Days to consider PRs as "old" |
131+
| `DELETE_ORPHANED_BRANCHES` | No | false | Auto-delete branches with closed/merged PRs during scan |
132+
133+
**Note:** The scanner automatically excludes:
134+
- Archived repositories (read-only)
135+
- Inactive repositories (no activity in last year)
136+
- Standard branches: `main`, `master`, `develop`, `development`, `dev`, `staging`, `stage`, `prod`, `production`, `test`, `testing`, `qa`, `uat`, `preprod`, `pre-prod`, `release`, `hotfix`, `stable`
122137

123138
### GitHub Token
124139

125140
- **Without token**: 60 requests/hour (rate limited)
126141
- **With token**: 5000 requests/hour
127-
- **Permissions needed**: `repo` (for private repos) or `public_repo` (for public repos only)
142+
- **Permissions needed**:
143+
- `repo` - Full control of private repositories (required for deletion operations)
144+
- `read:org` - Read organization membership (required for org scanning)
145+
- For read-only scanning of public repos: `public_repo` + `read:org`
128146

129147
## Makefile Commands
130148

131149
| Command | Description |
132150
|---------|-------------|
133151
| `make scan` | Build and run scanner (JSON output) |
134152
| `make pretty` | Build and run with pretty table output |
153+
| `make dbr` | **Delete orphaned branches** (requires confirmation) |
154+
| `make dpr` | **Close stale PRs** (requires confirmation) |
135155
| `make build` | Build Docker image |
136156
| `make rebuild` | Force rebuild (no cache) |
137157
| `make run` | Run existing image |
@@ -240,6 +260,55 @@ The Markdown file can be:
240260
- Converted to PDF or other formats
241261
- Included in documentation or reports
242262

263+
## Cleanup Operations
264+
265+
### Delete Orphaned Branches (`make dbr`)
266+
267+
Deletes all branches that don't have open PRs (excluding default and protected branches):
268+
269+
```bash
270+
make dbr
271+
```
272+
273+
**What it does:**
274+
- Scans all repositories for orphaned branches
275+
- Excludes default branches (main, master, etc.)
276+
- Excludes protected branches
277+
- Prompts for confirmation before deletion
278+
- Shows real-time deletion status for each branch
279+
280+
**⚠️ Warning:** This is a destructive operation. Deleted branches cannot be recovered unless you have local copies.
281+
282+
### Close Stale PRs (`make dpr`)
283+
284+
Closes all pull requests that exceed the configured threshold:
285+
286+
```bash
287+
make dpr
288+
```
289+
290+
**What it does:**
291+
- Identifies PRs older than `OLD_PR_THRESHOLD_DAYS`
292+
- Prompts for confirmation before closing
293+
- Closes PRs with status update
294+
- Shows real-time closure status for each PR
295+
296+
**Note:** Closed PRs can be reopened if needed.
297+
298+
### Auto-Delete Closed/Merged PR Branches
299+
300+
Set the `DELETE_ORPHANED_BRANCHES` environment variable to automatically delete branches with closed or merged PRs during scanning:
301+
302+
```bash
303+
DELETE_ORPHANED_BRANCHES=true make scan
304+
```
305+
306+
**What it does:**
307+
- Automatically deletes branches whose PRs have been closed or merged
308+
- Runs during normal scan operation
309+
- No confirmation prompt (use with caution)
310+
- Useful for automated cleanup in CI/CD pipelines
311+
243312
### Closed/Merged PR Branches Section
244313

245314
The report now includes a dedicated section for branches that still exist even though their PRs have been closed or merged:
@@ -271,6 +340,36 @@ The workflow automatically uploads the generated Markdown report to Discord:
271340
- Includes organization name in the message
272341
- Webhook URL is configured in the workflow file
273342

343+
## Command-Line Options
344+
345+
The scanner supports the following command-line flags:
346+
347+
```bash
348+
python scanner.py [OPTIONS]
349+
350+
Options:
351+
-p, --pretty Output human-friendly table format
352+
--delete-branches Delete all orphaned branches
353+
--delete-prs Close all stale PRs exceeding threshold
354+
-h, --help Show help message
355+
```
356+
357+
**Examples:**
358+
359+
```bash
360+
# Generate pretty report
361+
python scanner.py --pretty
362+
363+
# Delete orphaned branches (with warning)
364+
python scanner.py --delete-branches
365+
366+
# Close stale PRs (with warning)
367+
python scanner.py --delete-prs
368+
369+
# Combine operations
370+
python scanner.py --pretty --delete-branches
371+
```
372+
274373
## Security
275374

276375
- **Docker isolation**: Runs in isolated container environment
@@ -279,14 +378,46 @@ The workflow automatically uploads the generated Markdown report to Discord:
279378
- **Environment variables**: Secure credential management
280379
- **Local reports**: Reports saved to mounted volume only
281380
- **No data persistence**: No data stored in container
381+
- **Deletion safeguards**: Confirmation prompts for destructive operations
382+
- **Protected branches**: Never deletes default or protected branches
282383

283384
## Performance
284385

285386
- **Optimized API calls**: Reduced API requests for better performance
286387
- **Pagination handling**: Automatic handling of large datasets
287388
- **Progress indicators**: Real-time feedback for long-running scans
288-
- **Error handling**: Graceful handling of API failures
389+
- **Error handling**: Graceful handling of API failures with automatic retry
390+
- **403 Error Retry**: Automatic 5-second delay and retry for transient access errors
289391
- **Rate limit aware**: Respects GitHub API rate limits
392+
- **Batch operations**: Efficient deletion of multiple branches/PRs
393+
- **Smart filtering**: Early exclusion of archived and inactive repositories
394+
395+
## Best Practices
396+
397+
### Before Running Cleanup Operations
398+
399+
1. **Run a scan first**: Always run `make scan` or `make pretty` to see what will be affected
400+
2. **Review the report**: Check the generated report to understand what will be deleted
401+
3. **Backup important branches**: Ensure you have local copies of any branches you might need
402+
4. **Test on a single repo**: Use `GITHUB_REPO=test-repo make dbr` to test on one repository first
403+
5. **Use in CI/CD carefully**: Only enable `DELETE_ORPHANED_BRANCHES=true` if you're confident in the logic
404+
405+
### Recommended Workflow
406+
407+
```bash
408+
# 1. Scan and review
409+
make pretty
410+
411+
# 2. Review the Markdown report
412+
cat reports/scan_*.md
413+
414+
# 3. If satisfied, run cleanup
415+
make dbr # Delete orphaned branches
416+
make dpr # Close stale PRs
417+
418+
# 4. Verify results
419+
make pretty
420+
```
290421

291422
## Troubleshooting
292423

@@ -304,3 +435,19 @@ The workflow automatically uploads the generated Markdown report to Discord:
304435

305436
- Ensure your GitHub token has access to all repositories in the organization
306437
- Private repositories require `repo` scope, public repositories need `public_repo`
438+
439+
### Deletion Failures
440+
441+
- Ensure your token has `repo` scope (not just `public_repo`)
442+
- Protected branches cannot be deleted (this is intentional)
443+
- Standard branches (main, staging, dev, prod, etc.) are automatically excluded
444+
- Archived repositories are skipped entirely (read-only)
445+
- Check that branches still exist before attempting deletion
446+
- Verify organization permissions allow branch deletion
447+
448+
### 403 Errors During Scan
449+
450+
- The scanner automatically retries 403 errors after a 5-second delay
451+
- Transient 403 errors are common during rapid API requests and are handled automatically
452+
- Persistent 403 errors are silently skipped (usually archived repos or permission issues)
453+
- Check rate limit status if you see many 403 errors: `curl -H "Authorization: token YOUR_TOKEN" https://api.github.com/rate_limit`

docker-compose.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ services:
1010
- GITHUB_ORG=${GITHUB_ORG}
1111
- OLD_PR_THRESHOLD_DAYS=${OLD_PR_THRESHOLD_DAYS}
1212
- GITHUB_REPO=${GITHUB_REPO}
13+
- DELETE_ORPHANED_BRANCHES=${DELETE_ORPHANED_BRANCHES}
1314
volumes:
1415
- ./reports:/app/reports
1516
networks:

0 commit comments

Comments
 (0)