Skip to content

手把手教你之 Git Submodule #30

Open
@xhlwill

Description

@xhlwill

在一个项目需要引入另一个项目时通常有两种方法:submodule or subtree,关于它们的特点和如何选择它们在 #21 中有说明就不赘述了,我们来看看如何使用submodule。

1. 初始化父项目my

开发人员A克隆父项目my到目录my-A

# git clone git@github.com:xhlwill/git-example-my.git my-A        
正克隆到 'git-example-my'...
warning: 您似乎克隆了一个空仓库。

然后新建一个文件a.txt,commit并推送到远程仓库。

# git commit -m "Initial commit"                                          
[master(根提交) 750bd31] Initial commit
 1 file changed, 1 insertion(+)
 create mode 100644 a.txt

# git push                                                                  
枚举对象: 3, 完成.
对象计数中: 100% (3/3), 完成.
写入对象中: 100% (3/3), 226 bytes | 226.00 KiB/s, 完成.
总共 3 (差异 0),复用 0 (差异 0)
To github.com:xhlwill/git-example-my.git
 * [new branch]      master -> master

2. 初始化公共子模块项目sub

克隆子模块项目sub,并新建一个文件sub.txt,然后推送到远程。

# git clone git@github.com:xhlwill/git-example-sub.git                                        
正克隆到 'git-example-sub'...
warning: 您似乎克隆了一个空仓库。

# git commit -m "Initial"                                                
[master(根提交) f2e5348] Initial
 1 file changed, 1 insertion(+)
 create mode 100644 sub.txt

# git push origin                                                        
枚举对象: 3, 完成.
对象计数中: 100% (3/3), 完成.
写入对象中: 100% (3/3), 232 bytes | 232.00 KiB/s, 完成.
总共 3 (差异 0),复用 0 (差异 0)
To github.com:xhlwill/git-example-sub.git
 * [new branch]      master -> master

3. 为父项目添加子模块

开发人员 A 在父项目 my 中,把公共子项目 sub 添加到它的 libs 目录中

# git submodule add git@github.com:xhlwill/git-example-sub.git libs      
正克隆到 '/Users/lovej/projects/github.com/xhlwill/git-example-my-A/libs'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
接收对象中: 100% (3/3), 完成.

# cd libs
# ls
sub.txt

# git status                                                            
位于分支 master
您的分支与上游分支 'origin/master' 一致。

要提交的变更:
  (使用 "git reset HEAD <文件>..." 以取消暂存)

	新文件:   .gitmodules
	新文件:   libs

添加后用 git status 查看发现多了子项目sub的文件夹和一个新文件.gitmodules

查看 .gitmodules 内容发现它记录了每个子模块的引用信息、在当前项目的位置、仓库地址

# cat .gitmodules           
[submodule "libs"]
	path = libs
	url = git@github.com:xhlwill/git-example-sub.git

然后别忘了把这些更改commit并推送到远程,这样my就是一个带有子模块项目(sub)的项目了

# git commit -m "add submodule"
# git push

4. 其他成员克隆带有子模块的仓库

项目组其他成员如何 clone 该项目呢……
假设开发人员 B 检出父项目到 my-B 目录,会发现在libs目录下并没有sub.txt文件,为什么呢?我们用 git submodule 查看子模块状态,会看到 submodule 状态的hash码和目录,注意前面有一个减号“-”,含义是子模块还没检出

# git clone git@github.com:xhlwill/git-example-my.git git-example-my-B
正克隆到 'git-example-my-B'...
remote: Enumerating objects: 6, done.
remote: Counting objects: 100% (6/6), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 0), reused 6 (delta 0), pack-reused 0

# git submodule               
-f2e5348513f60f212033722d72703ccc2344b8c6 libs

接着我们检出 submodule

# git submodule init          
子模组 'libs'(git@github.com:xhlwill/git-example-sub.git)已对路径 'libs' 注册

# git submodule update        
正克隆到 '/Users/lovej/projects/github.com/xhlwill/git-example-my-B/libs'...
Submodule path 'libs': checked out 'f2e5348513f60f212033722d72703ccc2344b8c6'

这时候我们就能看到sub.txt文件了

5. 修改 Submodule

开发人员 B 要修改 submodule 的内容时(比如发现sub里某个组件有bug),进入到libs目录,修改前先看一下submodule的状态:

# git status                           
头指针分离于 f2e5348
无文件要提交,干净的工作区

为什么是 detached 状态呢?不应该在 master 分支吗?
Git 对于 Submodule 有特殊的处理方式,在一个主项目中引入了 Submodule 其实 Git 做了3件事情:

  • 记录引用的仓库
  • 记录主项目中Submodules的目录位置
  • 记录引用Submodule的commit id

A 在 my-A 中 push 之后其实就是更新了引用的 commit id,然后 B 在 clone 的时候获取到了 submodule 的 commit id,然后当执行 git submodule update 的时候 git 就根据 gitlink 获取submodule 的 commit id,最后获取 submodule 的文件,所以 clone 之后不在任何分支上;但是 master 分支的 commit id 和 HEAD 保持一致。

现在要修改子模块项目 sub 需要先切换到 master 分支,再提交修改:

# git checkout master

# touch sub-2.txt

# git commit -m "add sub-2 by B"

我们加了一个 sub-2.txt 文件,在主项目中修改 submodule 并推送到远程稍微繁琐一点,在 git push 之前我们先看看 my-B 的状态:

# git status               
位于分支 master
您的分支与上游分支 'origin/master' 一致。

尚未暂存以备提交的变更:
  (使用 "git add <文件>..." 更新要提交的内容)
  (使用 "git checkout -- <文件>..." 丢弃工作区的改动)

	修改:     libs (新提交)

修改尚未加入提交(使用 "git add" 和/或 "git commit -a"

libs 的状态表示有新的提交,这个比较特殊,git diff 看看 my-B 的状态会发现 libs 的 commit id 由 f2e5348513f60f212033722d72703ccc2344b8c6 变为 c70bc69f6f4479921fb8fca1552635a69c395ab4

现在回到 libs 目录,前面我们还没把子模块的更改推送到远程,先git push 把子模块 libs 目录的修改提交到仓库。

# git push

这仅仅完成了一步,还要在主项目 my-B 提交推送 (更改了引用子模块的 commit id):

# git add -u
# git commit -m "update libs"
# git push

好了,我们完成了子模块项目的修改并把主项目 libs 的最新 commit id 提交到了仓库。
接下来看看开发人员 A 在项目 my-A 中怎么获取新的 submodule 了。

6. 其他人员更新主项目

开发人员 A 进入 my-A 目录同步仓库:

# git pull                   
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 2 (delta 1), reused 2 (delta 1), pack-reused 0
展开对象中: 100% (2/2), 完成.
来自 github.com:xhlwill/git-example-my
   b64b53b..13fe210  master     -> origin/master
Fetching submodule libs
来自 github.com:xhlwill/git-example-sub
   f2e5348..c70bc69  master     -> origin/master
更新 b64b53b..13fe210
Fast-forward
 libs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

# git status               
位于分支 master
您的分支与上游分支 'origin/master' 一致。

尚未暂存以备提交的变更:
  (使用 "git add <文件>..." 更新要提交的内容)
  (使用 "git checkout -- <文件>..." 丢弃工作区的改动)

	修改:     libs (新提交)

pull 完之后会发现 libs 是 modified 的状态,但是并没有sub-2.txt 文件,这是为什么呢?
我们用 git diff 比较一下不同,从结果分析发现 submodule 的 commit id 更改了。
前面说了要在父项目更新 submodule 的内容首先要提交 submdoule 的内容,然后再更新父项目中引用的 submodule 的 commit id。现在我们看到的不同就是因为刚刚更改了 my-B 的submodule 的 commit id。
现在我们来更新 my-A 中的公共子模块

# git submodule update      
Submodule path 'libs': checked out 'c70bc69f6f4479921fb8fca1552635a69c395ab4'

现在再查看libs里子模块的文件内容,就已经更新到最新了。

7. 同步子模块的修改到主项目

sub项目通常为公用的模块,例如一套样式或一套images,风格改动后要同步到多个系统中,怎么办呢?
git submodule foreach
此命令循环进入每个子模块的目录,然后执行 foreach 后面的命令。
注:第一次用 git submodule foreach git pull 发现只会 fetch 下来但不会 merge……根据 git 的提示是因为当前子模块不在任何分支,用 git submodule foreach git pull origin master 就可以了。

# git submodule foreach git pull
进入 'libs'
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
展开对象中: 100% (3/3), 完成.
来自 github.com:xhlwill/git-example-sub
   c70bc69..b885e96  master     -> origin/master
您当前不在一个分支上。
请指定您要合并哪一个分支。
详见 git-pull(1)。

    git pull <远程> <分支>

fatal: 对 libs 执行 run_command 返回非零值

# git submodule foreach git pull origin master
进入 'libs'
来自 github.com:xhlwill/git-example-sub
 * branch            master     -> FETCH_HEAD
更新 c70bc69..b885e96
Fast-forward
 sub-3.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 sub-3.txt

注意这时用git status查看libs是modified状态,记得把更改commit并推送到远程仓库

8. 新员工加入项目,一次性Clone项目及Submodules

一般人使用的时候都是使用如下命令:

# git clone git@github.com:xhlwill/git-example-my.git
# git submodule init
# git submodule update

上面的命令简直弱暴了,直接一行命令搞定:

# git clone --recursive git@github.com:xhlwill/git-example-my.git

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions