#!/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()