11 KiB
Git diff
git add / git commit / git diff / git stash / .gitignore
✍️ 童仲毅 | ⏳ 2018 年 12 月 11 日
©️ 本文演绎自 Atlassian 编写的 Saving Changes。页面上所有内容采用知识共享-署名(CC BY 2.5 AU)许可协议。
使用 git diff 对比更改
diff 命令读入两个数据集,并输出两者之间的更改。git diff 是一个用途广泛的 Git 命令,对 Git 中的数据进行 diff 差异比较。它接受的参数可以是提交、分支和文件等数据。本文将会介绍 git diff 的常见使用场景和差异差异比较的工作流。git diff 命令通常和 git status和 git log 一同使用来分析 Git 仓库当前的状态。
读懂 diff 命令的输出
原始输出
以下栗子都是在同一个仓库中执行的。使用以下命令来创建这个仓库:
$:> mkdir diff_test_repo
$:> cd diff_test_repo
$:> touch diff_test.txt
$:> echo "this is a git diff test example" > diff_test.txt
$:> git init .
Initialized empty Git repository in /Users/kev/code/test/.git/
$:> git add diff_test.txt
$:> git commit -am "add diff test file"
[master (root-commit) 6f77fc3] add diff test file
1 file changed, 1 insertion(+)
create mode 100644 diff_test.txt
如果我们现在执行 git diff,我们不会看到任何输出。这和我们的预期相符,因为目前仓库里还没有任何更改。当我们创建好仓库并缓存了 diff_test.txt 文件后,我们就可以通过修改这个文件来实验 diff 命令的输出:
$:> echo "this is a diff example" > diff_test.txt
执行这个命令将会改变 diff_test.txt 文件的内容。修改后,你可以看到前后差异并分析输出。现在执行 git diff 命令将会产生下面的输出:
diff --git a/diff_test.txt b/diff_test.txt
index 6b0c6cf..b37e70a 100644
--- a/diff_test.txt
+++ b/diff_test.txt
@@ -1 +1 @@
-this is a git diff test example
+this is a diff example
现在,让我们更仔细地研究 diff 命令的输出内容。
1. 进行对比的数据源
diff --git a/diff_test.txt b/diff_test.txt
这一行显示了 diff 命令的数据来源。我们可以看到 a/diff_test.txt 和 b/diff_test.txt 被传给了 diff 命令。
2. 元数据
index 6b0c6cf..b37e70a 100644
这一行显示了一些 Git 自身的元数据。一般来说你不会用到这个信息。输出中的数字是 Git 对象的哈希版本标识。
3. 更改标记
--- a/diff_test.txt
+++ b/diff_test.txt
这些行包含了每个输入来源的更改标记。如上所示,a/diff_test.txt被标记为 --- (删除),而 b/diff_test.txt 被标记为 +++(增加)。
4. 差异片段
diff 输出的剩余部分是一些差异片段。一个差异片段只显示文件中一处存在差异的区域。在这个简单的栗子中,我们只有一处差异。这些片段有自己的输出粒度语义。
@@ -1 +1 @@
-this is a git diff test example
+this is a diff example
第一行是片段头部信息。头部信息位于每个片段开头的 @@ 标记内。头部的内容是一份对该文件更改的摘要。在这个简单的栗子中,-1 +1 表示第 1 行存在变动。在一个真实的栗子中,你会看到这样的头部:
@@ -34,6 +34,8 @@
在这个头部栗子中,从 34 行起有 6 行被删除,从 34 行起有 8 行被增加。
差异片段中接下来的内容是最近进行的更改。被更改的每一行开头都有一个 + 或 - 标记,表示差异的来源版本。 -表示源于 a/diff_test.txt 的更改,+ 表示源于 b/diff_test.txt 的更改。
高亮变更内容
1. git diff --color-words
git diff 有一个特别的模式,可以用更细的粒度高亮变更内容:‐‐color-words。这个模式根据空格将增加和删除的行切分成单词,然后再进行差异比较。
$:> git diff --color-words
diff --git a/diff_test.txt b/diff_test.txt
index 6b0c6cf..b37e70a 100644
--- a/diff_test.txt
+++ b/diff_test.txt
@@ -1 +1 @@
this is agit difftest example
现在,输出中只显示存在改动的单词,增加和删除被不同颜色高亮。
2. git diff-highlight
如果你拷贝了 git 的源码,你会发现有一个 contrib 子目录。它包含了一系列 git 相关的工具和还没有选入 git 核心代码的一些有趣的东西。Diff-highlight 这个 Perl 脚本就是其中之一。它将差异输出的行进行配对,然后高亮单词中改变的字符。
$:> git diff | /your/local/path/to/git-core/contrib/diff-highlight/diff-highlight
diff --git a/diff_test.txt b/diff_test.txt
index 6b0c6cf..b37e70a 100644
--- a/diff_test.txt
+++ b/diff_test.txt
@@ -1 +1 @@
-this is a git diff test example
+this is a diff example
这已经是我们可以看到的最小可能更改了。
对比二进制文件
除了我们之前演示的文本文件之外, git diff 还可以用于二进制文件。不过,默认的输出并没有什么用。
$:> git diff
Binary files a/script.pdf and b/script.pdf differ
Git 有一个功能是允许你指定一个命令行命令,在执行对比之前将二进制文件的内容转换成文本。不过在这之前你需要一些设置。首先,你需要设置 textconv 过滤器描述如何将某种类型的二进制转换成文本。我们使用的是一个叫做 pdftohtml(可以通过 homebrew 安装)的工具,它可以将你的 PDF 转换成可读的 HTML。你可以编辑 .git/config 文件为某个特定的仓库设置该过滤器,或者编辑 ~ /.gitconfig 应用至全局。
[diff "pdfconv"]
textconv=pdftohtml -stdout
然后,你只需要将一个或多个文件名规则关联到这个 pdfconv 过滤器。你可以在仓库根目录创建一个 .gitattributes 文件来执行此操作。
*.pdf diff=pdfconv
配置完成后,git diff 会首先使用配置好的转换脚本运行该二进制文件,然后再对转换后的输出进行差异对比。同样的技术可以用于任意一种二进制文件来获得有意义的差异对比。例如,使用 unzip -l 或类似命令替换 pdf2html,从而解压 zip 和 jar等格式的压缩文件,你可以看到不同提交镜像中新增或删除文件的路径;使用 exiv2 显示图像尺寸等元数据改动;另外还有转换工具可以将 .odf、.doc 和其他格式的文档转换成纯文本文件。简而言之,对于没有严格转换器的二进制文件来说,我们通常可以转换成字符串。
对比特定文件
git diff 命令可以传入一个显式的文件路径选项。当文件路径被传入 git diff 命令时,diff 操作将会被限定在某个特定的文件。下面这个例子展示了这种用法。
git diff HEAD ./path/to/file
这个例子中,该命令的作用域在使用时被限制在 ./path/to/file,它会对比工作目录和缓存区下特定的修改,显示尚未缓存的修改。默认情况下,git diff 的对比是相较 HEAD 而言。忽略上述例子中的 HEAD 后的git diff ./path/to/file 命令有着相同作用。
git diff --cached ./path/to/file
如果在 git diff 执行时使用 --cached 选项,该命令会对比本地仓库和缓存区中的差异。--cached 选项与 --staged 同义。
对比所有更改
不加文件路径的 git diff 命令会对比仓库中所有的更改。上述针对特定文件的栗子,在执行时也可以移除 ./path/to/file 参数从而使本地仓库中所有文件获得相同的输出。
上次提交之后的更改
默认情况下, git diff 会显示上次提交后所有未提交的更改。
git diff
两次提交之间的更改
git diff 可以将 Git 提交引用传给 diff 命令。例如,引用包括 HEAD、标签和分支名称。每个 Git 中的提交都有一个提交编号,你就有执行 git log 命令获得。你也可以将这个编号传给 git diff。
$:> git log --prety=oneline
957fbc92b123030c389bf8b4b874522bdf2db72c add feature
ce489262a1ee34340440e55a0b99ea6918e19e7a rename some classes
6b539f280d8b0ec4874671bae9c6bed80b788006 refactor some code for feature
646e7863348a427e1ed9163a9a96fa759112f102 add some copy to body
$:> git diff 957fbc92b123030c389bf8b4b874522bdf2db72c ce489262a1ee34340440e55a0b99ea6918e19e7a
对比分支
对比两个分支
分支的比较与其他传给 git diff 的引用相同:
git diff branch1..other-feature-branch
这个栗子引入了“点”操作符。其中两个点表示 diff 的输入是两个分支的顶端。当你用空格替代这两个点时,它们的效果相同。另外还有三点操作符:
This example introduces the dot operator. The two dots in this example indicate the diff input is the tips of both branches. The same effect happens if the dots are omitted and a space is used between the branches. Additionally, there is a three dot operator:
git diff branch1...other-feature-branch
三点操作符首先将第一个输入参数 branch1 修改成两个 diff 输入 branch1 分支和 other-feature-branch 分支的共同公共祖先的引用。最后一个输入参数保留不变,为 other-feature-branch 分支的顶端。
对比两个分支中的文件
将文件名作为第三个参数传入 git diff 命令,以比较不同分支上的同一文件:
git diff master new_branch ./diff_test.txt
总结
本文讨论了 Git 差异比较的过程和 git diff 命令。我们讨论了如何阅读 git diff 输出和输出中的各种数据。我们举例讨论了如何为 git diff 输出加上高亮和颜色。我们太累了不同的差异对比策略,例如如何对比分支和特定提交上的更改。除了 git diff 命令外,我们还使用了 git log 和 git checkout。
这篇文章是「git-recipes」的一部分,点击 目录 查看所有章节。
如果你觉得文章对你有帮助,欢迎点击右上角的 Star 🌟 或 Fork 🍴。
如果你发现了错误,或是想要加入协作,请参阅 Wiki 协作说明。