commit 13240b8eae1c1463d838e1860c56d7c9765e485e Author: admin Date: Fri Oct 2 06:22:00 2026 +0800 AI 生成: 一个命令行工具,输入 Git 仓库路径后统计每个作者的提交次数并生成排名。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..98f569b --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +# git-author-stats + +一个命令行工具,用于统计 Git 仓库中每个作者的提交次数并生成排名。 + +## 功能特点 + +- 输入 Git 仓库路径,统计所有作者的提交次数 +- 按提交次数降序排列,生成排名 +- 支持显示提交占比 +- 支持查看指定分支的统计 +- 无需安装额外依赖(仅使用 Python 标准库) + +## 环境要求 + +- Python 3.6+ +- 系统中已安装 Git,并且 `git` 命令可在终端中直接调用 + +## 安装 + +无需安装,直接下载 `git_author_stats.py` 文件即可使用。 + +## 使用方法 + +```bash +git_author_stats.py <仓库路径> [选项] +``` + +### 参数说明 + +| 参数 | 说明 | +| --- | --- | +| `repo_path` | Git 仓库的路径(必填) | +| `--branch` / `-b` | 指定分支,默认为当前分支 | +| `--top` / `-t` | 只显示前 N 名作者 | +| `--percent` / `-p` | 显示每位作者的提交占比 | + +### 示例 + +```bash +# 统计当前仓库 +git_author_stats.py . + +# 统计指定路径的仓库 +git_author_stats.py /path/to/repo + +# 统计指定分支并显示前 5 名 +git_author_stats.py /path/to/repo --branch main --top 5 + +# 显示提交占比 +git_author_stats.py /path/to/repo --percent +``` + +### 输出示例 + +``` +仓库: /path/to/repo +分支: main +总提交数: 128 + +排名 作者 提交次数 占比 +------------------------------------------------ +1 Alice 68 53.13% +2 Bob 42 32.81% +3 Charlie 18 14.06% +``` + +## 实现原理 + +工具通过调用 `git log` 命令获取提交记录,并解析每条提交的作者信息, +然后使用字典统计每位作者的提交次数,最后按次数排序输出。 + +## 目录结构 + +``` +git-author-stats/ +├── README.md +└── git_author_stats.py +``` + +## 许可证 + +MIT diff --git a/git_author_stats.py b/git_author_stats.py new file mode 100644 index 0000000..f6fe59e --- /dev/null +++ b/git_author_stats.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +git-author-stats + +统计 Git 仓库中每个作者的提交次数并生成排名。 + +用法: + git_author_stats.py <仓库路径> [--branch 分支] [--top N] [--percent] +""" + +import argparse +import os +import subprocess +import sys +from collections import Counter + + +def run_git_log(repo_path, branch=None): + """调用 git log 获取作者列表。 + + 使用 --format=%aN 只输出作者名字,每行一个。 + 返回作者名字的列表。 + """ + cmd = ["git", "-C", repo_path, "log", "--format=%aN"] + if branch: + cmd.append(branch) + + try: + result = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + text=True, + encoding="utf-8", + errors="replace", + ) + except FileNotFoundError: + print("错误: 未找到 git 命令,请先安装 Git。", file=sys.stderr) + sys.exit(1) + except subprocess.CalledProcessError as e: + print("错误: 执行 git log 失败。", file=sys.stderr) + if e.stderr: + print(e.stderr.strip(), file=sys.stderr) + sys.exit(1) + + authors = [line.strip() for line in result.stdout.splitlines() if line.strip()] + return authors + + +def count_authors(authors): + """统计每位作者的提交次数,返回按次数降序排列的列表。""" + counter = Counter(authors) + # 按提交次数降序,次数相同按作者名字升序,保证结果稳定 + return sorted(counter.items(), key=lambda item: (-item[1], item[0])) + + +def format_table(rows, total, show_percent): + """格式化输出表格。""" + # 计算列宽 + name_width = max([len(name) for name, _ in rows] + [len("作者")]) + 2 + rank_width = max(len(str(len(rows))), len("排名")) + 2 + count_width = max([len(str(c)) for _, c in rows] + [len("提交次数")]) + 2 + + header = ( + f"{'排名':<{rank_width}}" + f"{'作者':<{name_width}}" + f"{'提交次数':<{count_width}}" + ) + if show_percent: + header += "占比" + + lines = [header, "-" * (rank_width + name_width + count_width + (10 if show_percent else 0))] + + for idx, (name, count) in enumerate(rows, start=1): + line = ( + f"{idx:<{rank_width}}" + f"{name:<{name_width}}" + f"{count:<{count_width}}" + ) + if show_percent: + percent = (count / total * 100) if total else 0.0 + line += f"{percent:.2f}%" + lines.append(line) + + return "\n".join(lines) + + +def parse_args(argv=None): + parser = argparse.ArgumentParser( + prog="git-author-stats", + description="统计 Git 仓库中每个作者的提交次数并生成排名。", + ) + parser.add_argument("repo_path", help="Git 仓库路径") + parser.add_argument( + "-b", "--branch", + default=None, + help="指定分支(默认当前分支)", + ) + parser.add_argument( + "-t", "--top", + type=int, + default=None, + help="只显示前 N 名作者", + ) + parser.add_argument( + "-p", "--percent", + action="store_true", + help="显示每位作者的提交占比", + ) + return parser.parse_args(argv) + + +def main(argv=None): + args = parse_args(argv) + + repo_path = os.path.abspath(args.repo_path) + if not os.path.isdir(repo_path): + print(f"错误: 路径不存在或不是目录: {args.repo_path}", file=sys.stderr) + sys.exit(1) + + # 检查是否是 Git 仓库 + if not os.path.isdir(os.path.join(repo_path, ".git")): + # 也可能是在 worktree 中,.git 是文件 + git_marker = os.path.join(repo_path, ".git") + if not os.path.exists(git_marker): + print(f"错误: 该路径不是 Git 仓库: {args.repo_path}", file=sys.stderr) + sys.exit(1) + + authors = run_git_log(repo_path, args.branch) + if not authors: + print("该仓库没有任何提交记录。") + return + + ranking = count_authors(authors) + total = len(authors) + + if args.top is not None and args.top > 0: + ranking = ranking[: args.top] + + branch_display = args.branch if args.branch else "(当前分支)" + print(f"仓库: {repo_path}") + print(f"分支: {branch_display}") + print(f"总提交数: {total}") + print() + print(format_table(ranking, total, args.percent)) + + +if __name__ == "__main__": + main()