HOWTO · Git

Git 中比較兩個提交之間的差異

本教程使用 Git diff 命令演示了兩次提交之間的區別。

本頁內容

使用 git diff <commit1> <commit2> 檢視兩個已提交快照之間的差異。Git 會顯示將第一個端點轉換為第二個端點所需的變更。這在檢閱變更、確認某次發布帶來哪些內容,或了解檔案如何演變時很實用。參照可以是提交 ID、分支名稱、標籤,或其他可解析為提交的名稱。

git diff 如何比較變更

git diff 的作用取決於傳入的參數。提供兩個提交參照時,Git 會比較這兩個提交所記錄的樹狀目錄。若未提供提交參數,git diff 會比較工作目錄與索引,因此顯示尚未暫存的編輯,不包含已暫存的變更。使用 git diff --cached 可比較索引與 HEAD,檢視已暫存的變更。

閱讀修補檔時,兩個端點的順序很重要:輸出中的新增內容存在於第二個端點,刪除內容則存在於第一個端點。這個指令只會顯示比較結果,不會變更任一提交或工作目錄。

找出提交參照

使用 git log --oneline 列出近期提交的短 ID 和摘要行。你可以複製清單中的 ID,或使用 HEAD~1 這類參照來指向目前提交的上一個提交。例如,HEAD~2 表示 HEAD 的第二個祖先。只要短 ID 在此儲存庫中能唯一識別某個提交,就可以使用;如果 Git 回報有歧義,請從記錄中複製更多字元。

以下獨立範例會建立兩個提交,並請 Git 統計兩者之間新增與刪除的行數。請在已安裝 Git 的 Bash shell 中執行此範例:

本節中的程式碼以及顯示的標籤或輸出仍為英文。

tmp_dir=$(mktemp -d)
trap 'rm -rf "$tmp_dir"' EXIT
git -C "$tmp_dir" init -q
git -C "$tmp_dir" config user.name "Example User"
git -C "$tmp_dir" config user.email "example@example.com"
printf 'Draft\n' > "$tmp_dir/notes.txt"
git -C "$tmp_dir" add notes.txt
git -C "$tmp_dir" commit -qm "Add draft"
first=$(git -C "$tmp_dir" rev-parse HEAD)
printf 'Reviewed\n' > "$tmp_dir/notes.txt"
git -C "$tmp_dir" commit -qam "Review draft"
second=$(git -C "$tmp_dir" rev-parse HEAD)
git -C "$tmp_dir" diff --numstat "$first" "$second"
1	1	notes.txt

輸出會指出 notes.txt 新增一行並刪除一行。在現有儲存庫中,請將產生的參照替換為該儲存庫中的 ID 或名稱。

比較兩個提交

基本形式是 git diff <commit1> <commit2>。請將每個佔位符替換成有效的參照。例如,git diff HEAD~2 HEAD 會比較目前提交與它的第二個祖先,並顯示這段範圍內的整體差異。Git 比較的是兩個端點的樹狀目錄,因此輸出不一定會列出每個個別提交的修補內容;中間若有互相抵銷的變更,就不會出現在最終比較結果中。

閱讀 diff 輸出

修補檔通常以 diff --git 開頭,並列出舊路徑與新路徑。--- 行標示舊的一側,+++ 行標示新的一側。以 @@ 開頭的行會指出所顯示區塊的位置與大小。在區塊中,行首的空格表示上下文,- 表示第一個端點中存在的行,+ 表示第二個端點中存在的行。這些前綴描述的是比較結果,不一定代表某個特定提交中的時間順序或操作。

Git 可能省略檔案中未變更的部分,只顯示每個變更區塊周圍的上下文。如果檔案曾重新命名、新增或刪除,標頭與狀態可能不同於一般編輯。若修補內容很長,可以加入 --stat 查看檔案層級的摘要,或使用 --name-only 列出受影響的路徑,而不顯示行內容。

比較特定檔案或分支

若要只查看某個路徑,請在提交參照後放入 --,再指定路徑:git diff <commit1> <commit2> -- <file_path>。這個分隔符會告訴 Git 後續的參數是路徑,避免路徑名稱看起來像版本參照時造成混淆。例如,從儲存庫根目錄執行 git diff HEAD~2 HEAD -- src/app.py,會將比較範圍限制在 src/app.py。Git 通常會以目前工作目錄為基準解析 pathspec;如果你在子目錄中執行指令,請提供相對於該目錄的路徑。若要無論目前所在目錄為何,都以儲存庫根目錄為基準指定路徑,可使用頂層 magic pathspec,例如 :(top)src/app.py。

分支名稱也可以作為端點,因為每個分支名稱都會解析到目前的提交。例如,git diff main feature 會比較 main 和 feature 分支尖端的快照;顯示為新增的行,是相對於 main 而存在於 feature 的內容。這是直接比較兩個端點,不會自動整理出只屬於功能分支的提交變更。

選擇比較範圍

若要取得兩個快照之間的整體差異,請使用兩個端點,例如 git diff main feature。若要比較功能分支與它從 main 分岔時的基準點,請使用 git diff main...feature。在這種三點形式中,Git 會比較 main 與 feature 的合併基底和 feature 的尖端。這通常可回答功能分支自分岔以來變更了什麼;而兩端點形式則會顯示目前兩個分支尖端之間的差異。

如果輸出看起來與預期檢閱的工作無關,請使用 git log --oneline --decorate 確認每個名稱都解析到預期的提交。接著確認你需要的是直接比較兩個端點,還是比較合併基底;只有在想排除其他檔案時,才加入路徑限制。