Initial commit
This commit is contained in:
50
skills/git.cleanupbranches/README.md
Normal file
50
skills/git.cleanupbranches/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# git.cleanupbranches
|
||||
|
||||
Clean up merged and stale git branches both locally and remotely. Analyzes branch status, identifies branches that are safe to delete (merged or stale), and provides interactive cleanup with safety checks. - git-repository - Local git repository with branch information - branch-metadata - Branch merge status and last commit dates - branch-cleanup-report - Report of branches analyzed and deleted - cleanup-summary - Summary with statistics (branches deleted, kept, errors) - dry_run (boolean): Show what would be deleted without deleting (default: true) - include_remote (boolean): Also clean up remote branches (default: false) - stale_days (integer): Consider branches stale after N days of no commits (default: 30) - protected_branches (array): Branches to never delete (default: ["main", "master", "develop", "development"]) - interactive (boolean): Ask for confirmation before deleting (default: true) - merged_only (boolean): Only delete merged branches, ignore stale (default: false) - git command line tool - Access to git repository (read for analysis, write for deletion) - Access to remote repository (if include_remote=true) 1. Validate we're in a git repository 2. Get list of all local branches 3. Identify current branch (never delete) 4. For each branch: - Check if in protected list - Check if merged into main/master/develop - Check last commit date for staleness - Calculate deletion recommendation 5. Build list of branches to delete (merged or stale) 6. Display analysis results to user 7. If interactive, ask for confirmation 8. If confirmed (or not interactive): - Delete local branches - If include_remote, delete from remote - Track successes and failures 9. Generate cleanup report with statistics 10. Return structured results - Never deletes current branch - Never deletes protected branches (main, master, develop) - Default is dry_run=true (shows what would happen) - Interactive confirmation by default - Detailed logging of all operations - Rollback information provided ```python python3 skills/git.cleanupbranches/git_cleanupbranches.py --dry-run python3 skills/git.cleanupbranches/git_cleanupbranches.py --no-dry-run python3 skills/git.cleanupbranches/git_cleanupbranches.py --no-dry-run --stale-days 60 python3 skills/git.cleanupbranches/git_cleanupbranches.py --no-dry-run --include-remote python3 skills/git.cleanupbranches/git_cleanupbranches.py --no-dry-run --no-interactive --merged-only ``` ```json { "status": "success", "analyzed": 25, "deleted": 5, "kept": 20, "branches_deleted": ["feature/old-feature", "fix/old-bug"], "branches_kept": ["feature/active", "main", "develop"], "protected": 3, "dry_run": false, "errors": [] } ``` - git - cleanup - maintenance - branches - automation This skill requires SKILL_AND_COMMAND pattern due to: - 8-10 steps (exceeds threshold) - Medium autonomy (analyzes and recommends deletions) - Reusable for CI/CD and release workflows - Complex logic with safety checks and interactive confirmation
|
||||
|
||||
## Overview
|
||||
|
||||
**Purpose:** Clean up merged and stale git branches both locally and remotely. Analyzes branch status, identifies branches that are safe to delete (merged or stale), and provides interactive cleanup with safety checks. - git-repository - Local git repository with branch information - branch-metadata - Branch merge status and last commit dates - branch-cleanup-report - Report of branches analyzed and deleted - cleanup-summary - Summary with statistics (branches deleted, kept, errors) - dry_run (boolean): Show what would be deleted without deleting (default: true) - include_remote (boolean): Also clean up remote branches (default: false) - stale_days (integer): Consider branches stale after N days of no commits (default: 30) - protected_branches (array): Branches to never delete (default: ["main", "master", "develop", "development"]) - interactive (boolean): Ask for confirmation before deleting (default: true) - merged_only (boolean): Only delete merged branches, ignore stale (default: false) - git command line tool - Access to git repository (read for analysis, write for deletion) - Access to remote repository (if include_remote=true) 1. Validate we're in a git repository 2. Get list of all local branches 3. Identify current branch (never delete) 4. For each branch: - Check if in protected list - Check if merged into main/master/develop - Check last commit date for staleness - Calculate deletion recommendation 5. Build list of branches to delete (merged or stale) 6. Display analysis results to user 7. If interactive, ask for confirmation 8. If confirmed (or not interactive): - Delete local branches - If include_remote, delete from remote - Track successes and failures 9. Generate cleanup report with statistics 10. Return structured results - Never deletes current branch - Never deletes protected branches (main, master, develop) - Default is dry_run=true (shows what would happen) - Interactive confirmation by default - Detailed logging of all operations - Rollback information provided ```python python3 skills/git.cleanupbranches/git_cleanupbranches.py --dry-run python3 skills/git.cleanupbranches/git_cleanupbranches.py --no-dry-run python3 skills/git.cleanupbranches/git_cleanupbranches.py --no-dry-run --stale-days 60 python3 skills/git.cleanupbranches/git_cleanupbranches.py --no-dry-run --include-remote python3 skills/git.cleanupbranches/git_cleanupbranches.py --no-dry-run --no-interactive --merged-only ``` ```json { "status": "success", "analyzed": 25, "deleted": 5, "kept": 20, "branches_deleted": ["feature/old-feature", "fix/old-bug"], "branches_kept": ["feature/active", "main", "develop"], "protected": 3, "dry_run": false, "errors": [] } ``` - git - cleanup - maintenance - branches - automation This skill requires SKILL_AND_COMMAND pattern due to: - 8-10 steps (exceeds threshold) - Medium autonomy (analyzes and recommends deletions) - Reusable for CI/CD and release workflows - Complex logic with safety checks and interactive confirmation
|
||||
|
||||
**Command:** `/git/cleanupbranches`
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
python3 skills/git/cleanupbranches/git_cleanupbranches.py
|
||||
```
|
||||
|
||||
### With Arguments
|
||||
|
||||
```bash
|
||||
python3 skills/git/cleanupbranches/git_cleanupbranches.py \
|
||||
--output-format json
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
This skill can be used in agents by including it in `skills_available`:
|
||||
|
||||
```yaml
|
||||
name: my.agent
|
||||
skills_available:
|
||||
- git.cleanupbranches
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run tests with:
|
||||
|
||||
```bash
|
||||
pytest skills/git/cleanupbranches/test_git_cleanupbranches.py -v
|
||||
```
|
||||
|
||||
## Created By
|
||||
|
||||
This skill was generated by **meta.skill**, the skill creator meta-agent.
|
||||
|
||||
---
|
||||
|
||||
*Part of the Betty Framework*
|
||||
1
skills/git.cleanupbranches/__init__.py
Normal file
1
skills/git.cleanupbranches/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Auto-generated package initializer for skills.
|
||||
472
skills/git.cleanupbranches/git_cleanupbranches.py
Executable file
472
skills/git.cleanupbranches/git_cleanupbranches.py
Executable file
@@ -0,0 +1,472 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
git.cleanupbranches - Clean up merged and stale git branches
|
||||
|
||||
Analyzes branch status, identifies branches that are safe to delete (merged or stale),
|
||||
and provides interactive cleanup with safety checks.
|
||||
|
||||
Generated by meta.skill with Betty Framework certification
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import yaml
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Optional
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
from betty.config import BASE_DIR
|
||||
from betty.logging_utils import setup_logger
|
||||
from betty.certification import certified_skill
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
class GitCleanupbranches:
|
||||
"""
|
||||
Clean up merged and stale git branches both locally and remotely.
|
||||
"""
|
||||
|
||||
def __init__(self, base_dir: str = "."):
|
||||
"""Initialize skill"""
|
||||
self.base_dir = Path(base_dir)
|
||||
self.protected_branches = ["main", "master", "develop", "development"]
|
||||
|
||||
def run_git_command(self, command: List[str]) -> tuple[bool, str]:
|
||||
"""
|
||||
Run a git command and return success status and output
|
||||
|
||||
Args:
|
||||
command: Git command as list of arguments
|
||||
|
||||
Returns:
|
||||
Tuple of (success, output)
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
command,
|
||||
cwd=self.base_dir,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
return True, result.stdout.strip()
|
||||
except subprocess.CalledProcessError as e:
|
||||
return False, e.stderr.strip()
|
||||
|
||||
def is_git_repository(self) -> bool:
|
||||
"""Check if current directory is a git repository"""
|
||||
success, _ = self.run_git_command(["git", "rev-parse", "--is-inside-work-tree"])
|
||||
return success
|
||||
|
||||
def get_current_branch(self) -> Optional[str]:
|
||||
"""Get the current branch name"""
|
||||
success, output = self.run_git_command(["git", "branch", "--show-current"])
|
||||
return output if success else None
|
||||
|
||||
def get_all_local_branches(self) -> List[str]:
|
||||
"""Get list of all local branches"""
|
||||
success, output = self.run_git_command(["git", "branch", "--format=%(refname:short)"])
|
||||
if not success:
|
||||
return []
|
||||
return [b.strip() for b in output.split('\n') if b.strip()]
|
||||
|
||||
def is_branch_merged(self, branch: str, into_branch: str = "main") -> bool:
|
||||
"""
|
||||
Check if a branch is merged into another branch
|
||||
|
||||
Args:
|
||||
branch: Branch to check
|
||||
into_branch: Branch to check against
|
||||
|
||||
Returns:
|
||||
True if merged, False otherwise
|
||||
"""
|
||||
# Try multiple base branches
|
||||
for base in ["main", "master", "develop"]:
|
||||
success, output = self.run_git_command(
|
||||
["git", "branch", "--merged", base, "--format=%(refname:short)"]
|
||||
)
|
||||
if success and branch in output.split('\n'):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_branch_last_commit_date(self, branch: str) -> Optional[datetime]:
|
||||
"""
|
||||
Get the date of the last commit on a branch
|
||||
|
||||
Args:
|
||||
branch: Branch name
|
||||
|
||||
Returns:
|
||||
Datetime of last commit or None
|
||||
"""
|
||||
success, output = self.run_git_command(
|
||||
["git", "log", "-1", "--format=%ct", branch]
|
||||
)
|
||||
if success and output:
|
||||
try:
|
||||
timestamp = int(output)
|
||||
return datetime.fromtimestamp(timestamp)
|
||||
except (ValueError, OSError):
|
||||
return None
|
||||
return None
|
||||
|
||||
def is_branch_stale(self, branch: str, days: int = 30) -> bool:
|
||||
"""
|
||||
Check if a branch is stale (no commits for N days)
|
||||
|
||||
Args:
|
||||
branch: Branch name
|
||||
days: Number of days to consider stale
|
||||
|
||||
Returns:
|
||||
True if stale, False otherwise
|
||||
"""
|
||||
last_commit = self.get_branch_last_commit_date(branch)
|
||||
if last_commit is None:
|
||||
return False
|
||||
|
||||
cutoff_date = datetime.now() - timedelta(days=days)
|
||||
return last_commit < cutoff_date
|
||||
|
||||
def delete_local_branch(self, branch: str, force: bool = False) -> bool:
|
||||
"""
|
||||
Delete a local branch
|
||||
|
||||
Args:
|
||||
branch: Branch name
|
||||
force: Use -D instead of -d
|
||||
|
||||
Returns:
|
||||
True if deleted successfully
|
||||
"""
|
||||
flag = "-D" if force else "-d"
|
||||
success, output = self.run_git_command(["git", "branch", flag, branch])
|
||||
if success:
|
||||
logger.info(f"Deleted local branch: {branch}")
|
||||
else:
|
||||
logger.warning(f"Failed to delete branch {branch}: {output}")
|
||||
return success
|
||||
|
||||
def delete_remote_branch(self, branch: str) -> bool:
|
||||
"""
|
||||
Delete a remote branch
|
||||
|
||||
Args:
|
||||
branch: Branch name
|
||||
|
||||
Returns:
|
||||
True if deleted successfully
|
||||
"""
|
||||
success, output = self.run_git_command(["git", "push", "origin", "--delete", branch])
|
||||
if success:
|
||||
logger.info(f"Deleted remote branch: {branch}")
|
||||
else:
|
||||
logger.warning(f"Failed to delete remote branch {branch}: {output}")
|
||||
return success
|
||||
|
||||
@certified_skill("git.cleanupbranches")
|
||||
def execute(
|
||||
self,
|
||||
dry_run: bool = True,
|
||||
include_remote: bool = False,
|
||||
stale_days: int = 30,
|
||||
protected_branches: Optional[List[str]] = None,
|
||||
interactive: bool = True,
|
||||
merged_only: bool = False
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Execute the skill
|
||||
|
||||
Args:
|
||||
dry_run: Show what would be deleted without deleting
|
||||
include_remote: Also clean up remote branches
|
||||
stale_days: Consider branches stale after N days
|
||||
protected_branches: Branches to never delete
|
||||
interactive: Ask for confirmation before deleting
|
||||
merged_only: Only delete merged branches
|
||||
|
||||
Returns:
|
||||
Dict with execution results
|
||||
"""
|
||||
try:
|
||||
logger.info("Executing git.cleanupbranches...")
|
||||
|
||||
# Validate we're in a git repository
|
||||
if not self.is_git_repository():
|
||||
return {
|
||||
"ok": False,
|
||||
"status": "failed",
|
||||
"error": "Not in a git repository"
|
||||
}
|
||||
|
||||
# Use provided protected branches or defaults
|
||||
if protected_branches:
|
||||
self.protected_branches = protected_branches
|
||||
|
||||
# Get current branch (never delete)
|
||||
current_branch = self.get_current_branch()
|
||||
if not current_branch:
|
||||
return {
|
||||
"ok": False,
|
||||
"status": "failed",
|
||||
"error": "Could not determine current branch"
|
||||
}
|
||||
|
||||
# Get all local branches
|
||||
all_branches = self.get_all_local_branches()
|
||||
logger.info(f"Found {len(all_branches)} local branches")
|
||||
|
||||
# Analyze branches
|
||||
branches_to_delete = []
|
||||
branches_kept = []
|
||||
analysis = []
|
||||
|
||||
for branch in all_branches:
|
||||
# Skip current branch
|
||||
if branch == current_branch:
|
||||
branches_kept.append(branch)
|
||||
analysis.append({
|
||||
"branch": branch,
|
||||
"action": "keep",
|
||||
"reason": "current branch"
|
||||
})
|
||||
continue
|
||||
|
||||
# Skip protected branches
|
||||
if branch in self.protected_branches:
|
||||
branches_kept.append(branch)
|
||||
analysis.append({
|
||||
"branch": branch,
|
||||
"action": "keep",
|
||||
"reason": "protected"
|
||||
})
|
||||
continue
|
||||
|
||||
# Check if merged
|
||||
is_merged = self.is_branch_merged(branch)
|
||||
|
||||
# Check if stale
|
||||
is_stale = False if merged_only else self.is_branch_stale(branch, stale_days)
|
||||
|
||||
# Determine if should delete
|
||||
should_delete = is_merged or (is_stale and not merged_only)
|
||||
|
||||
if should_delete:
|
||||
reason = "merged" if is_merged else f"stale ({stale_days}+ days)"
|
||||
branches_to_delete.append(branch)
|
||||
analysis.append({
|
||||
"branch": branch,
|
||||
"action": "delete",
|
||||
"reason": reason,
|
||||
"is_merged": is_merged,
|
||||
"is_stale": is_stale
|
||||
})
|
||||
else:
|
||||
branches_kept.append(branch)
|
||||
analysis.append({
|
||||
"branch": branch,
|
||||
"action": "keep",
|
||||
"reason": "active"
|
||||
})
|
||||
|
||||
# Display analysis
|
||||
logger.info(f"Analysis complete:")
|
||||
logger.info(f" Total branches: {len(all_branches)}")
|
||||
logger.info(f" To delete: {len(branches_to_delete)}")
|
||||
logger.info(f" To keep: {len(branches_kept)}")
|
||||
|
||||
if dry_run:
|
||||
logger.info("DRY RUN - No branches will be deleted")
|
||||
logger.info("Branches that would be deleted:")
|
||||
for item in analysis:
|
||||
if item["action"] == "delete":
|
||||
logger.info(f" - {item['branch']} ({item['reason']})")
|
||||
else:
|
||||
# Interactive confirmation
|
||||
if interactive and branches_to_delete:
|
||||
logger.info("Branches to delete:")
|
||||
for item in analysis:
|
||||
if item["action"] == "delete":
|
||||
logger.info(f" - {item['branch']} ({item['reason']})")
|
||||
|
||||
response = input("\nProceed with deletion? (yes/no): ").strip().lower()
|
||||
if response not in ["yes", "y"]:
|
||||
logger.info("Aborted by user")
|
||||
return {
|
||||
"ok": True,
|
||||
"status": "aborted",
|
||||
"message": "Aborted by user",
|
||||
"analyzed": len(all_branches),
|
||||
"would_delete": len(branches_to_delete)
|
||||
}
|
||||
|
||||
# Delete branches
|
||||
deleted = []
|
||||
errors = []
|
||||
|
||||
for branch in branches_to_delete:
|
||||
# Delete local
|
||||
if self.delete_local_branch(branch):
|
||||
deleted.append(branch)
|
||||
|
||||
# Delete remote if requested
|
||||
if include_remote:
|
||||
self.delete_remote_branch(branch)
|
||||
else:
|
||||
errors.append(f"Failed to delete {branch}")
|
||||
|
||||
logger.info(f"Deleted {len(deleted)} branches")
|
||||
if errors:
|
||||
logger.warning(f"{len(errors)} errors occurred")
|
||||
|
||||
# Build result
|
||||
result = {
|
||||
"ok": True,
|
||||
"status": "success",
|
||||
"analyzed": len(all_branches),
|
||||
"deleted": len(branches_to_delete) if not dry_run else 0,
|
||||
"kept": len(branches_kept),
|
||||
"branches_to_delete": branches_to_delete,
|
||||
"branches_kept": branches_kept,
|
||||
"protected": len([b for b in all_branches if b in self.protected_branches]),
|
||||
"dry_run": dry_run,
|
||||
"analysis": analysis
|
||||
}
|
||||
|
||||
if not dry_run and branches_to_delete:
|
||||
result["branches_deleted"] = deleted if not dry_run else []
|
||||
result["errors"] = errors if not dry_run else []
|
||||
|
||||
logger.info("Skill completed successfully")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error executing skill: {e}")
|
||||
return {
|
||||
"ok": False,
|
||||
"status": "failed",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""CLI entry point"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Clean up merged and stale git branches"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
default=True,
|
||||
help="Show what would be deleted without deleting (default: true)"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--no-dry-run",
|
||||
dest="dry_run",
|
||||
action="store_false",
|
||||
help="Actually delete branches"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--include-remote",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Also delete remote branches"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--stale-days",
|
||||
type=int,
|
||||
default=30,
|
||||
help="Consider branches stale after N days (default: 30)"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--protected-branches",
|
||||
nargs="+",
|
||||
default=["main", "master", "develop", "development"],
|
||||
help="Branches to never delete"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--interactive",
|
||||
action="store_true",
|
||||
default=True,
|
||||
help="Ask for confirmation before deleting (default: true)"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--no-interactive",
|
||||
dest="interactive",
|
||||
action="store_false",
|
||||
help="Don't ask for confirmation"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--merged-only",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Only delete merged branches, ignore stale"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--output-format",
|
||||
choices=["json", "yaml", "human"],
|
||||
default="human",
|
||||
help="Output format"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Create skill instance
|
||||
skill = GitCleanupbranches()
|
||||
|
||||
# Execute skill
|
||||
result = skill.execute(
|
||||
dry_run=args.dry_run,
|
||||
include_remote=args.include_remote,
|
||||
stale_days=args.stale_days,
|
||||
protected_branches=args.protected_branches,
|
||||
interactive=args.interactive,
|
||||
merged_only=args.merged_only
|
||||
)
|
||||
|
||||
# Output result
|
||||
if args.output_format == "json":
|
||||
print(json.dumps(result, indent=2))
|
||||
elif args.output_format == "yaml":
|
||||
print(yaml.dump(result, default_flow_style=False))
|
||||
else:
|
||||
# Human-readable output
|
||||
if result.get("ok"):
|
||||
print(f"\n✓ Branch Cleanup {'(DRY RUN)' if result.get('dry_run') else ''}")
|
||||
print(f" Analyzed: {result.get('analyzed', 0)} branches")
|
||||
if result.get('dry_run'):
|
||||
print(f" Would delete: {len(result.get('branches_to_delete', []))} branches")
|
||||
else:
|
||||
print(f" Deleted: {result.get('deleted', 0)} branches")
|
||||
print(f" Kept: {result.get('kept', 0)} branches")
|
||||
print(f" Protected: {result.get('protected', 0)} branches")
|
||||
|
||||
if result.get('branches_to_delete'):
|
||||
print(f"\nBranches to delete:")
|
||||
for branch in result.get('branches_to_delete', []):
|
||||
print(f" - {branch}")
|
||||
else:
|
||||
print(f"\n✗ Error: {result.get('error', 'Unknown error')}")
|
||||
|
||||
# Exit with appropriate code
|
||||
sys.exit(0 if result.get("ok") else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
47
skills/git.cleanupbranches/skill.yaml
Normal file
47
skills/git.cleanupbranches/skill.yaml
Normal file
@@ -0,0 +1,47 @@
|
||||
name: git.cleanupbranches
|
||||
version: 0.1.0
|
||||
description: 'Clean up merged and stale git branches both locally and remotely. Analyzes
|
||||
branch status, identifies branches that are safe to delete (merged or stale), and
|
||||
provides interactive cleanup with safety checks. - git-repository - Local git repository
|
||||
with branch information - branch-metadata - Branch merge status and last commit
|
||||
dates - branch-cleanup-report - Report of branches analyzed and deleted - cleanup-summary
|
||||
- Summary with statistics (branches deleted, kept, errors) - dry_run (boolean):
|
||||
Show what would be deleted without deleting (default: true) - include_remote (boolean):
|
||||
Also clean up remote branches (default: false) - stale_days (integer): Consider
|
||||
branches stale after N days of no commits (default: 30) - protected_branches (array):
|
||||
Branches to never delete (default: ["main", "master", "develop", "development"])
|
||||
- interactive (boolean): Ask for confirmation before deleting (default: true) -
|
||||
merged_only (boolean): Only delete merged branches, ignore stale (default: false)
|
||||
- git command line tool - Access to git repository (read for analysis, write for
|
||||
deletion) - Access to remote repository (if include_remote=true) 1. Validate we''re
|
||||
in a git repository 2. Get list of all local branches 3. Identify current branch
|
||||
(never delete) 4. For each branch: - Check if in protected list - Check if merged
|
||||
into main/master/develop - Check last commit date for staleness - Calculate deletion
|
||||
recommendation 5. Build list of branches to delete (merged or stale) 6. Display
|
||||
analysis results to user 7. If interactive, ask for confirmation 8. If confirmed
|
||||
(or not interactive): - Delete local branches - If include_remote, delete from remote
|
||||
- Track successes and failures 9. Generate cleanup report with statistics 10. Return
|
||||
structured results - Never deletes current branch - Never deletes protected branches
|
||||
(main, master, develop) - Default is dry_run=true (shows what would happen) - Interactive
|
||||
confirmation by default - Detailed logging of all operations - Rollback information
|
||||
provided ```python python3 skills/git.cleanupbranches/git_cleanupbranches.py --dry-run
|
||||
python3 skills/git.cleanupbranches/git_cleanupbranches.py --no-dry-run python3 skills/git.cleanupbranches/git_cleanupbranches.py
|
||||
--no-dry-run --stale-days 60 python3 skills/git.cleanupbranches/git_cleanupbranches.py
|
||||
--no-dry-run --include-remote python3 skills/git.cleanupbranches/git_cleanupbranches.py
|
||||
--no-dry-run --no-interactive --merged-only ``` ```json { "status": "success", "analyzed":
|
||||
25, "deleted": 5, "kept": 20, "branches_deleted": ["feature/old-feature", "fix/old-bug"],
|
||||
"branches_kept": ["feature/active", "main", "develop"], "protected": 3, "dry_run":
|
||||
false, "errors": [] } ``` - git - cleanup - maintenance - branches - automation
|
||||
This skill requires SKILL_AND_COMMAND pattern due to: - 8-10 steps (exceeds threshold)
|
||||
- Medium autonomy (analyzes and recommends deletions) - Reusable for CI/CD and release
|
||||
workflows - Complex logic with safety checks and interactive confirmation'
|
||||
inputs: []
|
||||
outputs: []
|
||||
status: active
|
||||
permissions: []
|
||||
entrypoints:
|
||||
- command: /git/cleanupbranches
|
||||
handler: git_cleanupbranches.py
|
||||
runtime: python
|
||||
description: Clean up merged and stale git branches both locally and remotely. Analyzes
|
||||
branch status, identifies
|
||||
62
skills/git.cleanupbranches/test_git_cleanupbranches.py
Normal file
62
skills/git.cleanupbranches/test_git_cleanupbranches.py
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tests for git.cleanupbranches
|
||||
|
||||
Generated by meta.skill
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Add parent directory to path
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
||||
|
||||
from skills.git_cleanupbranches import git_cleanupbranches
|
||||
|
||||
|
||||
class TestGitCleanupbranches:
|
||||
"""Tests for GitCleanupbranches"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Setup test fixtures"""
|
||||
self.skill = git_cleanupbranches.GitCleanupbranches()
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test skill initializes correctly"""
|
||||
assert self.skill is not None
|
||||
assert self.skill.base_dir is not None
|
||||
|
||||
def test_execute_basic(self):
|
||||
"""Test basic execution"""
|
||||
result = self.skill.execute()
|
||||
|
||||
assert result is not None
|
||||
assert "ok" in result
|
||||
assert "status" in result
|
||||
|
||||
def test_execute_success(self):
|
||||
"""Test successful execution"""
|
||||
result = self.skill.execute()
|
||||
|
||||
assert result["ok"] is True
|
||||
assert result["status"] == "success"
|
||||
|
||||
# TODO: Add more specific tests based on skill functionality
|
||||
|
||||
|
||||
def test_cli_help(capsys):
|
||||
"""Test CLI help message"""
|
||||
sys.argv = ["git_cleanupbranches.py", "--help"]
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
git_cleanupbranches.main()
|
||||
|
||||
assert exc_info.value.code == 0
|
||||
captured = capsys.readouterr()
|
||||
assert "Clean up merged and stale git branches both locall" in captured.out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
Reference in New Issue
Block a user