git diff 沒有輸出卻有修改?檢查暫存區與 HEAD 的差異
執行 git add 後,git diff 突然沒有輸出,但編輯器仍顯示修改,先看 git diff --cached。不帶參數的 diff 比較暫存區與工作檔案;stage 完成後,這兩份內容相同就會空白。 已暫存的修改仍可進入一般 commit。
還有一種更容易誤判的情況:stage 後在編輯器把內容改回原樣,git diff HEAD 也是空白,但暫存區仍保存先前的修改。要確認下次提交內容,必須看對比較的兩端。
先用唯讀命令找出是哪份快照
在專案目錄執行:
git status --shortgit diff --name-statusgit diff --cached --name-statusgit diff HEAD --name-status依 Git diff 文件,這三個比較回答不同問題:
| 命令 | 比較的兩端 | 用途 |
|---|---|---|
git diff | 暫存區 → 工作檔案 | stage 後又改了什麼 |
git diff --cached | HEAD → 暫存區 | 一般 commit 準備提交什麼 |
git diff HEAD | HEAD → 工作檔案 | 磁碟上的檔案相對上次 commit 改了什麼 |
--staged 和 --cached 是同義選項。要看某個檔案的內容差異,把確切路徑放在 -- 後面,例如 git diff --cached -- settings.txt。
暫存區(index)保存的是 stage 當時的內容,並不會持續追蹤編輯器的新版本。Pro Git 的記錄變更章節 說明了「stage 後再編輯」為何會同時出現兩份修改。
status 的兩欄各看一個比較
對已有追蹤、沒有未解 merge 的一般檔案,short status 第一欄描述 index 相對 HEAD,第二欄描述工作檔案相對 index:
M settings.txt M settings.txtMM settings.txt?? notes.txt這是四種不同情境,不是同一個檔案同時列出四次。M 是已 stage; M 是尚未 stage;MM 表示兩端都有修改,要分別看 cached 與一般 diff。?? 是未追蹤檔案,上面三種一般 diff 不會自動列出其內容。
如果 MM 搭配空白的 git diff HEAD,可能是磁碟內容回到 HEAD,而 index 還在另一個版本。不能據此說「Git 狀態壞掉」,也不需要先 reset 或刪除 .git。
未追蹤的新檔案,怎麼不 stage 就預覽內容?
?? draft notes.txt 不在 index,也不在 HEAD,所以一般 diff、cached diff 與 HEAD diff 都可能空白。若只想先看新檔內容,不必為了預覽就改動暫存區;在 macOS/Linux 可用 git diff 的 no-index 模式比較空檔來源 /dev/null 與確切檔案:
git diff --no-index -- /dev/null 'draft notes.txt'這裡比較的是兩個檔案系統路徑,不依 tracked 身分決定是否顯示;-- 區分選項與路徑,引號讓空白留在同一個參數。這不是將檔案加入 index,也不會讓它進入下一個一般 commit。預覽後決定要提交,才對確切檔案執行 git add -- 'draft notes.txt',再看 cached diff。
no-index 隱含 --exit-code:0 表示相同、1 表示不同,其他非零值才當命令錯誤。自動化不能因預覽新檔退出 1 就宣稱 Git 失敗,也不能用無條件忽略錯誤的寫法遮掉讀取失敗。
下面 Bash fixture 只操作新暫存 repository,保留一個已 stage 的無關修改,確認預覽沒有改動它;沒有 remote,不會推送:
set -eupreview_fixture_dir="$(mktemp -d)"git init -q -b main "$preview_fixture_dir"cd "$preview_fixture_dir"git config user.name Fixturegit config user.email fixture@example.invalidprintf 'baseline\n' > tracked.txtgit add -- tracked.txtgit -c commit.gpgsign=false commit -qm Baselineprintf 'staged edit\n' > tracked.txtgit add -- tracked.txtprintf 'new content\n' > 'draft notes.txt'index_before="$(git write-tree)"
git diff --quiet -- 'draft notes.txt'git diff --cached --quiet -- 'draft notes.txt'git diff HEAD --quiet -- 'draft notes.txt'if git diff --no-index -- /dev/null 'draft notes.txt' > "$preview_fixture_dir/preview.patch"; then echo '預期新檔與空內容不同' >&2 exit 1else preview_status=$? test "$preview_status" -eq 1fitest "$(git write-tree)" = "$index_before"test "$(git show :tracked.txt)" = 'staged edit'if git ls-files --error-unmatch -- 'draft notes.txt' > /dev/null 2>&1; then echo '預期新檔仍未追蹤' >&2 exit 1fitest "$(cat 'draft notes.txt')" = 'new content'printf 'no-index 預覽且 index 未變:%s\n' "$preview_fixture_dir"這份新增範例於 2026-10-30 由維護流程在 macOS、Git 2.54.0(Apple Git-157)執行;沒有測試 Windows shell、二進位內容、自訂 diff driver 或並行寫入。輸出的 patch 保留在暫存目錄,與新檔一樣不會被這次 fixture stage。正式專案若涉及金鑰,不要把預覽內容貼到公開日誌。若檔案已 tracked,卻以為新增 gitignore 就能讓它消失,則接著看 gitignore 與已追蹤檔案的差別。
重現工作檔案改回去,暫存區仍保留修改
以下用 Bash 執行,只在 mktemp 建立的獨立目錄操作,沒有 remote。本文維護流程於 2026-10-10 在 macOS、Git 2.54.0(Apple Git-157)執行;斷言涵蓋一般文字檔,不包含衝突、submodule 或自訂 diff driver。
set -eufixture_dir="$(mktemp -d)"git init -q -b main "$fixture_dir"cd "$fixture_dir"git config user.name Fixturegit config user.email fixture@example.invalidprintf 'theme=light\n' > settings.txtgit add -- settings.txtgit -c commit.gpgsign=false commit -qm Baseline
printf 'theme=dark\n' > settings.txtgit add -- settings.txtgit diff --quiet -- settings.txtif git diff --cached --quiet -- settings.txt; then echo '預期暫存區應有修改' >&2 exit 1else test "$?" -eq 1fi
printf 'theme=light\n' > settings.txtgit diff HEAD --quiet -- settings.txttest "$(git status --short)" = 'MM settings.txt'test "$(git show :settings.txt)" = 'theme=dark'
git -c commit.gpgsign=false commit -qm 'Commit staged content'test "$(git show HEAD:settings.txt)" = 'theme=dark'test "$(cat settings.txt)" = 'theme=light'test "$(git status --short)" = ' M settings.txt'printf '暫存快照檢查通過:%s\n' "$fixture_dir"git diff --quiet 的狀態碼 0 代表沒有差異,1 代表有差異;其他非零狀態要當錯誤處理。這裡第一個比較成功,接著確認 cached 比較並非空白。
工作檔案改回 light 後,三個位置是:HEAD 為 light、index 為 dark、工作檔案為 light。兩端相同,所以 git diff HEAD 空白;一般 commit 仍提交 index 的 dark,磁碟內容則維持 light。
測試不會清掉暫存 repository,最後一行會顯示路徑,方便查看。不要把這些初始化與提交命令換成正式專案路徑。
決定要交哪個版本,再改暫存區
先確認目前暫存的內容:
git diff --cached -- settings.txtgit show :settings.txt如果準備提交的就是這一版,可以保留後續未 stage 的編輯。若應提交的是磁碟上的新版本,才再次 stage 該確切檔案:
git add -- settings.txtgit diff --cached -- settings.txtgit status --short在上面 fixture 的提交前狀態,重新 stage light 會讓 index 回到 HEAD,該檔案的修改消失;這是選擇了目前工作內容,不是 Git 遺失資料。
cached diff 的預覽適用於一般、沒有路徑參數或 -a 的 commit。Git commit 文件 說明 -a 會先納入已追蹤檔案的修改與刪除,帶路徑的提交則有不同的內容選擇規則。不要看完 cached diff,卻換用另一種提交方式後仍假設結果相同。
commit —only 指定檔案,讀的是目前工作內容
若其他工作已 stage,git commit --only -- file 會忽略其他路徑的 staged 修改,但指定路徑取自目前工作檔案。這正是 Git commit 的 —only 定義:它限定路徑,沒有把該路徑鎖在先前 stage 的版本。
假設自己的 owned.txt stage 時是 staged,後來又改成 working;另外 other.txt 已 stage。這時 cached diff 顯示自己的 staged,但指定 --only -- owned.txt 會交出 working。其他檔案的 staged 內容則留下,供後續工作提交。
以下 Bash fixture 只操作新的暫存 repository,不讀寫正式專案,也沒有 remote。它接著驗證提交範圍、提交內容與另一個檔案仍在暫存區:
set -euonly_fixture_dir="$(mktemp -d)"git init -q -b main "$only_fixture_dir"cd "$only_fixture_dir"git config user.name Fixturegit config user.email fixture@example.invalidprintf 'baseline\n' > owned.txtprintf 'baseline\n' > other.txtgit add -- owned.txtgit add -- other.txtgit -c commit.gpgsign=false commit -qm Baseline
printf 'staged\n' > owned.txtgit add -- owned.txtprintf 'working\n' > owned.txtprintf 'other staged\n' > other.txtgit add -- other.txttest "$(git show :owned.txt)" = staged
git -c commit.gpgsign=false commit --only -qm 'Only owned' -- owned.txttest "$(git show --name-only --format= HEAD)" = owned.txttest "$(git show HEAD:owned.txt)" = workingtest "$(git show HEAD:other.txt)" = baselinetest "$(git show :other.txt)" = 'other staged'test "$(git status --short)" = 'M other.txt'printf 'only 提交範圍檢查通過:%s\n' "$only_fixture_dir"這份刊登範例由維護流程於 2026-10-23 在 macOS、Git 2.54.0(Apple Git-157)執行。它驗證一般追蹤文字檔的選取規則,沒有涵蓋 hook 改寫、merge 衝突或其他程序同時寫入;暫存目錄保留供檢查。
實際操作時,對自己的確切路徑先看 git diff HEAD -- owned.txt,再以相同路徑做 git commit --dry-run --only -- owned.txt 檢查預計範圍。dry-run 不建立 commit,也不凍結檔案;提交後仍須用 git show --name-only --format= HEAD 和 git show HEAD:owned.txt 驗收。新檔案要先對確切路徑執行 git add -- file,讓 Git 認識它。
不要以整個目錄代替檔案清單,也不要把 --only 當成同檔案多人編輯的隔離工具。它能排除其他路徑已 stage 的內容;同一個指定檔案內的所有目前修改,仍可能一起進入 commit。
多個 Agent 在同一專案工作時,先用 Git worktree 隔離工作目錄,再各自確認 HEAD、index 與工作檔案。即使只剩一位操作者,也應在 commit 前看暫存版本,而不是用空白的 git diff HEAD 代替驗收。
參考資料: