diff --git a/README.md b/README.md index 9435595..7b43afc 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ - 📈 Commit statistics at a glance - 🗓️ Activity heatmap by day of week - ⏰ Peak coding hours analysis +- 🔥 Commit streaks tracking - 👥 Contributor insights - 🎨 Beautiful terminal output - 📤 JSON output for scripting diff --git a/gitstats/cli.py b/gitstats/cli.py index b94a386..c3eb0ad 100644 --- a/gitstats/cli.py +++ b/gitstats/cli.py @@ -49,7 +49,12 @@ def stats( """Show commit statistics for a git repository.""" import json - from gitstats.parser import get_commit_stats, get_hourly_activity, get_weekly_activity + from gitstats.parser import ( + get_commit_stats, + get_commit_streaks, + get_hourly_activity, + get_weekly_activity, + ) stats_data = get_commit_stats(path) @@ -60,6 +65,8 @@ def stats( console.print("[red]No commits found or not a git repository.[/]") raise typer.Exit(1) + streaks = get_commit_streaks(stats_data["commits"]) + if json_output: # Build JSON output output = { @@ -69,6 +76,7 @@ def stats( "first_commit": stats_data["first_commit"], "last_commit": stats_data["last_commit"], "authors": stats_data["author_stats"], + "streaks": streaks, "weekly_activity": get_weekly_activity(stats_data["commits"]), "hourly_activity": get_hourly_activity(stats_data["commits"]), } @@ -91,6 +99,9 @@ def stats( # Show activity heatmap _print_activity_heatmap(stats_data["commits"]) + # Show streaks + _print_streaks(streaks) + def _print_author_table(author_stats: list[dict]) -> None: """Print a table of author statistics.""" @@ -152,5 +163,25 @@ def _print_activity_heatmap(commits: list[dict]) -> None: console.print() +def _print_streaks(streaks: dict) -> None: + """Print commit streak statistics.""" + console.print("[bold]🔥 Commit Streaks[/]\n") + + current = streaks["current_streak"] + longest = streaks["longest_streak"] + active_days = streaks["total_active_days"] + + # Current streak with fire emojis based on length + if current > 0: + fires = "🔥" * min(current, 5) + console.print(f" [green]Current streak:[/] {current} days {fires}") + else: + console.print(" [dim]Current streak:[/] 0 days (no recent commits)") + + console.print(f" [yellow]Longest streak:[/] {longest} days") + console.print(f" [cyan]Total active days:[/] {active_days}") + console.print() + + if __name__ == "__main__": app() diff --git a/gitstats/parser.py b/gitstats/parser.py index 4f85c19..e27acf8 100644 --- a/gitstats/parser.py +++ b/gitstats/parser.py @@ -186,3 +186,59 @@ def get_hourly_activity(commits: list[dict]) -> list[dict]: } for h in range(24) ] + + +def get_commit_streaks(commits: list[dict]) -> dict: + """ + Calculate commit streaks (consecutive days with commits). + + Args: + commits: List of commit dictionaries + + Returns: + Dictionary with streak statistics + """ + from datetime import timedelta + + if not commits: + return {"current_streak": 0, "longest_streak": 0, "total_active_days": 0} + + # Get unique dates (just the date part, no time) + commit_dates = sorted({commit["date"].date() for commit in commits}) + + if not commit_dates: + return {"current_streak": 0, "longest_streak": 0, "total_active_days": 0} + + # Calculate streaks + streaks = [] + current_streak = 1 + + for i in range(1, len(commit_dates)): + if commit_dates[i] - commit_dates[i - 1] == timedelta(days=1): + current_streak += 1 + else: + streaks.append(current_streak) + current_streak = 1 + + streaks.append(current_streak) + + longest_streak = max(streaks) if streaks else 0 + + # Check if current streak is active (last commit was today or yesterday) + from datetime import date + + today = date.today() + last_commit_date = commit_dates[-1] + days_since_last = (today - last_commit_date).days + + if days_since_last <= 1: + active_streak = streaks[-1] if streaks else 0 + else: + active_streak = 0 + + return { + "current_streak": active_streak, + "longest_streak": longest_streak, + "total_active_days": len(commit_dates), + "last_commit_date": str(last_commit_date), + }