前言
最近用 Hugo 重新架了新的部落格,部落格本身是一個 git repo,但 Theme 的資料夾的內容是另一個 git repo,導致在不同電腦想要同步部落格內容時,遇到 Git Submodule 的議題,是一個實務開發上較少遇到的情境,所以順手紀錄一下操作記錄.
何謂 Git Submodule?
使用 Git Submodule 的情境是在開發專案時資料夾內有第三方 Library 或你正單獨開發並被多個父專案使用的子專案,此時你想要將兩個專案視為獨立的,但又希望可以在其中一個專案使用到另一個專案的內容.
Git Submodule 其實就是一個巢狀的 Git 檔案結構,他會幫你把專案內某個子資料夾視為 Library 處理,不會受到主專案的 Git 操作影響.
以下是我遇到 Git Submodule 的情境:
我的部落格資料夾內含有 Theme 主題,部落格資料夾是父資料夾,而 Theme 是子資料夾,因為他參照的是另一個第三方的 git remote url,也就是一個第三方維護的靜態主題專案,未來我可能會需要同步他上面最新的更新,或是切換到他專案某個時間的 commit,這時候我就需要將他視為和我部落格是不同的專案,但彼此卻又需要被互相引用,這時候 Git Submodule 就可以很好的幫我解決這個問題.
實戰演練
為了模擬巢狀的 Git 結構,在 GitLab 上分別建立一組父子專案,父專案名稱是 Parent,子專案名稱是 Child.建立好之後,分別 clone 到本地端的 parent 和 child 資料夾.
.
├── child
│ └── README.md
└── parent
└── README.md
Git Submodule 連結父子專案
cd 進到 parent 資料夾後,用底下指令將 child 資料夾加為 parent 資料夾的 submodule
# git submodule add <remote repository> <local path>
git submodule add https://gitlab.com/DysonMa/child.git child
- remote repository: 遠端子專案的 git remote url
- local path: 預計要在 parent 資料夾的哪一個路徑底下存放 child 專案
執行後會發現 parent 資料夾多了 .gitmodules
檔案和 child
資料夾
.
├── child
│ └── README.md
└── parent
├── README.md
├── .gitmodules # add
└── child # add
└── README.md
打開 .gitmodules
內容如下:
[submodule "child"]
path = child
url = https://gitlab.com/DysonMa/child.git
這個檔案負責記錄子模組的參數,包含剛剛設定的 local path 還有子專案的 git remote url
情境一:修改父專案,觀察子專案變化
該情境模擬的是最基本的情境,當我部落格發布新文章後,推上去遠端 git repo 後,Theme 子資料夾會如何顯示在遠端 GitLab repo 上呢?
首先在 parent 資料夾內加上一個文字檔,並將他推上去父專案的遠端 repo,觀察 child 專案的變化
echo "Hello" >> parent.txt
git add .
git commit -m "Add parent.txt"
git push origin main
打開瀏覽器,看一下 GitLab 上剛剛推上去的 parent 專案
跟以往不同的是 child @ fb9ec1ef
並非資料夾的連結,而是連結到 child 專案的 remote url,所以點選後會跳轉到 child 的 repo,而後面的 fb9ec1ef
其實就是目前 child 專案的 commit hash 值.可以去本地端的 child 資料夾 git log 驗證
cd ../child
git log --oneline
fb9ec1e (HEAD -> main, origin/main, origin/HEAD) Initial commit
由此可知,我們可以透過該 commit 值知道父專案目前關聯的子專案是哪一個版本的.
情境二:子專案更新後,父專案內關聯的 submodule 如何同步?
該情境模擬的是哪一天 Theme 的專案有新的 feature,而我希望我的部落格專案也能夠同步享有該主題的新 feature,這時候該怎麼做?
在 child 專案內加上一個文字檔,並推上去遠端 child repo
echo "Hello" >> child.txt
git add .
git commit -m "Add child.txt"
git push origin main
回到 parent 專案,將 sub module 所關聯的 child 專案更新,預計會看到剛剛新增的文字檔
cd ../parent
# 將sub module所關聯的遠端子專案內容抓取下來並merge
git submodule update --remote --merge
.
├── README.md
├── child
│ ├── README.md
│ └── child.txt # 成功
└── parent.txt
需注意的是,目前只是 parent 的本地端更新,遠端 repo 並沒有更新,所以需要將這個改動 push 上去
git add .
git commit -m "Update sub module"
git push origin main
打開瀏覽器觀察 parent 遠端 GitLab repo 的 child ,的確如預期的更新 commit hash 值了,跳轉到 child 遠端 repo 也發現該版本確實是增加 child.txt 的版本
情境三:首次 clone 含有 sub module 的父子專案
該情境是模擬另台電腦首次 clone 部落格專案的情況,希望同步把 sub module 的內容依照
.gitmodules
的設定 clone 下來
首先,再建立一個新資料夾,將 parent 專案 clone 下來
mkdir new_repo
cd new_repo
git clone https://gitlab.com/DysonMa/parent.git
觀察檔案結構發現 child 是空的資料夾,因為 Git submodule 會將他視為不同的專案,也就是視為父專案的 Library,所以不會隨著 git clone 複製下來本地端.
.
└── parent
├── README.md
├── child # child是sub module,但是是空資料夾
└── parent.txt
如果想同步把子模組的 child 專案內容也一併 clone 下來,需要 cd 進到剛剛 clone 下來的 parent 資料夾內,再執行底下指令
cd ./parent
# 首次clone,用recursive方式把所有sub modules都clone下來
git submodule update --init --recursive
Git 會依照父專案內的 .gitmodules
配置,從子模組的 remote url 拉取關聯到的 commit hash 值的子專案版本
重新觀察檔案結構,發現 child 資料夾不再為空,而是剛剛 child 專案的 5c1f8871
這個 commit hash 的版本(有增加 child.txt)
.
├── README.md
├── child
│ ├── README.md # 成功
│ └── child.txt # 成功
└── parent.txt
情境四:同步 pull 更新父子專案
該情境是模擬非首次更新含有 sub module 的父子專案,適用於不同電腦已經 clone 過了,但需要再同步到最新的文章內容
模擬父子專案都有更動,所以各自在資料夾內增加新的文字檔,並且 push 到遠端 repo
# child資料夾
echo "new update" >> new_update_child.txt
git add .
git commit -m "Add new file new_update_child.txt"
git push origin main
# parent資料夾
echo "new update" >> new_update_parent.txt
git submodule update --remote --merge # 同步child的更新
git add .
git commit -m "Add new file new_update_parent.txt and update sub module"
git push origin main
回到 new_repo 的 parent 資料夾,把 parent 和 child 兩個專案的更新都 pull 下來
# 遞迴的同步所有sub modules
git pull --recurse-submodules
觀察 new_repo
的檔案結構,確認剛剛的改動都已經反映到另一個本地端的 repo
.
└── parent
├── README.md
├── child
│ ├── README.md
│ ├── child.txt
│ └── new_update_child.txt # 成功
├── new_update_parent.txt # 成功
└── parent.txt
總結
以上就是 Git Submodule 的學習筆記,透過部落格專案實際遇到的問題來切入,並透過 GitLab 的 sample repo 來 demo,給自己留個紀錄,供未來參考