我想我理解了Git 中裸倉庫和非裸倉庫之間的實際區別,但我真的不明白為什么在邏輯上存在這種區別:為什么 Git 必須實作裸倉庫和非裸倉庫的概念?我知道已經有大量關于該主題的執行緒和文章,但我真的錯過了一些具體的例子來完全理解這個主題。
回顧一下,非裸倉庫和裸倉庫之間的實際差異(即檔案方面)應該如下:
非裸回購是以下各項的組合:
- 一個.git檔案夾,這是一個“特殊”檔案夾,Git 作為一個軟體,用來存盤所有資料(例如專案檔案的 blob 是版本控制)和元資料(例如提交歷史)以便能夠作業適當地。
- 作業樹,即代表我們專案的實際檔案和檔案夾。要記住的一件重要事情是,作業樹并不是存盤專案內容的唯一地方。專案的資料也由 Git 使用一些內部工具(如.git/objects )以特殊格式保存在 .git 檔案夾中。作業樹之所以存在,是因為它是所有非 Git 都可以作業的方式(因此得名作業樹)并編輯專案檔案和檔案夾。
一個裸回購是:
- 主目錄本身中的 .git 子目錄的內容
- 沒有作業樹
問題是:為什么我需要一個中間裸倉庫以方便的方式同步兩個非裸倉庫?許多執行緒和文章都在回答說,沒有中央裸倉庫會導致中央作業樹不同步(請參閱此處)。好的,但是為什么呢?有人可以提供一個具體的例子嗎?
我能想象的情況如下:
- A是本地倉庫,B是本地倉庫,C是中央遠程倉庫。都是非裸的。
- A進行提交
c并將其推送到C上。C更新其 .git 檔案夾,并根據 .git 中的更改相應地作業樹c。重要提示:在我看來, “更新作業樹”意味著用A作業樹替換C作業樹(即C檔案和檔案夾)。 - B從C中提取更改并相應地更新其 .git 檔案夾和作業樹
c
像上面描述的這種情況怎么會使 C 的作業樹不同步?C 不同步是什么意思?
到目前為止,我理解的唯一真正的優勢是,對于像 Github 或 Gitlab 這樣的服務,不為每個 repo 和每個分支維護作業樹(即擁有一個裸 repo)非常方便節省存盤空間。他們可以利用 Git 工具即時重建作業樹。
uj5u.com熱心網友回復:
比較簡單,真的。裸存盤庫沒有作業樹,因此它不能有活動的簽出。1 而且,正如您在其他地方看到的那樣,問題是推動某些分支的主動簽出會導致不同步的簽出。所以 Git 禁止推送到簽出的分支。2 由于裸存盤庫沒有作業樹,因此沒有簽出分支,因此裸存盤庫回避了這個問題。
[非裸中央倉庫] 不同步意味著什么?
讓我們省去第三臺機器:我們只需要client, 一個非裸存盤庫和server, 應該是但不是裸存盤庫。
在 上server,分支main被主動簽出。有人可能會也可能不會登錄并在server那里編輯檔案。
同時,在 上client,您進行了一些新的提交,然后運行git push并將新的提交發送到server. 如果服務器接受這個提交,現在有兩種可能性:
server的 Git 存盤庫不更新簽出的作業樹,或者server的 Git 存盤庫確實更新了簽出的作業樹。
這兩種情況都會產生不好的結果。在我們開始之前,讓我們稍微探索一下 Git 的作業原理。
1以前是這樣,現在git worktree add不是了。因此,在 Git 2.5 之前,這種簡單性一直存在,而現在則不然。
2在各種配置項發明之前的原始 Git 中也是如此。現在不是。因此,簡單性曾經存在,但現在不再存在。(這些receive.denyCurrentBranch東西發生在git worktree命令之前,但我不記得那是哪個版本。)
Git 是關于提交的;提交已編號;分支名稱查找提交
Git 存盤庫主要由兩個資料庫組成,一個通常比另一個大得多。更大的資料庫包含提交和支持 Git 物件。較小的資料庫包含名稱,例如分支和標記名稱。
提交物件被編號,數字表示為十六進制哈希 ID。Git需要哈希 ID 來查找提交:大資料庫僅由哈希 ID 索引。
提交本身包含兩件事:
- 每個檔案的完整快照,截至該提交應具有的狀態;和
- 元資料:關于提交本身的資訊,例如提交人、時間和原因(日志訊息)。
在任何給定提交的元資料中,Git 存盤該提交的父級或父級的原始哈希 ID 。因此,提交具有存盤在其元資料中的先前提交哈希 ID 的串列。這形成了存盤庫中的歷史記錄。
為了能夠獲取任何給定分支的最新提交,Git 將最新refs/heads/main提交的原始哈希 ID存盤在分支名稱(例如)中。該提交在其元資料中包含前一個(父)提交的哈希 ID,該哈希 ID 又包含另一個父提交的另一個哈希 ID,依此類推。
當我們使用git checkout或git switch帶有分支名稱時,我們是在告訴 Git:提取該分支的最新提交。那就是哈希 ID 存盤在分支名稱中的那個。因此,使用git switch main,Git 查找refs/heads/main,找到一個哈希 ID,例如a123456...,并在資料庫中查找該提交。該提交有一組與之關聯的檔案。Git 將這些檔案從提交中復制出來——提交中的檔案通常不能被作業系統使用,因為它們是只讀的、壓縮的、僅 Git 的、去重復的形式——到你的作業樹。
但是,Git也會將檔案(或者更確切地說,有關檔案的資訊(名稱和 blob 哈希 ID))復制到 Git 的index中,該 index 與作業樹一起出現。這定義了跟蹤哪些檔案,幫助 Git 快速運行,并且通常需要知道在下一次提交中放入什么。
一切就緒后,Git 會設定特殊名稱HEAD以包含分支名稱。(在最初的 Git 中,這是一個指向refs/heads/main檔案的符號鏈接,但與 Git 的許多位一樣,它在十多年前就被取消了。)
現在有一組定義明確、精心協調的資料:
HEAD包含當前分支名稱;- 分支名稱包含哈希 ID;
- Git 的索引包含下一次提交的檔案名和 blob 哈希 ID ,跟蹤作業樹中的檔案;和
- 作業樹包含從提交中復制的檔案。
您處理檔案,運行git add以告訴 Git 更新 Git 索引中的內容,并最終運行git commit. 此時Git:
- 讀取 HEAD,然后讀取分支名稱以查找當前提交;
- 收集所有必要的元資料;
- 打包索引的內容;
- 將所有這些寫入一個新的提交,該提交將獲得一個新的唯一哈希 ID;和
- 將哈希 ID 寫入分支名稱。
精心協調的資料讓 Git 可以完成所有這些作業,并且仍然是精心協調的。
如果其他人在您作業時提交...
現在假設我們在服務器上,正在作業,并且客戶端上有人提交。這不是問題,因為客戶端 Git 存盤庫有自己的分支名稱。它在其提交資料庫中獲得一個新的提交,其分支名稱main存盤一個新的哈希 ID。但是在這里server,我們的Git 資料庫沒有改變。
但是如果他們現在運行git push main并發送他們的提交,我們的 Git 要么接受他們的提交,要么拒絕它。如果我們拒絕它,那很好:我們的資料庫保持不變,一切仍然協調。
假設相反,我們接受了推送。服務器 Git 更新refs/heads/main以存盤其提交哈希 ID。我們的兩種可能性是:
- 不要更新索引和作業樹;
- 更新索引和作業樹。
如果我們選擇可能性#1,那么我們就會有一個“過時的簽出”:我們的檔案來自上一次提交。但分支名稱包含新的提交哈希 ID。所以我們不同步。如果我們更新任何檔案然后提交,我們將恢復其他人的作業(請記住,我們的 Git 軟體使用我們索引中的內容,這與我們的作業樹匹配)。這不是很好,所以讓我們繼續選擇選項 2。
如果我們選擇選項 2,我們的檔案將被從我們手中奪走并替換。我們的索引和作業樹與更新的分支名稱重新同步。這更好......除了,如果我們正在積極處理某個檔案,我們的作業會發生什么?也許我們的編輯注意到底層檔案發生了變化,并給了我們一個修復問題的機會。也許它只是覆寫了底層檔案。無論哪種方式,它都可能是一個問題。
因此,更新服務器存盤庫的作業樹可能比不這樣做要好,這就是receive.denyCurrentBranch's updateInsteadsetting 所做的。不過,它并不完美。“完美”就是沒有作業樹,這樣就不會出錯,我們用--bare.
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/470955.html
上一篇:在某些提交后查找詳細的git日志
下一篇:拉取請求上的git子模塊
