0%

Git Submodule使用教程

引子

Git Submodule使用教程

Submodule 是什么?

项目的版本库在某些情况下需要引用其他版本库的文件,例如公司积累了一套常用的函数库,被多个项目调用. 显然这个函数库的代码不能直接放到某个项目的代码中,而是要独立为一个代码库。这个时候就需要使用Submodule。

1.对于公共资源各种程序员的处理方式
每个公司的系统都会有一套统一的系统风格,或者针对某一个大客户的多个系统风格保持统一,而且如果风格改动后要同步到多个系统中;这样的需求几乎每个开发人员都遇到,下面看看各个层次的程序员怎么处理:

假如对于系统的风格需要几个目录:css、images、js。

  • 普通程序员,把最新版本的代码逐个复制到每个项目中,如果有N个项目,那就是要复制N x 3次;如果漏掉了某个文件夹没有复制…
  • 文艺程序员,使用Git Submodule功能,执行:git submodule update,然后冲一杯咖啡悠哉的享受着。

类似于SVN的svn:externals.

功能 svn:externals git submodule
如何记录外部版本库的地址 目录 svn:externals 属性 项目根目录下的 .gitmodules 文件
默认是否自动检出外部版本库 是\n在使用svn checkout 检出时若使用 —ignore-externals可以忽略对外部版本库的引用,不检出 否\n默认不克隆外部版本库。若要克隆则需要使用git submodule init及git submodule update命令
是否能部分引用外部版本库内容 是\n因为svn支持部分检出 否\n必须克隆整个外部版本库
是否可以指向分支而随之改变 否\n固定于外部版本库的某个提交

准备工作

说明:本例采用两个项目以及两个公共类库演示对submodule的操作。

准备环境

创建公共类库的本地仓库

1
2
3
4
5
6
7
8
9
10
[root@localhost projects]# git --git-dir=/root/repos/libA.git init --bare
Initialized empty Git repository in /root/repos/libA.git/
[root@localhost projects]# git --git-dir=/root/repos/libB.git init --bare
Initialized empty Git repository in /root/repos/libB.git/
[root@localhost projects]# ls -al /root/repos
total 4
drwxr-xr-x. 4 root root 38 Nov 20 04:35 .
dr-xr-x---. 13 root root 4096 Nov 20 04:34 ..
drwxr-xr-x. 7 root root 119 Nov 20 04:35 libA.git
drwxr-xr-x. 7 root root 119 Nov 20 04:35 libB.git

初始化公共库

获取

1
2
3
4
5
6
7
8
9
10
[root@localhost projects]# git clone /root/repos/libA.git
Cloning into 'libA'...
warning: You appear to have cloned an empty repository.
done.
[root@localhost projects]# git clone /root/repos/libB.git
Cloning into 'libB'...
warning: You appear to have cloned an empty repository.
done.
[root@localhost projects]#

初始化libA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost projects]# cd libA/
[root@localhost libA]# echo "libA" > readme
[root@localhost libA]# git add readme
[root@localhost libA]# git commit -m "init" readme
[master (root-commit) ee8edb6] init
1 file changed, 1 insertion(+)
create mode 100644 readme
[root@localhost libA]# git push origin master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 207 bytes | 69.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /root/repos/libA.git
* [new branch] master -> master

初始化libB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost libA]# cd ../libB/
[root@localhost libB]# echo "libB">readme
[root@localhost libB]# git add readme
[root@localhost libB]# git commit -m"init" readme
[master (root-commit) f70d346] init
1 file changed, 1 insertion(+)
create mode 100644 readme
[root@localhost libB]# git push origin master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 200 bytes | 100.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /root/repos/libB.git
* [new branch] master -> master

创建项目使用两个模块

创建两个项目仓库

1
2
3
4
5
[root@localhost projects]# git --git-dir=/root/repos/project1.git init --bare
Initialized empty Git repository in /root/repos/project1.git/
[root@localhost projects]# git --git-dir=/root/repos/project2.git init --bare
Initialized empty Git repository in /root/repos/project2.git/
[root@localhost projects]#

获取project1到本地

1
2
3
4
5
6
[root@localhost libB]# cd ../
[root@localhost projects]# git clone /root/repos/project1.git
Cloning into 'project1'...
warning: You appear to have cloned an empty repository.
done.
[root@localhost projects]# cd project1/

使用 git submodule add 添加子模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@localhost project1]# git submodule add /root/repos/libA.git libs/libA
Cloning into '/root/projects/project1/libs/libA'...
done.
[root@localhost project1]# git submodule add /root/repos/libB.git libs/libB
Cloning into '/root/projects/project1/libs/libB'...
done.
[root@localhost project1]# git status
On branch master

No commits yet

Changes to be committed:
(use "git rm --cached <file>..." to unstage)

new file: .gitmodules
new file: libs/libA
new file: libs/libB

好了,到目前为止我们已经使用git submodule add命令为project1成功添加了两个公共类库(libA、libB),查看了当前的状态发现添加了一个新文件(.gitmodules)和两个文件夹(libs/libA、libs/libB);那么新增的.gitmodules文件是做什么用的呢?我们查看一下文件内容便知晓了:

模块的记录文件 在.gitmodules存取

1
2
3
4
5
6
7
[root@localhost project1]# cat .gitmodules
[submodule "libs/libA"]
path = libs/libA
url = /root/repos/libA.git
[submodule "libs/libB"]
path = libs/libB
url = /root/repos/libB.git

顺便看一下 .git/config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@localhost project1]# cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = /root/repos/project1.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[submodule "libs/libA"]
url = /root/repos/libA.git
active = true
[submodule "libs/libB"]
url = /root/repos/libB.git
active = true

可以看到 libs目录下已经存在两个子模块的内容了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@localhost project1]# tree -L 2 libs/
libs/
├── libA
│   └── readme
└── libB
└── readme

2 directories, 2 files
[root@localhost project1]# tree -L 2 .git/modules/libs/
.git/modules/libs/
├── libA
│   ├── branches
│   ├── config
│   ├── description
│   ├── HEAD
│   ├── hooks
│   ├── index
│   ├── info
│   ├── logs
│   ├── objects
│   ├── packed-refs
│   └── refs
└── libB
├── branches
├── config
├── description
├── HEAD
├── hooks
├── index
├── info
├── logs
├── objects
├── packed-refs
└── refs

14 directories, 10 files

还可以查看当前submodule状态

1
2
3
[root@localhost project1]# git submodule status
ee8edb6e68cf05a4cceeccb3e0344dc149c5a1ea libs/libA (heads/master)
f70d346153fe4020db82d3a8b990c1898b6c86fb libs/libB (heads/master)

将修改提交到仓库中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost project1]# git commit -am"add submodules to project"
[master (root-commit) fabea54] add submodules to project
3 files changed, 8 insertions(+)
create mode 100644 .gitmodules
create mode 160000 libs/libA
create mode 160000 libs/libB
[root@localhost project1]# git push
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 360 bytes | 180.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To /root/repos/project1.git
* [new branch] master -> master

其他人使用带有Submodule的仓库

开发人员B …

使用git clone获取远程仓库

1
2
3
[root@localhost projects]# git clone /root/repos/project1.git project1-b
Cloning into 'project1-b'...
done.

使用git submodule status查看当前子模块状态

1
2
3
[root@localhost project1-b]# git submodule status
-ee8edb6e68cf05a4cceeccb3e0344dc149c5a1ea libs/libA
-f70d346153fe4020db82d3a8b990c1898b6c86fb libs/libB

其中 - 代表资源未初始化到模块中
后面的代码为 对应模块git库的commitID

未初始化检出 即未调用过 git submodule init

此时.git/config 中是不存在 submodule 的信息的

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost project1-b]# cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = /root/repos/project1.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master

初始化 git submodule init

执行 git submodule init 实际上修改了 .git/config 文件,对子模块进行了注册。

1
2
3
[root@localhost project1-b]# git submodule init
Submodule 'libs/libA' (/root/repos/libA.git) registered for path 'libs/libA'
Submodule 'libs/libB' (/root/repos/libB.git) registered for path 'libs/libB'

查看状态

1
2
3
[root@localhost project1-b]# git submodule status
ee8edb6e68cf05a4cceeccb3e0344dc149c5a1ea libs/libA
f70d346153fe4020db82d3a8b990c1898b6c86fb libs/libB

此时前面以及没有-符号了
查看文件.git/config
(以加号开始的行为新增加的行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = /root/repos/project1.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
+[submodule "libs/libA"]
+ active = true
+ url = /root/repos/libA.git
+[submodule "libs/libB"]
+ active = true
+ url = /root/repos/libB.git

查看 工作空间中的文件信息

1
2
3
4
5
6
[root@localhost project1-b]# tree -L 2 libs/
libs/
├── libA
└── libB

2 directories, 0 files

发现并不存在文件,这是需要 检出 子模块的代码信息

使用 git submodule update 检出代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@localhost project1-b]# git submodule update
Cloning into '/root/projects/project1-b/libs/libA'...
done.
Cloning into '/root/projects/project1-b/libs/libB'...
done.
Submodule path 'libs/libA': checked out 'ee8edb6e68cf05a4cceeccb3e0344dc149c5a1ea'
Submodule path 'libs/libB': checked out 'f70d346153fe4020db82d3a8b990c1898b6c86fb'
[root@localhost project1-b]# tree -L 2 libs/
libs/
├── libA
│   └── readme
└── libB
└── readme

2 directories, 2 files

修改子模块

查看当前 仓库状态

1
2
3
4
5
[root@localhost project1-b]# git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

如果我们进入子模块A 查看状态呢

1
2
3
4
5
6
7
[root@localhost project1-b]# cd libs/libA/
[root@localhost libA]# git status
HEAD detached at ee8edb6
nothing to commit, working tree clean
[root@localhost libA]# git branch
* (HEAD detached at ee8edb6)
master

我们发现 当前 git的头指针不是指向master的,而是指向固定的commitID的
如果这个时候我们修改子模块A中的文件,提交就会丢失。

我们看 .gitmodule 文件

1
2
3
4
5
6
7
[root@localhost libA]# cat ../../.gitmodules
[submodule "libs/libA"]
path = libs/libA
url = /root/repos/libA.git
[submodule "libs/libB"]
path = libs/libB
url = /root/repos/libB.git

每一个 submodule 记录了

  • submodule 名称 submodule “libs/libA”
  • submodule 在主项目中的位置 path = libs/libA
  • submodule 使用的仓库地址 url = /root/repos/libB.git
  • submodule 版本默认commitID [default:master]

这个时候如果我们需要修改 子模块的内容
需要先将 当前Head 指向 需要修改的分支,修改完内容后提交到分支上
比如当前的分支master

切换分支 修改内容,并提交

1
2
3
4
5
6
7
[root@localhost libA]# git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
[root@localhost libA]# echo "add by developer B" >> readme
[root@localhost libA]# git commit -am "update libA by developer B"
[master 0d13d75] update libA by developer B
1 file changed, 1 insertion(+)

将工作空间切换到主项目下,查看当前主项目状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@localhost libA]# cd ../../
[root@localhost project1-b]# 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 checkout -- <file>..." to discard changes in working directory)

modified: libs/libA (new commits)

no changes added to commit (use "git add" and/or "git commit -a")
[root@localhost project1-b]# git diff
diff --git a/libs/libA b/libs/libA
index ee8edb6..0d13d75 160000
--- a/libs/libA
+++ b/libs/libA
@@ -1 +1 @@
-Subproject commit ee8edb6e68cf05a4cceeccb3e0344dc149c5a1ea
+Subproject commit 0d13d7551eea10cb8c1407055fa674109753f360

我们可以发现commitID 以及被修改了

注意:如果现在执行了 git submodule update 操作那么libs/libA的commit id又会还原到ee8edb6e68cf05a4cceeccb3e0344dc149c5a1ea

这样的话刚刚的修改是不是就丢了呢?不会,因为修改已经提交到了master分支,只要再git checkout master就可以了。

我们现在先将 libs/libA 的修改提交到仓库

1
2
3
4
5
6
7
8
[root@localhost project1-b]# cd libs/libA/
[root@localhost libA]# git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 265 bytes | 88.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /root/repos/libA.git
ee8edb6..0d13d75 master -> master

现在仅仅只完成了一步,下一步要提交主项目引用submodule的commit id:

1

参考

Git Submodule使用完整教程
git submodule 的使用
git submodule 使用小结

欢迎关注我的其它发布渠道