Git Tools - Submodules

Git Tools - Submodules

1. 应用场景

需求

当你在一个项目 project1 上工作时,你需要在其中使用另外一个项目 lib1 。也许它是一个第三方开发的库或者是你独立开发和并在多个父项目 (priject2,prject2 ... ...)中使用的。这个场景下一个常见的问题产生了:你想将两个项目 priject1、lib1 单独处理但是又需要在其中一个 project1 中使用另外一个 lib1。

解决方案

不使用 git submodules 的方法:包含库

  • 包含库:把 lib1 代码包含到 project1 (project2 、project3 ... ...)项目中
  • 问题 : 定制和部署 project 更加困难了。
  • 产生问题的原因 :因为你必须确保每个客户都拥有 lib1 。并且当 lib1 被修改时(fix bug),任何在 project1 (project2 、project3 ... ...) 中对 lib1 的修改都很难归并。
《Git权威指南》
项目的版本库在某些情况下需要引用其他版本库中的文件,
例如公司积累了一套常用的函数库,被多个项目调用,显然这个函数库的代码不能直接放到某个项目的代码中,而是要独立为一个代码库,那么其他项目要调用公共函数库该如何处理呢?
分别把公共函数库的文件拷贝到各自的项目中会造成冗余,丢弃了公共函数库的维护历史,这显然不是好的方法。

Git Submodules

Git 通过子模块处理这个问题。子模块允许你将一个 Git 仓库当作另外一个Git仓库的子目录。这允许你克隆另外一个仓库到你的项目中并且保持你的提交相对独立。学会 git submodule 可以使项目中再也不会出现大量
重复的资源文件、公共类库。更不会出现多个版本,甚至一个客户多个项目风格存在各种差异。

2. 使用 Git Submodules

本例子采用两个项目以及两个公共类库学习对submodule的操作

2.1 创建 Git Submodule 测试项目

2.1.1 准备环境

  • 当前目录
➜  ~ pwd
/Users/yjizhang
➜  ~ mkdir -p submd/repos
  • 创建需要的本地仓库
➜  ~ cd ~/submd/repos
➜  repos git --git-dir=lib1.git init --bare
Initialized empty Git repository in /Users/yjizhang/submd/repos/lib1.git/
➜  repos git --git-dir=lib2.git init --bare
Initialized empty Git repository in /Users/yjizhang/submd/repos/lib2.git/
➜  repos git --git-dir=p1.git init --bare
Initialized empty Git repository in /Users/yjizhang/submd/repos/p1.git/
➜  repos git --git-dir=p2.git init --bare
Initialized empty Git repository in /Users/yjizhang/submd/repos/p2.git/
➜  repos ls
lib1.git lib2.git p1.git   p2.git
  • 初始化工作区
➜  ~ mkdir ~/submd/ws
➜  ~ cd submd/ws

2.1.2 初始化项目

  • 初始化 p1 (project1)
➜  ~ cd ~/submd/ws
➜  ws git clone ../repos/p1.git
Cloning into 'p1'...
warning: You appear to have cloned an empty repository.
done.
➜  ws cd p1
➜  p1 git:(master) echo "p1" >> p1-infos.txt
➜  p1 git:(master) ✗ ls
p1-infos.txt
➜  p1 git:(master) ✗ gst
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    p1-infos.txt

nothing added to commit but untracked files present (use "git add" to track)
➜  p1 git:(master) ✗ git add p1-infos.txt
➜  p1 git:(master) ✗ gst
On branch master

Initial commit

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

    new file:   p1-infos.txt

➜  p1 git:(master) ✗ gc -m "init p1"
[master (root-commit) 960cb75] init p1
 1 file changed, 1 insertion(+)
 create mode 100644 p1-infos.txt
➜  p1 git:(master) git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 217 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/p1.git
 * [new branch]      master -> master
➜  p1 git:(master)
  • 初始化 p2
➜  ~ cd ~/submd/ws
➜  ws git clone ../repos/p2.git
Cloning into 'p2'...
warning: You appear to have cloned an empty repository.
done.
➜  ws cd p2
➜  p2 git:(master) gst
On branch master

Initial commit

nothing to commit (create/copy files and use "git add" to track)
➜  p2 git:(master) echo "p2" >> p2-infos.txt
➜  p2 git:(master) ✗ ls
p2-infos.txt
➜  p2 git:(master) ✗ gst
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    p2-infos.txt

nothing added to commit but untracked files present (use "git add" to track)
➜  p2 git:(master) ✗ git add p2-infos.txt
➜  p2 git:(master) ✗ gst
On branch master

Initial commit

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

    new file:   p2-infos.txt

➜  p2 git:(master) ✗ gc -m "init p2"
[master (root-commit) 449e7ed] init p2
 1 file changed, 1 insertion(+)
 create mode 100644 p2-infos.txt
➜  p2 git:(master) gst
On branch master
Your branch is based on 'origin/master', but the upstream is gone.
  (use "git branch --unset-upstream" to fixup)
nothing to commit, working tree clean
➜  p2 git:(master) git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 217 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/p2.git
 * [new branch]      master -> master
➜  p2 git:(master)

2.1.3 初始化公共类库

  • 初始化公共类库 lib1
➜  ~ cd ~/submd/ws
➜  ws git clone ../repos/lib1.git
Cloning into 'lib1'...
warning: You appear to have cloned an empty repository.
done.
➜  ws cd lib1
➜  lib1 git:(master) echo "I'm lib1" > lib1-features
➜  lib1 git:(master) ✗ git add lib1-features
➜  lib1 git:(master) ✗ gc -m "init lib1"
[master (root-commit) 101b2e6] init lib1
 1 file changed, 1 insertion(+)
 create mode 100644 lib1-features
➜  lib1 git:(master) git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 224 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/lib1.git
 * [new branch]      master -> master
  • 初始化公共类库 lib2
➜  lib1 git:(master) cd ~/submd/ws
➜  ws git clone ../repos/lib2.git
Cloning into 'lib2'...
warning: You appear to have cloned an empty repository.
done.
➜  ws cd lib2
➜  lib2 git:(master) echo "I'm lib2" >> lib2-features
➜  lib2 git:(master) ✗ git add lib2-features
➜  lib2 git:(master) ✗ gc -m "init lib2"
[master (root-commit) fa8fdee] init lib2
 1 file changed, 1 insertion(+)
 create mode 100644 lib2-features
➜  lib2 git:(master) git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 225 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/lib2.git
 * [new branch]      master -> master

2.2 为主项目添加 Submodules

2.2.1 为 p1 添加 lib1 和 lib2

  • 命令git submodule add
➜  cd ~/submd/ws/p1
➜  p1 git:(master) ls
p1-infos.txt
➜  p1 git:(master) git submodule add ~/submd/repos/lib1.git libs/lib1
Cloning into '/Users/yjizhang/submd/ws/p1/libs/lib1'...
done.
➜  p1 git:(master) ✗ git submodule add ~/submd/repos/lib2.git libs/lib2
Cloning into '/Users/yjizhang/submd/ws/p1/libs/lib2'...
done.
➜  p1 git:(master) ✗ gst
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitmodules
    new file:   libs/lib1
    new file:   libs/lib2

➜  p1 git:(master) ✗ tree
.
├── libs
│   ├── lib1
│   │   └── lib1-features
│   └── lib2
│       └── lib2-features
└── p1-infos.txt

3 directories, 3 files
➜  p1 git:(master) ✗ cat libs/lib1/lib1-features
I'm lib1
➜  p1 git:(master) ✗ cat libs/lib2/lib2-features
I'm lib2

此时我们已经成功使用 git submodule add 命令为 p1 添加了两个公共类库 lib1 lib2,那么我们怎样查看子模块是否已经添加成功了呢 ?

2.2.2 查看 .gitmodules 的内容

  • .gitmodules : 该文件记录了每个 submodule 的引用信息以及其在当前项目的位置以及仓库所在地址
➜  p1 git:(master) ✗ cat .gitmodules
[submodule "libs/lib1"]
    path = libs/lib1
    url = /Users/yjizhang/submd/repos/lib1.git
[submodule "libs/lib2"]
    path = libs/lib2
    url = /Users/yjizhang/submd/repos/lib2.git

2.2.3 提交添加子模块的更改

➜  p1 git:(master) ✗ gst
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitmodules
    new file:   libs/lib1
    new file:   libs/lib2
➜  p1 git:(master) ✗ git commit -a -m "add submodule [lib1、lib2] to p1"
[master 65a41b4] add submodule [lib1、lib2] to p1
 3 files changed, 8 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 libs/lib1
 create mode 160000 libs/lib2
➜  p1 git:(master) git push
Counting objects: 4, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 488 bytes | 0 bytes/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/p1.git
   960cb75..65a41b4  master -> master

假设你是第一次引入公共类库 lib1、lib2 到 p1 的开发人员,name项目组的其他人要怎么 Clone 带有 Submodules 的项目呢 ?

2.3 Clone 带有 Submodule 的仓库

模拟开发人员 B Clone 带有 Submodule 的项目 p1

2.3.1 Clone 带有 submodule 的库 p1

➜  ~ cd ~/submd/ws
➜  ws git clone ../repos/p1.git p1-b
Cloning into 'p1-b'...
done.
➜  ws ls
lib1 lib2 p1   p1-b p2

# clone 成功,查看 p1-b  dir tree

➜  ws cd p1-b
➜  p1-b git:(master) tree
.
├── libs
│   ├── lib1
│   └── lib2
└── p1-infos.txt

# 对比 p1-b 和 p1 的 dir tree

➜  p1-b git:(master) cd ../p1
➜  p1 git:(master) tree
.
├── libs
│   ├── lib1
│   │   └── lib1-features
│   └── lib2
│       └── lib2-features
└── p1-infos.txt

3 directories, 3 files

发现 我们 clone 的 p1-b 中 submodule [lib1、lib2] 的内容并没有 clone 下来。我们可以查看一下子模块,那么怎么查看子模块状态呢 ?

2.3.2 查看子模块状态 git submodule

➜  p1-b git:(master) git submodule
-101b2e60e0f8c759d2f60c7fca12f3b1e474de21 libs/lib1
-fa8fdeed9d7c8938c0fe470766c568fb32b1ed14 libs/lib2

➜  p1-b git:(master) cd ../p1
➜  p1 git:(master) git submodule
 101b2e60e0f8c759d2f60c7fca12f3b1e474de21 libs/lib1 (heads/master)
 fa8fdeed9d7c8938c0fe470766c568fb32b1ed14 libs/lib2 (heads/master)

- 表示该子模块还没有检出,怎么检出子模块内容 ?

2.3.3 检出子模块内容 git submodule init & git submodule update

  • git submodule init 初始化本地配置文件 .git/config
  • git submodule update 从指定项目 .git/config 拉取所有数据并检出你上层项目里所列的合适的提交
➜  p1-b git:(master) git submodule
-101b2e60e0f8c759d2f60c7fca12f3b1e474de21 libs/lib1
-fa8fdeed9d7c8938c0fe470766c568fb32b1ed14 libs/lib2
➜  p1-b git:(master) cat .git/config
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
[remote "origin"]
    url = /Users/yjizhang/submd/ws/../repos/p1.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
    remote = origin
    merge = refs/heads/master
➜  p1-b git:(master) git submodule init
Submodule 'libs/lib1' (/Users/yjizhang/submd/repos/lib1.git) registered for path 'libs/lib1'
Submodule 'libs/lib2' (/Users/yjizhang/submd/repos/lib2.git) registered for path 'libs/lib2'
➜  p1-b git:(master) cat .git/config
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
[remote "origin"]
    url = /Users/yjizhang/submd/ws/../repos/p1.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
    remote = origin
    merge = refs/heads/master
[submodule "libs/lib1"]
    url = /Users/yjizhang/submd/repos/lib1.git
[submodule "libs/lib2"]
    url = /Users/yjizhang/submd/repos/lib2.git
➜  p1-b git:(master) git submodule update
Cloning into '/Users/yjizhang/submd/ws/p1-b/libs/lib1'...
done.
Cloning into '/Users/yjizhang/submd/ws/p1-b/libs/lib2'...
done.
Submodule path 'libs/lib1': checked out '101b2e60e0f8c759d2f60c7fca12f3b1e474de21'
Submodule path 'libs/lib2': checked out 'fa8fdeed9d7c8938c0fe470766c568fb32b1ed14'
➜  p1-b git:(master) git submodule
 101b2e60e0f8c759d2f60c7fca12f3b1e474de21 libs/lib1 (heads/master)
 fa8fdeed9d7c8938c0fe470766c568fb32b1ed14 libs/lib2 (heads/master)
➜  p1-b git:(master) tree
.
├── libs
│   ├── lib1
│   │   └── lib1-features
│   └── lib2
│       └── lib2-features
└── p1-infos.txt

3 directories, 3 files
➜  p1-b git:(master) cat libs/lib1/lib1-features libs/lib2/lib2-features
I'm lib1
I'm lib2

2.4 修改该有 Submodule 的库

模拟开发人员 B 修改子模块内容

2.4.1 查看子模块

➜  p1-b git:(master) cd libs/lib1
➜  lib1 git:(101b2e6) gst
HEAD detached at 101b2e6
nothing to commit, working tree clean

发现子模块 lib1 在一个匿名的分支上,为什么 ?

  • 原因: Git 对于 Submodule 有特殊的处理方式。

  • 一个主项目中引入 Submodule 其实 Git 做了三件事

    • 记录引用的仓库 (子模块仓库描述)
    • 记录主项目中 Submodule 的目录位置
    • 记录引用 Submodule 的 commit id
  • 说明
    在 p1 中 push 之后其实是在 p1 中记录了对 submodule 提交的引用 commit id
    然后 p1-b 在 clone p1 的时候获取到了 p1 对 submodule 提交的引用 commit id
    当 p1-b 中执行 git submodule update 的时候, git 根据 modules 获取到了 submodule 的 commit id , 然后获取 submodule 的文件。
    所以 clone 之后子模块不在任何分支上,但是 master 分支和 commit id 和 HEAD 保持一致。

2.4.2 更新子模块

  • 切换到 master 分支
  • 更新子模块内容
  • 提交子模块更新
  • 提交主项目中子模块更新引用
#  切换到 master 分支

➜  p1-b git:(master) cd libs/lib1
➜  lib1 git:(101b2e6) gco master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.

#  更新子模块内容

➜  lib1 git:(master) echo "add by developer b" >> lib1-features

#  提交子模块更新

➜  lib1 git:(master) ✗ ga .
➜  lib1 git:(master) ✗ gc -m "update lib1-feature by dev b"
[master 241e03b] update lib1-feature by dev b
 1 file changed, 1 insertion(+)
➜  lib1 git:(master) git push
Counting objects: 3, done.
Writing objects: 100% (3/3), 286 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/repos/lib1.git
   101b2e6..241e03b  master -> master
   
#  提交主项目中子模块更新引用

➜  lib1 git:(master) cd ../../
➜  p1-b git:(master) ✗ git diff
diff --git a/libs/lib1 b/libs/lib1
index 101b2e6..241e03b 160000
--- a/libs/lib1
+++ b/libs/lib1
@@ -1 +1 @@
-Subproject commit 101b2e60e0f8c759d2f60c7fca12f3b1e474de21
+Subproject commit 241e03bf0bf574063417c05a1d558fa4a6006425
(END)   

➜  p1-b git:(master) ✗ gst
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/lib1 (new commits)

no changes added to commit (use "git add" and/or "git commit -a")
➜  p1-b git:(master) ✗ git add libs/lib1
➜  p1-b git:(master) ✗ gst
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   libs/lib1

➜  p1-b git:(master) ✗ gc -m "update libs/lib1 to lastest commit id"
[master ea785a2] update libs/lib1 to lastest commit id
 1 file changed, 1 insertion(+), 1 deletion(-)
➜  p1-b git:(master) git push
Counting objects: 3, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 389 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/p1.git
   65a41b4..ea785a2  master -> master

现在我们已经在 p1-b 中更新了子模块 lib1 的内容。那么我们怎样同步更新 p1 中 lib1 的内容呢 ?

2.5 更新主项目的 Submodule

  • 拉取主项目最新更新 git pullgit pull -r (推荐.其会将本地提交放在远程提交的最后,减少冲突)
  • 拉取子模块更新的内容 git submodule update (注:在执行前明确保已经执行了 git submodule init 将子模块信息注册到 .git/congfig 中)
➜  p1-b git:(master) cd ../p1
➜  p1 git:(master) gst
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
➜  p1 git:(master) git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /Users/yjizhang/submd/ws/../repos/p1
   65a41b4..ea785a2  master     -> origin/master
Fetching submodule libs/lib1
From /Users/yjizhang/submd/repos/lib1
   101b2e6..241e03b  master     -> origin/master
Updating 65a41b4..ea785a2
Fast-forward
 libs/lib1 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
➜  p1 git:(master) ✗ gst
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/lib1 (new commits)

no changes added to commit (use "git add" and/or "git commit -a")
➜  p1 git:(master) ✗ git submodule update
Submodule path 'libs/lib1': checked out '241e03bf0bf574063417c05a1d558fa4a6006425'
➜  p1 git:(master) cat libs/lib1/lib1-features
I'm lib1
add by developer b

2.6 为 p2 添加子模块 lib1 lib2

➜  ws cd ~/submd/ws/p2
➜  p2 git:(master) git submodule add ../../repos/lib1.git  libs/lib1
Cloning into '/Users/yjizhang/submd/ws/p2/libs/lib1'...
done.
➜  p2 git:(master) ✗ git submodule add ../../repos/lib2.git  libs/lib2
Cloning into '/Users/yjizhang/submd/ws/p2/libs/lib2'...
done.
➜  p2 git:(master) ✗ git add .
➜  p2 git:(master) ✗ gc -m "add submodules [lib1 lib2] to p2"
[master 742934c] add submodules [lib1 lib2] to p2
 3 files changed, 8 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 libs/lib1
 create mode 160000 libs/lib2
➜  p2 git:(master) git push origin master
Counting objects: 4, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 467 bytes | 0 bytes/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/p2.git
   449e7ed..742934c  master -> master
➜  p2 git:(master)

2.7 修改 lib1 lib2 并同步到 p1 p2

  • 使用场景: 假设开发人员 C 同时负责 p1 和 p2 两个项目,当他在开发 p2 的时候发现 lib1 / lib2 有 bug, 我们应该怎么做 ?
  • 示例需求如下:
    • 在 lib1 中添加一个文件 README
    • 在 lib2 中 lib2-features 中添加文字 learn git submodule

2.7.1 在 p2 的 lib1 中添加 README

➜  p2 git:(master) cd libs/lib1
➜  lib1 git:(master) touch README
➜  lib1 git:(master) ✗ ga .
➜  lib1 git:(master) ✗ gc -m "add readme to lib1"
[master 2c0e667] add readme to lib1
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 README
➜  lib1 git:(master) git push
Counting objects: 3, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 281 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/lib1.git
   241e03b..2c0e667  master -> master

2.7.2 在 p2 的 lib2 中添加 learn git submodule

➜  lib1 git:(master) cd ../lib2
➜  lib2 git:(master) echo "learn git submodule" >> lib2-features
➜  lib2 git:(master) ✗ ga .
➜  lib2 git:(master) ✗ gc -m "add text to lib2 feature"
[master cc6dc70] add text to lib2 feature
 1 file changed, 1 insertion(+)
➜  lib2 git:(master) git push
Counting objects: 3, done.
Writing objects: 100% (3/3), 284 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/lib2.git
   fa8fdee..cc6dc70  master -> master

2.7.3 更新 p2 中引用的 commit id

➜  p2 git:(master) ✗ gst
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/lib1 (new commits)
    modified:   libs/lib2 (new commits)

no changes added to commit (use "git add" and/or "git commit -a")
➜  p2 git:(master) ✗ git add libs/lib1
➜  p2 git:(master) ✗ gc -m "update lib1 to lastest commit id"
[master 6be8d5f] update lib1 to lastest commit id
 1 file changed, 1 insertion(+), 1 deletion(-)
➜  p2 git:(master) ✗ git add libs/lib2
➜  p2 git:(master) ✗ gc -m "update lib2 to lastest commit id"
[master deebabf] update lib2 to lastest commit id
 1 file changed, 1 insertion(+), 1 deletion(-)
 
 ➜  p2 git:(master) git push
Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 730 bytes | 0 bytes/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/p2.git
   742934c..deebabf  master -> master

2.7.4 同步 p2 中 lib1 lib2 的修改到 p1

➜  p1 git:(master) ✗ git pull
Already up-to-date.
➜  p1 git:(master) git submodule
 241e03bf0bf574063417c05a1d558fa4a6006425 libs/lib1 (remotes/origin/HEAD)
 fa8fdeed9d7c8938c0fe470766c568fb32b1ed14 libs/lib2 (heads/master)

# 对比 p2

➜  p2 git:(master) git submodule
 2c0e66770cc2b2f697cf0e86f3b51d737a5e6e93 libs/lib1 (heads/master)
 cc6dc70a8a46965cd1b7860a859f9e4f2a0c34f7 libs/lib2 (heads/master) 

# 两个项目有两个区别:
 
* commit id各不相同
* libs/lib1 所处的分支不同

# 可以发现 p1 的 lib1 不在 master 分支上,所以我们要把 p1 lib1 切换到 master 分支上,拉取代码

➜  p1 git:(master) ✗ cd libs/lib1
➜  lib1 git:(241e03b) gco master
Previous HEAD position was 241e03b... update lib1-feature by dev b
Switched to branch 'master'
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
➜  lib1 git:(master) git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /Users/yjizhang/submd/repos/lib1
   241e03b..2c0e667  master     -> origin/master
Updating 101b2e6..2c0e667
Fast-forward
 README        | 0
 lib1-features | 1 +
 2 files changed, 1 insertion(+)
 create mode 100644 README
 
# 拉取 lib2 的更新
 
 ➜  lib1 git:(master) cd ../lib2
➜  lib2 git:(master) git pull -r
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /Users/yjizhang/submd/repos/lib2
   fa8fdee..cc6dc70  master     -> origin/master
First, rewinding head to replay your work on top of it...
Fast-forwarded master to cc6dc70a8a46965cd1b7860a859f9e4f2a0c34f7.

# 查看 p1 当前子模块状态

➜  lib2 git:(master) cd ../../
➜  p1 git:(master) ✗ git submodule
+2c0e66770cc2b2f697cf0e86f3b51d737a5e6e93 libs/lib1 (heads/master)
+cc6dc70a8a46965cd1b7860a859f9e4f2a0c34f7 libs/lib2 (heads/master)

# 对比 p2

➜  p2 git:(master) git submodule
 2c0e66770cc2b2f697cf0e86f3b51d737a5e6e93 libs/lib1 (heads/master)
 cc6dc70a8a46965cd1b7860a859f9e4f2a0c34f7 libs/lib2 (heads/master)  

2.7.5 更新 p1 的 submodule 的引用

在上一步中我们更新了 p1 的 lib1 和 lib2 的最新版本,现在要把最新的commit id保存到 p1 中以保持最新的引用。

➜  p1 git:(master) ✗ gst
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/lib1 (new commits)
    modified:   libs/lib2 (new commits)

no changes added to commit (use "git add" and/or "git commit -a")
➜  p1 git:(master) ✗ ga .
➜  p1 git:(master) ✗ gc -m "update lib1 lib2 commit id to new version"
[master 141bf28] update lib1 lib2 commit id to new version
 2 files changed, 2 insertions(+), 2 deletions(-)
➜  p1 git:(master) git push
Counting objects: 3, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 393 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/p1.git
   ea785a2..141bf28  master -> master

2.8 更新 p1-b 子模块内容

2.8.1 更新 p1-b 引用子模块的 commit id

➜  p1-b git:(master) gst
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
➜  p1-b git:(master) git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /Users/yjizhang/submd/ws/../repos/p1
   ea785a2..141bf28  master     -> origin/master
Fetching submodule libs/lib1
From /Users/yjizhang/submd/repos/lib1
   241e03b..2c0e667  master     -> origin/master
Fetching submodule libs/lib2
From /Users/yjizhang/submd/repos/lib2
   fa8fdee..cc6dc70  master     -> origin/master
Updating ea785a2..141bf28
Fast-forward
 libs/lib1 | 2 +-
 libs/lib2 | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
➜  p1-b git:(master) ✗ gst
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/lib1 (new commits)
    modified:   libs/lib2 (new commits)

no changes added to commit (use "git add" and/or "git commit -a")

Git提示lib1和lib2有更新内容,这个判断的依据来源于submodule commit id的引用。

那么现在怎么更新 p1-b 子模块的内容呢?

2.8.2 更新 p1-b 子模块的内容

现在我们只有两个子模块,我们固然可以像 p1 中那样进入子模块的目录然后 git checkout master ,接着 git pull ,然后更新主库引用的 commit id ; 但是,当我们的项目中有很多个子模块呢,我们一个一个地进入子模块的目录中去更新子模块内容是不现实的。解决这个问题的方式两种:

  • git submodule foreach git pull
  • .sh 脚本
  • 使用 git submodule foreach git pull 更新子模块内容

➜  p1-b git:(master) ✗ git submodule foreach git pull
Entering 'libs/lib1'
Updating 241e03b..2c0e667
Fast-forward
 README | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 README
Entering 'libs/lib2'
You are not currently on a branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.

    git pull <remote> <branch>

Stopping at 'libs/lib2'; script returned non-zero status.
➜  p1-b git:(master) ✗ ls
libs         p1-infos.txt
➜  p1-b git:(master) ✗ tree
.
├── libs
│   ├── lib1
│   │   ├── README
│   │   └── lib1-features
│   └── lib2
│       └── lib2-features
└── p1-infos.txt

3 directories, 4 files

  • 使用.sh 脚本更新子模块内容
➜  p1-b git:(master) ✗ mkdir bin
➜  p1-b git:(master) ✗ vim bin/update-submodule.sh

# 将下面的脚本复制到 update-submodule.sh 中

#!/bin/bash
grep path .gitmodules | awk '{ print $3 }' > /tmp/study-git-submodule-dirs
 
# read
while read LINE
do
    echo $LINE
    (cd ./$LINE && git checkout master && git pull)
done < /tmp/study-git-submodule-dirs

# 脚本执行过程 
* 首先把子模块的路径写入到文件**/tmp/study-git-submodule-dirs**中;
* 然后读取文件中的子模块路径,依次切换到master分支(修改都是在master分支上进行的),最后更新最近改动。

# 给 .sh 脚本添加可执行权限

➜  p1-b git:(master) ✗ cd bin
➜  bin git:(master) ✗ ll
total 8
-rw-r--r--  1 yjizhang  staff   219B Jan  7 20:11 update-submodule.sh
➜  bin git:(master) ✗ chmod +x update-submodule.sh
➜  bin git:(master) ✗ ll
total 8
-rwxr-xr-x  1 yjizhang  staff   219B Jan  7 20:11 update-submodule.sh
➜  bin git:(master) ✗ cd ../
➜  p1-b git:(master) ✗ ./bin/update-submodule.sh
libs/lib1
Already on 'master'
Your branch is up-to-date with 'origin/master'.
Already up-to-date.
libs/lib2
Switched to branch 'master'
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
Updating fa8fdee..cc6dc70
Fast-forward
 lib2-features | 1 +
 1 file changed, 1 insertion(+)
 ➜  p1-b git:(master) ✗ git submodule
 2c0e66770cc2b2f697cf0e86f3b51d737a5e6e93 libs/lib1 (heads/master)
 cc6dc70a8a46965cd1b7860a859f9e4f2a0c34f7 libs/lib2 (heads/master)
 
# 更新后的 p1-b 子模块 commit id 与 p2 的保持一致
  
# 添加 .sh 文件到 p1-b 库中
➜  p1-b git:(master) ✗ gst
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    bin/

nothing added to commit but untracked files present (use "git add" to track)
➜  p1-b git:(master) ✗ git add bin/update-submodule.sh
➜  p1-b git:(master) ✗ gst
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   bin/update-submodule.sh

➜  p1-b git:(master) ✗ gc -m "add update submodules shell to p1"
[master beca8c0] add update submodules shell to p1
 1 file changed, 9 insertions(+)
 create mode 100755 bin/update-submodule.sh
➜  p1-b git:(master) git push
Counting objects: 4, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 560 bytes | 0 bytes/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/p1.git
   141bf28..beca8c0  master -> master 

2.9 一次克隆包子模块的库

git submodule foreach git pull 一样, git 提供了一次 clone 包含所有子模块内容的命令 git clone --recursive xx.git

➜  ws git clone --recursive ../repos/p1.git p1-submodules
Cloning into 'p1-submodules'...
done.
Submodule 'libs/lib1' (/Users/yjizhang/submd/repos/lib1.git) registered for path 'libs/lib1'
Submodule 'libs/lib2' (/Users/yjizhang/submd/repos/lib2.git) registered for path 'libs/lib2'
Cloning into '/Users/yjizhang/submd/ws/p1-submodules/libs/lib1'...
done.
Cloning into '/Users/yjizhang/submd/ws/p1-submodules/libs/lib2'...
done.
Submodule path 'libs/lib1': checked out '2c0e66770cc2b2f697cf0e86f3b51d737a5e6e93'
Submodule path 'libs/lib2': checked out 'cc6dc70a8a46965cd1b7860a859f9e4f2a0c34f7'
➜  ws cd p1-submodules
➜  p1-submodules git:(master) tree
.
├── bin
│   └── update-submodule.sh
├── libs
│   ├── lib1
│   │   ├── README
│   │   └── lib1-features
│   └── lib2
│       └── lib2-features
└── p1-infos.txt

4 directories, 5 files

3.移除 Git Submodules

3.1 Clone 测试库

从 p1 一个库

➜  ws git clone --recursive ../repos/p1.git p1-remove-submodules
Cloning into 'p1-remove-submodules'...
done.
Submodule 'libs/lib1' (/Users/yjizhang/submd/repos/lib1.git) registered for path 'libs/lib1'
Submodule 'libs/lib2' (/Users/yjizhang/submd/repos/lib2.git) registered for path 'libs/lib2'
Cloning into '/Users/yjizhang/submd/ws/p1-remove-submodules/libs/lib1'...
done.
Cloning into '/Users/yjizhang/submd/ws/p1-remove-submodules/libs/lib2'...
done.
Submodule path 'libs/lib1': checked out '2c0e66770cc2b2f697cf0e86f3b51d737a5e6e93'
Submodule path 'libs/lib2': checked out 'cc6dc70a8a46965cd1b7860a859f9e4f2a0c34f7'
➜  ws cd p1-remove-submodules

3.2 移除子模块

  • 删除git cache和物理文件夹
  • 删除.gitmodules的内容(或者整个文件:如果要移除所有子模块)
  • 删除.git/config的submodule配置

3.2.1 删除git cache和物理文件夹

➜  p1-remove-submodules git:(master) git rm -r --cached libs/
rm 'libs/lib1'
rm 'libs/lib2'
➜  p1-remove-submodules git:(master) ✗ rm -rf libs
➜  p1-remove-submodules git:(master) ✗ ls
bin          p1-infos.txt

3.2.2 删除.gitmodules的内容(或者整个文件:如果要移除所有子模块)

➜  p1-remove-submodules git:(master) ✗ rm -rf .gitmodules

# 将 p1-remove-submodules 所有的子模块都删除了
# 如果要移除其中一个子模块,那么打开 .gitmodules 删除该文件中需要移除的子模块配置即可

3.2.3 删除.git/config的submodule配置

➜  p1-remove-submodules git:(master) ✗ cat .git/config
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
[remote "origin"]
    url = /Users/yjizhang/submd/ws/../repos/p1.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
    remote = origin
    merge = refs/heads/master
[submodule "libs/lib1"]
    url = /Users/yjizhang/submd/repos/lib1.git
[submodule "libs/lib2"]
    url = /Users/yjizhang/submd/repos/lib2.git
    
# 删除 lib1 lib2 的配置

➜  p1-remove-submodules git:(master) ✗ cat .git/config
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
[remote "origin"]
    url = /Users/yjizhang/submd/ws/../repos/p1.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
    remote = origin
    merge = refs/heads/master
    
➜  p1-remove-submodules git:(master) ✗ gst
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    deleted:    libs/lib1
    deleted:    libs/lib2

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    deleted:    .gitmodules

➜  p1-remove-submodules git:(master) ✗ git add .
➜  p1-remove-submodules git:(master) ✗ gc -m "delete submodule lib1 and lib2"
[master 28e23f9] delete submodule lib1 and lib2
 3 files changed, 8 deletions(-)
 delete mode 100644 .gitmodules
 delete mode 160000 libs/lib1
 delete mode 160000 libs/lib2
➜  p1-remove-submodules git:(master) git push
Counting objects: 2, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 279 bytes | 0 bytes/s, done.
Total 2 (delta 0), reused 0 (delta 0)
To /Users/yjizhang/submd/ws/../repos/p1.git
   beca8c0..28e23f9  master -> master

# 移除成功  

参考

http://www.kafeitu.me/git/2012/03/27/git-submodule.html

推荐阅读更多精彩内容

  • Git Tools - Submodules 1. 应用场景 需求 当你在一个项目 project1 上工作时,你...
    z嘉嘉嘉阅读 470评论 0 0
  • 开发中我们经常会遇到这样的情况:项目越来越大,一些通用的模块我们希望将他抽离出来作为单独的项目,以便其他项目也可以...
    feil0n9wan9阅读 8,750评论 0 15
  • 本文作者陈云峰,转载请注明。 这篇文章记录个人常用的一些命令,和记不住的一些命令,转载了并不断更新。 Git官网 ...
    陳云峰阅读 1,517评论 0 23
  • 如若不曾相遇 我们彼此守护着自己的牢房 不哭不弃 我们眺望着远景 不悲不伤 那怕秋叶落尽 哪怕容颜已老 如若不曾相...
    佑译站阅读 58评论 0 0
  • 玛丽案例发生在1986年,罗杰斯去世之前的几个月。 玛丽案例是在一个“表现治疗”的培训班上所做的示范面谈。表现治疗...
    暖阳_1332阅读 144评论 0 0