黑帽联盟

标题: Git之旅(9):比较差异 [打印本页]

作者: 定位    时间: 2020-4-3 16:25
标题: Git之旅(9):比较差异
前文中,我们一直在总结git中的各种概念,现在,我们可以换一个方向,从一些简单的使用场景出发,了解一些新的Git命令,不过我们前面的努力并没有白费,因为站在前文概念的基础上,能够很快的理解这些命令。

这篇文章主要来聊聊怎样比较差异,比较差异是我们在使用git的过程中经常会遇到的场景,我们边做边聊,为了方便演示,重新创建一个git仓库,并且创建两个测试文件,命令如下:
$ git init test_repo
$ cd test_repo
$ echo 1 >> test1
$ echo 2 >> test1
$ echo 3 >> test1
$ echo a >> test2
$ echo b >> test2
$ echo c >> test2
$ git add test1 test2
$ git commit -m "init test file"
我创建了两个测试文件,test1和test2,然后将这两个文件添加到了暂存区,最后创建了一个提交,也就是说,最新的提交中保存了test1文件和test2文件的当前的状态。
目前test1和test2的文件内容如下:
$ cat test1
1
2
3

$ cat test2
a
b
c
现在,我想要修改一下这两个文件,我想把test1文件的第2行删除,然后再添加一行,我想把test2文件的最后一行的'c'改成'cc',修改后的结果如下:
$ cat test1
1
3
new

$ cat test2
a
b
cc
上述修改的操作其实是在我的工作目录中进行的,上述操作完成后,我们并没有使用git add命令将修改添加到暂存区,更没有创建提交,所以说,当前工作区的test1文件和test2文件的内容与暂存区和提交中的内容是不同的,那么我们怎样对比这种不同呢?很简单,我们只要使用git diff命令就能够进行差异比较了,比如,我想比较一下,当前的工作目录和暂存区中都有哪些差异呢?则可以直接使用git diff命令进行对比,示例如下:
注:最好在你的命令窗口中键入同样的命令,有语法着色的情况下更加直观
$ git diff
diff --git a/test1 b/test1
index 01e79c3..397b006 100644
--- a/test1
+++ b/test1
@@ -1,3 +1,3 @@
1
-2
3
+new
diff --git a/test2 b/test2
index de98044..278314e 100644
--- a/test2
+++ b/test2
@@ -1,3 +1,3 @@
a
b
-c
+cc
执行git diff命令后,返回信息如上,可以看到,test1文件中的第二行被删除了,因为"2"前面有一个减号,并且,添加了新的一行,新行的内容为"new",因为"new"前面有一个加号,test2文件中的差异也被显示了出来,我们把test2文件中的第三行的'c'改成了'cc',对于git来说,git认为我们把'c'这一行删除了,然后又在原位置添加了新的一行'cc',可以看出,当我们使用git diff命令时,git会将工作区与暂存区中的所有文件差异一次性的全部显示出来,如果我们只想查看某个特定文件在这两个区域的差异,也可以指定要查看的文件,比如,我们只想看看test1文件在工作区和暂存区是否存在差异,只需要执行如下命令即可:
$ git diff -- test1
diff --git a/test1 b/test1
index 01e79c3..397b006 100644
--- a/test1
+++ b/test1
@@ -1,3 +1,3 @@
1
-2
3
+new
上例中的'--'后面可以跟随多个文件路径,每个文件路径用空格隔开,上述命令中我们只指定了test1文件的文件路径。
由于我们修改文件以后并没有暂存这些修改,更没有创建提交,所以,目前来说,暂存区和提交中的文件内容是相同的,也就是说,"暂存区和提交中指向的文件" 与 "工作区中的文件" 之间的差异是相同的,那么,怎样查看工作区和提交中的文件差异呢?如果想要查看工作区和最新提交之间的差异,则可以使用如下命令查看:
$ git diff HEAD
diff --git a/test1 b/test1
index 01e79c3..397b006 100644
--- a/test1
+++ b/test1
@@ -1,3 +1,3 @@
1
-2
3
+new
diff --git a/test2 b/test2
index de98044..278314e 100644
--- a/test2
+++ b/test2
@@ -1,3 +1,3 @@
a
b
-c
+cc
没错,使用"git diff HEAD"命令即可查看工作区与当前分支最新的提交之间的差异,还记得上一篇文章中我们总结的HEAD指针么?前一篇文章中我们总结过,通常情况下,HEAD指针总是间接的指向了当前所在分支的最新提交,所以,你可以把"git diff HEAD"命令中的HEAD理解成当前所在分支的最新提交的别名,聪明如你一定想到了,我能不能把HEAD替换成当前分支最新提交的哈希码呢?当然可以,示例如下(下例命令中的哈希码就是最新提交的hash值):
$ git diff a63d5b2f38dffb0c0e749a4c49a924490e23a190
diff --git a/test1 b/test1
index 01e79c3..397b006 100644
--- a/test1
+++ b/test1
@@ -1,3 +1,3 @@
1
-2
3
+new
diff --git a/test2 b/test2
index de98044..278314e 100644
--- a/test2
+++ b/test2
@@ -1,3 +1,3 @@
a
b
-c
+cc
可以看到,上述命令返回的结果与"git diff HEAD"命令所返回的结果是完全相同的,你可以无差别的使用这两种方式去查看当前的工作区与最新提交之间的差异,之前说过,修改操作完成后我们并没有暂存修改,更没有机会提交修改,所以,以目前的状态来说,使用"git diff命令"和使用"git diff HEAD命令"查看到的差异信息是完全一样的,因为以目前的情况来说,暂存区和提交中的状态是完全相同的,小结一下:
我们可以使用下图中的命令,比较"工作区和暂存区的差异"以及"工作区和提交中的差异"
1.png
刚才我们只是进行了修改操作,没有进行任何暂存和提交,现在,我们来将刚才的修改添加到暂存区,执行如下命令:
$ git add -A
我使用上述命令,一次性将所有文件的所有变更都进行了暂存,从目前状态来看,工作区中的文件状态已经和暂存区的文件状态相同了,因为我已经把所有变更都暂存了,所以,此时再次使用"git diff"命令查看工作区和暂存区的文件差异,则不会显示任何内容,因为此时这两个区域没有差异,但是如果我执行"git diff HEAD"命令,仍然可以查看到工作区和提交之间的差异。

如果此时,我再次在工作区进行一些变更,会怎样呢?
我们来试试,我们尝试着在test2文件中添加一行,命令如下:
$ cat test2
a
b
cc

$ echo dd >> test2

$ cat test2
a
b
cc
dd
我们在test2命令中添加了一行文本为"dd"的新行。

此时,再次查看工作区和另外两个区域的差异,如下:
查看工作区和暂存区的差异
$ git diff
diff --git a/test2 b/test2
index 278314e..e165bb5 100644
--- a/test2
+++ b/test2
@@ -1,3 +1,4 @@
a
b
cc
+dd
查看工作区和最新提交的差异
$ git diff HEAD
diff --git a/test1 b/test1
index 01e79c3..397b006 100644
--- a/test1
+++ b/test1
@@ -1,3 +1,3 @@
1
-2
3
+new
diff --git a/test2 b/test2
index de98044..e165bb5 100644
--- a/test2
+++ b/test2
@@ -1,3 +1,4 @@
a
b
-c
+cc
+dd
没错,我们通过 "git diff" 和 "git diff HEAD" 命令已经可以清楚的查看到了工作区和另外两个逻辑区域的差异了,你可能会问,如果在这个时候,我想要查看暂存区和最新提交之间的差异,该怎样查看呢?很简单,使用"git diff --cached"命令即可查看,示例如下:
$ git diff --cached
diff --git a/test1 b/test1
index 01e79c3..397b006 100644
--- a/test1
+++ b/test1
@@ -1,3 +1,3 @@
1
-2
3
+new
diff --git a/test2 b/test2
index de98044..278314e 100644
--- a/test2
+++ b/test2
@@ -1,3 +1,3 @@
a
b
-c
+cc
由于最新的修改(在test2中添加"dd"文本的操作)还没有暂存到暂存区,所以,当我们查看暂存区和最新提交之间的差异时,是看不到最新添加的"dd"的,因为在此时,暂存区和提交中都没有"dd"这一行,目前来说,"dd"这一行只存在于工作区的test2文件中。

也就是说,我们可以通过下图中的命令,比较各个"区域"之间的差异。
2.png

此时,我们执行"git status"命令,查看当前仓库的状态,如下
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified:   test1
modified:   test2

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

modified:   test2
可以看到,第一次的修改操作已经添加到了暂存区,但是第二次的修改操作(在test2中添加"dd"的操作)还没有被暂存区,为了方便演示,我先将最新的修改暂存,然后一次性的将所有修改全部提交,命令如下:
$ git add test2
#你可以在执行上述命令后,再次对比一下各个区域的差异,此处为了方便进行后面的演示,将所有修改一次性提交
$ git commit -m "test"
我们已经将之前所有的变更操作提交了,到目前为止,我们的仓库中已经保存了两个状态,也就是说,当前的git仓库中一共有两个提交,我们能不能对比一下这两个提交之间的差异呢?当然能了,同样使用git diff命令就能完成,首先,我们来查看一下当前的提交历史,如下:
$ git log --oneline
aafbaa8 (HEAD -> master) test
a63d5b2 init test file
如你所见,我们已经创建了两个提交,第一个提交是'a63d5b2',第二个提交是'aafbaa8',如果我想要对比这两个提交之间的差异,则可以使用如下命令:
$ git diff a63d5b2 aafbaa8
diff --git a/test1 b/test1
index 01e79c3..397b006 100644
--- a/test1
+++ b/test1
@@ -1,3 +1,3 @@
1
-2
3
+new
diff --git a/test2 b/test2
index de98044..e165bb5 100644
--- a/test2
+++ b/test2
@@ -1,3 +1,4 @@
a
b
-c
+cc
+dd
如你所见,执行上述命令后,会显示出第二个提交相对于第一个提交之间的文件内容差异。
上文中说过,可以使用"HEAD"代指最新的提交,所以,上述命令可以改成如下命令,它们的效果是一样的。
$ git diff a63d5b2 HEAD
其实,我们还有更简便的方法,比如,我们可以把上述命令改成如下命令:
$ git diff HEAD~ HEAD
上述命令中的"HEAD"代表最新的提交,上述命令中的"HEAD~"代表最新提交的前一个提交,由于我们只有两个提交,所以,"HEAD"代表最新的提交(即 aafbaa8 ),"HEAD~ "代表最新提交的前一个提交(即 a63d5b2 ),没错,这是GIT中的一种简易写法,当我们想要操作git中的"最新提交"或者"最新提交之前的某个提交"时,可以使用这种简易写法,这种简易写法的规则如下:
HEAD 表示当前分支的最新提交
HEAD~ 表示当前分支的最新提交的前一个提交(即最新的第二个提交,也就是最新提交的父提交)
HEAD~~ 表示当前分支的最新提交的前前提交(即最新的第三个提交,也就是最新提交的祖父提交)
HEAD~~~ 表示当前分支的最新提交的前前前提交(即最新的第四个提交,也就是最新提交的曾祖父提交)
那么,如果我想要表示最新的第五个提交,难道必须写够4个 ~ 符号才行么?就没有更简单的表示方法么?当然有了,"HEAD~~~~"可以写成"HEAD~4",它们两个是等效的,它们都表示最新的第5个提交。
HEAD~~~等效于HEAD~3
HEAD~~等效于HEAD~2
HEAD~等效于HEAD~1
HEAD等效于HEAD~0
上述简易的写法被称作"相对提交名",我们可以使用相对提交名来快速的定位(或者代指)最新的几个提交,这样做的方便之处就在于我们不用通过"git log"命令查看最近的几个提交的哈希值,就可以直接通过相对提交名快速的操作最新的几个提交了,还是很方便的。
注:你可能还看到过类似"HEAD^"写法的相对提交名,这样的相对提交名在提交有多个直系父提交时比较常用,由于我们还没有总结怎样merge分支,所以暂时不用纠结于这几种写法。

其实,我们还可以反过来操作,利用相对提交名获取到对应提交的哈希码,只要借助"git rev-parse"命令即可。
比如,获取到最新的第二个提交的哈希码
$ git rev-parse HEAD~
a63d5b2f38dffb0c0e749a4c49a924490e23a190
你还记得"git rev-parse"命令吗,我们在前文中用过这个命令,此命令可以通过哈希码的缩写获取到完整的哈希码,比如如下命令:
$ git rev-parse a63d5b
a63d5b2f38dffb0c0e749a4c49a924490e23a190
"git rev-parse"命令不仅能通过相对提交名和哈希码缩写获取到完整的哈希码,还可以通过分支名或者标签名获取到对应提交的哈希码,比如获取master分支上最新提交的哈希码
$ git rev-parse master
aafbaa85ae9f11a5875ba0b66daa76e9afb13c35
你可以将上述命令master分支名换成任何一个别的分支的分支名,比如,test分支、develop分支、任何一个其他分支的名字都可以,而且你不用切换到对应的分支上,就能够获取到对应分支上的最新提交的哈希码。

我们似乎跑题了,现在,我们把话题扯回到"git diff"命令上。
我们刚才用"git diff a63d5b2 aafbaa8"命令比较了这两个提交,除了这种写法可以比较两个commit,如下写法也是等效的:
$ git diff a63d5b2..aafbaa8
没错,我们无非是在两个"commit ID"之间添加了两个点".."而已,这种语法也可以比较两个commit之间的差异,当我们使用这种语法时,可以省略任意一边的哈希值,省略的那边的哈希值会被'HEAD'替代,也就是说,如下两条命令是等效的。
$ git diff a63d5b2..
$ git diff a63d5b2..HEAD
到目前为止,我们都是在新创建的测试仓库的master分支上做测试的,现在我们来创建一个新分支,看看在多分支下进行差异对比会不会有什么新发现。

首选,创建一个新的test分支,命令如下:
$ git checkout -b test
Switched to a new branch 'test'
通过上述命令,我们基于master分支创建了新的test分支,并且切换到了test分支上。

现在,我们分别在test分支和master分支上做一些修改,并且创建提交,然后再使用"git diff"命令进行一些比较差异的测试。
首先,在test分支上做一些变更,并且创建提交,操作如下:
/d/workspace/git/test_repo (test)
$ ls
test1  test2

/d/workspace/git/test_repo (test)
$ echo "test branch has been created" > test3

/d/workspace/git/test_repo (test)
$ git add test3

/d/workspace/git/test_repo (test)
$ git commit -m "add new file test3 in test branch"
[test b5543f1] add new file test3 in test branch
1 file changed, 1 insertion(+)
create mode 100644 test3
然后,我们切换到master分支,做一些变更,并且创建提交,操作如下:
/d/workspace/git/test_repo (test)
$ git checkout master
Switched to branch 'master'

/d/workspace/git/test_repo (master)
$ ls
test1  test2

/d/workspace/git/test_repo (master)
$ cat test1
1
3
new

/d/workspace/git/test_repo (master)
$ echo 'New changes in the master branch' >> test1

/d/workspace/git/test_repo (master)
$ cat test1
1
3
new
New changes in the master branch

/d/workspace/git/test_repo (master)
$ git add test1

/d/workspace/git/test_repo (master)
$ git commit -m "New changes in the master branch"
[master ba4b75f] New changes in the master branch
1 file changed, 1 insertion(+)
上例中,我们在master分支的test1分支中添加了一行新行,新行的内容为"New changes in the master branch",并且为此修改操作创建了提交。

此时,使用"gitk --all"命令查看分支,如下图所示
3.png
现在我们已经有了两个分支,master分支和test分支,并且两个分支上都有了属于各自分支的提交,那么此时,我们来尝试使用"git diff"命令来尝试对比一下这两个分支吧,命令如下:
/d/workspace/git/test_repo (master)
$ git diff test master
diff --git a/test1 b/test1
index 397b006..7e5632c 100644
--- a/test1
+++ b/test1
@@ -1,3 +1,4 @@
1
3
new
+New changes in the master branch
diff --git a/test3 b/test3
deleted file mode 100644
index 2116a15..0000000
--- a/test3
+++ /dev/null
@@ -1 +0,0 @@
-test branch has been created
上例中,我们尝试使用"git diff test master"命令对比"两个分支"的差异,其实,"git diff test master"命令并没有对比这两个分支的差异,而是对比了这两个分支上最新提交之间的差异,也就是说,上述命令的作用仍然是两个提交的对比,是master分支上最新的提交与test分支上最新的提交之间的差异比较,我们完全可以将上述命令中的test换成test分支上最新提交的哈希码,将上述命令中的master换成master分支上最新提交的哈希码,我们得到的结果将和上述命令完全相同,不信?我们来试试,测试如下
首先,我们通过git log命令找到test分支和master分支上的最新的提交的哈希码,如下
/d/workspace/git/test_repo (master)
$ git log --oneline --graph --all
* ba4b75f (HEAD -> master) New changes in the master branch
| * b5543f1 (test) add new file test3 in test branch
|/
* aafbaa8 test
* a63d5b2 init test file
从上述命令可以看出,test分支上最新提交的哈希码为b5543f1,master分支上最新提交的哈希码为ba4b75f ,然后,我们将"git diff test master"命令中的test和master分别换成这两个哈希码,操作如下:
/d/workspace/git/test_repo (master)
$ git diff b5543f1 ba4b75f
diff --git a/test1 b/test1
index 397b006..7e5632c 100644
--- a/test1
+++ b/test1
@@ -1,3 +1,4 @@
1
3
new
+New changes in the master branch
diff --git a/test3 b/test3
deleted file mode 100644
index 2116a15..0000000
--- a/test3
+++ /dev/null
@@ -1 +0,0 @@
-test branch has been created
可以看到,上述命令的返回结果与"git diff test master"命令的返回结果完全相同,也就是说,虽然"git diff test master"命令中写的是分支名,但是"分支名"对应的其实是"分支上最新的提交"。
所以说,我们也可以把上述命令写成如下命令
$ git diff test..master
它们都是等效的。


小结
此处把上文中的常用的git diff命令总结一下,以便回顾。
git diff
比较工作区和暂存区

git diff HEAD
比较工作区和当前分支最新的提交,你可以把HEAD换成别的分支的名字,比如test分支,"git diff test"表示比较当前工作区和test分支最新的提交之间的差异,也可以把HEAD替换成任何一个commit的ID,表示比较当前工作区和对应提交之间的差异。

git diff --cached
比较暂存区和当前分支最新的提交

上述命令都是比较所有文件的差异,如果想要指定文件,可以使用"--"指定文件的路径,文件路径可以有多个,用空格隔开。

git diff -- file1
git diff -- ./file1
只比较工作区和暂存区中file1文件的差异

git diff -- file1 file2
只比较工作区和暂存区中file1以及file2文件的差异

git diff -- dir1/d1/f1
只比较工作区和暂存区中dir1/d1/f1文件的差异

git diff -- dir1/
只比较工作区和暂存区中dir1目录中所有文件的差异

git diff HEAD -- ./file1
只比较工作区和当前分支最新的提交中file1文件的差异,HEAD可以替换成分支名或者commitID

git diff testbranch -- ./file1
只比较工作区和testbranch分支最新的提交中file1文件的差异

git diff --cached testbranch
比较暂存区和testbranch分支最新的提交

git diff --cached testbranch --./file1
只比较暂存区和testbranch分支最新的提交中file1文件的差异

git diff HEAD~ HEAD
比较当前分支中最新的两个提交之间的差异

git diff HEAD~ HEAD -- file1
比较当前分支中最新的两个提交中的file1文件的差异

git diff commitID1 commitID2
比较两个commit之间的差异
git diff commitID1..commitID2
同上,比较两个commit之间的差异,两个命令等效

git diff branch1 branch2
比较两个分支上最新提交之间的差异
git diff branch1..branch2
同上,比较两个分支上最新提交之间的差异,两个命令等效
这篇文章就先总结到这里,希望能够帮助到你~加油~







欢迎光临 黑帽联盟 (https://bbs.cnblackhat.com/) Powered by Discuz! X2.5