Git 完全指南 / 07 - 历史查看:log、blame、bisect、reflog
第七章:历史查看
Git 不仅记录历史,还能帮你追溯每一行代码的来龙去脉。
7.1 git log 进阶
7.1.1 自定义格式
# 常用别名设置
$ git config --global alias.lg "log --oneline --graph --decorate --all --date=short --format='%C(yellow)%h%C(reset) %C(green)%ad%C(reset) %C(blue)%an%C(reset) %C(red)%d%C(reset) %s'"
# 效果
$ git lg
* abc1234 2024-01-15 John (HEAD -> main, origin/main) Latest feature
| * def5678 2024-01-14 Jane (feature) Add login
|/
* ghi9012 2024-01-13 John Initial commit
7.1.2 格式占位符完整表
| 占位符 | 含义 | 占位符 | 含义 |
|---|---|---|---|
%H | 完整 commit hash | %h | 简短 commit hash |
%T | 完整 tree hash | %t | 简短 tree hash |
%P | 完整 parent hash | %p | 简短 parent hash |
%an | 作者名 | %ae | 作者邮箱 |
%cn | 提交者名 | %ce | 提交者邮箱 |
%ad | 作者日期 | %ar | 相对日期 |
%s | 提交标题 | %b | 提交正文 |
%d | 引用(分支/标签) | %C(...) | 颜色 |
7.1.3 高级过滤
# 合并提交
$ git log --merges
$ git log --no-merges
# 按文件名过滤
$ git log --follow -p -- src/app.js
# 搜索引入特定代码的提交
$ git log -S "functionName" # 字符串搜索
$ git log -G "regex_pattern" # 正则搜索
# 按时间范围
$ git log --after="2024-01-01" --before="2024-06-30"
$ git log --since="2 weeks ago"
# 按作者
$ git log --author="John"
$ git log --author="john\|jane" # 多个作者
# 按提交大小(引入/删除行数)
$ git log --stat --diff-filter=A # 只看新增文件
# 比较两个分支
$ git log main..feature # feature 有而 main 没有的提交
$ git log main...feature # 两个分支各自的独有提交
$ git log --left-right main...feature # 标记来源方向
# 拓扑排序
$ git log --topo-order # 按拓扑顺序显示
7.2 git blame — 逐行追溯
git blame 显示文件每一行的最后修改信息。
# 查看文件每行的修改者
$ git blame src/app.js
abc1234 (John 2024-01-10 09:00 +0800 1) const express = require('express');
abc1234 (John 2024-01-10 09:00 +0800 2) const app = express();
def5678 (Jane 2024-01-12 14:30 +0800 3)
def5678 (Jane 2024-01-12 14:30 +0800 4) app.get('/', (req, res) => {
ghi9012 (John 2024-01-14 10:15 +0800 5) res.send('Hello Updated!');
abc1234 (John 2024-01-10 09:00 +0800 6) });
# 只显示特定行范围
$ git blame -L 10,20 src/app.js
# 显示完整哈希
$ git blame -l src/app.js
# 显示邮箱
$ git blame -e src/app.js
# 检测行移动(跨文件)
$ git blame -M src/app.js
# 检测行复制
$ git blame -C src/app.js
# 从特定提交开始追溯
$ git blame abc1234^ -- src/app.js
使用场景:
- 找出谁修改了某行代码
- 追溯 bug 引入的提交
- 理解代码变更历史
7.3 git bisect — 二分查找 bug
git bisect 通过二分搜索快速定位引入 bug 的提交。
7.3.1 基本使用
# 开始二分查找
$ git bisect start
# 标记当前版本为有问题
$ git bisect bad
# 标记某个版本为正常
$ git bisect good v1.0.0
# Git 自动切换到中间提交
# Bisecting: 50 revisions left to test after this (roughly 6 steps)
# [abc1234] Add feature X
# 运行测试,标记结果
$ npm test
# 如果测试通过
$ git bisect good
# 如果测试失败
$ git bisect bad
# Git 继续二分,直到找到第一个坏提交
# abc1234 is the first bad commit
# commit abc1234
# Author: John <[email protected]>
# Date: Mon Jan 15 10:00:00 2024
#
# Add feature X that breaks tests
# 结束二分查找,回到原始分支
$ git bisect reset
7.3.2 自动化二分查找
# 使用脚本自动标记(0=good, 1-124=bad, 125=skip)
$ git bisect start v1.2.0 v1.0.0
$ git bisect run npm test
# 使用自定义脚本
$ git bisect run ./test-script.sh
# 脚本示例
$ cat test-script.sh
#!/bin/bash
make
if ./run-tests; then
exit 0 # good
else
exit 1 # bad
fi
# 跳过无法测试的提交
$ git bisect skip
7.3.3 bisect 可视化
# 查看当前 bisect 状态
$ git bisect log
# 重放之前的 bisect 会话
$ git bisect replay bisect.log
# 可视化 bisect 范围
$ git bisect visualize --oneline
7.4 git reflog — 引用日志
git reflog 记录 HEAD 和分支引用的所有变更历史,即使提交被"删除"也能找回。
7.4.1 基本使用
# 查看 HEAD 的变更历史
$ git reflog
abc1234 HEAD@{0}: commit: Add feature
def5678 HEAD@{1}: checkout: moving from develop to main
ghi9012 HEAD@{2}: commit: Fix bug
jkl3456 HEAD@{3}: reset: moving to HEAD~1
# 查看特定分支的 reflog
$ git reflog main
$ git reflog develop
# 查看指定时间的 reflog
$ git reflog --since="2 days ago"
$ git reflog --until="1 week ago"
7.4.2 使用 reflog 恢复
# 恢复到某个历史状态
$ git reset --hard HEAD@{2}
# 查看某个 reflog 条目的内容
$ git show HEAD@{5}
# 基于 reflog 创建分支
$ git branch recovered HEAD@{3}
# 恢复误删的分支
$ git branch -D feature
# 哎呀,删错了!
$ git reflog
abc1234 HEAD@{5}: commit: Last commit on feature branch
$ git branch feature abc1234
# 恢复误删的提交
$ git reset --hard HEAD~3
# 找回!
$ git reflog
$ git reset --hard HEAD@{1}
💡 reflog 默认保留 90 天,是数据恢复的最后保障。
7.5 git diff — 差异比较
7.5.1 基本比较
# 工作区 vs 暂存区
$ git diff
# 暂存区 vs 最新提交
$ git diff --staged
$ git diff --cached
# 工作区 vs 最新提交
$ git diff HEAD
# 两个提交之间
$ git diff abc1234 def5678
# 两个分支之间
$ git diff main..feature
# 指定文件的差异
$ git diff HEAD -- src/app.js
# 统计差异(不显示具体内容)
$ git diff --stat
# 只显示文件名
$ git diff --name-only
# 只显示状态(A/M/D)
$ git diff --name-status
7.5.2 diff 选项
# 忽略空白字符变化
$ git diff -w
$ git diff --ignore-all-space
# 显示单词级差异
$ git diff --word-diff
# 并排显示差异
$ git diff --side-by-side
# 使用外部 diff 工具
$ git difftool
# 比较不同分支的特定文件
$ git diff main:src/app.js feature:src/app.js
7.6 git shortlog — 提交统计
# 按作者统计提交数
$ git shortlog -sn
150 John Doe
80 Jane Smith
30 Bob Wilson
# 按作者统计(包含合并提交)
$ git shortlog -sn --all
# 按日期范围统计
$ git shortlog -sn --since="2024-01-01"
# 按文件统计
$ git shortlog -sn -- src/
7.7 git pickaxe — 内容搜索
# 搜索引入/删除特定字符串的提交
$ git log -S "functionName" --oneline
# 使用正则表达式
$ git log -G "def\s+\w+" --oneline
# 结合 -p 查看具体变更
$ git log -S "functionName" -p
# 搜索特定文件
$ git log -S "functionName" -- src/app.js
7.8 实用日志别名
# 设置常用别名
$ git config --global alias.lg "log --oneline --graph --decorate --all"
$ git config --global alias.last "log -1 HEAD --stat"
$ git config --global alias.today "log --since=midnight --oneline"
$ git config --global alias.week "log --since='1 week ago' --oneline"
$ git config --global alias.mine "log --author=$(git config user.email) --oneline"
$ git config --global alias.changes "diff --name-status"
业务场景
| 场景 | 推荐命令 |
|---|---|
| 追溯某行代码作者 | git blame -L <start>,<end> <file> |
| 定位 bug 引入提交 | git bisect start + git bisect run |
| 恢复误删分支/提交 | git reflog + git reset/cherry-pick |
| 查找引入特定代码的提交 | git log -S "keyword" |
| 审查近期变更 | git log --since="1 week" --stat |
| 比较分支差异 | git diff main..feature --stat |
| 团队贡献统计 | git shortlog -sn --all |