git log
一句话总结:git log 是审计项目历史的主要工具。它从当前检出位置(HEAD)开始,读取提交的父提交哈希,并递归地向时间更早的方向回溯,直到最初的根提交,从而遍历提交组成的有向无环图(DAG)。它是强大的搜索与分析工具,而不只是一个基于文本的列表。
**关于下方哈希值的说明:**本课中的提交 ID 和树 ID 仅用于示例。提交哈希由其树、父提交、作者/提交者身份以及提交时间戳共同计算,因此你亲自运行相同命令时,会得到结构相同但 ID 不同的图。显示的时间戳是带有
+0000(UTC)偏移量的 Unix epoch 秒数。
git log 到底做了什么?
要理解 git log 如何生成历史,首先要看看 Git 如何组织提交。Git 不会把提交存储成扁平列表或数组,而是将其组织成有向无环图(Directed Acyclic Graph,DAG)。
在这张图中:
- **节点:**提交对象。
- **边:**从子提交指向其父提交的指针。它们是有向的(指向更早的时间),并且无环(历史不能回到自身)。
graph RL
C3[Commit 3<br>HEAD / main] -->|parent| C2[Commit 2]
C2 -->|parent| C1[Commit 1<br>Root Commit]
调用 git log 时,Git 不会扫描整个磁盘。相反,它会从一个给定的入口点开始遍历图;默认入口是 HEAD 所指向的提交(你当前所在的位置,链接到 git-checkout)。
遍历算法
- Git 读取
.git/HEAD,确定当前活动分支(例如ref: refs/heads/main)。 - Git 读取
.git/refs/heads/main,将活动分支引用解析为 40 个字符的提交哈希(例如8a4c10...)。 - Git 从对象数据库(
.git/objects/)加载提交对象8a4c10...,并提取元数据:作者、提交者、时间戳、消息以及parent哈希。 - Git 将该提交的详细信息输出到控制台。
- Git 将父提交哈希(例如
55a420...)放入遍历队列。 - 循环重复:Git 从队列中取出下一个提交哈希,读取并显示它,然后将其父提交加入队列。
- 当某个提交没有
parent字段时,遍历停止;这表示它是仓库最早的提交(根提交)。
对于合并提交,提交对象会列出多个 parent 行。Git 会将所有父提交哈希加入队列。根据排序设置(例如按时间、拓扑关系或日期排序),Git 会交错显示来自合并分支的提交,以重建统一的时间线。
.git/ 内部发生了什么(动手探索)
下面创建一个包含多个提交和分支的仓库,逐步跟踪 Git 如何解析其中的文件并生成 git log 输出。
第 1 步:设置包含多个提交的仓库
先初始化一个仓库,并在 main 分支上快速创建两个提交。
$ mkdir git-log-demo && cd git-log-demo
$ git init
Initialized empty Git repository in /git-log-demo/.git/
$ echo "v1" > file.txt
$ git add file.txt
$ git commit -m "First commit"
[main (root-commit) 5be36ab] First commit
$ echo "v2" > file.txt
$ git add file.txt
$ git commit -m "Second commit"
[main ae39f81] Second commit
第 2 步:创建分支并生成合并提交
现在创建名为 feature 的分支(链接到 git-branch),在该分支上创建提交,切回原分支并合并它。
$ git checkout -b feature
Switched to a new branch 'feature'
$ echo "feature-work" > feature.txt
$ git add feature.txt
$ git commit -m "Feature commit"
[feature c4f8101] Feature commit
$ git checkout main
Switched to branch 'main'
$ git merge feature -m "Merge feature branch"
Merge made by the 'ort' strategy.
feature.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 feature.txt
第 3 步:解析引用并记录提交
先看看 HEAD 现在指向哪里:
$ cat .git/HEAD
ref: refs/heads/main
再看看 main 指向哪个提交哈希:
$ cat .git/refs/heads/main
6df8d0354178de3d038fa61dfb63e8a157580b33
这就是我们的合并提交。使用底层命令 git cat-file 查看这个提交对象:
$ git cat-file -p 6df8d0354178de3d038fa61dfb63e8a157580b33
tree e7c3909ea1dc725f187e1451f2ad490e5b0213ef
parent ae39f81ca99d45e5b619d854cf78d1f2a36b1d2e
parent c4f810129a8a72cf5a1db5b2f270a2936274b1e2
author Purna Pattela <purna@example.com> 1772960500 +0000
committer Purna Pattela <purna@example.com> 1772960500 +0000
Merge feature branch
注意,由于这是合并提交,它有两个父提交:
parent ae39f81...(合并前main上的最后一个提交)。parent c4f8101...(来自feature分支的提交)。
运行 git log 时,Git 会输出合并提交(6df8d03),然后读取它的父提交。它会按时间顺序遍历父提交 ae39f81 和 c4f8101。下面检查 ae39f81:
$ git cat-file -p ae39f81ca99d45e5b619d854cf78d1f2a36b1d2e
tree b4f93dc52c0dfa6c7f8a9a2a6dfba1d2a36b567d
parent 5be36abc0e9df7f2a7db2e9b0cf2a3d2427a1d1e
author Purna Pattela <purna@example.com> 1772960400 +0000
committer Purna Pattela <purna@example.com> 1772960400 +0000
Second commit
这个提交指向最初的父提交 5be36ab...。下面检查 5be36ab:
$ git cat-file -p 5be36abc0e9df7f2a7db2e9b0cf2a3d2427a1d1e
tree f3b3a3c9e6db5832a875a6c11d2b270a2936274b
author Purna Pattela <purna@example.com> 1772960300 +0000
committer Purna Pattela <purna@example.com> 1772960300 +0000
First commit
由于 5be36ab... 没有 parent 行,Git 知道这是这棵树的根,于是停止遍历。最终的日志输出按时间倒序列出全部四个提交。
命令选项详解与高级用法
git log 的默认输出详细且按顺序排列。你可以使用各种标志格式化、筛选和跟踪历史。
1. 高级格式化选项
--oneline
将每个提交压缩为一行,其中包含缩写后的提交哈希和主题行,适合快速浏览提交历史。
$ git log --oneline
6df8d03 Merge feature branch
c4f8101 Feature commit
ae39f81 Second commit
5be36ab First commit
--graph --decorate
在分支执行路径旁绘制 ASCII 图,并显示分支标签(HEAD、分支名、标签)。
$ git log --graph --oneline --decorate --all
* 6df8d03 (HEAD -> main) Merge feature branch
|\
| * c4f8101 (feature) Feature commit
* | ae39f81 Second commit
|/
* 5be36ab First commit
自定义格式(--pretty=format:)
可以使用占位符设计自己的输出模板。例如,只打印哈希、作者姓名、日期和提交消息:
$ git log --pretty=format:"%h - %an (%ar): %s"
6df8d03 - Purna Pattela (2 minutes ago): Merge feature branch
c4f8101 - Purna Pattela (5 minutes ago): Feature commit
常用占位符包括:
%h:缩写后的提交哈希。%an:作者姓名。%ar:作者日期(相对格式,例如 “3 days ago”)。%ad:作者日期(绝对格式)。%s:主题行(提交消息的第一行)。%d:引用装饰信息(例如(HEAD -> main))。
2. 取证式历史筛选
按代码变更搜索(Pickaxe:-S)
如果想找出哪个提交向代码库中加入或移除了某个特定文本字符串(例如函数名或 API 端点),可以使用 -S 标志:
$ git log -S "payment_processor_v2" --oneline
这叫作 Pickaxe。它搜索每个提交状态下的文件内容,而不只是搜索提交消息文本。
按提交消息搜索(--grep)
只按关键词搜索提交消息:
$ git log --grep="bugfix"
按路径筛选
只查看修改过某个文件或目录的提交:在命令末尾加上双破折号,后跟文件路径。
$ git log --oneline -- src/utils/helper.js
真实场景:调试回归问题
设想你的团队部署了应用的新版本,用户反馈点击“提交”按钮时应用会崩溃,而你知道这个按钮一周前还运行正常。
要找出问题来源,可以组合使用历史筛选条件:
# Step 1: List all commits from the last week that modified the checkout button component
$ git log --since="1 week ago" --oneline -- src/components/SubmitButton.js
a3d5f9c refactor: rewrite submit click handler
54f8b9e chore: update button styling
# Step 2: Show the actual code modifications in the suspicious commit (a3d5f9c)
$ git log -p -1 a3d5f9c
使用补丁标志 -p,你可以查看处理函数中的确切变更,发现缺少事件参数,然后编写热修复。
常见问题与误区
1. 困在分页器里
默认情况下,如果日志输出超过屏幕高度,Git 会把输出交给分页工具(通常是 less)。初学者常常不知道如何回到普通终端。
- **解决办法:**按
q退出分页器。 - **提示:**如果希望 Git 直接把输出打印到终端而不打开分页器,可以将分页器配置为
cat:
$ git --no-pager log -n 5
2. 看不到其他分支上的提交
运行 git log 只会显示从当前分支历史可达的提交。如果你在 main 上,而其他人把变更推送到了 feature-b,你不会看到他们的提交。
- **解决办法:**使用
--all标志显示所有分支上的全部提交:
$ git log --oneline --all --graph
3. 把 git log 和 git reflog 混为一谈
如果执行 rebase(链接到 git-rebase)或重置提交,这些提交会从分支历史中移除,因此不再显示在 git log 中。
- 原因:
git log只遍历活动指针。 - **解决办法:**使用
git reflog。引用日志会记录本地计算机上对HEAD指针进行的每一次变更(包括切换检出、amend、reset 和 rebase)。它是恢复“已删除”提交的最后一道安全保障。
命令速查
| 命令 | 作用 |
|---|---|
git log | 显示完整提交历史(哈希、作者、日期、消息)。 |
git log --oneline | 为每个提交显示紧凑的单行摘要。 |
git log --graph --oneline --all | 以可视化树显示所有分支和合并。 |
git log -p | 显示每个提交引入的补丁(代码差异)。 |
git log -n <limit> | 将输出限制为指定数量的提交(例如 -n 5)。 |
git log --author="Name" | 按作者姓名筛选历史。 |
git log --grep="pattern" | 按消息中的关键词筛选提交。 |
git log -S "code" | 查找加入或移除某一特定代码行/单词的提交。 |
git log --no-merges | 从历史日志中排除合并提交。 |
常见问答(大家也会问)
为什么我的提交没有显示在 git log 中?
如果刚运行了 git commit,但它没有出现在日志中,请检查以下事项:
- **暂存检查:**提交前是否运行了
git add?如果没有,可能因为没有内容被暂存而导致提交被拒绝。 - **分离的 HEAD:**如果处于 detached HEAD 状态,你的提交不会关联到任何分支。签出
main后,它们看起来就像从日志中消失了。运行git reflog找回缺失的哈希。
作者日期和提交者日期有什么区别?
- **Author Date:**提交最初创建时的时间戳(例如使用
git commit时)。 - **Committer Date:**提交最后一次写入仓库时的时间戳(例如被 cherry-pick、rebase 或 amend 时)。
- 默认情况下,
git log显示 Author Date。要查看 Committer Date,请使用:git log --pretty=fuller。
如何查看是谁修改了文件中的某一行?
git log -S 可以找到引入某个字符串的提交,而 git blame <file> 是查看逐行归属最快的方法。它会为文件的每一行显示提交哈希、作者姓名和时间戳。
如何按特定日期范围搜索提交历史?
可以组合 --since(或 --after)和 --until(或 --before)标志:
$ git log --since="2026-06-01" --until="2026-07-01" --oneline
Git 也能解析相对的人类日期,例如 --since="3 weeks ago" 或 --until="yesterday"。
检验你的知识
正在加载测验…