之前在搭建Hugo博客时,由于对Git子模块不熟悉,折腾了很久,本文对Git子模块进行基本了解

什么是Git子模块?

在一个项目中,有可能需要包含并使用另一个项目,它们虽然是两个独立的项目,但是处于一个项目中,为了避免混乱,Git引入了子模块submodule的概念

子模块允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。 它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立

Git子模块基本使用

给项目添加子模块

将一个已存在的 Git 仓库添加为正在工作的仓库的子模块

git submodule add <GIT-PROJECT-URL>

添加成功后会在项目下创建一个.gitmodules文件,该文件保存了子模块的基本信息,包括名称、path、url等,该文件也像 .gitignore 文件一样受到(通过)版本控制。 它会和该项目的其他部分一同被拉取推送

$ cat .gitmodules
[submodule "DbConnector"]
	path = DbConnector
	url = https://github.com/chaconinc/DbConnector

克隆含有子模块的项目

克隆含有子模块的项目,有两种方式

  • 使用git clone克隆主项目,克隆后默认会包含该子模块目录,但子模块中没有任何文件,只是一个空目录

    git clone <GIT-PROJECT-URL>
    

    必须运行两个命令:git submodule init 用来初始化本地配置文件,而 git submodule update 则从该项目中抓取所有数据并检出父项目中列出的合适的提交

    git submodule init
    git submodule update
    
  • 另一个方法是克隆主项目时给 git clone 命令传递 --recursive 选项,它就会自动初始化并更新仓库中的每一个子模块

    git clone --recursive <GIT-PROJECT-URL>
    

在包含子模块的项目上工作

拉取上游修改

如果想要在子模块中查看新工作,可以进入到目录中运行 git fetchgit merge,合并上游分支来更新本地代码

$ git fetch
$ git merge origin/master

如果你不想在子目录中手动抓取与合并,那么还有种更容易的方式。 运行 git submodule update --remote,Git 将会进入子模块然后抓取并更新。Git 默认会尝试更新所有子模块,所以如果有很多子模块的话,你可以传递想要更新的子模块的名字。

$ git submodule update --remote

上面的命令只会更新并检出子模块的master分支,如果想要指定子模块的分支,如stable分支,执行下面的设置

$ git config -f .gitmodules submodule.SUB_PROJECT.branch stable
$ git submodule update --remote

在子模块上工作

当我们运行 git submodule update 从子模块仓库中抓取修改时,Git 将会获得这些改动并更新子目录中的文件,但是会将子仓库留在一个称作 “游离的 HEAD” 的状态。 这意味着没有本地工作分支(例如 “master”)跟踪改动。 所以你做的任何改动都不会被跟踪

为了将子模块设置得更容易进入并修改,你需要做两件事。 首先,进入每个子模块并检出其相应的工作分支。 接着,若你做了更改就需要告诉 Git 它该做什么,然后运行 git submodule update --remote 来从上游拉取新工作。 你可以选择将它们合并到你的本地工作中,也可以尝试将你的工作变基到新的更改上

  1. 首先,进入子模块目录然后检出一个分支

    $ git checkout master
    
  2. 然后尝试用 “merge” 选项。 为了手动指定它,我们只需给 update 添加 --merge 选项即可

    $ git submodule update --remote --merge
    
  3. 如果我们现在更新子模块,就会看到当我们在本地做了更改时上游也有一个改动,我们需要将它并入本地

    $ git submodule update --remote --rebase
    
  4. 如果你忘记 --rebase--merge,Git 会将子模块更新为服务器上的状态。并且会将项目重置为一个游离的 HEAD 状态

    $ git submodule update --remote
    

    出现这个问题,只需回到目录中再次检出你的分支(即还包含着你的工作的分支)然后手动地合并或变基 origin/stable(或任何一个你想要的远程分支)就行了

如果你没有提交子模块的改动,那么运行一个子模块更新也不会出现问题,此时 Git 会只会抓取更改而并不会覆盖子模块目录中未保存的工作

发布子模块改动

如果我们在主项目中提交并推送但并不推送子模块上的改动,其他人尝试检出我们修改会遇到麻烦,因为他们无法得到依赖的子模块改动。 那些改动只存在于我们本地的拷贝中。

为了确保这不会发生,你可以让 Git 在推送到主项目前检查所有子模块是否已推送。 git push 命令接受可以设置为 “check” 或 “on-demand” 的 --recurse-submodules 参数。 如果任何提交的子模块改动没有推送那么 “check” 选项会直接使 push 操作失败

$ git push --recurse-submodules=check

另一个选项是使用 “on-demand” 值,Git 进入到 子模块中然后在推送主项目前推送子模块。 如果那个子模块因为某些原因推送失败,主项目也会推送失败

$ git push --recurse-submodules=on-demand

删除子模块

先反初始化,再删除子模块,最后提交。模块名后面不要加/

#反初始化
git submodule deinit <submodule>
#不保留在工作目录
git rm <submodule>
#保留在工作目录
git rm --cached <submodule>
#提交
git commit -am "Remove a submodule."

修改子模块URL

  1. 编辑.gitmodules文件

  2. 执行下列命令,执行后可以看到.git/config中url更新了

    $ git submodule sync
    $ git commit -am "Update submodule url." # 提交变更
    

合并子模块改动

  1. 首先解决冲突
  2. 然后返回到主项目目录中
  3. 再次检查 SHA-1 值
  4. 解决冲突的子模块记录
  5. 提交我们的合并

参考

Git官方文档

[Git] 如何优雅的删除子模块(submodule)或修改Submodule URL