Description
在一个项目需要引入另一个项目时通常有两种方法:submodule or subtree,关于它们的特点和如何选择它们在 #21 中有说明就不赘述了,我们来看看如何使用submodule。
1. 初始化父项目my
开发人员A克隆父项目my到目录my-A
# git clone git@github.com:xhlwill/git-example-my.git my-A
正克隆到 'git-example-my'...
warning: 您似乎克隆了一个空仓库。
然后新建一个文件a.txt,commit并推送到远程仓库。
# git commit -m "Initial commit"
[master(根提交) 750bd31] Initial commit
1 file changed, 1 insertion(+)
create mode 100644 a.txt
# git push
枚举对象: 3, 完成.
对象计数中: 100% (3/3), 完成.
写入对象中: 100% (3/3), 226 bytes | 226.00 KiB/s, 完成.
总共 3 (差异 0),复用 0 (差异 0)
To github.com:xhlwill/git-example-my.git
* [new branch] master -> master
2. 初始化公共子模块项目sub
克隆子模块项目sub,并新建一个文件sub.txt,然后推送到远程。
# git clone git@github.com:xhlwill/git-example-sub.git
正克隆到 'git-example-sub'...
warning: 您似乎克隆了一个空仓库。
# git commit -m "Initial"
[master(根提交) f2e5348] Initial
1 file changed, 1 insertion(+)
create mode 100644 sub.txt
# git push origin
枚举对象: 3, 完成.
对象计数中: 100% (3/3), 完成.
写入对象中: 100% (3/3), 232 bytes | 232.00 KiB/s, 完成.
总共 3 (差异 0),复用 0 (差异 0)
To github.com:xhlwill/git-example-sub.git
* [new branch] master -> master
3. 为父项目添加子模块
开发人员 A 在父项目 my 中,把公共子项目 sub 添加到它的 libs 目录中
# git submodule add git@github.com:xhlwill/git-example-sub.git libs
正克隆到 '/Users/lovej/projects/github.com/xhlwill/git-example-my-A/libs'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
接收对象中: 100% (3/3), 完成.
# cd libs
# ls
sub.txt
# git status
位于分支 master
您的分支与上游分支 'origin/master' 一致。
要提交的变更:
(使用 "git reset HEAD <文件>..." 以取消暂存)
新文件: .gitmodules
新文件: libs
添加后用 git status 查看发现多了子项目sub的文件夹和一个新文件.gitmodules
查看 .gitmodules 内容发现它记录了每个子模块的引用信息、在当前项目的位置、仓库地址
# cat .gitmodules
[submodule "libs"]
path = libs
url = git@github.com:xhlwill/git-example-sub.git
然后别忘了把这些更改commit并推送到远程,这样my就是一个带有子模块项目(sub)的项目了
# git commit -m "add submodule"
# git push
4. 其他成员克隆带有子模块的仓库
项目组其他成员如何 clone 该项目呢……
假设开发人员 B 检出父项目到 my-B 目录,会发现在libs目录下并没有sub.txt文件,为什么呢?我们用 git submodule 查看子模块状态,会看到 submodule 状态的hash码和目录,注意前面有一个减号“-”,含义是子模块还没检出
# git clone git@github.com:xhlwill/git-example-my.git git-example-my-B
正克隆到 'git-example-my-B'...
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 0), reused 6 (delta 0), pack-reused 0
# git submodule
-f2e5348513f60f212033722d72703ccc2344b8c6 libs
接着我们检出 submodule
# git submodule init
子模组 'libs'(git@github.com:xhlwill/git-example-sub.git)已对路径 'libs' 注册
# git submodule update
正克隆到 '/Users/lovej/projects/github.com/xhlwill/git-example-my-B/libs'...
Submodule path 'libs': checked out 'f2e5348513f60f212033722d72703ccc2344b8c6'
这时候我们就能看到sub.txt文件了
5. 修改 Submodule
开发人员 B 要修改 submodule 的内容时(比如发现sub里某个组件有bug),进入到libs目录,修改前先看一下submodule的状态:
# git status
头指针分离于 f2e5348
无文件要提交,干净的工作区
为什么是 detached 状态呢?不应该在 master 分支吗?
Git 对于 Submodule 有特殊的处理方式,在一个主项目中引入了 Submodule 其实 Git 做了3件事情:
- 记录引用的仓库
- 记录主项目中Submodules的目录位置
- 记录引用Submodule的commit id
A 在 my-A 中 push 之后其实就是更新了引用的 commit id,然后 B 在 clone 的时候获取到了 submodule 的 commit id,然后当执行 git submodule update 的时候 git 就根据 gitlink 获取submodule 的 commit id,最后获取 submodule 的文件,所以 clone 之后不在任何分支上;但是 master 分支的 commit id 和 HEAD 保持一致。
现在要修改子模块项目 sub 需要先切换到 master 分支,再提交修改:
# git checkout master
# touch sub-2.txt
# git commit -m "add sub-2 by B"
我们加了一个 sub-2.txt 文件,在主项目中修改 submodule 并推送到远程稍微繁琐一点,在 git push 之前我们先看看 my-B 的状态:
# git status
位于分支 master
您的分支与上游分支 'origin/master' 一致。
尚未暂存以备提交的变更:
(使用 "git add <文件>..." 更新要提交的内容)
(使用 "git checkout -- <文件>..." 丢弃工作区的改动)
修改: libs (新提交)
修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
libs 的状态表示有新的提交,这个比较特殊,git diff 看看 my-B 的状态会发现 libs 的 commit id 由 f2e5348513f60f212033722d72703ccc2344b8c6 变为 c70bc69f6f4479921fb8fca1552635a69c395ab4
现在回到 libs 目录,前面我们还没把子模块的更改推送到远程,先git push 把子模块 libs 目录的修改提交到仓库。
# git push
这仅仅完成了一步,还要在主项目 my-B 提交推送 (更改了引用子模块的 commit id):
# git add -u
# git commit -m "update libs"
# git push
好了,我们完成了子模块项目的修改并把主项目 libs 的最新 commit id 提交到了仓库。
接下来看看开发人员 A 在项目 my-A 中怎么获取新的 submodule 了。
6. 其他人员更新主项目
开发人员 A 进入 my-A 目录同步仓库:
# git pull
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 2 (delta 1), reused 2 (delta 1), pack-reused 0
展开对象中: 100% (2/2), 完成.
来自 github.com:xhlwill/git-example-my
b64b53b..13fe210 master -> origin/master
Fetching submodule libs
来自 github.com:xhlwill/git-example-sub
f2e5348..c70bc69 master -> origin/master
更新 b64b53b..13fe210
Fast-forward
libs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
# git status
位于分支 master
您的分支与上游分支 'origin/master' 一致。
尚未暂存以备提交的变更:
(使用 "git add <文件>..." 更新要提交的内容)
(使用 "git checkout -- <文件>..." 丢弃工作区的改动)
修改: libs (新提交)
pull 完之后会发现 libs 是 modified 的状态,但是并没有sub-2.txt 文件,这是为什么呢?
我们用 git diff 比较一下不同,从结果分析发现 submodule 的 commit id 更改了。
前面说了要在父项目更新 submodule 的内容首先要提交 submdoule 的内容,然后再更新父项目中引用的 submodule 的 commit id。现在我们看到的不同就是因为刚刚更改了 my-B 的submodule 的 commit id。
现在我们来更新 my-A 中的公共子模块
# git submodule update
Submodule path 'libs': checked out 'c70bc69f6f4479921fb8fca1552635a69c395ab4'
现在再查看libs里子模块的文件内容,就已经更新到最新了。
7. 同步子模块的修改到主项目
sub项目通常为公用的模块,例如一套样式或一套images,风格改动后要同步到多个系统中,怎么办呢?
用 git submodule foreach
此命令循环进入每个子模块的目录,然后执行 foreach 后面的命令。
注:第一次用 git submodule foreach git pull 发现只会 fetch 下来但不会 merge……根据 git 的提示是因为当前子模块不在任何分支,用 git submodule foreach git pull origin master 就可以了。
# git submodule foreach git pull
进入 'libs'
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
展开对象中: 100% (3/3), 完成.
来自 github.com:xhlwill/git-example-sub
c70bc69..b885e96 master -> origin/master
您当前不在一个分支上。
请指定您要合并哪一个分支。
详见 git-pull(1)。
git pull <远程> <分支>
fatal: 对 libs 执行 run_command 返回非零值
# git submodule foreach git pull origin master
进入 'libs'
来自 github.com:xhlwill/git-example-sub
* branch master -> FETCH_HEAD
更新 c70bc69..b885e96
Fast-forward
sub-3.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 sub-3.txt
注意这时用git status查看libs是modified状态,记得把更改commit并推送到远程仓库
8. 新员工加入项目,一次性Clone项目及Submodules
一般人使用的时候都是使用如下命令:
# git clone git@github.com:xhlwill/git-example-my.git
# git submodule init
# git submodule update
上面的命令简直弱暴了,直接一行命令搞定:
# git clone --recursive git@github.com:xhlwill/git-example-my.git