現代軟件項目大多依賴於其他項目的工作成果。如果別人已經編寫了優秀的解決方案,而你卻在代碼中重新發明輪子,那將是極大的時間浪費。這就是為什麼許多項目使用第三方代碼,例如庫或模塊。
Git,全球最流行的版本控制系統,提供了一種優雅而強大的方法來管理這些依賴項。其“子模塊”概念允許我們包含和管理第三方庫,同時保持它們與我們自己的代碼清晰分離。
本文將闡述Git子模塊為何如此有用,它們究竟是什麼以及它們的工作原理。
關鍵要點
保持代碼分離
為了清楚地說明Git子模塊為何是一種寶貴的結構,讓我們來看一個沒有子模塊的案例。當您需要包含第三方代碼(例如開源庫)時,您可以選擇簡單的方法:只需從GitHub下載代碼並將其放入項目的某個位置。雖然這種方法很快,但由於以下幾個原因,它絕對是不干淨的:
軟件開發中“將不同的事物分開”的普遍規則並非沒有道理。對於在您自己的項目中管理第三方代碼,這一點尤其正確。幸運的是,Git的子模塊概念正是為這些情況而設計的。
當然,子模塊並不是解決此類問題的唯一解決方案。您還可以使用許多現代語言和框架提供的各種“包管理器”系統。這樣做並沒有錯!
但是,您可以認為Git的子模塊架構具有一些優勢:
Git子模塊的本質
Git中的子模塊實際上只是標準的Git存儲庫。沒有花哨的創新,只是我們現在都非常熟悉的相同的Git存儲庫。這也是子模塊強大功能的一部分:它們之所以如此強大而直接,是因為它們從技術的角度來看是如此“枯燥”並且經過了充分的測試。
使Git存儲庫成為子模塊的唯一一點是它位於另一個父Git存儲庫內部。
除此之外,Git子模塊仍然是一個功能齊全的存儲庫:您可以執行您已經從“普通”Git工作中了解到的所有操作——從修改文件到提交、拉取和推送。子模塊中的一切都是可能的。
添加子模塊
讓我們以一個經典的例子為例,假設我們要向項目添加一個第三方庫。在我們獲取任何代碼之前,創建一個單獨的文件夾來存放此類內容是有意義的:
<code class="language-bash">$ mkdir lib $ cd lib</code>
現在我們準備以有序的方式使用子模塊將一些第三方代碼導入我們的項目。假設我們需要一個小的“時區轉換器”JavaScript庫:
<code class="language-bash">$ git submodule add https://github.com/spencermountain/spacetime.git</code>
當我們運行此命令時,Git會將存儲庫克隆到我們的項目中,作為一個子模塊:
<code>Cloning into 'carparts-website/lib/spacetime'... remote: Enumerating objects: 7768, done. remote: Counting objects: 100% (1066/1066), done. remote: Compressing objects: 100% (445/445), done. remote: Total 7768 (delta 615), reused 975 (delta 588), pack-reused 6702 Receiving objects: 100% (7768/7768), 4.02 MiB | 7.78 MiB/s, done. Resolving deltas: 100% (5159/5159), done.</code>
如果我們查看我們的工作副本文件夾,我們可以看到庫文件實際上已經到達了我們的項目中。
您可能會問:“有什麼區別呢?”畢竟,第三方庫的文件就在這裡,就像我們複製粘貼它們一樣。關鍵的區別在於它們包含在它們自己的Git存儲庫中!如果我們只是下載了一些文件,將它們扔到我們的項目中,然後提交它們——就像我們項目中的其他文件一樣——它們將成為同一個Git存儲庫的一部分。但是,子模塊確保庫文件不會“洩漏”到我們主項目的存儲庫中。
讓我們看看還發生了什麼:在主項目根文件夾中創建了一個新的.gitmodules文件。以下是其內容:
<code class="language-bash">$ mkdir lib $ cd lib</code>
這個.gitmodules文件是Git跟踪項目中子模塊的多個位置之一。另一個是.git/config,現在結尾如下:
<code class="language-bash">$ git submodule add https://github.com/spencermountain/spacetime.git</code>
最後,Git還在內部.git/modules文件夾中保留每個子模塊的.git存儲庫的副本。
所有這些都是您不必記住的技術細節。但是,了解Git子模塊的內部維護相當複雜可能會有所幫助。這就是為什麼記住一件事很重要:不要手動修改Git子模塊配置!如果您想移動、刪除或以其他方式操作子模塊,請幫自己一個忙,不要手動嘗試這樣做。可以使用適當的Git命令或像“Tower”這樣的Git桌面GUI,它會為您處理這些細節。
讓我們看看我們添加子模塊後主項目的狀態:
<code>Cloning into 'carparts-website/lib/spacetime'... remote: Enumerating objects: 7768, done. remote: Counting objects: 100% (1066/1066), done. remote: Compressing objects: 100% (445/445), done. remote: Total 7768 (delta 615), reused 975 (delta 588), pack-reused 6702 Receiving objects: 100% (7768/7768), 4.02 MiB | 7.78 MiB/s, done. Resolving deltas: 100% (5159/5159), done.</code>
如您所見,Git將添加子模塊視為與其他更改一樣的更改。因此,我們必須像其他任何更改一樣提交此更改:
<code>[submodule "lib/spacetime"] path = lib/spacetime url = https://github.com/spencermountain/spacetime.git</code>
克隆包含Git子模塊的項目
在我們上面的例子中,我們向現有的Git存儲庫添加了一個新的子模塊。但是,“反過來”呢,當您克隆已經包含子模塊的存儲庫時會發生什麼?
如果我們在命令行上執行了普通的git clone ,我們將下載主項目——但是我們會發現任何子模塊文件夾都是空的!這再次生動地證明了子模塊文件是獨立的,並且不包含在其父存儲庫中。
在這種情況下,要在克隆其父存儲庫後填充子模塊,您可以簡單地執行git submodule update --init --recursive。更好的方法是在第一次調用git clone時直接添加--recurse-submodules選項。
檢出版本
在“普通”Git存儲庫中,我們通常檢出分支。通過使用git checkout 或更新的git switch ,我們告訴Git我們當前活動的分支應該是什麼。當在這個分支上進行新的提交時,HEAD指針會自動移動到最新的提交。理解這一點很重要——因為Git子模塊的工作方式不同!
在子模塊中,我們始終檢出一個特定的版本——而不是分支!即使您在子模塊中執行類似於git checkout main的命令,在後台,也會記錄該分支上當前最新的提交——而不是分支本身。
當然,這種行為並非錯誤。考慮一下:當您包含第三方庫時,您希望完全控制在主項目中使用哪些確切的代碼。當庫的維護者發布新版本時,這很好……但是您不一定希望自動在您的項目中使用這個新版本。因為您不知道這些新更改是否會破壞您的項目!
如果您想找出您的子模塊正在使用哪個版本,您可以在主項目中請求此信息:
<code class="language-bash">$ mkdir lib $ cd lib</code>
這將返回我們lib/spacetime子模塊當前檢出的版本。它還讓我們知道這個版本是一個名為“6.16.3”的標籤。在使用Git子模塊時,大量使用標籤是很常見的。
假設您希望您的子模塊使用較舊的版本,該版本標記為“6.14.0”。首先,我們必須更改目錄,以便我們的Git命令將在子模塊的上下文中執行,而不是我們的主項目。然後,我們可以簡單地使用標籤名運行git checkout:
<code class="language-bash">$ git submodule add https://github.com/spencermountain/spacetime.git</code>
如果我們現在回到我們的主項目並再次執行git submodule status,我們將看到我們的檢出結果:
<code>Cloning into 'carparts-website/lib/spacetime'... remote: Enumerating objects: 7768, done. remote: Counting objects: 100% (1066/1066), done. remote: Compressing objects: 100% (445/445), done. remote: Total 7768 (delta 615), reused 975 (delta 588), pack-reused 6702 Receiving objects: 100% (7768/7768), 4.02 MiB | 7.78 MiB/s, done. Resolving deltas: 100% (5159/5159), done.</code>
仔細查看輸出:該SHA-1哈希前面的 符號告訴我們子模塊的版本與當前存儲在父存儲庫中的版本不同。由於我們剛剛更改了檢出的版本,這看起來是正確的。
現在,在我們的主項目中調用git status也會告知我們這一事實:
<code>[submodule "lib/spacetime"] path = lib/spacetime url = https://github.com/spencermountain/spacetime.git</code>
您可以看到Git將移動子模塊的指針視為與其他更改一樣的更改:如果我們想存儲它,我們必須將其提交到存儲庫:
<code>[submodule "lib/spacetime"] url = https://github.com/spencermountain/spacetime.git active = true</code>
更新Git子模塊
在上述步驟中,是我們自己移動了子模塊指針:我們是那些選擇檢出不同版本、提交它並將其推送到我們團隊的遠程存儲庫的人。但是,如果我們的同事更改了子模塊版本會怎樣——也許是因為發布了子模塊的有趣的新版本,並且我們的同事決定在我們的項目中使用它(當然,在徹底測試之後……) 。
讓我們在主項目中執行一個簡單的git pull——因為我們可能經常這樣做——以從共享的遠程存儲庫獲取新的更改:
<code class="language-bash">$ git status On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) new file: .gitmodules new file: lib/spacetime</file></code>
倒數第二行表示子模塊中的某些內容已更改。但是讓我們仔細看看:
<code class="language-bash">$ git commit -m "Add timezone converter library as a submodule"</code>
我相信您還記得那個小的 號:這意味著子模塊指針已移動!要將我們本地檢出的版本更新到我們的隊友選擇的“官方”版本,我們可以運行update命令:
<code class="language-bash">$ git submodule status ea703a7d557efd90ccae894db96368d750be93b6 lib/spacetime (6.16.3)</code>
好了!我們的子模塊現在已檢出到記錄在我們主項目存儲庫中的版本!
使用Git子模塊
我們已經介紹了使用Git子模塊的基本構建塊。其他工作流程非常標準!
例如,檢查子模塊中的新更改就像在任何其他Git存儲庫中一樣:您在子模塊存儲庫中運行git fetch命令,如果確實要使用更新,之後可能會運行類似於git pull origin main的命令。
更改子模塊也可能適合您,特別是如果您自己管理庫代碼(因為它是內部庫,而不是來自第三方)。您可以像使用任何其他Git存儲庫一樣使用子模塊:您可以進行更改、提交它們、推送它們等等。
充分利用Git的強大功能
Git在幕後擁有強大的功能。但是,許多高級工具(如Git子模塊)並不為人所知。許多開發人員錯過了很多強大的功能,這實在令人遺憾!
如果您想深入了解一些其他高級Git技術,我強烈推薦“高級Git工具包”:這是一個(免費的!)短視頻合集,它將向您介紹Reflog、交互式變基、Cherry- Picking甚至分支策略等主題。
祝您成為更好的開發者!
關於Git子模塊的常見問題
什麼是Git子模塊? Git子模塊是一種將另一個Git存儲庫作為子目錄包含到您自己的Git存儲庫中的方法。它允許您將單獨的存儲庫作為子項目維護在主項目中。
為什麼要使用Git子模塊? Git子模塊對於將外部存儲庫合併到您的項目中非常有用,尤其是在您希望將它們的開發歷史與主項目分開時。這對於管理依賴項或包含外部庫非常有益。
主項目中關於子模塊存儲了哪些信息? 主項目將子模塊的URL和提交哈希存儲在父存儲庫中的特殊條目中。這允許任何克隆主項目的人也克隆引用的子模塊。
如何克隆包含子模塊的Git存儲庫? 克隆包含子模塊的存儲庫時,您可以使用git clone命令的--recursive標誌自動初始化和克隆子模塊。或者,您可以在克隆後使用git submodule update --init。
我可以嵌套子模塊嗎? 是的,Git支持嵌套子模塊,這意味著子模塊可以包含它自己的子模塊。但是,管理嵌套子模塊可能會變得複雜,並且必須確保每個子模塊都已正確初始化和更新。
以上是理解和與git中的子模型合作的詳細內容。更多資訊請關注PHP中文網其他相關文章!