按:子模块并不是必须的,如果项目比较简单,完全没必要使用子模块。本章内容可以在需要时再看。
一般一个项目的代码用一个仓库管理比较方便,而不同的项目之间有时会有一些代码依赖关系,此时我们可以用子模块的方式来建立这种关系——类似 Java 项目中的 maven 依赖。
子模块是一个独立的仓库,它的版本管理由它自己。一个仓库 A 将另外一个仓库 B 用作子模块,则 A 持有 B 的 repo URL 以及用作子模块的 B 的版本号(commit hash)。
IP: 192.168.0.102
Create two git repositories:
$ pwd
/home/caoyi/sandbox/test
$ git init --bare project-main.git
$ git init --bare project-sub.git
远程的仓库创建好后,再将它们 clone 到本地。
$ pwd
/d/sandbox/temp
$ git clone ssh://caoyi@192.168.0.102/home/caoyi/sandbox/test/project-main.git
$ git clone ssh://caoyi@192.168.0.102/home/caoyi/sandbox/test/project-sub.git
main repo
$ pwd
/d/sandbox/temp/project-main
$ echo "hello from main repo" > hello.txt
$ git add .
$ git commit -m "first commit in main repo"
$ git push origin main
sub repo:
$ pwd
/d/sandbox/temp/project-sub
$ echo "hello from the sub repo" > hello-sub.txt
$ git add .
$ git commit -m "first commit in the sub repo"
$ git push origin main
$ pwd
/d/sandbox/temp/project-main
$ git submodule add ssh://caoyi@192.168.0.102/home/caoyi/sandbox/test/project-sub.git
(本例只添加了一个子模块,实际开发中,可以根据需要添加若干。添加的模块默认以仓库的名创建一个新的目录,如果要使用其他名称,可以将自定义名称跟在上面的指令最后。)
通过 git status
发现多了两个文件
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: .gitmodules
new file: project-sub
.gitmodules
里记录了子模块的信息
$ cat .gitmodules
[submodule "project-sub"]
path = project-sub
url = ssh://caoyi@192.168.0.102/home/caoyi/sandbox/test/project-sub.git
project-sub 就是引入的子模块的目录,主库只会把这个目录名当作一个文件加入版本管理的信息中,而不会去管理目录内部的内容,那是子模块自己去管理的,因此,即使使用 git add
也不会把子模块目录里的内容添加到主库的 Staging Area 中,这样的设计很合理,否则就不需要 submodule 了。主库和子模块的版本管理是相对独立的。
子模块入库:
$ git commit -m "add submodule"
[main 99d7ec7] add submodule
2 files changed, 4 insertions(+)
create mode 100644 .gitmodules
create mode 160000 project-sub
$ git push origin main
查看子模块的状态
$ git submodule status
-bf676211dc70759919951a9b8974e57b816d3568 project-sub
如果直接 clone,不带参数,得到的仓库,是不会有子模块的,需要继续执行 submodule init + update
才行,即:
$ git clone ssh://caoyi@192.168.0.102/home/caoyi/sandbox/test/project-main.git
$ git submodule init
$ git submodule update
init
和 update
两句还可以合写为 git submodule update --init
, 如果考虑模块嵌套,还可以写作 git submodule update --init --recursive
.
也可以使用 --recurse-submodules
or --recursive
一步到位:
$ git clone --recurse-submodules ssh://caoyi@192.168.0.102/home/caoyi/sandbox/test/project-main.git
or
$ git clone --recursive ssh://caoyi@192.168.0.102/home/caoyi/sandbox/test/project-main.git
子模块是独立的仓库,主库不能管理它的版本,它也不知它是哪些库的子模块。这也符合「最小知识原则」,主仓库仅仅持有子模块的「引用」——模块名、仓库地址、版本号(commit hash)。
当我们通过主库进入到子模块中并做了修改时,这些修改只能通过子模块的仓库去处理,然后主库更新引用。
我们现在修改子模块的一点内容:
$ pwd
/d/sandbox/temp/project-main/project-sub
$ echo "hello from sub in main" >> hello-sub.txt
$ git status
On branch main
Your branch is up to date with 'origin/main'.
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: hello-sub.txt
然后退到主库用 git status
查看状态
$ pwd
/d/sandbox/temp/project-main
$ git status
On branch main
Your branch is up to date with 'origin/main'.
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)
(commit or discard the untracked or modified content in submodules)
modified: project-sub (modified content)
no changes added to commit (use "git add" and/or "git commit -a")
屏幕提示:主库无任何变化,但子模块有变化,不过需要进入子模块去处理。(这里本应该用 git diff
查看下具体改动内容,但做实验时忘了,不过没关系,继续往下走,后边的 git show
也会看到改了哪些内容。) OK,我们现在去子模块里处理一下:
$ pwd
/d/sandbox/temp/project-main/project-sub
$ git add .
$ git commit -m "hello from sub module in main/sub"
$ git push origin main
退到主库用 git status
查看状态:
$ pwd
/d/sandbox/temp/project-main
$ git status
On branch main
Your branch is up to date with 'origin/main'.
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: project-sub (new commits)
no changes added to commit (use "git add" and/or "git commit -a")
屏幕提示:主库无任何变化,但子模块有新的 commit. 假若我们的主模块需要使用这个更新后的子模块,我们按如下操作:
$ pwd
/d/sandbox/temp/project-main
$ git add .
$ git commit -m "update submodule"
$ git push origin main
可用 git show
看看刚才的到底修改了什么
$ git show
commit 93e78e5971682ec1765cde2a24732ab565538892 (HEAD -> main, origin/main)
Author: Cao Yi <iridiumcao@gmail.com>
Date: Thu Dec 21 21:03:23 2023 +0800
update submodule
diff --git a/project-sub b/project-sub
index bf67621..950e20e 160000
--- a/project-sub
+++ b/project-sub
@@ -1 +1 @@
-Subproject commit bf676211dc70759919951a9b8974e57b816d3568
+Subproject commit 950e20e33528800ca4aa1255446c6f51b1019f97
可见主库里那个和 submodule 文件夹同名的文件里记录的是子模块的某个 commit hash,即当前主库使用的子模块的版本号。这些内容都在主库的 .git/modules/
中存放。
如果子模块的远程有更新,可以先到子模块中更新,然后主库考虑是否使用这个最新的版本。
可以在主库使用 --recurse-submodules
一步到位,把子模块也更新了:
git pull origin --recurse-submodules
这里更新子模块,不一定是把子模块更新到它本身的最新版本,而是更新到主库持有的 commit hash 对应的版本。
删除子模块需要先从主库中移除相关引用信息,然后再物理删除子模块的目录。
$ pwd
/d/sandbox/temp/project-main
$ git submodule deinit project-sub
$ git rm project-sub
查看一下状态
$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: .gitmodules
deleted: project-sub
提交并推送
$ git commit -m "remove submodule"
$ git push origin main
删除一个子模块,提交,再添加,会出现下面的报错:
$ git submodule add ssh://caoyi@192.168.0.102/home/caoyi/sandbox/test/project-sub.git
fatal: A git directory for 'project-sub' is found locally with remote(s):
origin ssh://caoyi@192.168.0.102/home/caoyi/sandbox/test/project-sub.git
If you want to reuse this local git directory instead of cloning again from
ssh://caoyi@192.168.0.102/home/caoyi/sandbox/test/project-sub.git
use the '--force' option. If the local git directory is not the correct repo
or you are unsure what this means choose another name with the '--name' option.
按屏幕提示加 --force
改成这样就好了:
$ git submodule add --force ssh://caoyi@192.168.0.102/home/caoyi/sandbox/test/project-sub.git
Reactivating local git directory for submodule 'project-sub'
注意 --force
的位置,不能放在指令最后。
参考:
submodule
参数解释)