引言
在日常工作场景中,一直都在用IntelliJ IDEA中的Git插件管理版本分支,偶尔使用命令行或SourceTree。在提交commit时更多使用图形化工具查看改动点,因此对于git diff命令使用的较少。
最近,尝试将OpenAI融入到git commit工作流中,期望可以自动生成待提交改动的摘要。为此,需要先通过git diff命令把diff的描述文件解析出来,因此,对git的diff操作再次详细的回顾了一番。
本篇文章核心包含两个方面:git diff命令详解和git diff描述文件详解。
Git Diff 命令
git diff是 Git 版本控制系统中用于比较不同版本之间差异的核心命令。它允许用户查看尚未提交的更改(即工作目录与暂存区之间的差异)、暂存区与最近一次提交之间的差异,以及任意两个提交之间的差异。这对于开发者来说是一个极其重要的工具,有助于审查代码变化、理解改动内容,并决定何时以及如何提交代码。
基本语法
git diff [<options>] [<commit>] [--] [<path>...]
git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>…]
git diff [<options>] [--merge-base] <commit> [<commit>…?] <commit> [--] [<path>…?]
git diff [<options>] <commit>…?<commit> [--] [<path>…?]
git diff [<options>] <blob> <blob>
git diff [<options>] --no-index [--] <path> <path>
- [<options>]: 指定一些修改 git diff 行为的可选参数。例如,-w 用来忽略空白差异,--color 用来以彩色显示差异,等等。
- [<commit>]: 指定要比较的两个版本,默认情况下,如果不提供 commit,则比较的是当前工作目录与暂存区的差异。如果提供了两个 commit,则比较这两个 commit 间的差异。
- [--]:这是一个命令行参数的分隔符,用于明确指示其后跟的是文件路径,用于防止路径名被误识别为 commit 参数。
- [<path>…]: 可以指定特定的文件或目录路径来进行局部差异的比较。
- --cached:这个选项告诉 git diff 显示暂存区与指定提交(通常是最后一次提交)之间的差异。这是查看已经暂存但还未提交的更改的常用方法。
- --merge-base:当使用这个选项时,git diff 将会使用当前分支和指定的 <commit> 或分支的最近共同祖先作为比较的基准。这个选项在你想要比较的两个分支并没有直接的父子关系时非常有用。
- <blob>:这是要比较的两个blob对象的引用。blob对象的引用通常是文件在特定提交中的SHA-1哈希,这个哈希可以通过git ls-tree或git rev-parse等命令检索。
- --no-index:这个选项告诉git diff命令,你想要比较的文件或目录不在Git的索引中。这是比较非Git管理的文件或目录的关键选项。
options可选参数
- 输出选项
- --stat: 显示差异摘要的统计信息。
- --shortstat: 仅显示 --stat 中的行更改/添加/删除的总数。
- --name-only: 只显示有差异的文件的名称。
- --name-status: 显示有差异的文件的名称及其状态(如修改、添加、删除)。
- 格式化输出选项
- --color[=<when>]: 显示颜色输出;<when> 可以是 never、always 或 auto。
- --word-diff[=<mode>]: 以词为单位显示差异;<mode> 可以是 color、plain、porcelain 或 none。
- --word-diff-regex=<regex>: 用于 --word-diff 的自定义正则表达式。
- --no-prefix: 不显示文件名前缀(a/ 或 b/)。
- 比较选项
- --cached, --staged: 比较暂存文件和上一次提交。
- HEAD: 比较工作目录和最新提交。
- <commit>: 比较工作目录和某次特定提交。
- <commit>..<commit>: 比较两次提交记录的差异。
- 忽略选项
- -w, --ignore-all-space: 忽略所有空白差异。
- --ignore-space-at-eol: 忽略行尾的空白差异。
- --ignore-space-change, -b: 忽略空格数量的变化。
- --ignore-blank-lines: 忽略空白行的变化。
- 路径选项
- -- <path>: 限制输出只显示某些文件或路径。
- --no-index: 比较两个不在版本控制下的文件。
常见用例
1. 比较工作目录与暂存区(或指定commit)
git diff
此命令将显示尚未添加到暂存区的所有更改。
git diff -w
使用 -w 选项比较时忽略空白变化。这会输出比较结果,但不会包括空白字符的差异。
git diff <commit>
显示某个特定提交与当前工作目录之间的差异:
git diff commit -- file.txt
显示指定路径 file.txt 在 commit 提交和当前工作目录之间的差异。
git --no-pager diff --ignore-all-space --ignore-blank-lines
该命令告诉git直接将差异结果输出到终端,但不使用分页器(在编写脚本或者只是想一次性看到所有输出而不想在分页器中滚动查看,这会很有用),展示上忽略空白字符或行的变化。
2. 比较暂存区与最近提交(或指定commit)
git diff --cached
或者
git diff --staged
此命令仅展示已暂存但未提交的更改。
git diff --cached <commit>
此命令比较暂存区与某个特定提交的差异。
git diff --cached --merge-base <other-branch>
此命令比较暂存区与当前分支和另一个分支的最近公共祖先之间的差异。
3. 比较两个分支或两个具体提交
git diff branchA branchB
或
git diff commitSHA1 commitSHA2
这里,branchA 和 branchB 分别代表分支名称,而 commitSHA1 和 commitSHA2 则是具体的提交哈希值。该命令显示两个分支或提交之间所有文件的差异。
- 查看两个分支的合并基准差异
git diff --merge-base branchA branchB
这会显示自从 branchA 和 branchB 分岔以来,在 branchB 上进行了哪些更改。
- 比较工作目录中的两个不在 Git 版本控制下的文件
git diff --no-index file1.txt file2.txt
这个命令会输出file1.txt和file2.txt之间的差异,就像在使用专门的差异比较工具一样。
Git Diff 描述文件
git diff 命令可以产生多种格式的输出,用于描述文件之间的差异。这些输出通常包含以下部分:
1. 差异元数据
这包括差异涉及的文件及其相关的 Git 索引信息,如下所示:
diff --git a/<文件路径> b/<文件路径>
index <哈希值>..<哈希值> <权限>
--- a/<文件路径>
+++ b/<文件路径>
- diff --git: 表示这是一个 git 差异,并显示了变化的文件。
- a/ 和 b/: 表示“旧”和“新”版本的文件,通常代表了比较的两个不同状态(例如,a/ 可能是某个提交中的文件状态,而 b/ 是工作目录中的状态)。
- index: 后面的哈希值代表文件在这两个状态下的 blob 哈希值。
- --- 和 +++: 分别指向比较中的旧文件和新文件。
2. 差异内容
每个文件的实际差异以一组以 @ 符号开头的行表示,如下所示:
@@ -x,y +a,b @@
- -x,y: 表示原文件中发生变化的区域,x 是起始行号,y 是该区域的行数。
- +a,b: 表示新文件中发生变化的区域,a 是起始行号,b 是该区域的行数。
- @@: 符号是差异区域的开始和结束标记。
在这之后,你会看到实际的行级差异,包括:
- 以 - 开头的行代表旧文件(a/)中的内容被移除或更改。
- 以 + 开头的行代表新文件(b/)中的内容被添加或更改。
- 以空格开头的行代表没有变化的上下文信息。
示例
diff --git a/example.txt b/example.txt
index 1234567..89abcde 100644
--- a/example.txt
+++ b/example.txt
@@ -1,4 +1,4 @@
-This is the original text.
+This is the revised text.
This line did not change.
-This line will be removed.
+This line has been added.
diff --git a/newfile.txt b/newfile.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/newfile.txt
@@ -0,0 +1 @@
+this is a new file.
diff --git a/oldfile.txt b/oldfile.txt
deleted file mode 100644
index e69de29..0000000
--- a/oldfile.txt
+++ /dev/null
@@ -1 +0,0 @@
-this file is going to be deleted.
diff --git a/oldname.txt b/newname.txt
similarity index 85%
rename from oldname.txt
rename to newname.txt
index 83ae2f1..a1e2f95 100644
--- a/oldname.txt
+++ b/newname.txt
@@ -1,4 +1,4 @@
-this is the old file.
+this is the new file.
Some content stays the same.
-Some old content.
+Some new content.
Bye!
示例中:
- diff --git a/example.txt b/example.txt:显示的是 example.txt 文件的变化。
- index 1e642e0..2c3a09b 100644:显示了 example.txt 文件的 哈希值变化和文件权限。
- --- a/example.txt 和 +++ b/example.txt:表示对比的是文件的哪两个版本。
- @@ -1,4 +1,4 @@:表示变化发生在哪些行,其中 -1,4 表示原文件的第1行开始的四行代码,+1,4 表示新文件的第1行开始的四行代码。
- -This is the original text.:行以 - 符号开始,表示这一行在新版本中被删除或者更改了。
- +This is the revised text.:行以 + 符号开始,表示这一行在新版本中是新增的或者是旧行的修改结果。
- This line did not change.:没有前缀的行表示两个版本中这行内容没有发生变化。
- new file mode 100644:这表明 newfile.txt 是一个新增的文件,100644 是文件的权限。
- index 0000000..e69de29:变化前的索引是 0000000(表示文件不存在),变化后是 e69de29(新文件的哈希值)。
- --- /dev/null:表示该文件在原来的版本中不存在。
- +++ b/newfile.txt:表示新的文件。
- +this is a new file.:前面的 + 符号表示这是文件中新增的内容。
- deleted file mode 100644:这表明 oldfile.txt 是一个被删除的文件。
- index e69de29..0000000:变化前的索引是 e69de29(文件的原始哈希值),变化后是 0000000(表示文件已经不存在)。
- --- a/oldfile.txt:表示原来的文件。
- +++ /dev/null:表示文件在新的版本中被删除。
- -this file is going to be deleted.:前面的 - 符号表示这些内容在文件中被删除了。
- similarity index 85%:表示重命名的文件 newname.txt 与原文件 oldname.txt 有 85% 的内容相似。Git 通过相似度指数(similarity index)来判断文件是否被重命名。如果重命名的文件与原文件足够相似,Git 将识别它为重命名操作,而不是删除旧文件和创建新文件两个独立的操作。
- rename from oldname.txt:表示文件原先的名称。
- rename to newname.txt:表示文件被重命名后的新名称。
- --- a/oldname.txt 和 +++ b/newname.txt:表示对比的是文件的两个版本,前者是原名称,后者是重命名后的名称。