git子模块使用和操作集锦

git子模块适用于某个工作中的项目需要包含并使用另一个项目。 也许是第三方库,也许自主开发的,用于多个主项目的库。 这个时候就可以使用子项目独立管理这个库和主项目的引入版本。

添加子模块

添加子模块的命令格式是:

git submodule [--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]

大部分情况直接用这个命令即可

git submodule add https://github.com/chaconinc/DbConnector

我们还可以指定子模块的分支和导入子模块的路径。

git submodule add -b v2 git@codeup.aliyun.com:ydt/server/test-submodule.git subv2

也可以单独设置子模块的分支

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 = git@codeup.aliyun.com: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

与子模块协作

当子模块有了新版本

子模块的普遍场景是我引入了一个第三方库,或者独立管理的一个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 = git@codeup.aliyun.com: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

发表评论