Git diff看不到剛改的內容:檢查暫存區與工作樹

你改了同一個檔案,git diff確實出現差異;執行git add後,再看git diff卻空白。這不表示改動消失,而是改動已進入暫存區。更容易誤判的情況是:先把一版內容加入暫存區,又繼續修改檔案,這時同一個檔案同時有兩組不同的變更。

排查時要先決定在比較哪兩個版本。本文用一個檔案,依序建立已提交、已暫存與工作樹三份內容,再對照三種diff輸出;另加一個未追蹤檔案,確認只看diff會漏掉什麼。所有操作在新建的示範repo進行,沒有更動任何產品倉庫或遠端。

先把三份內容分開看

HEAD在這個已有提交的範例中指向目前分支的最新提交;暫存區保存下一次提交準備使用的版本;工作樹是你現在編輯的檔案。git add會把當時指定路徑的內容更新到暫存區,之後再修改工作樹,不會自動同步到暫存區。

因此「這個檔案已經add過」不是對目前內容的永久保證。你可能先暫存一個可提交的修正,然後在同檔做下一段實驗。此時commit使用的是暫存區那一版,工作樹裡新加的修改還沒有被選入。

mkdir diff-example
cd diff-example
git init
# 僅在新示範repo設定提交身分
git config user.name 'Example'
git config user.email '[email protected]'
printf 'BASE\n' > message.txt
git add -- message.txt
git commit -m 'Initial example'

這些指令是新repo的建立步驟。若你已在團隊倉庫裡,只讀查看後面的diff即可,不必重新init,也不要為了照做改掉團隊身分設定。本文不建立遠端,不執行push,提交紀錄只存在示範repo。

同一個檔案先暫存,再修改一次

現在把檔案改成STAGED並add,再把工作樹改成WORKTREE。最後建立extra.txt,保留為未追蹤狀態。這三個英文單字只是讓版本容易辨認,不涉及真正專案內容。

printf 'STAGED\n' > message.txt
git add -- message.txt
printf 'WORKTREE\n' > message.txt
printf 'UNTRACKED\n' > extra.txt
git status --short

本機Git 2.47.1.windows.1實跑得到MM message.txt與?? extra.txt。短格式前一欄表示暫存區相對HEAD的狀態,後一欄表示工作樹相對暫存區的狀態。兩個M說明同檔在兩邊都改過,不能把它讀成兩個檔案或一次重複的修改。

??則表示未追蹤檔案。它還沒有進入一般暫存區比較,稍後看三種diff都不會列出它的內容。先看status再看diff,可以避免只檢查已有追蹤檔案,漏掉新建但準備一起提交的檔案。

git diff檢查還沒有暫存的那一段

git diff -- message.txt
# 本例內容差異:
-STAGED
+WORKTREE

沒有指定提交的git diff通常比較暫存區與工作樹。在本例中,暫存區已經是STAGED,所以不會顯示BASE到STAGED。它回答的是「目前工作樹有哪些內容還不同於已暫存的版本」,不是把所有尚未提交的變更都列一次。

如果你此刻再執行git add — message.txt,暫存區會更新成WORKTREE,這一個比較就會空白。那只表示這個路徑的工作樹與暫存區相同;暫存區可能仍不同於HEAD,也可能還有其他未追蹤檔案。

git diff –staged檢查下一次提交的候選內容

git diff --staged -- message.txt
# 本例內容差異:
-BASE
+STAGED

–staged是–cached的同義選項;在本例沒有另指定提交時,比較HEAD與暫存區。它顯示下一次普通git commit準備加入的內容。工作樹後來寫入的WORKTREE沒有出現在這裡,因為這版尚未add。

提交前讀這個比較,能確認實際選入哪些內容,而不是只看編輯器目前檔案。如果預期連第二次修改也要提交,就先核對未暫存差異,再明確選入對應路徑。不要只為了讓diff空白就一次add全部,可能把其他實驗或敏感檔案也帶進提交。

此例假定HEAD已存在。全新repo尚無第一次提交時,–cached未指定提交會顯示所有已暫存變更;不要因為沒有HEAD就套用本例的BASE內容推論。本文的三份比較建立在前面那個初始提交上。

git diff HEAD看工作樹相對最新提交的結果

git diff HEAD -- message.txt
# 本例內容差異:
-BASE
+WORKTREE

這次指定HEAD,比較目前工作樹與該提交。在本例,它讓你看到BASE到WORKTREE的最終差異,而不把中間STAGED單獨印出。這很適合確認「目前檔案總共偏離上次提交多少」,但無法取代暫存區檢查,因為它沒有說明下一次commit到底選入哪一版。

不要把前兩個patch當成可以直接貼在一起的文字總和。它們比較的起點不同,中間修改可能相互抵銷,diff顯示的段落也可能合併。若先把BASE暫存成STAGED,再在工作樹改回BASE,git diff HEAD可能沒有差異,暫存區卻仍準備提交STAGED;需要三個比較才能看懂這種情況。

未追蹤檔案要另外盤點

在這個實跑中,沒有路徑限制的git diff HEAD仍只列message.txt,不包含extra.txt。這個新檔案應由git status盤點。若它本來就不應提交,就保持未追蹤;若應該提交,再明確add並用–staged檢查。本文沒有為了預覽改變它的追蹤狀態。

範例把每個diff都限定到message.txt,路徑前的–用來隔開選項或提交名稱與檔案路徑。實際路徑包含空白時,還需按你的shell正確引用。若限定錯誤路徑,空白結果可能只是沒有命中該路徑,不能直接判定整個repo乾淨。

維護時可以按順序做三個問題:status列出的檔案是否符合預期?–staged準備提交的內容是否正確?普通diff是否仍有刻意保留的後續修改?需要看目前總結果時再加diff HEAD。這些都是查看,沒有回復或丟棄內容。

本文範例是普通文字檔與單一分支,未模擬合併衝突、子模組、忽略規則或外部diff工具;那些情況的狀態欄與顯示方式另有規則。當輸出和預期不同時,先保留現場並核對比較面,別立刻用reset或restore清掉看不懂的差異。

把比較結果帶進提交前檢查

例如修正設定檔後已暫存,接著又加入一段尚未完成的實驗。普通差異會顯示實驗部分,已暫存差異則保留原修正。此時不需要把整個檔案退回去,先讀兩組內容,再決定哪一版符合本次提交目的。若由其他人接手,說明目前哪些內容已選入、哪些仍在工作樹,也比只說「檔案改好了」清楚。

在編輯器的來源控制面板看到兩處同名檔案時,也可以用這個範例確認它們分別是哪個比較面。面板名稱會因工具不同而異,命令列的三個查詢可提供共同基準。不要只靠顏色判斷,更不要以其中一個面板空白推論所有變更都已提交。

核對完之後,保留你打算稍後繼續做的工作樹修改是正常的。乾淨不是每個工作階段都必須立即達到的目標;提交內容清楚、未提交內容的用途可追蹤,才方便下一次接續。本文沒有執行新的提交,讓三份內容保持不同,方便讀者自己反覆比對輸出。

參考資料

原文鏈接:https://wntheme.com/git-diff-staged-worktree/,轉載請註明出處。
0

評論0

顯示驗證碼
沒有帳號?註冊  忘記密碼?