准备工作

没有安装python的先安装再跑脚本

使用

解压后,在脚本目录cmd,然后输入以下命令启动脚本

python main.py 本地git仓库地址

或者也可以这样,先调起脚本,进去再输入本地git仓库地址

python main.py

统计完后也可以把结果输出到csv文件上

源码包:下载源码

import subprocess
import sys
from collections import defaultdict
import os
import time


def get_git_author_lines(repo_path):
    # 保存原目录
    original_dir = os.getcwd()

    try:
        # 切换到仓库目录
        os.chdir(repo_path)
    except FileNotFoundError:
        print(f"错误:无法找到路径 {repo_path}")
        return

    # 查看是否是 Git 仓库
    result = subprocess.run(
        ['git', 'rev-parse', '--is-inside-work-tree'], capture_output=True, text=True)
    if result.returncode != 0:
        print("错误:该路径不是 Git 仓库")
        return

    # 进度条特效
    progress_bar(f"正在统计仓库{repo_path}", 10)

    # 配置信息
    cmd = [
        'git', 'log',  # 获取作者和 numstat 信息
        '--no-merges',  # 排除合并提交
        '--pretty=format:%aN',  # 输出作者名
        '--numstat'  # 输出每文件增删行数
    ]

    # 拼接命令并执行
    result = subprocess.run(cmd, capture_output=True,
                            text=True, encoding='utf-8', errors='ignore')
    if result.returncode != 0:
        print("Git 拼接命令执行失败:", result.stderr)
        return

    # 分割result数据
    lines = result.stdout.splitlines()

    # 防止被调用到没有初始化的键,及可以直接使用author_stats[aaa][added]、author_stats[bbb][added]
    author_stats = defaultdict(lambda: {'added': 0, 'deleted': 0})

    # 循环遍历结果
    i = 0
    while i < len(lines):
        line = lines[i].strip()
        if not line:
            i += 1
            continue
        if line.startswith(('commit', 'Merge:', 'Author:', 'Date:')):
            # 跳过提交信息头
            i += 1
            continue

        # 作者名
        author = line.strip()
        i += 1

        # numstat 数据(插入、删除、文件)
        while i < len(lines) and lines[i].strip() and not lines[i].startswith(('commit', 'Author:', 'Date:', ' ')):
            parts = lines[i].split('\t')
            if len(parts) == 3:
                try:
                    added = int(parts[0]) if parts[0] != '-' else 0
                    deleted = int(parts[1]) if parts[1] != '-' else 0
                    author_stats[author]['added'] += added
                    author_stats[author]['deleted'] += deleted
                except ValueError:
                    pass  # 忽略错误
            i += 1
        else:
            i += 1
    if not author_stats:
        print("未找到提交记录。")
        return

    # 进度条特效
    progress_bar("正在输出结果", 10)

    # 创建抬头
    print(f"{'作者':<15} {'新增行数':<10} {'删除行数':<10} {'净增行数':<10}")

    # # 按新增行数降序排列
    sorted_authors = sorted(author_stats.items(),
                            key=lambda x: x[1]['added'], reverse=True)

    # 输出结果
    for author, stats in sorted_authors:
        added = stats['added']
        deleted = stats['deleted']
        net = added - deleted
        print(f"{author:<18} {added:<13} {deleted:<13} {net:<13}")

    print()

    # 导出为 CSV
    export = input("\n是否导出为 CSV 文件?(y/n): ").strip().lower()
    if export == 'y':
        csv_file = os.path.join(repo_path, 'git_stats.csv')
        with open(csv_file, 'w', encoding='utf-8') as f:
            f.write('提交人,新增行数,删除行数,净增行数\n')
            for author, stats in sorted_authors:
                f.write(f'{author},{stats["added"]},{stats["deleted"]},{stats["added"] - stats["deleted"]}\n')
        print(f"已导出到:{csv_file}")

    # 回到原来的目录
    os.chdir(original_dir)


# 进度条特效
def progress_bar(name, sum):
    print(), print()  # 换行
    for i in range(5, sum + 1):
        bar = '█' * i + '.' * (sum - i)
        print(f"\r{name}: {i}/{sum} [{bar}]", end="", flush=True)
        time.sleep(0.1)
    print()  # 换行


if __name__ == '__main__':
    # 如果已经配置了仓库地址,就直接拿取这个地址
    if len(sys.argv) > 1:
        repo_path = sys.argv[1]
    else:
        # 配置仓库地址,并且移除空格和引号
        repo_path = input("请输入 Git 仓库的本地路径: ").strip().strip('"\'')

    get_git_author_lines(repo_path)
最后修改:2025 年 10 月 15 日
如果觉得我的文章对你有用,请随意赞赏