git 如何查看某次提交修改的内容?
昨天晚上十一点多,我在公司楼下抽烟,风挺大,小李给我发了条语音:“东哥,我这边要看一个 merge 的提交到底改了啥,git show一堆红红绿绿我看花眼了,有没有一套更体系的玩法?”我当时困得眼睛打架,但这事儿我踩过太多坑,脑子一热就开始碎碎念,顺手把要点都整理出来了,免得你们再到处翻命令手册。
你想看“那一次”的改动,第一步还是把它“掐”出来。最快的套路是:用消息、范围、文件三种维度去锁定。
1)按消息搜,别翻屏:
git log --oneline --grep='登录.*超时' -n 20
2)按文件找“最后一次动它的人”,顺着回溯到那次提交:
git log --oneline -- path/to/login.py
3)你已经有 commit 哈希了?确认下是不是合并提交:
git cat-file -p <commit> | sed -n '1,3p'
# parent 出现不止一行,说明它是 merge
小插曲:单提交的“标准差异”其实等价于 git diff <commit>^ <commit>,而简写就是 git diff <commit>^!。所以你看到我下面老用 <commit>^!,是有来头的。
我一般只用三板斧,信息密度高、眼睛不累,尤其夜里盯屏的时候。
1. 看补丁本体(但干净点)
git show 很香,但默认上下文多、重命名不明显。稍微“洗”一下:
git show --patch --minimal -U3 -M -C --stat --summary <commit>
-U3:三行上下文,刚刚好,太多就淹没了改动;--minimal:让 diff 更“聪明”,少出“错觉性”改动;-M -C:识别重命名与拷贝,避免把“移动代码”误读成“删除+新增”;--stat --summary:先看面,再看里。
只想看某个文件在这次提交里怎么变的?
git show <commit> -- path/to/login.py
2. 只看“改了哪几样”,别展开
半夜扫一眼够了:
git show --name-status --stat <commit>
# A 新增, M 修改, D 删除, R 重命名, C 拷贝
更极端点,文件 + 行数统计即可:
git diff --stat --summary <commit>^!
3. 关注“词义上的变化”(不是缩进)
有些提交是“把 Tab 全换空格”,这种没意义的噪音得一把切掉:
git show -w <commit> # 忽略空白变化
git show --word-diff=plain <commit> # 词级别增删,更好读
git show --color-moved=dimmed_zebra --color-moved-ws=allow-indentation-change <commit>
# 看代码块的搬家,配合 -M 很稳
合并提交怎么读才专业
合并提交通常是“把 B 分支的改动合进来”,你直接 git show <merge-commit> 会看到“汇总出来”的 combined diff,读着容易迷路。我一般分三步:
1)按父提交逐个看“这次合并相对每个父亲”到底冲突/改了啥:
git show -m <merge-commit>
# -m 会拆成对每个 parent 的补丁
2)需要“冲突处到底怎么被解决的”就用合并视图:
git show --cc <merge-commit>
# 多父亲的合并上下文,一屏看懂解决方案
3)只看合并时“你手工改了的部分”,排除纯粹把分支合上来的噪音:
git diff --cc <merge-commit> # 或者配合 --word-diff 更聚焦
只看你在乎的范围:文件、函数、路径魔法
有时你就想看某个函数这次到底被谁改了:
git log -L :login:app/auth.py <commit>^! # -L 函数级追踪
路径过滤也别只会“放文件名”,Git 的 pathspec 有“魔法”:
git show <commit> -- ':(icase)*login*.py'# 忽略大小写匹配
git show <commit> -- ':(exclude)tests/**'':!docs/**'
用 pickaxe 精确找“哪次把这句改了”
两个利器:-S 和 -G。前者按字串,后者按正则。
# 哪次删了/加了 "timeout=30"
git log -S'timeout=30' -p -- path/to/login.py
# 哪次改了以 "Bearer " 开头的 token 处理逻辑
git log -G'^Bearer\s' -p -- path/to/login.py
锁定了提交,再 git show <commit> -- path/to/login.py 细看即可。
二进制和图片不是黑盒:让它“可读”
默认二进制只给你个“Binary files differ”。其实你可以让图片、PDF 也“文本化”对比。
.gitattributes 里挂上 textconv:
*.png diff=pngmeta
*.pdf diff=pdftxt
然后告诉 Git 怎么转:
git config diff.pngmeta.textconv "exiftool -s -ImageWidth -ImageHeight -ColorSpace -DateTimeOriginal -FileName -FileSize -Orientation -"
git config diff.pdftxt.textconv "pdftotext -layout -nopgbrk -"
再来一次:
git show <commit> -- path/to/screenshot.png
# 这次会打印前后图片的元数据差异
生成级文件别干扰阅读
比如前端构建出来的 bundle、后端生成的 _pb2.py,你可以在 .gitattributes 里告诉 Git 别生成 patch:
*.min.js -diff
*_pb2.py -diff
看提交的时候就清净多了。
读补丁的几个“舒适化”小开关
我这边常开的:
# 少上下文,但不至于丢信息
git config diff.context 3
# 颜色移动块,搬家可视化
git config diff.colorMoved dimmed_zebra
git config diff.colorMovedWS allow-indentation-change
遇到“大改动”(比如重命名+剪贴代码)时,适当把阈值放宽,准确识别移动:
git show -M50% -C50% <commit>
实战的三四个套路,真省眼睛
1)“只看这次是不是纯重命名”:
git show --name-status <commit> | awk '{print $1}' | sort | uniq
# 只有 Rnn 说明就是 rename,不用展开大补丁
2)“一屏看懂这个提交改了哪几个模块”:
git show --stat=200,120 --compact-summary <commit>
# 把 stat 的宽度调大,路径对齐更好读
3)“确定是不是只改了注释/空格”:
git diff --word-diff-regex='[^\s]+' -w <commit>^! | grep -vE '^\+|^-'
# 没有有意义的 token 变动,那大概率就是格式化
4)“子模块发生了啥”:
git show --submodule=diff <commit>
外部对比工具:有时 GUI 更省脑
你盯一天终端也会晕。装个 difftool,必要时开 GUI:
git config diff.tool vscode
git config difftool.vscode.cmd "code --wait --diff $LOCAL$REMOTE"
git difftool <commit>^! # 逐文件打开 VS Code 对比
git difftool --tool=meld <commit> -- path/to/login.py
写个能落地的小脚本:一条命令把信息摘干净
我懒得每次拼长命令,就用 Python 包了一层。这个脚本做几件事:自动识别 merge、先给你 summary,再按需展开 patch;支持 --path、--word、--ignore-space 等常用开关。
#!/usr/bin/env python3
import argparse, subprocess, sys, shutil
defrun(cmd, cwd=None):
p = subprocess.run(cmd, cwd=cwd, text=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if p.returncode != 0:
print(p.stderr, file=sys.stderr)
sys.exit(p.returncode)
return p.stdout
defis_merge(commit):
out = run(["git", "cat-file", "-p", commit])
return out.count("parent ") > 1
defshow_summary(commit, paths):
args = ["git", "show", "--name-status", "--stat", "--summary", "--no-color", commit]
if paths: args += ["--", *paths]
print(run(args))
defshow_patch(commit, word=False, ignore_ws=False, context=3, paths=None, moved=True):
args = ["git", "show", "--patch", f"-U{context}", "-M", "-C", "--minimal", commit]
if word: args += ["--word-diff=plain"]
if ignore_ws: args += ["-w"]
if moved: args += ["--color-moved=dimmed_zebra", "--color-moved-ws=allow-indentation-change"]
if paths: args += ["--", *paths]
print(run(args))
defshow_merge(commit, per_parent=False):
if per_parent:
print(run(["git", "show", "-m", commit]))
else:
print(run(["git", "show", "--cc", commit]))
defmain():
ap = argparse.ArgumentParser(description="clean viewer for a single git commit")
ap.add_argument("commit")
ap.add_argument("--path", action="append", help="limit to path (repeatable)")
ap.add_argument("--word", action="store_true", help="word-level diff")
ap.add_argument("--ignore-space", action="store_true", help="ignore whitespace")
ap.add_argument("--context", type=int, default=3, help="context lines")
ap.add_argument("--merge-parent", action="store_true", help="for merges, diff to each parent")
ap.add_argument("--patch", action="store_true", help="print full patch after summary")
args = ap.parse_args()
commit = args.commit
if is_merge(commit):
print(f"[merge] {commit}")
show_summary(commit, args.path)
show_merge(commit, per_parent=args.merge_parent)
else:
show_summary(commit, args.path)
if args.patch or sys.stdout.isatty():
show_patch(commit, word=args.word, ignore_ws=args.ignore_space,
context=args.context, paths=args.path)
if __name__ == "__main__":
ifnot shutil.which("git"):
print("git not found in PATH", file=sys.stderr); sys.exit(1)
main()
用法示例:
# 只看这次在 app 目录的有意义改动,词级别展示
python show_commit.py a1b2c3d --path app --word --ignore-space
# 合并提交,想看每个父提交的差异
python show_commit.py f7g8h9i --merge-parent
最后,再补几个“会被问到”的细节
git show默认不总是做重命名检测,所以你看到“删一堆、加一堆”时,果断加-M -C再看一眼;单提交用
<commit>^!是个小秘诀,合并提交时想要“更像平时的补丁”,用--cc;大补丁里关心“到底动了谁写的那几行”,先
git blame定位再git show精读:git blame -L 20,60 path/to/login.py
git show <拿到的commit> -- path/to/login.py读不懂是因为“上下文太多”,就把
-U调小;读不清是因为“噪音太多”,就加-w和--word-diff;读不准是因为“搬家被当成新增”,就把-M -C的百分比放宽。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领