git diff 从入门到精通
2026/5/28 4:49:57 网站建设 项目流程

从三个区域模型出发,拆解 git diff 的默认行为、区间语义、输出格式,以及那些让人困惑的设计选择。


前置知识:三个区域

理解git diff之前,必须先理解 Git 的三个状态区域:

工作区 暂存区 本地仓库 (Working Directory) (Staging / Index) (HEAD) ↓ ↓ ↓ 你编辑文件的地方 git add 进来的地方 git commit 进来的地方
区域是什么怎么进怎么出
工作区磁盘上的实际文件你保存编辑器git add复制到暂存区
暂存区下次提交的"待提交清单"git addgit commit变成新 HEAD
HEAD当前分支最后一次提交的快照git commit只能被下一次 commit 覆盖

有了这个模型,三个核心 diff 命令就一目了然:

gitdiff# 工作区 ←→ 暂存区gitdiff--staged# 暂存区 ←→ HEADgitdiffHEAD# 工作区 ←→ HEAD

为什么git diff默认比较工作区和暂存区

这个默认行为让很多人困惑。直觉上你会觉得默认应该比较「工作区和上一次提交」,因为那才是"我改了什么"。

但 Git 的设计哲学不同:Git 假设你git add时已经检查过那些改动了,“确认没问题,准备提交”。所以git diff默认展示的是"你还确认的那部分"——add 之后又改了什么,帮你决定要不要再 add 一次。

真正常用的提交预览其实是git diff --staged

# "看看我即将提交什么"——这才是你最常用的gitdiff--staged# 设个别名省事gitconfig--globalalias.ds'diff --staged'

一句话git add= 勾选,git diff= 还没勾的,git diff --staged= 已经勾了的。习惯了这个心理模型就不别扭了。


工作区、暂存区、HEAD 状态演练

假设你创建一个新文件333.txt,内容333,然后git add

# 修改文件 333.txt → git add 333.txtgitdiff# 空:工作区和暂存区内容一致gitdiff--staged# 有输出:暂存区有 333.txt,HEAD 没有gitdiffHEAD# 有输出:工作区有这个文件,HEAD 没有

三步的状态图:

工作区 暂存区 HEAD 333.txt ──(相同)──▶ 333.txt ──(有差异)──▶ 无此文件

git diff为空是因为 add 之后你没再改过文件——工作区和暂存区完全一致。git diff --staged展示的是「暂存了但还没提交」,这才是你下一步的动作预览。


未跟踪文件:diff 看不到

git diff只比较已跟踪内容,未跟踪文件不会出现在任何 diff 输出中。想看未跟踪的文件只能用git status

如果一定要让 diff 包含未跟踪文件,可以用--intent-to-add假装暂存一个空版本:

gitadd-Nuntracked-file.txt# 暂存一个空占位gitdiff# 现在能看到差异了

这不是日常操作——只是让你知道有这条路。


区间比较:两点 vs 三点

基础语法

gitdiffA..B# 从 A 到 B(A 之后的变化)gitdiffA...B# 从共同祖先到 B(B 分支独有的变化)gitdiffA# 省略第二个参数,默认对比工作区:A vs 当前工作区

闭区间还是开区间

A..B左开右闭(A, B]:以 A 为基准线,A 自己的改动不包含,A 之后到 B 的改动包含

A 的改动 commit C commit D B 的改动 ✗ ✓ ✓ ✓

想包含 A 的改动,基准线往前挪一位:

gitdiffA~1..B# A 的改动也被算进去了

两点 vs 三点在有分支时才有区别

线性历史上.....结果相同。分支场景下...才有特殊含义:

# 从 main 分叉出去后,feature 分支上独有的变化gitdiffmain...feature-branch# feature 分支从分叉点到现在的全部变化(含 main 合进来的)gitdiffmain..feature-branch

...常用于 PR review:“别人在这个分支上到底改了啥,去掉 main 上混入的”。


实用输出格式

快速浏览

# 只看改了哪些文件、改了多少行gitdiff--stat# 额外标注新增/删除/重命名gitdiff--stat--compact-summary# 只看文件名gitdiff--name-only

控制上下文行数

# 默认 3 行上下文gitdiff# 精简到 1 行gitdiff--unified=1# 或 -U1# 只显示改动行,完全不要上下文gitdiff-U0

其他有用选项

# 只看新增的行(过滤掉整个 diff 的元信息)gitdiff|grep'^+'|grep-v'^+++'# 忽略空白变化gitdiff-w# 单词级别的 diff(改动浓缩到一行内)gitdiff--word-diff=plain# 按文件类型过滤gitdiff--'*.py'gitdiff-- src/

高级用法

查看最近 N 个提交的累计差异

# 最近 3 个提交 + 当前未暂存修改gitdiffHEAD~3# 只看最近 3 个提交(不含未暂存)gitdiffHEAD~3..HEAD

指定文件在某区间内的变化

gitdiffmain..HEAD -- path/to/file.py

查看某次提交本身做了什么

gitshow<commit-hash># 等价于gitdiff<commit-hash>~1..<commit-hash>

查看暂存区中某个文件的改动

gitdiff--staged-- path/to/file.py

分支合并前的预览

# 合入 main 会带进去什么gitdiffmain...feature-branch# 如果有冲突,只看冲突文件gitdiff--name-only --diff-filter=U

比较两个分支的文件差异(不看内容)

gitdiff--name-status main..feature# 输出每行:A/M/D + 文件名

检查是否有改动(脚本中常用)

gitdiff--quiet# 工作区干净 → exit 0;有改动 → exit 1gitdiff--quiet--staged# 同上但检查暂存区

常见场景速查

你想看什么命令
改了还没 add 的git diff
add 了还没 commit 的git diff --staged
所有还没 commit 的git diff HEAD
上一次 commit 改了什么git show HEAD
最近 3 个 commit 总共改了啥git diff HEAD~3
某两个 commit 之间的差异git diff A..B
PR 里这个分支独有改动git diff main...feature
只列文件名和统计git diff --stat --compact-summary
只看 .py 文件的改动git diff -- '*.py'

总结

git diff的复杂性来自 Git 的三区域模型——工作区、暂存区、HEAD 各司其职。一旦理解了这个模型,各种 diff 变体只是"选两个点做比较"而已。

日常只用三个命令就够了:

gitdiff--staged# 提交前最后看一眼gitdiffHEAD~3# 回顾最近的改动gitdiffmain...HEAD# 分支合并前确认

剩下的--stat-U0--word-diff是调味料,按需加。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询