正在加载,请稍候…

Git Diff 不只是看改动:先分清你到底在比较哪两边

深入解析 git diff 的六种比较模式(工作区 vs 暂存区、提交间比较等),帮助你正确理解 diff 输出并避免常见陷阱。

简介

git diff 是最常用的 Git 命令之一,但也是最容易被误解的。许多开发者认为它只是显示“改了什么”,但这忽略了关键点:git diff 比较的是两个特定的对象。如果你不清楚是哪两个对象在比较,输出结果可能会误导你。

本文澄清了六种最常见的比较模式,解释了如何解读 diff 输出,并提供了日常开发中的实用技巧。读完本文,你将知道在什么场景下使用哪个变体,以及为什么。

developer looking at git diff output on terminal

核心概念:关键在于比较基线

官方 git-diff 文档指出,git diff 可以比较:

  • 工作区 vs 暂存区
  • 暂存区 vs 某个树对象(通常是 HEAD)
  • 工作区 vs 某个提交
  • 两个任意提交
  • 合并结果
  • 两个 blob 对象
  • 磁盘上的两个文件

在运行任何 git diff 命令之前,问自己:“我在比较哪两个东西?” 答案决定了使用哪个变体,并确保你得到预期的输出。

六种最常见的比较模式

1. 默认 git diff:工作区 vs 暂存区

这是最常见的误解。许多人认为它比较工作区和 HEAD,但实际上它显示的是工作区和暂存区之间的差异

git diff

这显示了你尚未暂存的更改。如果你已经用 git add 暂存了一些更改,这些更改不会出现在这里。

何时使用:在决定下一步暂存什么之前,审查未暂存的更改。

2. git diff --cached:暂存区 vs HEAD

这将暂存区与最近一次提交(默认是 HEAD)进行比较。它精确显示了如果你现在运行 git commit 将会提交的内容。

git diff --cached
# 或
git diff --staged

何时使用:审查即将提交的精确补丁。

3. git diff HEAD:工作区 + 暂存区 vs HEAD

这结合了未暂存和已暂存的更改,显示自上次提交以来的所有差异。

git diff HEAD

何时使用:获取自上次提交以来所有更改的完整概览。

4. git diff A Bgit diff A..B:比较两个提交

两种形式都比较两个任意提交的状态。这里的 .. 语法不同于 git log 中的语义,它只是一个分隔符。

git diff main feature
git diff main..feature  # 同上

何时使用:查看两个分支或标签之间的差异。

5. git diff A...B:基于共同祖先的比较

这将 A 和 B 的合并基础与 B 进行比较。它显示了 B 从与 A 分叉以来引入了哪些更改。

git diff main...feature

这等价于:

git diff $(git merge-base main feature) feature

何时使用:代码审查时,只看特性分支上的更改,忽略 main 上同时发生的更改。

6. git diff --merge-base:显式指定共同祖先

为了清晰,你可以使用 --merge-base 标志:

git diff --merge-base A B

这在脚本和 CI 流水线中很有用,显式性提高了可读性。

完整示例:比较两个分支

让我们演练一个真实场景。你有一个 main 分支和一个 feature 分支。你想看看 feature 引入了什么。

设置

# 创建两个分支
git init
echo "Initial content" > file.txt
git add file.txt && git commit -m "Initial commit"

# 创建 feature 分支
git checkout -b feature
echo "Feature work" >> file.txt
git add file.txt && git commit -m "Feature commit"

# 回到 main 并添加一个提交
git checkout main
echo "Main work" >> file.txt
git add file.txt && git commit -m "Main commit"

错误方式

git diff main feature

这显示两个分支最新提交之间的差异。它既包含特性工作也包含 main 工作,使得难以隔离特性分支实际添加的内容。

正确方式

git diff main...feature

这只显示 featuremain 分叉以来所做的更改。输出干净且聚焦。

输出模式:选择合适的格式

git diff 提供多种输出格式,每种适用于不同需求。

模式 命令 使用场景
补丁(默认) git diff 详细审查更改
统计 git diff --stat 快速概览哪些文件更改及行数
仅文件名 git diff --name-only 更改的文件列表(机器友好)
文件名+状态 git diff --name-status 文件列表及更改类型(M, A, D, R)
数字统计 git diff --numstat 机器友好的统计(每个文件增删行数)
词级差异 git diff --word-diff 高亮单个词的变化(适用于文档、配置文件)

示例:--word-diff

git diff --word-diff

这在审查文档、配置文件或长字符串的更改时特别有用,因为行级差异过于粗糙。

常见陷阱

  • 认为默认 git diff 与 HEAD 比较:它比较工作区与暂存区。使用 git diff HEAD 查看总更改。
  • 误以为 git diff A..Bgit log A..B 语义相同:在 git diff 中,.. 只是分隔符,结果与 git diff A B 相同。
  • 误解 git diff A...B:它比较共同祖先,而不是直接比较两个最新提交。
  • 预测基于时间的令牌时忽略时区:始终使用服务器的时区,而不是本地假设。
  • 天真地解析 --name-only 输出:使用 -z 处理包含空格或特殊字符的文件名。

常见问题

git diffgit diff HEAD 有什么区别?

git diff 比较工作区与暂存区。git diff HEAD 比较工作区+暂存区与最近一次提交。前者显示未暂存的更改,后者显示自上次提交以来的所有更改。

何时使用 git diff --cachedgit diff --staged

它们是同义词。使用你觉得更清晰的即可。--staged 对许多开发者更直观。

如何比较 Git 仓库外的两个文件?

使用 git diff --no-index file1 file2。即使文件不在 Git 仓库中也能工作。

为什么 git diff 显示没有更改,但我明明做了修改?

你可能已经用 git add 暂存了这些更改。使用 git diff --cached 查看已暂存的更改,或 git diff HEAD 查看所有更改。

如何只查看更改的文件列表?

使用 git diff --name-only。为了机器安全,添加 -z 使用 NUL 作为分隔符。

结论

git diff 不是单个命令——它是一个比较工具家族。关键在于知道你在和什么比较。一旦掌握了这里描述的六种模式,你将避免常见陷阱,并自信地使用 git diff

试试我们的文本差异工具,交互式地可视化两个文本片段之间的差异。