AI 生成: 一个命令行工具,输入 Git 仓库路径后统计每个作者的提交次数并生成排名。
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user