From cdff2e561e0f22986a5f9b78eb7c6f6f0ed6307f Mon Sep 17 00:00:00 2001 From: Milan Date: Wed, 25 Feb 2026 18:56:58 +0100 Subject: [PATCH] feat: add author filtering Co-authored-by: Cursor --- README.md | 7 ++++++ gitstats/cli.py | 58 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index d179b9e..dc8d15e 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ - ⏰ Peak coding hours analysis - 🔥 Commit streaks tracking - 📅 Date range filtering +- 👤 Author filtering - 👥 Contributor insights - 🎨 Beautiful terminal output - 📤 JSON output for scripting @@ -46,6 +47,12 @@ gitstats stats --json # Filter by date range gitstats stats --since 2025-01-01 gitstats stats --since 2025-01-01 --until 2025-12-31 + +# Filter by author +gitstats stats --author "Milan" + +# Combine filters +gitstats stats --author "Milan" --since 2025-01-01 --json ``` ## 🛠️ Development diff --git a/gitstats/cli.py b/gitstats/cli.py index 8dc6aca..3340ac2 100644 --- a/gitstats/cli.py +++ b/gitstats/cli.py @@ -57,6 +57,12 @@ def stats( "-u", help="Show commits before this date (YYYY-MM-DD)", ), + author: str = typer.Option( + None, + "--author", + "-a", + help="Filter commits by author name (case-insensitive, partial match)", + ), ) -> None: """Show commit statistics for a git repository.""" import json @@ -96,23 +102,36 @@ def stats( console.print("[red]No commits found or not a git repository.[/]") raise typer.Exit(1) - # Filter commits by date range + # Filter commits by date range and/or author commits = stats_data["commits"] + filters_applied = [] + if since_date or until_date: commits = _filter_commits_by_date(commits, since_date, until_date) - if not commits: - if json_output: - console.print(json.dumps({"error": "No commits found in the specified date range"})) - else: - console.print("[red]No commits found in the specified date range.[/]") - raise typer.Exit(1) - # Recalculate author stats for filtered commits + filters_applied.append("date") + + if author: + commits = _filter_commits_by_author(commits, author) + filters_applied.append("author") + + if filters_applied and not commits: + error_msg = "No commits found matching the specified filters" + if json_output: + console.print(json.dumps({"error": error_msg})) + else: + console.print(f"[red]{error_msg}[/]") + raise typer.Exit(1) + + # Recalculate stats if any filters were applied + if filters_applied: from gitstats.parser import get_author_stats + unique_authors = {c["author"] for c in commits} stats_data = { **stats_data, "commits": commits, "total_commits": len(commits), + "total_authors": len(unique_authors), "first_commit": commits[0]["date"].strftime("%Y-%m-%d"), "last_commit": commits[-1]["date"].strftime("%Y-%m-%d"), "author_stats": get_author_stats(commits), @@ -128,9 +147,10 @@ def stats( "total_authors": stats_data["total_authors"], "first_commit": stats_data["first_commit"], "last_commit": stats_data["last_commit"], - "date_filter": { + "filters": { "since": since_date.strftime("%Y-%m-%d") if since_date else None, "until": until_date.strftime("%Y-%m-%d") if until_date else None, + "author": author, }, "authors": stats_data["author_stats"], "streaks": streaks, @@ -143,14 +163,16 @@ def stats( # Pretty terminal output console.print(f"\n[bold]📊 Git Statistics for:[/] [cyan]{path}[/]") - # Show date range if filtered - if since_date or until_date: - range_str = "" + # Show filters if applied + if filters_applied: + filter_parts = [] if since_date: - range_str += f"from {since_date.strftime('%Y-%m-%d')} " + filter_parts.append(f"from {since_date.strftime('%Y-%m-%d')}") if until_date: - range_str += f"to {until_date.strftime('%Y-%m-%d')}" - console.print(f"[dim]Filtered: {range_str.strip()}[/]") + filter_parts.append(f"to {until_date.strftime('%Y-%m-%d')}") + if author: + filter_parts.append(f"author: {author}") + console.print(f"[dim]Filtered: {' '.join(filter_parts)}[/]") console.print() @@ -231,6 +253,12 @@ def _print_activity_heatmap(commits: list[dict]) -> None: console.print() +def _filter_commits_by_author(commits: list[dict], author: str) -> list[dict]: + """Filter commits by author name (case-insensitive partial match).""" + author_lower = author.lower() + return [c for c in commits if author_lower in c["author"].lower()] + + def _filter_commits_by_date( commits: list[dict], since_date,