当程序比较大参与开发人员较多时,代码管理就复杂起来。代码如果全员可见,可以创建 share 分支维护共用代
码,可以创建 core 分支维护核心算法代码,各进程分别占一个分支,定期同步 share 和 core 分支。代码如果不
能全员可见,可以仓库中包含子仓库,子仓库管理模块代码,主仓库定时更新。
同时有种情况我们经常会遇到:某个工作中的项目需要包含并使用另一个项目。也许是第三方库,或者你独立开发
的,用于多个父项目的库。现在问题来了:你想要把它们当做两个独立的项目,同时又想在一个项目中使用另一
个。如果将另外一个项目中的代码复制到自己的项目中,那么你做的任何自定义修改都会使合并上游的改动变得困
难。Git 通过子模块来解决这个问题,允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。它能让你将另一个仓
库克隆到自己的项目中,同时还保持提交的独立。
# 添加子模块
# 在主项目中添加子项目,URL为子模块的路径,path为该子模块存储的目录路径
git submodule add [repository_url] [path]
# 克隆含有子项目的主项目
git clone [repository_url]
# 递归的方式克隆整个项目
git clone <repository> --recursive
# 当你在克隆这样的项目时,默认会包含该子项目的目录,但该目录中还没有任何文件
# 初始化本地配置文件
# 初始化子模块
git submodule init
# 从当前项目中抓取所有数据并检出父项目中列出的合适的提交
# 更新子模块
git submodule update
# 等价于git submodule init && git submodule update
git submodule update --init
# 自动初始化并更新仓库中的每一个子模块,包括可能存在的嵌套子模块
git clone --recurse-submodules [url]
# 拉取所有子模块
git submodule foreach git pull
例如我们要创建如下结构的项目:
project
|--moduleA
|--readme.txt
$ mkdir submoduletest
$ cd submoduletest/
创建 project
版本库,并提交 readme.txt
文件:
$ git init --bare project.git
Initialized empty Git repository in C:/Users/zhangshixing/Desktop/submoduletest/project.git/
$ git clone project.git project1
Cloning into 'project1'...
warning: You appear to have cloned an empty repository.
done.
$ cd project1
$ echo "This is a project." > readme.txt
$ git add .
$ git commit -m "add readme.txt"
[master (root-commit) 50a6933] add readme.txt
1 file changed, 1 insertion(+)
create mode 100644 readme.txt
$ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 229 bytes | 229.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To C:/Users/zhangshixing/Desktop/submoduletest/project.git
* [new branch] master -> master
$ cd ..
创建 moduleA
版本库,并提交 a.txt
文件:
$ git init --bare moduleA.git
Initialized empty Git repository in C:/Users/zhangshixing/Desktop/submoduletest/moduleA.git/
$ git clone moduleA.git moduleA1
Cloning into 'moduleA1'...
warning: You appear to have cloned an empty repository.
done.
$ cd moduleA1
$ echo "This is a submodule." > a.txt
$ git add .
$ git commit -m "add a.txt"
[master (root-commit) d0f22fb] add a.txt
1 file changed, 1 insertion(+)
create mode 100644 a.txt
$ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 224 bytes | 224.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To C:/Users/zhangshixing/Desktop/submoduletest/moduleA.git
* [new branch] master -> master
$ cd ..
在 project
项目中引入子模块 moduleA
,并提交子模块信息:
$ cd project1
$ git submodule add ../moduleA.git moduleA
Cloning into 'C:/Users/zhangshixing/Desktop/submoduletest/project1/moduleA'...
done.
$ ls
moduleA/ readme.txt
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD ..." to unstage)
new file: .gitmodules
new file: moduleA
$ git diff
$ git add .
$ git commit -m "add submodule"
[master 60d9847] add submodule
2 files changed, 4 insertions(+)
create mode 100644 .gitmodules
create mode 160000 moduleA
$ git push origin master
Counting objects: 3, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 352 bytes | 352.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To C:/Users/zhangshixing/Desktop/submoduletest/project.git
50a6933..60d9847 master -> master
$ cd ..
使用 git status
可以看到多了两个需要提交的文件,其中 .gitmodules
指定submodule的主要信息,包括子
模块的路径和地址信息,moduleA
指定了子模块的commit id,使用git diff
可以看到这两项的内容。这里需要
指出父项目的 git 并不会记录 submodule 的文件变动,它是按照 commit id 指定 submodule 的 git header,所
以 .gitmodules
和 moduleA
这两项是需要提交到父项目的远程仓库的。
方法一,先 clone 父项目,再初始化 submodule,最后更新 submodule,初始化只需要做一次,之后每次只需
要直接 update 就可以了,需要注意 submodule 默认是不在任何分支上的,它指向父项目存储的 submodule
commit id。
$ git clone project.git project2
Cloning into 'project2'...
done.
$ cd project2
$ ls
moduleA/ readme.txt
$ ls moduleA/
# 没有内容
$ git submodule init
Submodule 'moduleA' (C:/Users/zhangshixing/Desktop/submoduletest/moduleA.git) registered for path 'moduleA'
$ ls moduleA/
# 没有内容
$ git submodule update
Cloning into 'C:/Users/zhangshixing/Desktop/submoduletest/project2/moduleA'...
done.
Submodule path 'moduleA': checked out 'd0f22fbfd4336480863ed232ec785c0e507cf944'
$ ls moduleA/
a.txt
$ cd ..
方法二,采用递归参数 --recursive
,需要注意同样 submodule 默认是不在任何分支上的,它指向父项目存储
的 submodule commit id。
$ git clone project.git project3 --recursive
Cloning into 'project3'...
done.
Submodule 'moduleA' (C:/Users/zhangshixing/Desktop/submoduletest/moduleA.git) registered for path 'moduleA'
Cloning into 'C:/Users/zhangshixing/Desktop/submoduletest/project3/moduleA'...
done.
Submodule path 'moduleA': checked out 'd0f22fbfd4336480863ed232ec785c0e507cf944'
$ cd project3/
$ ls
moduleA/ readme.txt
$ ls moduleA/
a.txt
修改子模块之后只对子模块的版本库产生影响,对父项目的版本库不会产生任何影响,如果父项目需要用到最新的
子模块代码,我们需要更新父项目中 submodule commit id,默认的我们使用 git status
就可以看到父项目中
submodule commit id 已经改变了,我们只需要再次提交就可以了。
$ cd project1/moduleA
$ git branch
* master
$ echo "This is a submodule." > b.txt
$ git add .
$ git commit -m "add b.txt"
[master e9b6471] add b.txt
1 file changed, 1 insertion(+)
create mode 100644 b.txt
$ git push origin master
Counting objects: 2, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 230 bytes | 230.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0)
To C:/Users/zhangshixing/Desktop/submoduletest/moduleA.git
d0f22fb..e9b6471 master -> master
$ cd ..
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: moduleA (new commits)
no changes added to commit (use "git add" and/or "git commit -a")
$ git diff
diff --git a/moduleA b/moduleA
index d0f22fb..e9b6471 160000
--- a/moduleA
+++ b/moduleA
@@ -1 +1 @@
-Subproject commit d0f22fbfd4336480863ed232ec785c0e507cf944
+Subproject commit e9b647157369276e6e70fc148fc176676eab4476
$ git add .
$ git commit -m "update submodule add b.txt"
[master 312c8e0] update submodule add b.txt
1 file changed, 1 insertion(+), 1 deletion(-)
$ git push origin master
Counting objects: 2, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 308 bytes | 308.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0)
To C:/Users/zhangshixing/Desktop/submoduletest/project.git
60d9847..312c8e0 master -> master
$ cd ..
更新子模块的时候要注意子模块的分支默认不是 master。
方法一,先pull父项目,然后执行 git submodule update
,注意 moduleA 的分支始终不是master。
$ cd project2
$ git pull
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (2/2), done.
From C:/Users/zhangshixing/Desktop/submoduletest/project
60d9847..312c8e0 master -> origin/master
Fetching submodule moduleA
From C:/Users/zhangshixing/Desktop/submoduletest/moduleA
d0f22fb..e9b6471 master -> origin/master
Updating 60d9847..312c8e0
Fast-forward
moduleA | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ git submodule update
Submodule path 'moduleA': checked out 'e9b647157369276e6e70fc148fc176676eab4476'
$ cd ..
方法二,先进入子模块,然后切换到需要的分支,这里是 master 分支,然后对子模块 pull,这种方法会改变子模
块的分支。
$ cd project3/moduleA
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ cd ..
$ git submodule foreach git pull
Entering 'moduleA'
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (2/2), done.
From C:/Users/zhangshixing/Desktop/submoduletest/moduleA
d0f22fb..e9b6471 master -> origin/master
Updating d0f22fb..e9b6471
Fast-forward
b.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 b.txt
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: moduleA (new commits)
no changes added to commit (use "git add" and/or "git commit -a")
$ git diff
diff --git a/moduleA b/moduleA
index d0f22fb..e9b6471 160000
--- a/moduleA
+++ b/moduleA
@@ -1 +1 @@
-Subproject commit d0f22fbfd4336480863ed232ec785c0e507cf944
+Subproject commit e9b647157369276e6e70fc148fc176676eab4476
$ git add .
$ git commit -m "update submodule add b.txt"
[master 50e7e55] update submodule add b.txt
1 file changed, 1 insertion(+), 1 deletion(-)
$ git push origin master | git push -f origin master
Counting objects: 8, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (8/8), 824 bytes | 824.00 KiB/s, done.
Total 8 (delta 0), reused 0 (delta 0)
To C:/Users/zhangshixing/Desktop/submoduletest/project.git
+ 6e1a3d3...50e7e55 master -> master (forced update)
$ cd ..
有时子模块的项目维护地址发生了变化,或者需要替换子模块,就需要删除原有的子模块。
网上有好多用的是下面这种方法:
$ cd project1/
$ ls
moduleA/ readme.txt
$ git rm --cached moduleA
rm 'moduleA'
$ rm -rf moduleA
$ rm .gitmodules
$ vim .git/config
# 删除submodule相关的内容
[submodule "moduleA"]
url = C:/Users/zhangshixing/Desktop/submoduletest/moduleA.git
active = true
# 然后提交到远程服务器
$ git add .
$ git commit -m "remove submodule"
[master 93e32f8] remove submodule
2 files changed, 4 deletions(-)
delete mode 100644 .gitmodules
delete mode 160000 moduleA
$ git push origin master
Counting objects: 2, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (1/1), done.
Writing objects: 100% (2/2), 234 bytes | 234.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0)
To C:/Users/zhangshixing/Desktop/submoduletest/project.git
312c8e0..93e32f8 master -> master
但是我自己本地实验的时候,发现用下面的方式也可以,服务器记录的是 .gitmodules
和 moduleA
,本地只要
用 git 的删除命令删除 moduleA,再用 git status 查看状态就会发现 .gitmodules 和 moduleA 这两项都已经改变
了,至于 .git/config,仍会记录 submodule 信息,但是本地使用也没发现有什么影响,如果重新从服务器克隆
则 .git/config 中不会有submodule信息。
$ git rm moduleA
rm 'moduleA'
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD ..." to unstage)
modified: .gitmodules
deleted: moduleA
$ git commit -m "remove submodule"
[master 6e1a3d3] remove submodule
2 files changed, 4 deletions(-)
delete mode 160000 moduleA
$ git push origin master | git push -f origin master
Counting objects: 11, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (11/11), 1.05 KiB | 1.05 MiB/s, done.
Total 11 (delta 0), reused 0 (delta 0)
To C:/Users/zhangshixing/Desktop/submoduletest/project.git
+ 93e32f8...6e1a3d3 master -> master (forced update)