有种情况我们经常会遇到:某个工作中的项目需要包含并使用另一个项目。 也许是第三方库,或者你独立开发的,用于多个父项目的库。 现在问题来了:你想要把它们当做两个独立的项目,同时又想在一个项目中使用另一个。
在Git
中你可以用子模块submodule
来管理这些项目,submodule允许你将一个Git仓库当作另外一个Git仓库的子目录。这允许你克隆另外一个仓库到你的项目中并且保持你的提交相对独立。
添加子模块
我们首先将一个已存在的 Git 仓库添加为正在工作的仓库的子模块。 你可以通过在git submodule add
命令后面加上想要跟踪的项目的相对或绝对 URL 来添加新的子模块。 在本例中,我们将会添加一个名为 “material-store” 的库,该库包含一些用react实现的基础素材类组件,提供给其他库使用。该例中出现的git库为公司现有项目,故敏感信息用星号处理,但整体上不影响理解。
$ git submodule add https://****/material-store.git assets
默认情况下,子模块会将子项目放到一个与仓库同名的目录中,本例中是 “material-store”。 如果你想要放到其他地方,那么可以在命令结尾添加一个不同的路径。本例中将将material-store放到components/materials
下方便引用。
运行git status
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: .gitmodules
查看.gitmodules内容
// .gitmodules
[submodule "components/materials"]
path = components/materials
url = http://***/material-store.git
查看子模块
$ git submodule
8fdfa50364cc507352e9d668fa4bb9562dd0a44e components/materials (heads/master)
更新子模块
- 更新项目内子模块到最新版本
$ git submodule update
- 更新子模块为远程项目的最新版本
$ git submodule update --remote
克隆含有子模块的项目
- 克隆父项目,更新子模块
我们克隆一个含有子模块的项目项目时,默认会包含该子模块目录,但其中还没有任何文件,你必须运行两个命令:git submodule init
用来初始化本地配置文件,而 git submodule update
则从该项目中抓取所有数据并检出父项目中列出的合适的提交。
- 递归克隆一步到位
还有更简单一点的方式。 如果给git clone
命令传递--recurse-submodules
选项,它就会自动初始化并更新仓库中的每一个子模块, 包括可能存在的嵌套子模块。
修改子模块
在子模块中修改文件后,直接提交到远程项目分支。
$ git add .
$ git ci -m "commit"
$ git push origin HEAD:master
需要注意的是:默认 git submodule update 并不会将 submodule 切到任何 branch,所以,默认下 submodule 的 HEAD 是处于游离状态的 (‘detached HEAD’ state)
。所以在修改前,记得一定要用 git checkout master
将当前的 submodule 分支切换到 master
,然后才能做修改和提交。
如果你不慎忘记切换到 master 分支,又做了提交,可以用 cherry-pick
命令挽救。具体做法如下:
用 git checkout master
将 HEAD 从游离状态切换到 master 分支 , 这时候,git 会报 Warning 说有一个提交没有在 branch 上,记住这个提交的 change-id
(假如 change-id 为 aaaa)
用 git cherry-pick aaaa
来将刚刚的提交作用在 master 分支上
用 git push
将更新提交到远程版本库中
删除子模块
删除子模块比较麻烦,需要手动删除相关的文件,否则在添加子模块时有可能出现错误,以删除components/materials
文件夹为例,需要如下步骤:
1、删除子模块文件夹
$ git rm --cached components/materials
$ rm -rf components/materials
2、删除.gitmodules文件中相关子模块信息
[submodule "components/materials"]
path = components/materials
url = http://***/material-store.git
3、删除.git/config中的相关子模块信息
[submodule "components/materials"]
url = http://***/material-store.git
4、删除.git文件夹中的相关子模块文件
$ rm -rf .git/modules/components
子模块的问题
submodule 项目和它的父项目本质上是 2 个独立的 git 仓库。只是父项目存储了它依赖的 submodule 项目的版本号信息而已。如果你的同事更新了 submodule,然后更新了父项目中依赖的版本号。你需要在 git pull 之后,调用 git submodule update 来更新 submodule 信息。
这儿的坑在于,如果你 git pull 之后,忘记了调用 git submodule update,那么你极有可能再次把旧的 submodule 依赖信息提交上去。对于那些习惯使用 git commit -a 的人来说,这种危险会更大一些。所以建议大家:
git pull 之后,立即执行 git status, 如果发现 submodule 有修改,立即执行 git submodule update
。尽量不要使用 git commit -a, git add 命令存在的意义就是让你对加入暂存区的文件做二次确认,而 git commit -a 相当于跳过了这个确认过程。
更复杂一些,如果你的 submodule 又依赖了 submodule,那么很可能你需要在 git pull
和 git submodule update
之后,再分别到每个 submodule 中再执行一次 git submodule update
,这里可以使用 git submodule foreach
命令来实现: git submodule foreach git submodule update