跳转至

查看与搜索历史

本文你会学到:

  • git log 的各种展示形式(图形化、格式化、过滤)
  • git grep 在整个历史中搜索代码
  • git blame 追踪"这行代码是谁写的、为什么这么写"
  • git bisect 二分法定位引入 bug 的提交(进阶,先了解)

🔍 为什么需要"看历史"?

代码库就像一本连载小说——每次提交都是新章节。当你遇到"这功能怎么突然坏了?""这行代码为什么这么写?"这类问题时,查历史就是你的侦探工具。

📜 git log:项目的提交年鉴

基本用法

git log
# 每条提交显示:SHA、作者、日期、提交信息
# 按 q 退出翻页模式

git log --oneline
# 精简显示,每行一个提交:
# a1b2c3d feat: 添加用户登录
# e4f5g6h fix: 修复空指针

git log -5             # 只看最近 5 条
git log -p             # 显示每次提交的详细 diff
git log --stat         # 显示每次提交修改了哪些文件(增删行数)

图形化查看分支结构

最好看的 git log 命令
git log --oneline --graph --all --decorate

# 输出示例:
# * a1b2c3d (HEAD -> main) feat: 用户系统重构
# * b2c3d4e fix: 登录 bug
# | * c3d4e5f (feature/pay) feat: 支付接口
# | * d4e5f6g feat: 购物车
# |/
# * e5f6g7h 项目初始化

# 建议设置别名,以后直接用 git lg
git config --global alias.lg "log --oneline --graph --all --decorate"

按条件过滤历史

git log 过滤器大全
# 按时间范围
git log --since="2024-01-01"
git log --until="2024-12-31"
git log --since="2 weeks ago"
git log --since="3 days ago" --until="yesterday"

# 按作者
git log --author="张三"
git log --author="zhangsan@company.com"

# 按提交信息关键词
git log --grep="登录"           # 提交信息含"登录"
git log --grep="fix" -i         # 不区分大小写

# 按文件(只看影响了某个文件的提交)
git log -- src/app.py
git log -- "*.js"               # 任何 js 文件

# 按代码内容(找某个字符串的增删历史)
git log -S "def login"          # 代码中出现/消失了这个字符串
git log -G "user.*=.*admin"     # 正则匹配

# 组合使用
git log --author="张三" --since="1 month ago" --oneline

自定义输出格式

格式化输出,适合脚本处理
1
2
3
4
5
6
7
8
9
git log --pretty=format:"%h - %an, %ar : %s"
# 输出:
# a1b2c3d - 张三, 2 hours ago : feat: 添加登录

# 常用格式占位符:
# %H  完整 SHA   %h  短 SHA
# %an 作者名     %ae 作者邮箱
# %ar 相对时间   %ad 绝对时间
# %s  提交标题   %b  提交正文

🔎 git grep:在代码中搜索

git grep 比普通的 grep 更强,因为它了解 Git 的历史:

git grep vs 普通搜索
# 在当前工作区搜索
git grep "def authenticate"
git grep -n "TODO"               # 显示行号
git grep -l "password"           # 只显示文件名
git grep -i "error"              # 不区分大小写
git grep -c "import"             # 每个文件匹配次数

# 在特定提交/分支中搜索(无需切换分支!)
git grep "def login" HEAD~5      # 5个提交之前的版本
git grep "def login" v1.0.0      # 某个标签的版本
git grep "def login" feature/auth # 某个分支中

# 搜索特定文件类型
git grep "useState" -- "*.tsx"
git grep "SELECT" -- "*.sql"

# 显示上下文
git grep -A 3 "def login"       # 匹配行后3行
git grep -B 2 "def login"       # 匹配行前2行
git grep -C 2 "def login"       # 前后各2行

对比:为什么用 git grep 而不是系统的 grep

特性 git grep 系统 grep
自动跳过 .git/ 目录 ❌(需手动排除)
自动跳过 .gitignore 的文件
可搜索历史版本
速度 通常更快 视情况

👉 git blame:追责神器

当你看到一行代码,困惑地想"这谁写的?为什么这么写?"——git blame 帮你找到答案:

git blame 基本用法
git blame src/auth.py
# 每行显示:SHA | 作者 | 时间 | 行号 | 代码内容
# a1b2c3d4 (张三    2024-03-15 09:21:08 +0800 42) def authenticate(user, pwd):
# e5f6g7h8 (李四    2024-04-01 14:33:00 +0800 43)     if not user:
# a1b2c3d4 (张三    2024-03-15 09:21:08 +0800 44)         raise ValueError("user required")

# 只看某几行
git blame -L 40,55 src/auth.py     # 只看第40-55行
git blame -L "/def authenticate/,+10" src/auth.py  # 从函数开头起10行

# 忽略空白符变更(代码格式化引入的变更不影响溯源)
git blame -w src/auth.py

# 查看某个历史版本的 blame(不是当前代码)
git blame v1.2.0 -- src/auth.py

blame 不是"甩锅"

git blame 的真正用途是**找到提交该行代码的 commit**,从而: - 读那次提交的 commit message,了解**当时的背景** - 看那次提交的完整 diff,了解**上下文** - 用 git show <sha> 查看完整提交详情

从 blame 深入查看某次提交
1
2
3
git blame src/auth.py            # 找到某行的 SHA:a1b2c3d4
git show a1b2c3d4                # 查看这次提交的完整内容
git log a1b2c3d4 -1 --stat      # 这次提交改了哪些文件

📊 实战:用历史分析代码演变

综合运用——找出「谁、什么时候、为什么」删除了某功能
1
2
3
4
5
6
7
8
# 第一步:用 git log -S 找到某个函数被删除的提交
git log -S "def send_email" --oneline

# 第二步:查看那次提交的详情(含完整 diff)
git show abc1234

# 第三步:如果删除发生在很多次提交中,用 git log -p 过滤
git log -p -S "def send_email" -- src/notification.py

↩️ HEAD 引用:如何定位历史提交

理解 HEAD 引用符号,是能灵活使用 git loggit diffgit reset 等命令的前提。

HEAD 引用速查
1
2
3
4
5
6
7
8
HEAD        # 当前所在提交
HEAD~       # 上一个提交(等同于 HEAD~1)
HEAD~3      # 往前第3个提交(沿第一父链走3步)
HEAD^       # 上一个提交(与 HEAD~ 相同)
HEAD^2      # 合并提交的第2个父提交(仅对合并提交有效)
abc1234     # 直接用 SHA(可简写为前7位)
v1.2.0      # 通过 tag 名称引用
main        # 通过分支名称引用

~ vs ^ 的区别:对普通提交,二者等价;对**合并提交**(有两个父提交),~ 沿第一父链,^2 指向第二个父提交:

graph TD
    M["🔀 合并提交 M\n(HEAD)"]
    A["A(main 父提交)\nM^ = M^1 = M~"]
    B["B(feature 父提交)\nM^2"]
    A2["A2\nM~2 = M^^"]
    B2["B2\nM^2~"]
    A2 --> A --> M
    B2 --> B --> M
    classDef head fill:transparent,stroke:#f57c00,color:#adbac7,stroke-width:2px
    classDef first fill:transparent,stroke:#539bf5,color:#adbac7,stroke-width:2px
    classDef second fill:transparent,stroke:#388e3c,color:#adbac7,stroke-width:2px
    classDef older fill:transparent,stroke:#768390,color:#adbac7,stroke-width:1px
    class M head
    class A first
    class B second
    class A2,B2 older
实际使用场景
# 查看上3个提交的变化
git log HEAD~3..HEAD --oneline

# 比较当前和3个提交之前的差异
git diff HEAD~3 HEAD -- src/app.py

# 查看合并提交的两个父提交分别是什么
git log --merges --oneline -5
git show <merge-sha>^   # 第1个父提交(主线)
git show <merge-sha>^2  # 第2个父提交(功能分支)

🔍 bisect:二分法精确定位引入 bug 的提交

当你知道"某个版本好用,现在坏了",但中间有几十上百个提交,手动排查太慢——git bisect 帮你用**二分查找**把范围快速缩小到个位数:

graph LR
    A["v1.0(正常)"] --> B["?"] --> C["?"] --> D["?"] --> E["?"] --> F["HEAD(有bug)"]
    B -.->|"git bisect: 测这个"| GOOD["✅ 正常 → 向右缩"]
    D -.->|"git bisect: 测这个"| BAD["❌ 有bug → 向左缩"]
    classDef commit fill:transparent,stroke:#768390,color:#adbac7,stroke-width:1px
    classDef ok fill:transparent,stroke:#388e3c,color:#adbac7,stroke-width:2px
    classDef bad fill:transparent,stroke:#d32f2f,color:#adbac7,stroke-width:2px
    class A,B,C,D,E,F commit
    class GOOD ok
    class BAD bad

手动 bisect 步骤演练

git bisect 完整工作流
# 第1步:开始 bisect
git bisect start

# 第2步:标记当前版本(HEAD)有 bug
git bisect bad

# 第3步:标记某个已知正常的版本(tag、SHA 均可)
git bisect good v2.0.0
# Git 输出:Bisecting: 23 revisions left to test after this (roughly 5 steps)
# Git 自动检出中间那个提交

# 第4步:测试当前检出的版本(运行测试或手动验证)
# 有 bug → 告诉 Git:
git bisect bad
# 没 bug → 告诉 Git:
git bisect good
# Git 再次缩短范围并自动检出下一个

# ... 重复第4步约 5-6 次 ...

# 第5步:Git 找到了!输出类似:
# abc1234 is the first bad commit
# commit abc1234 Author: 张三 ...
# feat: 修改用户查询逻辑

# 第6步:结束 bisect(恢复到原来的 HEAD)
git bisect reset

自动 bisect:用脚本测试

如果有自动化测试脚本,可以让 Git 完全自动完成二分过程(脚本返回 0 表示正常,非 0 表示有 bug):

全自动 bisect
git bisect start
git bisect bad HEAD
git bisect good v2.0.0

# 提供自动测试脚本(Git 自动循环直到找到)
git bisect run python tests/test_login.py
# 或
git bisect run ./scripts/check-bug.sh

# 结束
git bisect reset

bisect 日志

git bisect log          # 查看本次 bisect 的所有标记历史
git bisect log > bisect.log && git bisect replay bisect.log  # 重放 bisect 过程
命令 用途
git log --oneline --graph --all 图形化查看分支历史
git log --author="..." --since="..." 按条件过滤提交
git log -S "关键词" 找包含某段代码变更的提交
git log -p 显示每次提交的详细 diff
git grep "关键词" 在当前或历史版本中搜索代码
git blame <file> 每行代码的最后修改者
git show <sha> 查看某次提交的详情
HEAD~N / HEAD^ 引用相对历史提交
git bisect start/good/bad 二分法定位引入 bug 的提交
git bisect run <script> 自动化二分法

下一篇「撤销时光机」将教你如何"反悔"——从小改动的撤销到"抹掉历史",以及每种方式的风险边界。