AI 生成: 一个命令行工具,输入 Git 仓库路径后统计每个作者的提交次数并生成排名。

This commit is contained in:
admin
2026-10-02 06:22:00 +08:00
commit 13240b8eae
2 changed files with 233 additions and 0 deletions
+151
View File
@@ -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()