Skip to content

Instantly share code, notes, and snippets.

@managedkaos
Last active September 25, 2025 04:34
Show Gist options
  • Save managedkaos/de142d19ea76286da8ad8202d3934636 to your computer and use it in GitHub Desktop.
Save managedkaos/de142d19ea76286da8ad8202d3934636 to your computer and use it in GitHub Desktop.
Generate reports showing the age of each branch in the repo, where age is based on the date of the last commit.

USAGE FOR branch_ages.py

Download the script and place it in a directory on your $PATH.

Step Description Command
1. Make the script executable chmod +x branch_ages.py
2. See all available options branch_ages.py --help
3. Get a formatted table, oldest first (default) branch_ages.py
4. Sort by newest first branch_ages.py --newest
5. Get CSV output branch_ages.py --csv
6. Get a Markdown table output branch_ages.py --markdown
#!/usr/bin/env python3
"""
Git Branch Age Report Generator
Generates formatted reports showing the age of all remote branches
"""
import subprocess
import sys
import csv
import argparse
from datetime import datetime
from io import StringIO
def run_git_command(command):
"""Execute a git command and return the output"""
try:
result = subprocess.run(
command.split(),
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
print(f"Git command failed: {e}", file=sys.stderr)
return None
except FileNotFoundError:
print("Error: git command not found", file=sys.stderr)
return None
def check_git_repo():
"""Check if we're in a git repository"""
result = run_git_command("git rev-parse --git-dir")
return result is not None
def get_branch_data(sort_newest=False):
"""Get branch data from git"""
sort_param = "-committerdate" if sort_newest else "committerdate"
command = (f"git for-each-ref --sort={sort_param} "
f"--format=%(refname:short)|%(committerdate:relative)|"
f"%(committerdate:short)|%(committerdate:iso) refs/remotes")
output = run_git_command(command)
if not output:
return []
branches = []
for line in output.split('\n'):
if not line or '/HEAD' in line:
continue
parts = line.split('|')
if len(parts) >= 4:
branch = parts[0].replace('origin/', '') # Clean branch name
relative_date = parts[1]
short_date = parts[2]
iso_date = parts[3]
branches.append({
'branch': branch,
'relative_date': relative_date,
'short_date': short_date,
'iso_date': iso_date
})
return branches
def output_table(branches, sort_newest=False):
"""Output formatted table"""
if not branches:
print("No remote branches found.")
return
# Calculate column widths
max_branch_len = max(len(b['branch']) for b in branches)
max_relative_len = max(len(b['relative_date']) for b in branches)
max_date_len = max(len(b['short_date']) for b in branches)
# Ensure minimum widths for headers
branch_width = max(max_branch_len, len('Branch'))
relative_width = max(max_relative_len, len('Last Activity'))
date_width = max(max_date_len, len('Date'))
# Print header
print(f"Git Branch Age Report - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Sort order: {'newest first' if sort_newest else 'oldest first'}")
print()
# Print table header with proper spacing
header_format = f"{{:<{branch_width}}} | {{:<{relative_width}}} | {{:<{date_width}}}"
separator_format = f"{{:-<{branch_width}}}-+-{{:-<{relative_width}}}-+-{{:-<{date_width}}}"
print(header_format.format('Branch', 'Last Activity', 'Date'))
print(separator_format.format('', '', ''))
# Print data rows
for branch_data in branches:
print(header_format.format(
branch_data['branch'],
branch_data['relative_date'],
branch_data['short_date']
))
def output_markdown(branches, sort_newest=False):
"""Output markdown table"""
print("# Git Branch Age Report")
print()
print(f"Report generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Sort order: {'newest first' if sort_newest else 'oldest first'}")
print()
if not branches:
print("No remote branches found.")
return
print("| Branch | Last Activity | Date |")
print("|--------|---------------|------|")
for branch_data in branches:
print(f"| `{branch_data['branch']}` | {branch_data['relative_date']} | {branch_data['short_date']} |")
print()
print("---")
print("*Generated with git for-each-ref*")
def output_csv(branches):
"""Output CSV format"""
if not branches:
print("branch,last_activity,date,iso_date")
return
output = StringIO()
writer = csv.writer(output)
# Write header
writer.writerow(['branch', 'last_activity', 'date', 'iso_date'])
# Write data
for branch_data in branches:
writer.writerow([
branch_data['branch'],
branch_data['relative_date'],
branch_data['short_date'],
branch_data['iso_date']
])
print(output.getvalue().strip())
def main():
parser = argparse.ArgumentParser(
description='Generate reports showing the age of git remote branches'
)
# Sort options
sort_group = parser.add_mutually_exclusive_group()
sort_group.add_argument(
'-n', '--newest',
action='store_true',
help='Sort by newest branches first (default: oldest first)'
)
sort_group.add_argument(
'-o', '--oldest',
action='store_true',
help='Sort by oldest branches first (default)'
)
# Output format options
format_group = parser.add_mutually_exclusive_group()
format_group.add_argument(
'-t', '--table',
action='store_true',
default=True,
help='Output as formatted table (default)'
)
format_group.add_argument(
'-m', '--markdown',
action='store_true',
help='Output as markdown table'
)
format_group.add_argument(
'-c', '--csv',
action='store_true',
help='Output as CSV'
)
args = parser.parse_args()
# Check if we're in a git repository
if not check_git_repo():
print("Error: Not in a git repository", file=sys.stderr)
sys.exit(1)
# Get branch data
sort_newest = args.newest
branches = get_branch_data(sort_newest)
# Output in requested format
if args.csv:
output_csv(branches)
elif args.markdown:
output_markdown(branches, sort_newest)
else: # default table format
output_table(branches, sort_newest)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment