前段时间部门技术老板要求每个组统计每个研发同学在某个版本的千行 bug 率,所以第一步就要能够统计某个人在某个指定分支的提交信息。
这个很容易做,利用
1 git log d313a36e199^..HEAD --shortstat --author="Kingsley Chen" 
就可以拿到包含修改记录的提交信息。但是要同时排除掉 merge 提交的数据,避免出现重复和乱记。
对于普通的 git merge,只需要在前面的命令中加入 --no-merge 即可,但是问题在于,我们项目的分支管理采用的不是 git flow branching model,而是类似 chromium 的 trunk-based model。
我们所有的提交都会在 develop 上完成,发布某个版本时切除 release 分支,版本的修改在 release 上完成,发布后通过 git merge --squash 将 release 分支的新提交合会 develop;所以前面的 --no-merge 便没有了效果。
想到的一个方案是使用 --grep='Merge commits from' 来获得和并提交,然后利用 --invert-grep 进行取反。
但是在实践中我发现,--invert-grep 虽然在文档上说是 invert the previous grep,但是它实际上会导致 --author='' 过滤失效,因为 --author 内部实质上也是等价于一个 grep
这里有一个相关的问题 https://public-inbox.org/git/xmqqshjj4ce5.fsf@gitster.mtv.corp.google.com/  但是看起来官方认为这个行为并不是一个 bug。
最后我选择的解决方案是用 python 写一个完整的 tool,手动做了排除,代码也比较简单,看了一下都不超过 80 LOC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 import  reimport  shleximport  subprocessimport  sysFIRST_REV = ''  LAST_REV = ''  def  run (cmd ):         return  subprocess.check_output(shlex.split(cmd)).decode('utf-8' ) def  query_authors ():    return  list (map (lambda  l: l.strip().split('\t' )[1 ],                     run('git shortlog -sn {}^..{}' .format (FIRST_REV, LAST_REV)).splitlines())) def  sum_commits (records ):    insertion_num = 0      deletion_num = 0      for  stat in  records:         mr = re.search(r'(\d+)(?= insertion)' , stat)         if  mr:             insertion_num += int (mr.groups()[0 ])         mr = re.search(r'(\d+)(?= deletion)' , stat)         if  mr:             deletion_num += int (mr.groups()[0 ])     return  insertion_num, deletion_num def  print_analyzed (results ):    print ('{0:>20}Insertion{0:>10}Deletion{0:>10}Total' .format (' ' ))     for  author, records in  results.items():         print ('{1:<20}{2:>6}{0:<10}{3:>6}{0:<10}{4:>6}' .format (' ' ,             author, records[0 ], records[1 ], records[2 ])) def  main ():    if  len (sys.argv) < 3 :         print ('No full range specified! Analyze entire history for current branch.' )         return      global  FIRST_REV, LAST_REV     FIRST_REV = sys.argv[1 ]     LAST_REV = sys.argv[2 ]     authors = query_authors()     results = {}     for  author in  authors:         commits_query = 'git log {}^..{} --shortstat --author="{}"' .\                             format (FIRST_REV, LAST_REV, author)         commits = list (filter (lambda  s: re.search('files? changed' , s),                               run(commits_query).splitlines()))         total_insertions, total_deletions = sum_commits(commits)         excluded_query = commits_query + ' --grep="Merge commits"'          excluded_commits = list (filter (lambda  s: re.search('files? changed' , s),                                        run(excluded_query).splitlines()))         excluded_insertions, excluded_deletions = sum_commits(excluded_commits)         total_insertions -= excluded_insertions         total_deletions -= excluded_deletions         results[author] = (total_insertions, total_deletions, total_insertions + total_deletions)     print_analyzed(results) if  __name__ == '__main__' :    main()