引子
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 | [root@localhost projects]# git --git-dir=/root/repos/libA.git init --bare |
初始化公共库
获取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 | [root@localhost projects]# cd libA/ |
初始化libB
1 | [root@localhost libA]# cd ../libB/ |
创建项目使用两个模块
创建两个项目仓库
1 | [root@localhost projects]# git --git-dir=/root/repos/project1.git init --bare |
获取project1到本地
1 | [root@localhost libB]# cd ../ |
使用 git submodule add 添加子模块
1 | [root@localhost project1]# git submodule add /root/repos/libA.git libs/libA |
好了,到目前为止我们已经使用git submodule add命令为project1成功添加了两个公共类库(libA、libB),查看了当前的状态发现添加了一个新文件(.gitmodules)和两个文件夹(libs/libA、libs/libB);那么新增的.gitmodules文件是做什么用的呢?我们查看一下文件内容便知晓了:
模块的记录文件 在.gitmodules存取
1 | [root@localhost project1]# cat .gitmodules |
顺便看一下 .git/config1
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 | [root@localhost project1]# tree -L 2 libs/ |
还可以查看当前submodule状态1
2
3[root@localhost project1]# git submodule status
ee8edb6e68cf05a4cceeccb3e0344dc149c5a1ea libs/libA (heads/master)
f70d346153fe4020db82d3a8b990c1898b6c86fb libs/libB (heads/master)
将修改提交到仓库中
1 | [root@localhost project1]# git commit -am"add submodules to project" |
其他人使用带有Submodule的仓库
开发人员B …
使用git clone获取远程仓库
1 | [root@localhost projects]# git clone /root/repos/project1.git project1-b |
使用git submodule status查看当前子模块状态
1 | [root@localhost project1-b]# git submodule status |
其中 - 代表资源未初始化到模块中
后面的代码为 对应模块git库的commitID
未初始化检出 即未调用过 git submodule init
此时.git/config 中是不存在 submodule 的信息的
1 | [root@localhost project1-b]# cat .git/config |
初始化 git submodule init
执行 git submodule init 实际上修改了 .git/config 文件,对子模块进行了注册。
1 | [root@localhost project1-b]# git submodule init |
查看状态
1 | [root@localhost project1-b]# git submodule status |
此时前面以及没有-符号了
查看文件.git/config
(以加号开始的行为新增加的行)
1 | [core] |
查看 工作空间中的文件信息
1 | [root@localhost project1-b]# tree -L 2 libs/ |
发现并不存在文件,这是需要 检出 子模块的代码信息
使用 git submodule update 检出代码
1 | [root@localhost project1-b]# git submodule update |
修改子模块
查看当前 仓库状态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 | [root@localhost project1-b]# cd libs/libA/ |
现在仅仅只完成了一步,下一步要提交主项目引用submodule的commit id:
1 |