當我們做了多次 git add
、git commit
指令,又在工作目錄中做了一些更改時,很容易忘記在哪邊改了什麼,這時就可以用 git diff
指令比較。
在 git diff
給予不同的引數(arguments)後,將可指定不同的比較對象。我們先複習在 Day 5 文章中,提到檔案從工作目錄經預存區到成為 commit 快照的流程:
接著,我們就可以開始進行實驗!
為了可以比較 git diff
不同用法,我們先讓實驗設定滿足以下條件:
index
,但還沒形成 commit。要形成 1.,我們依序在一個經 git init
的資料夾輸入下列指令:
echo "First content" > file.txt
git add file.txt
git commit -m "Initial commit"
echo "Second content" > file.txt
git add file.txt
git commit -m "Second commit"
這時候共有兩筆 commit、同時預存區 index
存放的是第二筆 commit 的內容:
接著透過以下指令,把預存區的內容也改到跟最新 commit 不同:
echo "Third content" > file.txt
git add file.txt
目前預存區的檔案變成 "Third content"
版本:
最後再輸入以下指令,把工作目錄檔案也成跟預存區不同:
echo "Fourth content" > file.txt
經過以上操作,目前整體結構如下(物件雜湊碼與其關係由查看 .git/
資料夾與多次下 git cat-file
指令得知):
接下來我們將比較 git diff
的不同使用方法,分別在什麼地方之間做比較。
git diff
如果單純下 git diff
,可發現下列結果:
現在我們依序檢視其內容:
diff --git a/file.txt b/file.txt
表示這是一個 diff
區塊,要比較的對象有舊的(以 a/
前綴)file.txt
與新的(以 b/
前綴)file.txt
。
index 55d4074..b88ab8e 100644
這個 index
不是檔案經 git add
指令會加進去的預存區,只是說明這行要比較雜湊碼為 55d4074...
與 b88ab8e...
兩者;結尾的 100644
為檔案模式,表示一個正常的不可執行檔案。
透過 git cat-file
指令可知道 55d4074...
是 "Third content"
版本的 blob 物件雜湊碼,而目前還沒有 b88ab8e...
這個物件,既然沒這個物件,那 git diff
在比什麼?
目前還沒有 b88ab8e 這個物件
git diff
非常聰明,即便目前在工作目錄的最新版本(即內容為 "Fourth content"
的 file.txt
檔案)還沒有經 git add
做成 blob 物件,但它已經知道如果做成 blob 物件,其雜湊碼就會變成 b88ab8e...
,以 git hash-object
指令觀察可證明:
以 git hash-object 觀察工作目錄檔案做成 blob 物件後的雜湊碼
為了方便解說,本文中如果再提到 b88ab8e...
,就用「準 blob 物件」表示。
--- a/file.txt
+++ b/file.txt
首行以 a/
為前綴,表示發生變動前的檔案、次行以 b/
為前綴,表示變動後的檔案;在此範例中,發生變動前後的檔案都是 file.txt
。
@@ -1 +1 @@
頭尾的 @@
稱為「塊標頭(chunk header 或 hunk header)」,中間 -1
代表從舊檔案的第一行開始有變化、共影響一行;+1
代表在新檔案的第一行開始有變化、共影響一行。
-Third content
+Fourth content
-Third content
表示存在於舊檔案、但新檔案沒有的內容;+Fourth content
表示新檔案才出現、新檔案沒有的內容。
值得留意的是,使用 git diff
核對都是直接看一整行,就算只是改變一個單字、甚至是一個標點符號,也是以行為單位在比較。
由上述實驗可發現,直接下 git diff
比較的是「預存區」與「工作目錄」之間的差異,也就是哪些改動有被 git add
、哪些還沒。
git diff <commit>
如果在 git diff
後面加上一個 commit 的雜湊碼,會出現什麼呢?
我們以既有的兩個 commit 雜湊碼 19b6609...
與 fd5ff72...
做實驗:
Git 雜湊碼為第二個 commit 19b6609...
時,由 index
開頭那行可發現比較的是 c44fde5...
這個 blob 物件與 b88ab8e...
這個「準 blob 物件」;最後兩行則顯示移除的內容為 "Second content"
、新增的內容為 "Fourth content"
。
Git 的雜湊碼為第一個 commit fd5ff72...
時,比較對象為 5db1980...
這個 blob 物件與 b88ab8e...
這個「準 blob 物件」;移除的內容為 "First content"
、新增的內容為 "Fourth content"
。
由上述實驗得知,當 git diff
後面接著一個 commit 雜湊碼時,比較對象為該 commit 指的 tree 物件所指向的 blob 物件,以及工作目錄中仍未被 git add
的狀態。
git diff --cached
/ --staged
/ --cached HEAD
這三個指令的結果相同,我們來觀察其比較的對象:
由三指令的 index
那行可以得知,比較對象為最新 commit 指的 tree 物件指向的 blob 物件 c44fde5...
,以及預存區 blob 物件雜湊碼 55d4074...
;移除的內容為 "Second content"
、新增的內容為 "Third content"
。
由此得知,使用這三個指令比較的對象,包含最新 commit 指的 tree 物件所指向之 blob 物件、以及預存區中的 blob 物件。
git diff <commit> <commit>
如果在 git diff
後面加上兩個 commit 的雜湊碼如下:
比較對象即為兩個 commit 各自指向的 tree 物件所指的 blob 物件。
值得留意的是,先打的 commit 雜湊碼會變成前綴為 a/
的舊檔案、後打的 commit 雜湊碼則為前綴為 b/
的新檔案。
git diff
可用來比較檔案差異,而依照後面接的引數不同,可指定不同比較對象:
git diff
:若無引數,比較對象為「預存區」與「工作目錄」。git diff <commit>
:以一個 commit 雜湊碼為引數,比較對象為「指定 commit」與「工作目錄」。git diff --cached
/ --staged
/ --cached HEAD
:三者相同,都是比較「最新的 commit」與「預存區」。git diff <commit> <commit>
:比較兩個指定的 commit。不管是哪一種情境,比較的都是 blob 物件(若從 commit 物件出發,則會先經過 tree 物件),以下圖示說明: