Skip to content

Instantly share code, notes, and snippets.

@marcaurele
Created January 12, 2026 21:06
Show Gist options
  • Select an option

  • Save marcaurele/e45a8fa4b411b8148eece62171c1952e to your computer and use it in GitHub Desktop.

Select an option

Save marcaurele/e45a8fa4b411b8148eece62171c1952e to your computer and use it in GitHub Desktop.
Git statistics over commits across a yearly time window.
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
# uv run git_stats.py 2025 <optional author>
import subprocess
import sys
from collections import defaultdict
def get_detailed_author_stats(year, target_author):
# Format: HASH | DATE | ADDED | REMOVED
cmd = [
"git", "log",
f"--since={year}-01-01",
f"--until={year}-12-31",
f"--author={target_author}",
"--numstat",
"--pretty=format:COMMIT:%h|%ad",
"--date=short",
"--",
"**/test_*.py",
# "**/tests/*.py",
]
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
commits = []
current_commit = None
for line in result.stdout.splitlines():
line = line.strip()
if not line: continue
if line.startswith("COMMIT:"):
details = line.replace("COMMIT:", "").split("|")
current_commit = {"hash": details[0], "date": details[1], "added": 0, "removed": 0}
commits.append(current_commit)
continue
if current_commit:
parts = line.split()
if len(parts) >= 3:
try:
current_commit["added"] += int(parts[0])
current_commit["removed"] += int(parts[1])
except ValueError:
continue
# Sort: (Added + Removed) descending
commits.sort(key=lambda x: x["added"] + x["removed"], reverse=True)
print(f"\n--- Commit Detail for '{target_author}' in {year} ---")
print(f"{'Hash':<8} | {'Date':<12} | {'Added':>8} | {'Removed':>8}")
print("-" * 45)
for c in commits:
if c["added"] + c["removed"] > 0:
print(f"{c['hash']:<8} | {c['date']:<12} | {c['added']:>8} | {c['removed']:>8}")
except subprocess.CalledProcessError as e:
print(f"Error executing git: {e}")
def get_summary_stats(year):
# (Existing summary logic from previous step)
cmd = [
"git", "log",
f"--since={year}-01-01",
f"--until={year}-12-31",
"--numstat",
"--pretty=format:AUTHOR:%aN",
"--",
"**/test_*.py",
# "**/tests/*.py",
]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
stats = defaultdict(lambda: {"added": 0, "removed": 0})
current_author = None
for line in result.stdout.splitlines():
if line.startswith("AUTHOR:"):
current_author = line.replace("AUTHOR:", "")
elif current_author and line.strip():
parts = line.split()
if len(parts) >= 3:
try:
stats[current_author]["added"] += int(parts[0])
stats[current_author]["removed"] += int(parts[1])
except ValueError: continue
print(f"\n--- Summary for {year} ---")
print(f"{'Author':<25} | {'Added':>10} | {'Removed':>10}")
print("-" * 51)
for author, counts in sorted(stats.items(), key=lambda x: x[1]['added'], reverse=True):
print(f"{author[:25]:<25} | {counts['added']:>10} | {counts['removed']:>10}")
if __name__ == "__main__":
year = sys.argv[1] if len(sys.argv) > 1 else "2025"
author = sys.argv[2] if len(sys.argv) > 2 else None
if author:
get_detailed_author_stats(year, author)
else:
get_summary_stats(year)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment