使用python基于git log统计开发代码量

前言

近日项目组开始统计开发的提交信息,本文依据本地仓库,使用Git log 进行统计数据,然后记录实现过程。几乎每步都有注释,希望能够帮助到各位QA及经理。欢迎加微信探讨wx06114296

  1. 找到代码库,获取最新的提交信息,本质就是bash 命令中的git fetch,用python程序来执行。将目录指针切换到代码目录,再git fetch即可。参考代码的git_fetch方法
  2. 在python中使用git log 获取提交信息。参考代码的get_object方法
  3. 将git log 得到的数据解析成矩阵,用于合并同一作者同一天的代码提交量,参考代码的write_data方法,其中translate_author是将提交人信息转换成中文,一个实际开发可能存在多个账户。
  4. 将合并前与合并后的数据,分别保存在Excel中,为后续代码提交量的图标分析做数据支撑。参考write_data_item,write_excel。
  5. 做成数据透析图,一眼看出趋势线以及最佳最差。此步骤暂未用代码实现
import re
import time
import pandas as pd
import xlrd
import xlwt
from git.repo import Repo
from xlutils.copy import copy
from git import Git
import datetime
# 定义全局变量代码仓库
global code_base

def git_fetch():
	# 根据仓库名称找到本地代码文件目录
    if code_base == "nky":
        code_path = "E:\\nkyworkspace\\nky\\nky"
    elif code_base == "webapp":
        code_path = "E:\\nkyworkspace\\webapp"
    else:
        code_path = "E:\\nkyworkspace\\mobile"
    # 定位到本地代码目录
    g = Git(code_path)
    # 获取远程仓库最新的提交信息,包含所有分支
    g.fetch()


def get_object(start_date):
    # 此方法用于针对固定的本地代码分支,拉取指定格式的Git_log信息
    if code_base == "nky":
        code_path = "E:\\nkyworkspace\\nky\\nky"
    elif code_base == "webapp":
        code_path = "E:\\nkyworkspace\\webapp"
    else:
        code_path = "E:\\nkyworkspace\\mobile"
    repo = Repo(path=code_path)
    # a=repo.iter_commits("master")
    # repo = repo.git.log("--since='2022-9-15'", "--until='2022-9-16'")
    # 执行git命令,参数用逗号隔开,Git_bash弹窗中 用空格隔开
    """
    # --stat 查看提交历史,并展示摘要内容(摘要会列出修改的文件以及每个文件中修改了多少行)
    # --shortstat 查看提交历史,并显示摘要内容(只是统计并展示修改了多少内容儿不显示具体哪些文件做出了修改)
    # --pretty xxx 该命令可以用来指定使用不同于默认格式的方式展示提交历史,后面的xxx表示具体的取值,取值有:oneline , short , full , fuller 等
    # --pretty=oneline
    # 执行该命令后会把提交历史的 commit 描述以及校验和 显示在同一行,并且省略默认格式下的其他内容
    # -pretty=short
    # 执行该命令后,只是比默认的格式少了Data日期的描述
    # --pretty=full
    # 执行该命令后,与默认的格式相比少了Data日期的描述,但是增加了commit 提交人信息
    # --name-only
    # 仅在默认格式后面展示已经修改的文件
    # 8.2 git log --abbrev-commit
    # 仅显示 SHA-1 的前几个字符,而非全部字符(这个 SHA-1 字符就是指的校验和,我习惯称为commit id),如下图:
    # 8.3 git log --relative-date
    # 以相对当前的时间展示提交历史
    # 8.4 git log --graph
    # 在展示提交历史前面加入简单的ASCII图形,区分每次提交历史
    # 8.5 git log --oneline
    # log后面直接跟 --oneline 时,显示短的 校验和,并与提交描述显示在同一行
    # 8.6 git log --oneline --graph
    # 以树形结构查看短描述和校验值
    # 8.7 git log -- author=用户名
    # 如:git log --author=CnPeng 就会展示出CnPeng这个用户的修改历史 。注意:这里的用户名,是初始化git 时传入的name . 运行效果如下图:
    # 8.8 git log -- commitor=用户名
    # 如:git log --commitor=CnPeng 就会展示出CnPeng这个用户的提交历史。注意:这里的用户名,是初始化git 时传入的name . 效果图参考上面的author图
    # --since=时间 如:git log --since=1days , 表示,展示1天前的提交历史,具体的时间取值,可以有如下格式: xxxdays , xxxweeks ,
    # 2016-11-25 , 或 2 years 1 day 3 minutes ago
    # 另外,除了可以使用 --since , 也可以使用 -- after , --util , --before , 取值方式相同
    # 也可以使用如下这种组合模式:
    # git log --pretty="%h - %s" --author=gitster --since="2008-10-01" \ --before="2008-11-01" --no-merges -- t/
    # 上面的组合模式中,%h , %s 是占位符, 详细的占位符以及含义如下:
    # %H 提交对象(commit)的完整哈希字串
    # %h 提交对象的简短哈希字串
    # %T 树对象(tree)的完整哈希字串
    # %t 树对象的简短哈希字串
    # %P 父对象(parent)的完整哈希字串
    # %p 父对象的简短哈希字串
    # %an 作者(author)的名字
    # %ae 作者的电子邮件地址
    # %ad 作者修订日期(可以用 -date= 选项定制格式)
    # %ar 作者修订日期,按多久以前的方式显示
    # %cn 提交者(committer)的名字
    # %ce 提交者的电子邮件地址
    # %cd 提交日期
    # %cr 提交日期,按多久以前的方式显示
    # %s 提交说明
    """
    # --branches 指所有分支 --remotes 指远程分支,为保证数据准确性,拉取最新的代码git fetch 和git pull
    # repo = str(repo.git.log("--since=1days", "--no-merges", "--branches", "--remotes", "--shortstat",
    #                         "--pretty=startLog%an-%cd"))
    repo = str(
        repo.git.log("--since='%s'" % str(start_date) + " 00:00:00", "--until='%s'" % str(start_date) + " 23:59:59",
                     "--no-merges", "--branches", "--remotes", "--shortstat", "--pretty=startLog%an-%cd"))
    # print(repo)
    # 去掉换行符
    repo = repo.replace('\n', '')
    # 由于git.log命名中,故意增加了startLog字符,用于分割提交记录
    repo = repo.split("startLog")
    # 将得到的list去除空字符串,最终得到的结果应该类似如下
    """
    
    repo = ['Mway-Fri Sep 16 14:47:54 2022 +0800 2 files changed, 2 insertions(+), 1 deletion(-)',
            'Mway-Fri Sep 16 09:44:08 2022 +0800 1 file changed, 2 insertions(+)',
            'yeweigang-Fri Sep 16 09:40:30 2022 +0800 1 file changed, 1 insertion(+), 1 deletion(-)',
            'yeweigang-Fri Sep 16 09:36:52 2022 +0800 55 files changed, 3331 insertions(+), 67 deletions(-)',
            'Mway-Thu Sep 15 18:26:38 2022 +0800 2 files changed, 12 deletions(-)']
    """
    repo = [x.strip() for x in repo if x.strip() != '']
    print(repo)
    return repo


def write_data(text, write_date):
    # 此方法用于数据解析,将git拿到的内容,转换成想要的数据并保存
    # 参数格式如下
    test_text = ['Mway-Fri Sep 16 14:47:54 2022 +0800 2 files changed, 2 insertions(+), 1 deletion(-)',
                 'Mway-Fri Sep 16 09:44:08 2022 +0800 1 file changed, 2 insertions(+)',
                 'yeweigang-Fri Sep 16 09:40:30 2022 +0800 1 file changed, 1 insertion(+), 1 deletion(-)',
                 'yeweigang-Fri Sep 16 09:36:52 2022 +0800 55 files changed, 3331 insertions(+), 67 deletions(-)',
                 'Mway-Thu Sep 15 18:26:38 2022 +0800 2 files changed, 12 deletions(-)']
    # 定义三个空列表,用于分别存放提交人、增加行、删除行,方便转换成矩阵视图,方便分组求和计数
    if len(text) == 0:
        print("无提交信息,无需记录表格")
    else:
        null_author = []
        null_add_line = []
        null_del_line = []
        # 将列表数据遍历,解析提交信息
        for text1 in text:
            # 用-拆解信息,取左边部分为提交人
            author = str(re.split("-", text1)[0])
            # 将获取到的提交人,添加到总的列表中
            null_author.append(author)
            # 获取提交日期信息,用于存放明细表
            sub_time = str(re.findall("\-(.*?)\+0800", text1)).replace('ybj-', "").replace("[\'", "").replace("\']", "")
            # print(sub_time)
            # 周
            week = str(re.split(" ", sub_time)[0])
            # 月
            month = str(re.split(" ", sub_time)[1])
            month_number = translate_month(month)
            # 日
            work_day = str(re.split(" ", sub_time)[2])
            # 时间
            work_time = str(re.split(" ", sub_time)[3])
            # 年
            work_year = str(re.split(" ", sub_time)[4])
            work_date = work_year + "-" + month_number + "-" + work_day
            # 用字符串changed分割字符串,取右侧部分,找到提交信息,如【 2 insertions(+), 1 deletion(-)】
            total_line = str(re.split("changed,", text1)[1])
            # 若提交信息中没有新增行,将字符串构造成 新增0行,方便解析
            if "+" not in total_line:
                total_line = "0 insertions(+)," + total_line
            # 若提交信息中没有删除行,将字符串构造成 删除0行,方便解析
            if "-" not in total_line:
                total_line = total_line + ",0 deletions(-)"
            # 用字符串insert分割【 2 insertions(+), 1 deletion(-)】,取左侧部分,替换空格,得到新增行
            add_line = int(re.split("insert", total_line)[0].replace(" ", ""))
            # 将取到的新增行,添加到列表中
            null_add_line.append(add_line)
            # 用字符串insert分割【 2 insertions(+), 1 deletion(-)】,取右侧部分,只截取数字部分,得到删除行
            del_line = int(re.findall(r'\d+', re.split("insert", total_line)[1])[0])
            # 将取到的删除行,添加到列表中
            null_del_line.append(del_line)
            # 写入明细表
            write_data_item(code_base, translate_author(author), work_date, week, work_time, add_line, del_line)

        # 将三个列表信息转换到矩阵二维表中,来进行分组求和,数据举例
        # 其中第一列为序号,author为提交者,add_line为新增行,del_line为删除行,数据与test_text一致
        """
              author  add_line  del_line
        0       Mway         2         1
        1       Mway         2         0
        2  yeweigang         1         1
        3  yeweigang      3331        67
        4       Mway         0        12
        """
        df = pd.DataFrame({'author': null_author, "add_line": null_add_line, "del_line": null_del_line})
        # 构造新的矩阵,用于存放提交次数,第一行为标题
        # 与mysql的 select count('add_line') from 表 group by 'author' 类似
        """
        author
        Mway         3
        yeweigang    2
        """
        df1 = df.groupby("author").size()
        # 构造了新的矩阵,用于存放新增行合计,数据举例
        # 与mysql的 select sum('add_line') from 表 group by 'author' 类似
        """
        author
        Mway            4
        yeweigang    3332
        """
        add_lines = df.groupby("author")["add_line"].sum()
        # 构造了新的矩阵,用于存放删除行合计,数据举例
        """
        author
        Mway         13
        yeweigang    68
        """
        del_lines = df.groupby("author")["del_line"].sum()
        # 去重代码提交者
        authors = list(set(null_author))
        for author in authors:
            # 根据提交者去寻找新增行的矩阵中对应新增行
            add_line = int(add_lines[author])
            # 根据提交者去寻找删除行的矩阵中对应删除行
            del_line = int(del_lines[author])
            # 根据提交者去寻找提交次数的矩阵中对应次数
            submit_number = int(df1[author])
            # print(author, submit_number, add_line, del_line)
            # 将得到的数据填写到Excel中
            write_excel(code_base, translate_author(author), submit_number, add_line, del_line, write_date)


def translate_month(_month):
	# 将git log中获取到的英文月份转成数字月份
    _data = {
        "Jan": "1", "Feb": "2", "Mar": "3", "Apr": "4", "May": "5", "Jun": "6", "Jul": "7", "Aug": "8", "Sep": "9",
        "Oct": "10", "Nov": "11", "Dec": "12"
    }
    if _month in _data:
        return _data[_month]
    else:
        return _month


def translate_author(_name):
	# 将git log 中获取到的作者转换成中文名称
	# 因一个用户可能存在多台电脑多个账户,因此中文名称会存在重复
    _data = {
        "bryan": "叶柏军",
        "huanglijie": "黄李洁",
        "lenovo": "lenovo",
        "Mway": "侯明未",
        "nkybj01": "纪向峰",
        "tangting": "唐婷",
        "wumusenlin000": "张森林",
        "yeweigang": "叶伟刚",
        "yingxu.xuying": "徐莹",
        "yingxu.yingxu": "徐莹",
        "ywg": "叶伟刚"
    }
    if _name in _data:
 		# 没找到中文名称,就返回英文名称
        return _data[_name]
    else:
        return _name


def write_data_item(code, author, work_date, week, work_time, add_line, del_line,
                    xls_path='code_log.xls'):
    # 将每次执行数据写入到Excel
    # 读取Excel文件,若该Excel已被其他程序打开,则会运行报错
    wb = xlrd.open_workbook('code_log.xls', encoding_override='utf-8',
                            formatting_info=True)
    # copy读取信息
    wt = copy(wb)
    # 获取Excel中第一个sheet
    st = wb.sheet_by_index(1)
    # 在此sheet中寻找数据的最大行数
    max_row = st.nrows
    # 获取复制后读取的Excel文件信息中的第一个sheet
    w = wt.get_sheet(1)
    # 开始写入信息,每次循环最大行数max_row固定
    w.write(max_row, 0, code)
    # 因考虑到代码每日运行统计代码提交量,记录执行日期
    w.write(max_row, 1, author)
    w.write(max_row, 2, work_date)
    w.write(max_row, 3, week)
    w.write(max_row, 4, work_time)
    w.write(max_row, 5, add_line)
    w.write(max_row, 6, del_line)
    # 根据日期返回填充第几周,方便通过周数统计代码量
    w.write(max_row, 7, "=WEEKNUM(C%s)" % str(max_row + 1))
    # 保存Excel文件
    wt.save('code_log.xls')


def write_excel(code, author, submit_number, add_line, del_line, write_date, xls_path='code_log.xls'):
    # 将每次执行数据写入到Excel
    # 读取Excel文件,若该Excel已被其他程序打开,则会运行报错
    wb = xlrd.open_workbook('code_log.xls', encoding_override='utf-8',
                            formatting_info=True)
    # copy读取信息
    wt = copy(wb)
    # 获取Excel中第一个sheet
    st = wb.sheet_by_index(0)
    # 在此sheet中寻找数据的最大行数
    max_row = st.nrows
    # 获取复制后读取的Excel文件信息中的第一个sheet
    w = wt.get_sheet(0)
    # 开始写入信息,每次循环最大行数max_row固定
    w.write(max_row, 0, code)
    # 因考虑到代码每日运行统计代码提交量,记录执行日期
    # w.write(max_row, 1, time.strftime("%Y-%m-%d %H:%M"))
    w.write(max_row, 1, str(write_date))
    w.write(max_row, 2, author)
    w.write(max_row, 3, submit_number)
    w.write(max_row, 4, add_line)
    w.write(max_row, 5, del_line)
    # 根据日期返回填充第几周,方便通过周数统计代码量
    w.write(max_row, 6, "=WEEKNUM(B%s)" % str(max_row + 1))
    # 保存Excel文件
    wt.save('code_log.xls')

if __name__ == "__main__":
    # a = get_object("webapp")
    # write_data(a)
    code_base = "mobile"
    git_fetch()
    begin = datetime.date(2022, 10, 16)
    end = datetime.date(2022, 10, 17)
    # 循环begin和end期间的每一天
    for d in range((end - begin).days + 1):
        day = begin + datetime.timedelta(d)
        # print(day)
        a = get_object(day)
        write_data(a, day)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值