通过父修订增删行计算代码行数失效,无法获取正确修订代码行
嘿,我懂你这种头疼的感觉——挨个git checkout再跑wc -l统计代码行数,不仅慢到让人抓狂,而且那个看起来逻辑通顺的公式(当前版本行数=父版本行数+新增行数-删除行数)居然还不好使,对吧?咱们来捋清楚问题出在哪,再给你几个高效的解决办法。
为啥你的公式会失效?
这个公式的逻辑本身没毛病,但Git的diff统计藏着几个容易踩的坑,直接套用就会出错:
- 文件重命名/移动:Git默认会自动识别文件重命名,这时候diff里不会把它算成“删除旧文件+新增新文件”,而是标记为重命名。但你的公式是基于“新增-删除”的净变更,这时候就会漏掉这种情况,导致行数计算偏差。
- 文件复制:如果是复制现有文件生成新文件,Git的diff可能会把新文件标记为“新增”,但其实内容是完全复用的,这时候按公式算就会多统计行数。
- 二进制文件:
wc -l对二进制文件完全无效,但Git diff默认不会区分文本和二进制文件,统计出来的新增/删除行数根本没意义。 - 空提交或无文件变更的提交:有些提交可能只改了提交信息,没有文件变更,这时候diff的新增/删除都是0,但如果处理不当会导致计算出错。
高效获取每个版本总行数的两种方法
方法一:直接统计每个版本的总行数(最准确)
不用挨个checkout,直接用git show配合awk提取每个版本的文件总行数,速度比checkout快太多。
运行这个命令就能按提交顺序(从最早到最新)输出每个commit的哈希和对应的总行数:
git log --reverse --pretty=format:"%H" | while read commit; do echo -n "$commit " git show $commit --stat --pretty=format: | awk ' BEGIN { total = 0 } /^ [^ ]/ { split($NF, line_count, /[()]/); total += line_count[2] } END { print total } ' done
命令解释:
git log --reverse --pretty=format:"%H":按提交时间从早到晚输出每个commit的唯一哈希值。- 循环每个commit,用
git show $commit --stat获取该版本下所有文件的统计信息(每个文件行最后会显示(XX lines))。 - 用awk提取每个文件的行数并求和,得到该版本的总行数。
方法二:修正你的公式(适合只关心净变更的场景)
如果你的仓库很少有文件重命名/复制操作,或者你只关心代码的净变更行数,可以改进原公式,处理那些坑:
# 先获取第一个commit的总行数 first_commit=$(git log --reverse --pretty=format:"%H" | head -1) first_total=$(git show $first_commit --stat --pretty=format: | awk 'BEGIN{t=0}/^ [^ ]/{split($NF,a,/[()]/);t+=a[2]}END{print t}') echo "$first_commit $first_total" # 遍历后续commit,计算相对于父版本的总行数 git log --reverse --skip=1 --pretty=format:"%H %P" | while read commit parent; do # 获取diff的新增/删除行数,排除重命名识别(把重命名当成删除+新增) diff_result=$(git diff $parent $commit --shortstat --no-renames | awk '{split($0, stats, /[, ]+/); add=stats[4]; del=stats[6]}END{print add, del}') add_lines=${diff_result%% *} del_lines=${diff_result##* } # 处理空值(比如无文件变更的提交) add_lines=${add_lines:-0} del_lines=${del_lines:-0} current_total=$((first_total + add_lines - del_lines)) echo "$commit $current_total" first_total=$current_total done
注意:
加上--no-renames参数后,Git会把重命名的文件当成“删除旧文件+新增新文件”,这时候公式就成立了,但代价是统计的总行数会包含重命名的文件(相当于重复统计了一次内容),所以这个方法只适合不需要考虑文件移动/重命名的场景。
一些优化小技巧
- 只统计文本文件:如果你的仓库里有二进制文件,可以加上
--text参数排除它们,比如git show $commit --stat --text。 - 排除指定文件/目录:如果想忽略
node_modules、日志文件等,可以用路径参数过滤,比如git show $commit --stat --pretty=format: -- . ':!node_modules' ':!*.log'。 - 加速大型仓库:对于超大型仓库,可以把
git log的输出先保存到文件,再并行处理,但一般情况下上面的方法已经足够高效。
内容的提问来源于stack exchange,提问作者coder.chenzhi
相关产品推荐
相关产品推荐

