管理带有子模块的git库(以 DragonBonesCPP 为例)

2015-09-06 更新: 加入 删除子模块 的内容。

DragonBonesCPP 是一个包含子模块的库,在 clone/commit/push 的时候需要一些额外的操作。

本文将描述这些操作。

子模块(submodule)

限(wei)于(le)篇(tou)幅(lan),请自行学习下面的内容:

clone DragonBonesCPP

蛋碎方法一

1git clone --recursive git@github.com:DragonBones/DragonBonesCPP.git

使用这个方法,将会自动 clone 该项目下的所有子模块。这其实是最简单的方法了。

为什么说它令人蛋碎呢?因为 DragoneBones 中包含的 engines/cocos2d-x 子模块也包含子模块。

这样就出现了嵌套子模块,一个 cocos2d-x 的所有历史加起来就超过1G了,再加上子模块的所有历史,这就……。

蛋疼方法二

为了避免蛋碎的问题,我们换一个方法,可是蛋疼是免不了了。

1# git clone git@github.com:DragonBones/DragonBonesCPP.git
2Cloning into 'DragonBonesCPP'...
3remote: Counting objects: 103, done.
4remote: Compressing objects: 100% (87/87), done.
5remote: Total 103 (delta 18), reused 96 (delta 13)
6Receiving objects: 100% (103/103), 88.96 KiB | 47 KiB/s, done.
7Resolving deltas: 100% (18/18), done.

此时已经将所有 DragonBonesCPP 的核心代码 clone 下来了,由于项目不大,这个过程会非常快。

但是子模块不会自动 clone,接下来需要设置子模块。

1# cd DragonBonesCPP
2# DragonBonesCPP git:(master) > git submodule status
3-a7709c35badbd930f98adaf1c172a3251f8c6543 engines/cocos2d-x

refid 前面的减号 - 代表子模块还没有加载,我们需要初始化并检出它:

1# DragonBonesCPP git:(master) > git submodule init
2Submodule 'cocos2d-x' (git@github.com:DragonBones/cocos2d-x.git) registered for path 'engines/cocos2d-x'
3
4# DragonBonesCPP git:(master) > git submodule update
5Cloning into 'engines/cocos2d-x'...
6remote: Reusing existing pack: 200502, done.
7Receiving objects: 100% (200502/200502), 763.78 MiB | 98 KiB/s, done.
8Resolving deltas: 100% (133006/133006), done.
9Submodule path 'engines/cocos2d-x': checked out 'a7709c35badbd930f98adaf1c172a32      51f8c6543'

等待是漫长的,看我的网速就知道这到底花了多长时间。

和蛋碎方法不同的是,这种方法不会 clone cocos2d-x 中的子模块(嵌套子模块)。若有需要,可以进入 cocos2d-x 中再次执行蛋疼方法二。

分支

DragonBonesCPP 的分支介绍

DragonBonesCPP 项目目前有2个分支,masterdev ,区别如下:

  • master 分支中包含的内容是稳定的或者基于稳定版本的引擎。
  • dev 分支中包含的内容是目前正在开发的功能或者基于开发版本的引擎。

以对 cocos2d-x 引擎的支持为例:

  • master 分支包含对 cocos2d-x 2.x 版本的支持;
  • dev 分支包含对 cocos2d-x 3.x 版本的支持。

切换分支

在开发的过程中,我们经常需要切换分支。例如对 cocos2d-x 2.x 的相关功能进行修改后(位于master分支),希望切换到 cocos2d-x 3.x 的另一些功能进行修改。

这种情况下,我们一般使用 git checkout 来切换分支。于是,奇怪的事情发生了:

 1# DragonBonesCPP git:(master) > git checkout dev
 2M       engines/cocos2d-x
 3Switched to branch 'dev'
 4# DragonBonesCPP git:(dev) > git status
 5 On branch dev
 6 Changes not staged for commit:
 7   (use "git add <file>..." to update what will be committed)
 8   (use "git checkout -- <file>..." to discard changes in working directory)
 9
10       modified:   engines/cocos2d-x (new commits)
11
12no changes added to commit (use "git add" and/or "git commit -a")

显然我们切换分支后还没有对dev进行修改,但dev分支中已经出现了一个 modified!

diff 看看有什么不同:

1DragonBonesCPP git:(dev) > git diff
2diff --git a/engines/cocos2d-x b/engines/cocos2d-x
3index 123b410..a7709c3 160000
4thumbnail = "/uploads/2014/04/gitsubmodule1.png"
5+++ b/engines/cocos2d-x
6@@ -1 +1 @@
7-Subproject commit 123b410b99510e4a33ceadc08abc09d292f5df49
8+Subproject commit a7709c35badbd930f98adaf1c172a3251f8c6543

从比较的结果看来,子模块指向的 refid 发生了变化,从 123b410... 变成了 a7709c3... 。

wwwwait! 这两个id怎么看着那么眼熟捏?原来,他们分别是 master 和 dev 分支中的 cocos2d-x 子模块指向的 refid。见下图:

refid in master

refid in dev

也就是说,虽然从 master 切换到了 dev 分支,但是子模块 cocos2d-x 中 HEAD 指向的 refid 并没有跟着改变到 dev 分支中应有的指向。

为了进一步证明这个推测,我们可以进入子模块 cocos2d-x 中查看所处分支: (注意下面的操作位于子模块 cocos2d-x 中)

1# DragonBonesCPP git:(dev) > cd engines/cocos2d-x
2# cocos2d-x git:(master) > git status
3# On branch master
4nothing to commit, working directory clean

注意,在这里可以看到,我们并不是位于我们期望的 cocos2d-x 的 develop 分支(这是 cocos2d-x 3.x的所在分支),而是依然位于 master(cocos2d-x 2.x的所在分支) 分支。

知道了原因,切换起来就简单了。我们只需要进入 cocos2d-x 子模块,将现有的指向改为 cocos2d-x 的 develop 分支即可。

 1# cocos2d-x git:(master) > git checkout develop
 2Switched to branch 'develop'
 3
 4# cocos2d-x git:(develop) cd ../../
 5# DragonBonesCPP git:(dev) git status
 6# On branch dev
 7# Your branch is ahead of 'origin/dev' by 3 commits.
 8#   (use "git push" to publish your local commits)
 9#
10nothing to commit, working directory clean

看吧,没有提交,世界清静了。

合并分支

由于我们使用了分支来区分不同的版本,因此在合并的时候,必须做一些处理确保正常。

例如,对于 renderer 中的内容,一定是基于分支独立的。它们不应该在合并的时候被处理。

  • master 分支中的 renderer ,与 dev 分支中的 renderer 就可能完全不同,我们不希望在将 master 分支合并到 dev 分支(或者相反)的时候,导致完全不同的两个渲染器互相冲突;
  • 而 library 中的内容,则恰恰相反,它们需要在分支之间合并。

gitattributes 文件可以解决这个问题,可参阅这里: Merge-Strategies

为了解决这个矛盾,我已在 .gitattributes 中将 renderer 和 engines 目录排除,在合并的时候,它们将保存各自的版本。

若有更深入的需求,例如 renderer 中的某些渲染器需要合并而有些不要,则后续进一步处理。

提交和推送

在提交 DragonBonesCPP 之前,若已经修改过子模块,请务必先提交子模块的修改并推送子模块,然后再提交并推送 DragonBonesCPP 。

删除子模块

为了方便查询,这里摘抄 Git Submodule使用完整教程 一文中的移除 Submodule 的方法如下:

1. 删除 git cache 和物理文件夹

以删除 engines/cocos2d-x 为例:

1git rm -r --cached engines/cocos2d-x/
2rm -rf engines/cocos2d-x/

2. 修改配置文件

  1. 删除 .gitmodules 文件中对应的配置内容;
  2. 删除 .git/config 中的对应 submodule remote 的内容。

3. 提交更改

(全文完)