1. 为什么 git log 值得单独花时间搞明白
很多人用 Git 的日常就是add、commit、push三件套,git log顶多敲一下看看最近提交了什么,然后按q退出。我刚开始也这样,直到有一次线上出了个诡异问题,需要回溯“这个配置项到底是哪个提交、哪个人、在什么背景下改的”,我才发现git log远不止“看提交记录”这么简单。它其实是 Git 里最被低估的排查工具,用好了能顶半个代码考古铲。
git log的核心作用是按时间倒序展示提交历史,默认输出每个提交的哈希、作者、日期和提交信息。但它的真正价值在于:你可以通过几十个参数把它裁剪成任何你想要的样子——只看某个人的提交、只看某个文件的变更、只看某个时间段、只看合并记录、甚至把每次提交改了哪几行都打出来。对于排查问题、做代码审查、写发布说明、追溯需求变更,这些都是实打实的刚需。
这篇文章适合谁看?如果你已经会基本的git commit,但git log只会用默认那一条命令,那这篇就是写给你的。我会从输出格式、过滤条件、搜索、统计、可视化几个维度把常用参数拆开讲,每个参数都配上真实场景和踩坑经验。看完之后,你至少能把git log从“随便看看”变成“精准定位”。
2. git log 的默认输出到底给了你什么
2.1 一行提交记录里藏着哪些信息
先跑一次最朴素的git log,你会看到类似这样的输出:
commit a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 Author: Zhang San <zhangsan@example.com> Date: Mon Mar 10 14:23:45 2025 +0800 fix: 修复订单金额计算精度丢失问题这里每一块都有用。第一行的commit后面那串 40 位十六进制是提交哈希,它是这个提交的全局唯一身份证,后面所有精确操作都靠它。Author是提交作者,注意它和Committer可能不是同一个人——比如你 rebase 别人的提交时,作者不变但提交者变成了你。Date是提交时间,带时区偏移。最后缩进的那段是提交信息。
默认输出是按提交时间倒序排列的,最新的在最上面。这个顺序很重要,因为 Git 的历史本质是一个有向无环图,log默认走的是“从当前分支往回追溯”的路径。
2.2 提交哈希为什么不用写全
你可能注意到很多教程里操作提交时只写前 7 位,比如git show a1b2c3d。这是因为 Git 允许缩写哈希,只要前几位在当前仓库里能唯一标识一个提交就行。一般 7 位足够,超大仓库偶尔需要 8 到 10 位。如果缩写有歧义,Git 会直接报错让你写长一点,不会猜错,这点可以放心。
提示:想快速拿到某个提交的完整哈希,用
git rev-parse HEAD或者git log -1 --format=%H,比手动复制靠谱。
2.3 HEAD 和分支名在 log 里的位置
git log不带参数时,默认从HEAD开始往回走。HEAD就是你当前所在的位置,通常指向某个分支的最新提交。你也可以显式指定起点,比如git log dev看 dev 分支的历史,git log HEAD~3从当前提交往前数三个开始看。理解“起点”这个概念很关键,因为后面所有的过滤和范围操作都建立在它之上。
3. 把输出改成你想要的样子:格式化参数实战
3.1 --oneline 和 --pretty 的取舍
最常用的格式化参数是--oneline,它把每个提交压成一行:
a1b2c3d fix: 修复订单金额计算精度丢失问题 b2c3d4e feat: 新增优惠券叠加逻辑短哈希加提交信息第一行,扫起来非常快。但它的局限是信息太少,看不到作者和时间。这时候就轮到--pretty出场了。--pretty=format:"..."允许你自定义每一行的输出内容,占位符非常丰富:
| 占位符 | 含义 |
|---|---|
%H | 完整提交哈希 |
%h | 缩写提交哈希 |
%an | 作者名字 |
%ae | 作者邮箱 |
%ad | 作者日期(可配合--date格式化) |
%s | 提交信息第一行 |
%b | 提交信息正文 |
%d | 引用名称(分支、标签) |
我平时最常用的一个组合是:
git log --pretty=format:"%h %ad %an %s" --date=short输出就是“短哈希 日期 作者 摘要”,做发布说明或者快速浏览时特别顺手。--date支持short、iso、relative、local等多种格式,relative会显示成“3 days ago”这种,看最近动态很直观。
3.2 --graph 让分支结构一目了然
单分支的时候--oneline够用,一旦有合并、有并行开发,纯文本就分不清谁从谁分出来的了。这时候加--graph:
git log --oneline --graph --all--all表示显示所有分支,--graph会在左侧用 ASCII 字符画出提交图。输出大概长这样:
* a1b2c3d (HEAD -> main) merge: 合并支付模块 |\ | * b2c3d4e feat: 支付回调重试 | * c3d4e5f feat: 支付下单接口 |/ * d4e5f6a chore: 初始化项目这个图能让你一眼看出分支从哪分出去、在哪合回来。我排查“这个改动为什么没进主干”这类问题时,第一步就是git log --graph --all --oneline,比看任何文档都快。
3.3 自定义颜色和别名提升效率
--pretty=format支持%C(color)占位符来上色,比如%C(yellow)%h%C(reset)。不过说实话,手动写颜色有点繁琐,更实用的做法是配一个 Git 别名。在~/.gitconfig里加:
[alias] lg = log --oneline --graph --all --decorate ll = log --pretty=format:"%C(yellow)%h%C(reset) %C(cyan)%ad%C(reset) %an %s" --date=short之后git lg和git ll就能直接用了。--decorate会把分支名和标签显示在提交后面,默认在新版 Git 里其实是开着的,但显式写上更保险。
注意:别名不要起成和内置命令冲突的名字,比如
git log本身就不能被覆盖,起lg、ll这种短名最安全。
4. 精准过滤:从海量提交里捞出你要的那几条
4.1 按作者、时间、数量过滤
--author按作者过滤,支持正则:
git log --author="zhangsan" git log --author="zhang\|li" # 张或李--since和--until按时间过滤,格式很灵活:
git log --since="2025-03-01" --until="2025-03-15" git log --since="2 weeks ago" git log --since="yesterday"-n限制条数,git log -5就是最近 5 条。这几个参数组合起来威力很大,比如“看张三上周改了哪些东西”:
git log --author="zhangsan" --since="1 week ago" --oneline这里有个容易踩的坑:--since和--until默认按提交时间过滤,不是作者时间。如果你 rebase 过,提交时间会变,可能导致过滤结果和你预期不一致。要按作者时间过滤,得加--date-order配合,或者用--author-date-order排序。我一般排查历史时更关注作者时间,所以会显式加上排序参数。
4.2 按文件或目录过滤
只看某个文件的变更历史:
git log --oneline -- src/utils/format.js注意--后面跟路径,这个分隔符很重要,它告诉 Git 后面是路径而不是分支名。如果文件名和分支名重名,不加--就会歧义。
看某个目录:
git log --oneline -- src/components/这个用法在排查“这个模块最近谁在动”时特别有用。配合--follow还能追踪文件重命名:
git log --follow -- src/old-name.js--follow会跨越重命名继续追踪历史,但它只对单个文件有效,不能用于目录,这是它的已知限制。
4.3 按提交信息内容搜索
--grep在提交信息里搜关键词:
git log --grep="修复" --oneline git log --grep="bugfix" -i # -i 忽略大小写--grep支持正则,也支持多个--grep做“或”逻辑,加--all-match变成“与”逻辑。这个功能在找“某个需求相关的所有提交”时非常好用,比如git log --grep="订单" --oneline就能把所有提到订单的提交捞出来。
4.4 按代码内容搜索:-S 和 -G 的区别
这是git log里最容易被忽略但最强大的功能。-S搜索某个字符串的出现次数发生变化的提交,也就是“新增或删除了这段代码”的提交:
git log -S "calculateTotal" --oneline-G则是搜索diff 内容匹配正则的提交,范围更宽:
git log -G "function\s+calculate" --oneline两者的区别我用一个例子说明:假设某次提交只是把calculateTotal改成了calculateSum,-S "calculateTotal"会命中(因为旧字符串消失了),而-G "calculate"也会命中(因为 diff 里有 calculate 相关行)。但如果是纯格式调整、字符串没增没减,-S不命中而-G可能命中。排查“这个函数是什么时候被删的”用-S,排查“这段逻辑什么时候被动过”用-G。
实操心得:
-S和-G在大仓库上会比较慢,因为它们要遍历每个提交的 diff。如果知道大概的时间范围,一定加上--since缩小范围,能快好几倍。
5. 看清单个提交改了什么:diff 相关参数
5.1 -p 和 --stat 的配合
git log -p会在每个提交下面附上完整 diff,信息量巨大但也很长。更实用的做法是先用--stat看概览:
git log --stat -3--stat会显示每个提交改了哪些文件、每个文件增删了多少行。输出像这样:
src/order.js | 12 ++++++------ src/utils.js | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-)先看--stat定位到可疑提交,再用git show <hash>看具体 diff,这个流程比一上来就-p高效得多。
5.2 --name-only 和 --name-status
--name-only只列文件名,--name-status还会带上变更类型(A 新增、M 修改、D 删除、R 重命名):
git log --name-status -1输出:
M src/order.js A src/new-feature.js D src/deprecated.js做代码审查时我特别喜欢--name-status,一眼就能看出这次提交有没有删文件、有没有加新文件,比看 diff 更快抓住重点。
5.3 合并提交怎么看:-m 和 --cc
合并提交默认在git log -p里是不显示 diff 的,因为合并有两个父提交,diff 没有唯一答案。想看合并引入的变更,用-m:
git log -m -p-m会为每个父提交分别生成 diff。另一个选项是--cc,它显示的是“合并结果相对于所有父提交的差异”,也就是真正被合并进来的内容,通常更符合直觉:
git log --cc -p我一般用--cc看合并到底带进来了什么,用-m的情况比较少,因为输出会翻倍。
6. 高级玩法:范围、排序与引用日志
6.1 提交范围 A..B 和 A...B
git log A..B表示“在 B 里但不在 A 里的提交”,也就是 A 之后 B 新增的提交。这个在对比两个分支差异时非常有用:
git log main..dev --oneline看 dev 比 main 多了哪些提交。反过来dev..main就是 main 比 dev 多的。
三个点的A...B表示“在 A 或 B 里但不同时在两者里的提交”,也就是对称差集。这个用得少一些,但在排查“两个分支各自独立改了什么”时有用。
6.2 排序参数:--date-order 和 --topo-order
默认排序是按提交时间倒序,但遇到合并时可能看起来有点乱。--topo-order保证父提交一定排在子提交后面,图看起来更符合逻辑:
git log --graph --topo-order --oneline--date-order则是按时间排但保证父在子后。我画分支图时基本都用--topo-order,出来的图更干净。
6.3 reflog:找回“丢失”的提交
git log看的是提交历史,git reflog看的是HEAD 的移动历史。这两者不一样:如果你 reset 掉了一个提交,git log里看不到了,但git reflog里还在。这是找回误删提交的最后一道保险:
git reflog输出会显示每次 HEAD 移动的记录,包括 commit、reset、checkout、merge 等操作。找到目标哈希后git cherry-pick或者git reset回去就行。我见过太多人以为 reset 之后提交就没了,其实只要没跑 gc,reflog 里都能捞回来。
注意:reflog 默认保留 90 天,过期后会被清理。所以发现误操作要尽快处理,别拖。
7. 常见问题与排查速查
7.1 git log 输出太长卡住怎么办
默认git log会调用分页器(通常是 less),按q退出,空格翻页,/搜索。如果你不想分页,加--no-pager:
git --no-pager log --oneline -5或者直接管道给head:
git log --oneline | head -20在脚本里跑git log一定要加--no-pager,否则可能卡在分页器上导致脚本挂起,这个坑我踩过不止一次。
7.2 中文提交信息乱码
如果提交信息是中文但显示成乱码,通常是编码设置问题。检查:
git config --global i18n.commitEncoding utf-8 git config --global i18n.logOutputEncoding utf-8设完之后新提交就正常了。老提交如果本身存的就是错误编码,改配置也救不回来,只能靠git log --encoding=gbk临时指定编码查看。
7.3 为什么 git log 看不到某个提交
几种常见原因:一是你在错误的分支上,git log默认只看当前分支,加--all看全部;二是提交被 reset 或 rebase 掉了,去git reflog找;三是提交在另一个远程分支上还没 fetch 下来。排查顺序建议是:先git log --all --oneline | grep 关键词,找不到再git reflog,还没有就git fetch --all再试。
7.4 常见问题速查表
| 现象 | 可能原因 | 解决方式 |
|---|---|---|
| 输出卡住不动 | 进了分页器 | 按 q 退出,或加 --no-pager |
| 中文乱码 | 编码配置不对 | 设置 i18n.logOutputEncoding |
| 看不到某提交 | 分支不对/被 reset/未 fetch | --all、reflog、fetch |
| 合并提交无 diff | 默认不显示 | 加 -m 或 --cc |
| 过滤结果不符预期 | 按提交时间而非作者时间 | 加 --author-date-order |
| 大仓库 log 很慢 | 遍历 diff 开销大 | 加 --since 缩小范围 |
7.5 几个我常用的组合命令
最后分享几个我日常高频使用的组合,可以直接抄:
# 最近一周我自己的提交 git log --author="$(git config user.name)" --since="1 week ago" --oneline # 某个文件最近 10 次变更的概览 git log --oneline --stat -10 -- path/to/file # 所有分支上提到某个关键词的提交 git log --all --grep="关键词" --oneline # 某个函数是什么时候被引入的 git log -S "functionName" --oneline --reverse | head -1 # 图形化看所有分支最近 20 条 git log --graph --oneline --all -20-S那条加--reverse是为了让最早的提交排在最前面,head -1取第一条就是引入时间,这个技巧在追溯“这段代码谁先写的”时特别好用。
git log这东西,参数多到一辈子用不完,但真正高频的就是上面这些。我的建议是先把--oneline --graph --all和--author、--since、-S这几个练熟,覆盖 80% 的日常场景。剩下的等真遇到需求再查,比一次性背参数表有效得多。毕竟工具这东西,用出来的记忆才牢。