長篇分享

為什麼我不推薦你使用 git submodule

最近,筆者在某些項目的開發中碰到了 git submodule 並結結實實地被它噁心到了,從奇怪的版本管理到與父項目的尷尬關係——總之處處透漏著不舒服,本以為只有我是這麼覺得,結果隨手一搜發現與我有同樣想法的人不在少數,這次就讓我結合@dizlet的一篇博文,來談談為什麼 git submodule 那麼令人生厭?又有什麼別的方式可以替換掉它?

太長不讀版

永遠別用 git submodule,即便你覺得現在碰到的情況很適合用它,也別用。

下面我會詳細解釋一下為啥不要用,以及各種替代方案。

Git Submodule 的問題

它的問題主要體現在兩點:

  • 底層設計上就有問題。它破壞了 git 的數據模型,包括但不限於以下幾個方面:
    1. 你的倉庫中的 git 對象不再一定能解析為有意義的數據。(淺克隆也有類似的問題,但那只是對於歷史記錄來說。而 git submodule 對樹也一樣有問題。)
    2. 它不符合 git 的一般規定。子模塊中的 URL 和主機名都是由 git 配置文件決定的,而不是通常的 git 倉庫本身。
    3. 它會導致 git 樹處於奇怪的狀態,而要排除起來則非常痛苦。
  • 細節實現上也問題多多。有的是從設計根上帶出來的,但更多的是實現上的問題,即便你壓根沒用git submodule init初始化各個子模塊,它還是會影響到你的倉庫,暴露出的問題如下:
    1. 用於切換分支的git checkout命令不再可靠。
    2. 編輯和提交會變得非常痛苦
    3. 從主分支拉取代碼會變麻煩
    4. git ls-files的輸出會和git loggit cat-file產生衝突
    5. .gitmodules中的 URL 可能包含惡意鏈接,它甚至能被緩存在本地的.git/config文件里。
      總的來說,很多非常常見的 git 操作,諸如git checkoutgit pull這樣的命令就會導致子模塊處於非常怪異的狀態,你必須要運行某個與子模塊相關的命令才能回到正常的狀態。對絕大部分人來說,他們可能更願意直接把倉庫刪了,然後重新克隆——我也是這麼乾的,除非你是某位 git 絕地大師,否則千萬別試圖排除奇怪的子模塊問題,純屬浪費時間。

所以,對我們開發者(庫的維護者)而言,就只剩兩個方案了:

  1. 乾脆別用,跟各位開發者講清楚子模塊的各種問題,並讓他們放棄。
  2. 捏著鼻子用,並準備好應對它給你帶來的各種麻煩核問題,並浪費大量的時間和精力處理這些問題。

有什麼替代品嗎?

但是,如果真的有類似需求,又不想在子模塊上浪費人生要怎麼辦呢?我推薦下面的這些解決方案,你可以根據自己的需求選擇合適的方案。

Git Subtree

Git Subtree 可以解決很多 git submodule 能解決的問題,同時不會破壞 git 的數據模型。
如果你的項目符合這些特點,可以考慮使用 subtree:

  • 你想要在你的代碼樹里跟蹤並使用另一個項目,但又想保存它的獨立性。
  • 與你的項目相比,子項目的大小相對合理。

如果你使用 git subtree,大部分開發者都不需要與子樹中的項目交互,他們甚至不會注意到這是一個子樹,可以隨便提交、切換分支,怎麼搞都沒問題。git subtree 可以自動從下游分離出對下游分支的更改,以應用於(或提交給)上游分支。

我之前在自己的項目中使用過 git subtree,並發現它強大、方便,且非常簡單直接,因此我推薦你也嘗試一下!

乾脆就用一個倉庫

如果你想引入的項目本來維護人就是你,那麼選擇用一個倉庫也是一個不錯的選項,因為你可以直接把另一個項目的 git 歷史合併進來。

如果你的項目符合這些特點,可以考慮直接使用單個倉庫:

  • 你倉庫中使用的各個項目能夠共享同樣的 git 歷史記錄
  • 整個項目的大小在合理的範圍內
  • 你項目中長期存在的分支只是為了維護髮版,而不是為了讓某個內部項目維持在不同的版本。(例如 PCRE2 與倉庫中的 sljit)

使用包管理系統和顯式的依賴

與其自己維護依賴,還不如直接用別人打包好的!這個方案的精髓就在於將你的項目與依賴分開,並使用它提供的各種 API 取代之前在樹中代碼的作用,然後用一個包管理器安裝它!(如有必要的話,你可以自己維護一個特殊版本的下游包)

可供選用的包管理系統包括:

  1. 給發行版用的包管理系統:比如 Debian 的 apt + dpkg + sbuild 的組合。
  2. 語言專屬的包管理系統,比如 pip 或者 cargo。

如果你的項目滿足以下要求,可以考慮這個方案:

  • 你正在使用、或者很熟悉一個合適的包管理器。
  • 這個包管理器可以將你需要的 API 以合理的方式暴露出來。

使用多倉庫工具:MR

mr 是一個可以讓你方便地管理多個代碼樹的工具,通常這些樹之間是並列關係。

我雖然沒有親自用過這個工具,但它看起來還不錯,你可以把它跟我下面要介紹的基於..的依賴解決方案結合起來。如果你的項目里有很多外部項目的話,可以考慮這個方案。

把依賴放在 ../dependency 里

這個解決方案非常輕量,不需要任何工具,只需要把你項目的依賴放到../dependency里就好了。並讓用戶手動選擇正確版本的依賴。

如果你的項目符合這些條件,可以考慮這個方案:

  • 你的項目處於初期起步階段,你不想過多操心依賴和構建的問題。
  • 依賴默認是被禁用的,並且幾乎不會被啟用。

每個需要依賴項的程序或人都需要明白如何克隆依賴項並更新,這可能會帶來一些麻煩,如果你正在使用 CI(持續集成),則需要編寫一些自定義的 CI 腳本。但這還是比 git submodule 強,至少對於大部分人來說,究竟發生了什麼、以及如何對依賴項進行更改等都是完全可見的。

提供一個臨時的內部腳本來下載依賴項

作為最後的手段,你可以在頂層軟體包的構建系統中嵌入用於查找依賴項的 URL 和下載指令。這種方法很笨、也很麻煩,但可能讓你驚訝的是,它還是比 git submodule 要強得多。

如果你的項目符合這些特點,可以考慮這個方案:

  • 大多數使用/構建你的軟體的人根本不需要依賴項。
  • 大多數人不需要編輯依賴項。

任何其他的情況下,都不要考慮這個方案。

通常情況下,下游構建過程應該使用 git clone 命令克隆依賴項,下游代碼樹應該指定所需的準確提交 ID。

盡量避免使用這個方案,這並不是一個優秀的解決方案。但是:

真的,git submodule 還不如 Makefile

臨時的 shell 腳本看起來很不讓人滿意。但是與 git submodule 相比,它還是有一些優點的。比如,與 git submodule 不同,這種方法(就像我提出的大多數其他方法)意味著:

  • 所有期望克隆你的存儲庫、進行更改、構建、跟蹤更改等的工具都能正常工作。
  • 你可以精確控制下載發生的時間/條件:也就是說,你可以安排在需要依賴項時精確下載它。
  • 你可以精確控制依賴項的版本管理和檢查:你的腳本控制著使用的依賴項版本以及是否「固定」或動態更新。

再次重申一遍,我並不喜歡臨時腳本,也不認為它是一個好主意,只是因為 git submodule 實在是太爛了,總之一句話,千萬別用!

參考文章:
https://diziet.dreamwidth.org/14666.html

對這篇文章感覺如何?

太棒了
0
不錯
2
愛死了
0
不太好
0
感覺很糟
1

You may also like

Leave a reply

您的電子郵箱地址不會被公開。 必填項已用 * 標註

此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

More in:長篇分享

信息安全

深度解析shellcode原理及編碼技術

本篇文章深度探討了Shellcode的原理,同時對64位和32位系統下的shellcode進行了詳細解讀。並詳細介紹了Shellcode的編碼技術,其中包括手寫Shellcode的兩種思路。對於編碼Shellcode,本文列舉了幾種常見的解碼器的實現,如FNSTENV XOR解碼器和JMP/CALL解碼器。最後,本文列舉了幾種常見的字符集以及Shellcode編碼工具,並推薦了兩個可查找現成Shellcode的資料庫。
教程

將USB設備連接到WSL2:分步教程

這篇教程詳細地介紹了如何在Windows Subsystem for Linux 2(WSL2)中使用USB設備。首先,我們需要安裝在Windows上共享USB設備的軟體usbipd-win,並進行相應的設置。接下來,我們會在WSL內部安裝USB/IP的用戶空間工具和USB硬體標識符的資料庫。再接下來的步驟是配置udev規則以允許非root用戶訪問設備。最後,我們將通過使用usbipd的WSL便利命令來連接設備。在連接或斷開設備後,我們可以在WSL內部檢查已連接的USB設備。