Git 版本控制教學 - submodule

Posted on  Dec 26, 2022  in  Git 版本控制  by  Amo Chen  ‐ 4 min read

Git 可以做到 repository 中嵌入其他 git repositories, 相當於版本控制原本的 repository 之外,也把內嵌的 repositories 也納入版本控制的範圍,這功能被稱為 submodule

不過 submodule 並不是將內嵌 repository 的所有檔案都做額外的版本控制,而是類似於做一個指標(pointer),將 submodule 的版本指向內嵌的 repository 的某 1 個 commit id 。

Submodule 適合應用在多個 repository 共享 library, utility functions 的情境,可以節省各個 repository 重複開發的成本,例如下圖, repo A 與 repo B 共用同一個 submodule, 所以 repo A 與 repo B 都只要共同維護一份 submodule 即可:

p.s. 另 1 種管理方式是將 submodule 變成可供安裝的套件,例如 npm 套件、 pip 套件等等⋯⋯

另外,由於 submodule 的運作類似於做一個指標(pointer),將 submodule 的版本指向內嵌的 repository 的某 1 個 commit id, 所以 repo A 與 repo B 可以指向各自的版本,也就是不同的 commit id, 所以不會因為某人更新了 submodule 之後,導致其他 repository 的版本跑掉(除非某人以暴力 rebase 或者砍掉 submodule 導致其他 repository 找不到對應的 commit id),本文稍後將會以實際的 GitHub 範例展示指向 commit id 的部分。

新增 submodule

新增 submodule 的指令為:

$ git submodule add <repository> <path>

例如以下範例將 mysubmodule 指向 repository https://github.com/username/gitsubmodule

$ git submodule add https://github.com/username/gitsubmodule mysubmodule

上述指令成功之後,會多出一個檔案 .gitmodules & 一個檔案 mysubmodule

$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   .gitmodules
	new file:   mysubmodule

.gitmodules 內容裡面會紀錄 submodule 的路徑以及指向的 repository, 例如:

[submodule "mysubmodule"]
	path = mysubmodule
	url = https://github.com/username/gitsubmodule

mysubmodule 內則是 https://github.com/username/gitsubmodule repository 的內容。

git submodule add 指令建立檔案之後, submodule 的新增步驟仍未結束,仍需要進行 commit ,如此才能讓 Git 紀錄 repository 底下有多 1 個 submodule:

$ git commit -m 'commit submodule'

接著,我們可以將 repository 推送到(push)到 GitHub:

$ git push origin main

最後就可以在 GitHub 的頁面看到類似以下的畫面,其中 mysubmodule @ 4c2c45d 代表 submodule 的版本定在 https://github.com/username/gitsubmodule 這個 repository 的 commit id 4c2c45d , 如果點進去該 submodule, 就會跳到相對應的 repository 與其相對應的 commit id:

更新 submodule

submodule 的更新就跟更新 Git repository 的方式一樣,都是在 submodule 內做完變更之後 commit, 例如以下更新 mysubmodule 內的 README.md 後,進行 commit:

$ cd mysubmodule
$ cat >> README.md
Hello
$ git commit -m 'update README.md'

這時候 mysubmodule 的所指向 commit id 仍未改變,所以執行 git status 指令後會看到類似以下的結果, Git 顯示 submodule 有新的變更:

$ cd ..
$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   mysubmodule (new commits)

no changes added to commit (use "git add" and/or "git commit -a")

只要透過 git addgit commit 指令將變更提交就可完成 submodule 的更新:

$ git add mysubmodule
$ git commit -m 'update submodule'
$ git push origin main

最後就可以在 GitHub 頁面看到 mysubmodule @ 之後的 commit id 變了:

將 Submodule 指定到特定 commit id 或 tag

如果想將 submodule 指定到特定的 commit id 或 tag, 可以進到 submodule 後,再使用 git checkout <commit id 或 tag> 指令,將版本切換過去:

$ git checkout <commit id 或 tag>

例如:

$ cd mysubmodule
$ git checkout v1.0

接著回到原 repository 後,輸入指令 git status , 就能夠看到 mysubmodule 已經被變更:

$ cd ..
$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   mysubmodule (new commits)

no changes added to commit (use "git add" and/or "git commit -a")

最後將 mysubmodule 提交,即完成將 submodule 指定到特定 commit id 或 tag:

$ git add mysubmodule
$ git commit -m 'stick submodule to v1.0'

Clone 有 submodule 的 repository

如果是 repository 早已有 submodule 的情況下,通常會遇到一種情況是——「 Clone repository 後, submodule 裡面沒有任何資料」

例如,當我們另外以指令 clone 前述範例之後:

$ git clone https://github.com/username/gittest.git

進到 mysubmodule 資料夾底下後,會發現沒有任何資料:

$ cd gittest
$ ls mysubmodule

這是由於 submodule 需要額外經過 2 道指令才可運作:

  1. git submodule init 將 submodule 初始化
  2. git submodule update 將 submodule 下載回來

git submodule init

The default behavior of git submodule init is to copy the mapping from the .gitmodules file into the local ./.git/config file.

git submodule init 的作用在於將 . gitmodules 的設定寫道 repository 的 config 內,也就是寫入 ./.git/config 檔案內:

$ git submodule init
Submodule 'mysubmodule' (https://github.com/username/gitsubmodule) registered for path 'mysubmodule'

執行完上述指令,會發現 .git/config 內多了 submodule 的設定:

$ cat .git/config
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
	ignorecase = true
	precomposeunicode = true
[remote "origin"]
	url = https://github.com/username/gittest.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
	remote = origin
	merge = refs/heads/main
[submodule "mysubmodule"]
	active = true
	url = https://github.com/username/gjtsubmodule

不過此時 submodule 的資料仍是空的,需要執行第 2 道指令才行。

git submodule update

Update the registered submodules to match what the superproject expects by cloning missing submodules, fetching missing commits in submodules and updating the working tree of the submodules.

git submodule update 的作用在於將 submodule 內的檔案/commit 等 clone 回來:

$ git submodule update
Cloning into '/Users/user/workspace/gittest/mysubmodule'...
Submodule path 'mysubmodule': checked out 'b1d45f5b18f760cb611347c3d2c8ede952e760d5'

懶人方法 git submodule update –init –recursive

git submodule initgit submodule update 其實可以合二為一,變成 git submodule update --init --recursive ,如此只要 1 道指令即可:

$ git submodule update --init --recursive
Submodule 'mysubmodule' (https://github.com/username/gjtsubmodule) registered for path 'mysubmodule'
Submodule path 'mysubmodule': checked out 'b1d45f5b18f760cb611347c3d2c8ede952e760d5'

最懶的 Clone 方式

Git 2.13 之後提供一同將 submodule 完整 clone 的指令, git clone --recurse-submodules <repository> , 例如下列範例:

$ git clone --recurse-submodules https://github.com/username/gittest.git

刪除 git submodule

刪除 submodule 的方式也很直觀,只要執行 git rm <submodule> 即可,例如:

$ git rm mysubmodule

Git 會自動更新 .gitmodules 與將 submodule 刪除:

$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   .gitmodules
	deleted:    mysubmodule

最後只要 commit 即可刪掉 submodule 。

以上就是關於 Git submodule 的教學。

Happy Coding!

References

Git submodule | Atlassian

Git - Submodules

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!