git子模块适用于某个工作中的项目需要包含并使用另一个项目。 也许是第三方库,也许自主开发的,用于多个主项目的库。 这个时候就可以使用子项目独立管理这个库和主项目的引入版本。
添加子模块
添加子模块的命令格式是:
git submodule [--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]
大部分情况直接用这个命令即可
git submodule add https://github.com/chaconinc/DbConnector
我们还可以指定子模块的分支和导入子模块的路径。下面例子指定了拉取 release 分支,放在modules/sub1 文件夹下面
git submodule add -b release [email protected]:ydt/server/test-submodule.git modules/sub1
也可以单独设置子模块的分支
git config -f .gitmodules submodule.subv2.branch v2
这个时候我们可以看到一个chached的修改,我们用git diff --cached --submodule
查看变更。
➜ test-main git:(master) ✗ git diff --cached --submodule
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..5dc8db5
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,4 @@
+[submodule "subv2"]
+ path = subv2
+ url = [email protected]:ydt/server/test-submodule.git
+ branch = v2
Submodule subv2 0000000...355fcd4 (new submodule)
我们可以看到上面的信息,这里面有两个信息,一个是子模块信息被放在.gitmodules文件中管理的,另一个是看到了我们指定的分支和路径。
值得注意的是,子模块引入后的分支是在主项目版本控制之内的,因此,这两者的对应关系要管理好。
拉子模块的代码
克隆主项目默认不会把子模块克隆下来,但是主项目中是包含了子模块信息的。因此,需要显式指定一些操作。
方法一:
这种方法适合第一次克隆的时候,可以直接把所有的子模块,以及子模块中的子模块递归的拉下来。
git clone --recurse-submodules https://github.com/chaconinc/MainProject
方法二:
对于已经克隆了主项目,或者子模块是后来引入的。我们可以不需要重新克隆主项目,我们可以这么干:
git submodule init
git submodule update
git submodule init
用来初始化本地配置文件,而 git submodule update
则从该项目中抓取所有数据并检出父项目中列出的合适的提交。init只需要第一次初始化执行,update需要在日后常用。也可以将这两个操作合并。
git submodule update --init
如果子模块还包含了子模块,推荐直接执行下面这个。
git submodule update --init --recursive
与子模块协作
在主项目操作
主项目拉取代码
git pull –recurse-submodules
在主项目切换分支
git checkout --recurse-submodules test
配置submodule.recurse
的值为true
,将默认携带--recurse-submodules
拉取和切换分支。
git config --global submodule.recurse true
当子模块有了新版本
子模块的普遍场景是我引入了一个第三方库,或者独立管理的一个lib。子模块集成进来的时候一定是有版本要求的,因此子模块的版本也是受主项目管理的。这也意味着即使子模块更新了版本,主项目引入的这一份也不会被动更新。需要我们主动更新子模块的版本。
当远程的子模块有了新版本,如果主项目决定更新到这个版本,可使用用下面命令来主动更新。
git submodule update --remote
git submodule update --remote subv2
上面第一条命令更新项目下所有子模块,第二条命令指定了需要更新的子模块的名称。
子模块更新之后还是一个modified状态的改动,我们依然可以用git diff --submodule来查看具体的变更内容。当我们测试无误可以更新之后,还需要主动提交这个变更。
我们也可以用git log -p --submodule查看日志
当主项目的协作者更新了子模块版本
下例是一个协作者更新了子模块版本,另一个协作者拉代码的结果。
➜ test-main git:(master) git pull
remote: Counting objects: 2, done.
remote: Total 2 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (2/2), 322 bytes | 64.00 KiB/s, done.
From codeup.aliyun.com:ydt/server/test-main
17898c1..17cd7d0 master -> origin/master
Fetching submodule subv2
From codeup.aliyun.com:ydt/server/test-submodule
9eda1df..82488d7 v2 -> origin/v2
Updating 17898c1..17cd7d0
Fast-forward
subv2 | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
我们可以看到拉代码的输出中有一个子模块的更新,但是!!!如果你去看子模块中的内容,其实并没有更新。这是一件非常奥义的事情。这时我们再执行一下status,发现有个modified
➜ test-main git:(master) ✗ git status
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 restore <file>..." to discard changes in working directory)
modified: subv2 (new commits)
no changes added to commit (use "git add" and/or "git commit -a")
我们来梳理一下,subv2这个子模块被更新了,但是本地的文件还是旧的,这个文件变成了modified。也就是说本地的提交结果已经更新下来了,但是文件内容没有变化(被检出),所以产生了一个差异。我们使用下面命令来更新本地文件。
git submodule update
这个过程也可以合并为
git pull --recurse-submodules
当然,如果你也不想老输这么长一串复杂的单词,那么可以配置 submodule.recurse
设置为 true
。这样,Git会总是以 --recurse-submodules
拉取。
日常操作集锦
子模块有新版本:git submodule update --remote 并提交
主仓库拉代码,一律使用:git pull --recurse-submodules
需要切换引入子模块的分支,建议直接编辑.gitmodules中的branch属性,然后运行git submodule update --remote更新并提交变更。
[submodule "subv2"]
path = subv2
url = [email protected]:ydt/server/test-submodule.git
branch = master
子模块产生modified,如何diff
git diff --submodule
git diff --cached --submodule
如果你不想每次运行 git diff
时都输入 --submodle
,那么可以将 diff.submodule
设置为 “log” 来将其作为默认行为。
git config --global diff.submodule log