Git 儲存不可變物件,並在它們之間移動小型參照。日常工作應使用 porcelain 命令,而儲存模型能說明為何重寫會建立新歷史,以及為何在一段期間內仍能復原。
檢查儲存庫資料庫 #
.git 目錄包含儲存庫本機狀態:
| 路徑 | 用途 |
|---|---|
HEAD | 已簽出的分支符號名稱,或分離的物件 ID |
config | 儲存庫設定 |
index | 下一次提交的提議快照 |
objects/ | 鬆散物件與封裝檔 |
refs/ 與 packed-refs | 分支、標籤與遠端追蹤名稱 |
logs/ | 參照的本機 reflog |
hooks/ | 用戶端 hook 範例與已安裝的 hook |
modules/ | 已初始化子模組的 Git 目錄 |
工作樹保存已簽出的檔案。索引記錄候選樹。提交指向一棵樹以及父代 ID 與中繼資料。分支是指向提交的可移動參照,而不是可變的提交容器。
不必手動解碼檔案即可檢查名稱與物件型別:
git rev-parse --git-dir
git show-ref --head
git cat-file -t HEAD
git cat-file -p HEAD
git count-objects -vH物件模型與內容定址 #
Git 會對標準位元組序列雜湊,以識別物件:
<type><space><decimal-size><NUL><content>SHA-1 儲存庫使用 160 位元物件 ID。新儲存庫可以使用 SHA-256。物件 ID 識別內容與結構;它不編碼時間或序號。
Git 有四種核心物件型別:
- blob 儲存不含檔名的檔案位元組。
- tree 將路徑名稱與模式對映到 blob 與子樹。
- commit 命名根樹、零個或多個父代、作者與提交者身分及時間,以及訊息。
- annotated tag 儲存標籤中繼資料並指向另一個物件。
樹項目模式 100644 是一般不可執行檔案,100755 是可執行檔案,120000 是符號連結,160000 是子模組 gitlink。gitlink 指向另一個儲存庫中的提交,而不是嵌入該儲存庫的檔案。
在可丟棄的儲存庫中建立與檢查物件:
printf 'hello\n' | git hash-object -w --stdin
git cat-file -p <object-id>
git ls-tree HEAD
git ls-tree -r HEAD鬆散物件位於 .git/objects/aa/bb... 下,其中 aa 是十六進位 ID 的第一個位元組。Git 會以 zlib 壓縮其標準表示。git cat-file 比手動加入壓縮標頭或使用臨時解壓縮管線更安全。
了解封裝、差異與可達性 #
大量鬆散物件會增加檔案系統中繼資料操作。git gc 可以將可達物件收集到 .git/objects/pack/ 中的封裝檔。.pack 檔案保存物件表示法,.idx 檔案將物件 ID 對映到封裝偏移,而 .rev 檔案可以加速需要封裝順序的操作。
封裝檔可以將物件相對於其他物件進行差異壓縮。差異是儲存最佳化,而不是 Git 的歷史模型:每個提交仍然命名完整的根樹,Git 可以透過物件圖重建任何被參照的 blob。提交不會只儲存相對於父代的文字差異。
可達性從分支、標籤、遠端追蹤分支與 reflog 等參照開始。垃圾回收會保留可達物件,並可在設定的到期時間後修剪不可達物件。git fsck 會回報連通性,並可以定位不可達物件:
git fsck --full --no-reflogs --unreachable
git cat-file -p <unreachable-object-id>讀取參照與索引 #
HEAD 通常包含 ref: refs/heads/main。分離的簽出會直接包含物件 ID。鬆散分支名稱是 refs/heads/ 下的檔案,而許多參照可以合併到 packed-refs。
索引不是變更檔案清單。它儲存下一棵樹的路徑、模式、物件 ID 與 stat 快取。合併衝突期間,索引可以為單一路徑攜帶多個階段:
| 階段 | 意義 |
|---|---|
| 1 | 合併基底 |
| 2 | 目前分支版本 |
| 3 | 其他分支版本 |
使用以下命令檢查索引項目:
git ls-files --stage
git diff --cached解決衝突並執行 git add 會將階段 1–3 替換成一個階段 0 項目。
解釋重寫為何會變更 ID #
提交包含其父代物件 ID。變更訊息、樹、作者、提交者、時間戳記或父代,會產生不同的提交 ID。每個後代都必須重新建立,因為其記錄的父代已變更。
當重寫能改善歷史時,可以自由重寫本機未發布工作。重寫共用分支前要先協調。強制更新可能捨棄協作者新的遠端提交;需要強制推送時使用 --force-with-lease:
git push --force-with-lease origin feature/topic修改最新提交 #
git commit --amend 會為 HEAD 建立替代提交,並移動目前分支:
git add README.md
git commit --amend --no-edit不使用 --no-edit 執行 git commit --amend 可替換訊息。原始提交在到期與修剪前,仍可透過 reflog 復原。
互動式編輯較早的提交 #
互動式 rebase 會重寫選定端點之後的提交:
git rebase -i HEAD~3命令會呈現最後三個提交。其待辦命令包括:
| 命令 | 效果 |
|---|---|
pick | 保留提交 |
reword | 替換其訊息 |
edit | 套用後停止 |
squash | 與前一個提交合併並編輯訊息 |
fixup | 合併並捨棄其訊息 |
drop | 略過 |
若要變更較早提交的內容,將它標記為 edit,在 rebase 停止時修改,然後繼續:
git add path/to/file
git commit --amend --no-edit
git rebase --continue解決衝突、暫存已解決路徑,然後繼續。git rebase --abort 會將分支返回 rebase 前的狀態。git rebase --edit-todo 會在 rebase 暫停時變更剩餘指示。
使用 Reflog 復原 #
每次本機參照移動都可能產生 reflog 項目。在進行有風險的重寫前建立明確的備份名稱:
git branch backup/before-rewrite接著定位並命名較早的狀態:
git reflog show --date=iso HEAD
git switch -c recovered-work HEAD@{1}Reflog 是本機實作歷史,不是分散式備份。發布重要工作、保留遠端備份,並避免在復原完成前執行破壞性清理。
評論
在 GitHub 上閱讀或評論(發表評論需要登入)