Git Tools - Submodules
1. 應(yīng)用場(chǎng)景
需求
當(dāng)你在一個(gè)項(xiàng)目 project1 上工作時(shí),你需要在其中使用另外一個(gè)項(xiàng)目 lib1 幽七。也許它是一個(gè)第三方開(kāi)發(fā)的庫(kù)或者是你獨(dú)立開(kāi)發(fā)和并在多個(gè)父項(xiàng)目 (priject2熄浓,prject2 ... ...)中使用的糜芳。這個(gè)場(chǎng)景下一個(gè)常見(jiàn)的問(wèn)題產(chǎn)生了:你想將兩個(gè)項(xiàng)目 priject1鲁纠、lib1 單獨(dú)處理但是又需要在其中一個(gè) project1 中使用另外一個(gè) lib1工禾。
解決方案
不使用 git submodules 的方法:包含庫(kù)
- 包含庫(kù):把 lib1 代碼包含到 project1 (project2 运提、project3 ... ...)項(xiàng)目中
- 問(wèn)題 : 定制和部署 project 更加困難了。
- 產(chǎn)生問(wèn)題的原因 :因?yàn)槟惚仨毚_保每個(gè)客戶(hù)都擁有 lib1 闻葵。并且當(dāng) lib1 被修改時(shí)(fix bug)民泵,任何在 project1 (project2 、project3 ... ...) 中對(duì) lib1 的修改都很難歸并槽畔。
《Git權(quán)威指南》
項(xiàng)目的版本庫(kù)在某些情況下需要引用其他版本庫(kù)中的文件栈妆,
例如公司積累了一套常用的函數(shù)庫(kù),被多個(gè)項(xiàng)目調(diào)用厢钧,顯然這個(gè)函數(shù)庫(kù)的代碼不能直接放到某個(gè)項(xiàng)目的代碼中鳞尔,而是要獨(dú)立為一個(gè)代碼庫(kù),那么其他項(xiàng)目要調(diào)用公共函數(shù)庫(kù)該如何處理呢早直?
分別把公共函數(shù)庫(kù)的文件拷貝到各自的項(xiàng)目中會(huì)造成冗余寥假,丟棄了公共函數(shù)庫(kù)的維護(hù)歷史,這顯然不是好的方法霞扬。
Git Submodules
Git 通過(guò)子模塊處理這個(gè)問(wèn)題昧旨。子模塊允許你將一個(gè) Git 倉(cāng)庫(kù)當(dāng)作另外一個(gè)Git倉(cāng)庫(kù)的子目錄。這允許你克隆另外一個(gè)倉(cāng)庫(kù)到你的項(xiàng)目中并且保持你的提交相對(duì)獨(dú)立祥得。學(xué)會(huì) git submodule 可以使項(xiàng)目中再也不會(huì)出現(xiàn)大量
重復(fù)的資源文件兔沃、公共類(lèi)庫(kù)。更不會(huì)出現(xiàn)多個(gè)版本级及,甚至一個(gè)客戶(hù)多個(gè)項(xiàng)目風(fēng)格存在各種差異乒疏。
2. Learning Example
本例子采用兩個(gè)項(xiàng)目以及兩個(gè)公共類(lèi)庫(kù)學(xué)習(xí)對(duì)submodule的操作
2.1 創(chuàng)建 Git Submodule 測(cè)試項(xiàng)目
2.1.1 準(zhǔn)備環(huán)境
- 當(dāng)前目錄
? ~ pwd
/Users/yjizhang
? ~ mkdir -p submd/repos
- 創(chuàng)建需要的本地倉(cāng)庫(kù)
? ~ cd ~/submd/repos
? repos git --git-dir=lib1.git init --bare
Initialized empty Git repository in /Users/yjizhang/submd/repos/lib1.git/
? repos git --git-dir=lib2.git init --bare
Initialized empty Git repository in /Users/yjizhang/submd/repos/lib2.git/
? repos git --git-dir=p1.git init --bare
Initialized empty Git repository in /Users/yjizhang/submd/repos/p1.git/
? repos git --git-dir=p2.git init --bare
Initialized empty Git repository in /Users/yjizhang/submd/repos/p2.git/
? repos ls
lib1.git lib2.git p1.git p2.git
- 初始化工作區(qū)
? ~ mkdir ~/submd/ws
? ~ cd submd/ws
2.1.2 初始化項(xiàng)目
- 初始化 p1 (project1)
? ~ cd ~/submd/ws
? ws git clone ../repos/p1.git
Cloning into 'p1'...
warning: You appear to have cloned an empty repository.
done.
? ws cd p1
? p1 git:(master) echo "p1" >> p1-infos.txt
? p1 git:(master) ? ls
p1-infos.txt
? p1 git:(master) ? gst
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
p1-infos.txt
nothing added to commit but untracked files present (use "git add" to track)
? p1 git:(master) ? git add p1-infos.txt
? p1 git:(master) ? gst
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: p1-infos.txt
? p1 git:(master) ? gc -m "init p1"
[master (root-commit) 960cb75] init p1
1 file changed, 1 insertion(+)
create mode 100644 p1-infos.txt
? p1 git:(master) git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 217 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/p1.git
* [new branch] master -> master
? p1 git:(master)
- 初始化 p2
? ~ cd ~/submd/ws
? ws git clone ../repos/p2.git
Cloning into 'p2'...
warning: You appear to have cloned an empty repository.
done.
? ws cd p2
? p2 git:(master) gst
On branch master
Initial commit
nothing to commit (create/copy files and use "git add" to track)
? p2 git:(master) echo "p2" >> p2-infos.txt
? p2 git:(master) ? ls
p2-infos.txt
? p2 git:(master) ? gst
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
p2-infos.txt
nothing added to commit but untracked files present (use "git add" to track)
? p2 git:(master) ? git add p2-infos.txt
? p2 git:(master) ? gst
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: p2-infos.txt
? p2 git:(master) ? gc -m "init p2"
[master (root-commit) 449e7ed] init p2
1 file changed, 1 insertion(+)
create mode 100644 p2-infos.txt
? p2 git:(master) gst
On branch master
Your branch is based on 'origin/master', but the upstream is gone.
(use "git branch --unset-upstream" to fixup)
nothing to commit, working tree clean
? p2 git:(master) git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 217 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/p2.git
* [new branch] master -> master
? p2 git:(master)
2.1.3 初始化公共類(lèi)庫(kù)
- 初始化公共類(lèi)庫(kù) lib1
? ~ cd ~/submd/ws
? ws git clone ../repos/lib1.git
Cloning into 'lib1'...
warning: You appear to have cloned an empty repository.
done.
? ws cd lib1
? lib1 git:(master) echo "I'm lib1" > lib1-features
? lib1 git:(master) ? git add lib1-features
? lib1 git:(master) ? gc -m "init lib1"
[master (root-commit) 101b2e6] init lib1
1 file changed, 1 insertion(+)
create mode 100644 lib1-features
? lib1 git:(master) git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 224 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/lib1.git
* [new branch] master -> master
- 初始化公共類(lèi)庫(kù) lib2
? lib1 git:(master) cd ~/submd/ws
? ws git clone ../repos/lib2.git
Cloning into 'lib2'...
warning: You appear to have cloned an empty repository.
done.
? ws cd lib2
? lib2 git:(master) echo "I'm lib2" >> lib2-features
? lib2 git:(master) ? git add lib2-features
? lib2 git:(master) ? gc -m "init lib2"
[master (root-commit) fa8fdee] init lib2
1 file changed, 1 insertion(+)
create mode 100644 lib2-features
? lib2 git:(master) git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 225 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/lib2.git
* [new branch] master -> master
2.2 為主項(xiàng)目添加 Submodules
2.2.1 為 p1 添加 lib1 和 lib2
-
命令 :
git submodule add
? cd ~/submd/ws/p1
? p1 git:(master) ls
p1-infos.txt
? p1 git:(master) git submodule add ~/submd/repos/lib1.git libs/lib1
Cloning into '/Users/yjizhang/submd/ws/p1/libs/lib1'...
done.
? p1 git:(master) ? git submodule add ~/submd/repos/lib2.git libs/lib2
Cloning into '/Users/yjizhang/submd/ws/p1/libs/lib2'...
done.
? p1 git:(master) ? gst
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: .gitmodules
new file: libs/lib1
new file: libs/lib2
? p1 git:(master) ? tree
.
├── libs
│ ├── lib1
│ │ └── lib1-features
│ └── lib2
│ └── lib2-features
└── p1-infos.txt
3 directories, 3 files
? p1 git:(master) ? cat libs/lib1/lib1-features
I'm lib1
? p1 git:(master) ? cat libs/lib2/lib2-features
I'm lib2
此時(shí)我們已經(jīng)成功使用 git submodule add
命令為 p1 添加了兩個(gè)公共類(lèi)庫(kù) lib1 lib2,那么我們?cè)鯓硬榭醋幽K是否已經(jīng)添加成功了呢 饮焦?
2.2.2 查看 .gitmodules 的內(nèi)容
- .gitmodules : 該文件記錄了每個(gè) submodule 的引用信息以及其在當(dāng)前項(xiàng)目的位置以及倉(cāng)庫(kù)所在地址
? p1 git:(master) ? cat .gitmodules
[submodule "libs/lib1"]
path = libs/lib1
url = /Users/yjizhang/submd/repos/lib1.git
[submodule "libs/lib2"]
path = libs/lib2
url = /Users/yjizhang/submd/repos/lib2.git
2.2.3 提交添加子模塊的更改
? p1 git:(master) ? gst
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: .gitmodules
new file: libs/lib1
new file: libs/lib2
? p1 git:(master) ? git commit -a -m "add submodule [lib1怕吴、lib2] to p1"
[master 65a41b4] add submodule [lib1窍侧、lib2] to p1
3 files changed, 8 insertions(+)
create mode 100644 .gitmodules
create mode 160000 libs/lib1
create mode 160000 libs/lib2
? p1 git:(master) git push
Counting objects: 4, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 488 bytes | 0 bytes/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/p1.git
960cb75..65a41b4 master -> master
假設(shè)你是第一次引入公共類(lèi)庫(kù) lib1、lib2 到 p1 的開(kāi)發(fā)人員转绷,name項(xiàng)目組的其他人要怎么 Clone 帶有 Submodules 的項(xiàng)目呢 伟件?
2.3 Clone 帶有 Submodule 的倉(cāng)庫(kù)
模擬開(kāi)發(fā)人員 B Clone 帶有 Submodule 的項(xiàng)目 p1
2.3.1 Clone 帶有 submodule 的庫(kù) p1
? ~ cd ~/submd/ws
? ws git clone ../repos/p1.git p1-b
Cloning into 'p1-b'...
done.
? ws ls
lib1 lib2 p1 p1-b p2
# clone 成功,查看 p1-b dir tree
? ws cd p1-b
? p1-b git:(master) tree
.
├── libs
│ ├── lib1
│ └── lib2
└── p1-infos.txt
# 對(duì)比 p1-b 和 p1 的 dir tree
? p1-b git:(master) cd ../p1
? p1 git:(master) tree
.
├── libs
│ ├── lib1
│ │ └── lib1-features
│ └── lib2
│ └── lib2-features
└── p1-infos.txt
3 directories, 3 files
發(fā)現(xiàn) 我們 clone 的 p1-b 中 submodule [lib1议经、lib2] 的內(nèi)容并沒(méi)有 clone 下來(lái)斧账。我們可以查看一下子模塊,那么怎么查看子模塊狀態(tài)呢 煞肾?
2.3.2 查看子模塊狀態(tài) git submodule
? p1-b git:(master) git submodule
-101b2e60e0f8c759d2f60c7fca12f3b1e474de21 libs/lib1
-fa8fdeed9d7c8938c0fe470766c568fb32b1ed14 libs/lib2
? p1-b git:(master) cd ../p1
? p1 git:(master) git submodule
101b2e60e0f8c759d2f60c7fca12f3b1e474de21 libs/lib1 (heads/master)
fa8fdeed9d7c8938c0fe470766c568fb32b1ed14 libs/lib2 (heads/master)
- 表示該子模塊還沒(méi)有檢出咧织,怎么檢出子模塊內(nèi)容 ?
2.3.3 檢出子模塊內(nèi)容 git submodule init
& git submodule update
-
git submodule init
初始化本地配置文件.git/config
-
git submodule update
從指定項(xiàng)目.git/config
拉取所有數(shù)據(jù)并檢出你上層項(xiàng)目里所列的合適的提交
? p1-b git:(master) git submodule
-101b2e60e0f8c759d2f60c7fca12f3b1e474de21 libs/lib1
-fa8fdeed9d7c8938c0fe470766c568fb32b1ed14 libs/lib2
? p1-b git:(master) cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = /Users/yjizhang/submd/ws/../repos/p1.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
? p1-b git:(master) git submodule init
Submodule 'libs/lib1' (/Users/yjizhang/submd/repos/lib1.git) registered for path 'libs/lib1'
Submodule 'libs/lib2' (/Users/yjizhang/submd/repos/lib2.git) registered for path 'libs/lib2'
? p1-b git:(master) cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = /Users/yjizhang/submd/ws/../repos/p1.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[submodule "libs/lib1"]
url = /Users/yjizhang/submd/repos/lib1.git
[submodule "libs/lib2"]
url = /Users/yjizhang/submd/repos/lib2.git
? p1-b git:(master) git submodule update
Cloning into '/Users/yjizhang/submd/ws/p1-b/libs/lib1'...
done.
Cloning into '/Users/yjizhang/submd/ws/p1-b/libs/lib2'...
done.
Submodule path 'libs/lib1': checked out '101b2e60e0f8c759d2f60c7fca12f3b1e474de21'
Submodule path 'libs/lib2': checked out 'fa8fdeed9d7c8938c0fe470766c568fb32b1ed14'
? p1-b git:(master) git submodule
101b2e60e0f8c759d2f60c7fca12f3b1e474de21 libs/lib1 (heads/master)
fa8fdeed9d7c8938c0fe470766c568fb32b1ed14 libs/lib2 (heads/master)
? p1-b git:(master) tree
.
├── libs
│ ├── lib1
│ │ └── lib1-features
│ └── lib2
│ └── lib2-features
└── p1-infos.txt
3 directories, 3 files
? p1-b git:(master) cat libs/lib1/lib1-features libs/lib2/lib2-features
I'm lib1
I'm lib2
2.4 修改該有 Submodule 的庫(kù)
模擬開(kāi)發(fā)人員 B 修改子模塊內(nèi)容
2.4.1 查看子模塊
? p1-b git:(master) cd libs/lib1
? lib1 git:(101b2e6) gst
HEAD detached at 101b2e6
nothing to commit, working tree clean
發(fā)現(xiàn)子模塊 lib1 在一個(gè)匿名的分支上,為什么 籍救?
原因: Git 對(duì)于 Submodule 有特殊的處理方式习绢。
-
一個(gè)主項(xiàng)目中引入 Submodule 其實(shí) Git 做了三件事
- 記錄引用的倉(cāng)庫(kù) (子模塊倉(cāng)庫(kù)描述)
- 記錄主項(xiàng)目中 Submodule 的目錄位置
- 記錄引用 Submodule 的 commit id
說(shuō)明:
在 p1 中 push 之后其實(shí)是在 p1 中記錄了對(duì) submodule 提交的引用 commit id
然后 p1-b 在 clone p1 的時(shí)候獲取到了 p1 對(duì) submodule 提交的引用 commit id
當(dāng) p1-b 中執(zhí)行 git submodule update 的時(shí)候, git 根據(jù) modules 獲取到了 submodule 的 commit id , 然后獲取 submodule 的文件。
所以 clone 之后子模塊不在任何分支上蝙昙,但是 master 分支和 commit id 和 HEAD 保持一致闪萄。
2.4.2 更新子模塊
- 切換到 master 分支
- 更新子模塊內(nèi)容
- 提交子模塊更新
- 提交主項(xiàng)目中子模塊更新引用
# 切換到 master 分支
? p1-b git:(master) cd libs/lib1
? lib1 git:(101b2e6) gco master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
# 更新子模塊內(nèi)容
? lib1 git:(master) echo "add by developer b" >> lib1-features
# 提交子模塊更新
? lib1 git:(master) ? ga .
? lib1 git:(master) ? gc -m "update lib1-feature by dev b"
[master 241e03b] update lib1-feature by dev b
1 file changed, 1 insertion(+)
? lib1 git:(master) git push
Counting objects: 3, done.
Writing objects: 100% (3/3), 286 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/repos/lib1.git
101b2e6..241e03b master -> master
# 提交主項(xiàng)目中子模塊更新引用
? lib1 git:(master) cd ../../
? p1-b git:(master) ? git diff
diff --git a/libs/lib1 b/libs/lib1
index 101b2e6..241e03b 160000
--- a/libs/lib1
+++ b/libs/lib1
@@ -1 +1 @@
-Subproject commit 101b2e60e0f8c759d2f60c7fca12f3b1e474de21
+Subproject commit 241e03bf0bf574063417c05a1d558fa4a6006425
(END)
? p1-b git:(master) ? gst
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: libs/lib1 (new commits)
no changes added to commit (use "git add" and/or "git commit -a")
? p1-b git:(master) ? git add libs/lib1
? p1-b git:(master) ? gst
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: libs/lib1
? p1-b git:(master) ? gc -m "update libs/lib1 to lastest commit id"
[master ea785a2] update libs/lib1 to lastest commit id
1 file changed, 1 insertion(+), 1 deletion(-)
? p1-b git:(master) git push
Counting objects: 3, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 389 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/p1.git
65a41b4..ea785a2 master -> master
現(xiàn)在我們已經(jīng)在 p1-b 中更新了子模塊 lib1 的內(nèi)容。那么我們?cè)鯓油礁?p1 中 lib1 的內(nèi)容呢 奇颠?
2.5 更新主項(xiàng)目的 Submodule
- 拉取主項(xiàng)目最新更新
git pull
或git pull -r (推薦.其會(huì)將本地提交放在遠(yuǎn)程提交的最后败去,減少?zèng)_突)
- 拉取子模塊更新的內(nèi)容
git submodule update
(注:在執(zhí)行前明確保已經(jīng)執(zhí)行了git submodule init
將子模塊信息注冊(cè)到.git/congfig
中)
? p1-b git:(master) cd ../p1
? p1 git:(master) gst
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
? p1 git:(master) git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /Users/yjizhang/submd/ws/../repos/p1
65a41b4..ea785a2 master -> origin/master
Fetching submodule libs/lib1
From /Users/yjizhang/submd/repos/lib1
101b2e6..241e03b master -> origin/master
Updating 65a41b4..ea785a2
Fast-forward
libs/lib1 | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
? p1 git:(master) ? gst
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: libs/lib1 (new commits)
no changes added to commit (use "git add" and/or "git commit -a")
? p1 git:(master) ? git submodule update
Submodule path 'libs/lib1': checked out '241e03bf0bf574063417c05a1d558fa4a6006425'
? p1 git:(master) cat libs/lib1/lib1-features
I'm lib1
add by developer b