跳轉到內容

Git/內部結構

來自華夏公益教科書
< Git

瞭解 Git 的內部結構對於理解 Git 的工作原理至關重要。

裸 Git 結構

[編輯 | 編輯原始碼]

以下是一個新初始化的 git v1.9.0 倉庫。[1]

.
└── .git/
    ├── HEAD
    ├── branches/
    ├── config
    ├── description
    ├── hooks/
    │   ├── applypatch-msg.sample
    │   ├── commit-msg.sample
    │   ├── post-update.sample
    │   ├── pre-applypatch.sample
    │   ├── pre-commit.sample
    │   ├── prepare-commit-msg.sample
    │   ├── pre-push.sample
    │   ├── pre-rebase.sample
    │   └── update.sample
    ├── info/
    │   └── exclude
    ├── objects/
    │   ├── info/
    │   └── pack/
    └── refs/
        ├── heads/
        └── tags/

隨著倉庫活動,可能會出現其他檔案和資料夾。

特定檔案

[編輯 | 編輯原始碼]

COMMIT_EDITMSG

[編輯 | 編輯原始碼]

文字編輯器會將提交訊息儲存到這裡。

FETCH_HEAD

[編輯 | 編輯原始碼]

這裡儲存了最後一次 git-fetch(1) 操作的資訊,供之後 git-merge(1) 操作使用。

HEAD 指示當前檢出的程式碼。它通常會指向您當前正在工作的分支。

您還可以進入 Git 所謂的“分離 HEAD”狀態,在這種狀態下,您不在本地分支上。在這種狀態下,HEAD 直接指向提交而不是分支。

此 Git 倉庫的配置檔案。它可以包含有關如何在本地倉庫中管理和儲存資料的設定、已知的遠端倉庫、有關本地使用者的詳細資訊以及其他有關 Git 本身的配置資料。

您可以使用文字編輯器編輯此檔案,也可以使用 git-config(1) 命令進行管理。

description

[編輯 | 編輯原始碼]

由倉庫瀏覽器工具使用 - 包含有關此專案的描述。在非共享倉庫中通常不會更改。

這是暫存區。它以緊湊的形式包含已暫存到下次提交的所有檔案更改。

info/exclude

[編輯 | 編輯原始碼]

這是您自己的個人排除檔案,用於您的倉庫副本。

info/refs

[編輯 | 編輯原始碼]

如果此檔案存在,它將包含分支(本地和遠端)以及為倉庫定義的標籤的定義,每行一個定義,除此之外,還可能在以下檔案中定義。refs/headsrefs/tags. 此檔案似乎用於包含大量分支或標籤的大型倉庫。

ORIG_HEAD

[編輯 | 編輯原始碼]

更改當前分支上的提交歷史記錄的操作會將 HEAD 的先前值儲存到這裡,以便從錯誤中恢復。

包含其他檔案的資料夾

[編輯 | 編輯原始碼]

似乎從不使用。

包含在 Git 倉庫中發生特定事件時執行的指令碼。Git 提供了一組初始示例指令碼,這些指令碼的名稱末尾帶有.sample(請參閱上面的樹列表);如果您刪除了.sample字尾,Git 將在適當的時候執行該指令碼。

例如,鉤子可用於在建立每個提交之前執行測試、過濾上傳的內容以及實現其他此類自定義要求。

Reflogs 儲存在這裡。

這是所有檔案、目錄列表、提交等儲存的地方。

這裡既有此目錄下編號目錄中的未打包物件,也有“包”,這些包包含壓縮在包目錄中的許多壓縮物件。未壓縮物件將定期透過自動“git gc”執行收集到包中。

refs/heads

[編輯 | 編輯原始碼]

可以包含一個檔案,定義每個本地分支的頭部提交(但請參見info/refs上面)。

refs/remotes

[編輯 | 編輯原始碼]

可以包含一個子目錄,用於每個已定義的遠端倉庫。在每個子目錄中,都包含一個檔案,定義該遠端倉庫上每個分支的尖端提交。

refs/tags

[編輯 | 編輯原始碼]

可以包含一個檔案,定義與每個標籤對應的提交(但請參見info/refs上面)。

如果您使用 git-svn(1) 與 Subversion 伺服器通訊,則此目錄將出現。


基本概念

[編輯 | 編輯原始碼]

物件型別

[編輯 | 編輯原始碼]

Git 倉庫由以下物件型別組成

  • 一個 blob 儲存單個檔案的全部內容。它不儲存有關檔名或任何其他元資料的任何資訊,只儲存內容。
  • 一個 tree 表示目錄樹的狀態。它包含所有元件檔案的路徑名及其模式,以及儲存其內容的 blob 的 ID。請注意,沒有單獨表示目錄,因此 Git 倉庫無法記錄建立或刪除子目錄的事實,只能記錄其中的檔案。
  • 一個 commit 指向一個樹,表示該提交之後立即源樹的狀態。它還記錄提交的日期/時間、作者/提交者資訊,以及指向該提交的任何父提交的指標,表示源樹的立即之前狀態。
  • 一個 tag 是一個指向提交的名稱。這些很有用,例如,用來標記發行版里程碑。標籤可以選擇進行數字簽名,以保證提交的真實性。
  • 一個 branch 是一個指向提交的名稱。分支和標籤之間的區別在於,當一個分支是當前簽出的分支時,新增一個新的提交將自動更新分支指標以指向新的提交。

Blob、樹和提交都有 ID,這些 ID 是根據其內容的 SHA-1 雜湊計算得出的。這些 ID 允許不同機器上的不同 Git 程序判斷它們是否具有相同副本,而無需傳輸其全部內容。由於 SHA-1 是一種密碼學上強健的雜湊演算法,因此幾乎不可能對任何這些物件的內容進行更改而不會更改其 ID。Git 不會阻止您重寫歷史記錄,但您無法隱藏您已經重寫歷史記錄的事實。

一個提交可能具有 0、1 或多個父提交。通常只有一個沒有父提交的提交——一個 root commit——這是倉庫中的第一個提交。對某個分支進行一些更改的提交將只有一個父提交,即該分支上的前一個提交。從兩個或多個分支合併的提交將有兩個或多個父提交。

請注意,一個分支指向一個 單個 提交;提交鏈隱含在該提交的父提交中,以及它們的父提交,等等。

提交的拓撲結構

[編輯 | 編輯原始碼]

Git 中的提交歷史記錄被組織成一個 有向無環圖 (DAG)。為了理解這意味著什麼,讓我們逐步解釋這些術語。

僅僅一個圖
  • 在數學術語中,一個 是由連線線 () 連線的點 (節點) 組成的。
有向邊
  • 一個 有向 圖是指每個邊都有一個方向的圖,這裡用箭頭表示。請注意,箭頭從子節點指向父節點,而不是反過來;是子節點記錄了其父節點是誰,父節點沒有記錄其子節點是誰,因為子節點集合可以隨時更改,但父節點不能更改,否則會導致其 SHA-1 雜湊無效。
不允許迴圈
  • 無環 指的是,如果您從任意一點開始,沿著箭頭方向遍歷邊,無論您在任何分支處做出何種選擇,您都永遠無法回到起點,任何子節點都永遠不能是它自己的(直接或間接)父節點!

在 Git 術語中,每個節點代表一個提交,線和箭頭代表父子關係。禁止迴圈僅僅意味著一個提交不能是它自己(直接或間接)的父節點或子節點!

Reflog 記錄了未作為提交歷史記錄一部分儲存的更改——比如變基、快進合併、重置等等。每個分支都有一個 reflog。Reflog 不是倉庫的公共部分,它嚴格地特定於您的本地副本,並且資訊只在其中保留有限的時間(預設情況下為 2 周)。它提供了一個安全網,允許您從錯誤中恢復,比如刪除或覆蓋您不想刪除或覆蓋的東西。

可達性和垃圾回收

[編輯 | 編輯原始碼]

如果一個提交被分支、標籤或 reflog 條目指向,或者是一個可達提交的父提交,則該提交是 可達 的。相應地,如果一個樹被可達提交指向,則該樹是可達的;如果一個 blob 被可達樹指向,則該 blob 是可達的。其他提交/樹/blob 物件是 不可達 的,除了佔用空間之外,它們實際上沒有起到任何作用。

您的倉庫隨著時間的推移積累不可達物件是完全正常的,這可能是由於中止提交、刪除不需要的分支、類似的事情造成的。這樣的物件將被 git gc 命令從倉庫中刪除。這也會在一些其他命令執行時定期自動執行,因此很少需要顯式呼叫 git gc

.git 資料夾之外的 Git 檔案

[編輯 | 編輯原始碼]

放在目錄中,它保證它會被提交,即使是空的。

.gitignore

[編輯 | 編輯原始碼]

包含要從版本控制中排除的檔案和資料夾。例如

/var/
/vendor/
/.env.*.local

.gitattributes

[編輯 | 編輯原始碼]

包含一些屬性[1]。例如,要忽略 .gitattributes 和 .gitignore 在 "git archive" 匯出中的內容

.gitattributes export-ignore
.gitignore export-ignore
  1. ^ 使用 tree v1.5.1.1 透過 tree -AnaF 生成的。
  1. https://git-scm.tw/docs/gitattributes
華夏公益教科書