如何有效的管理项目中的共享代码

人与人之间的交互是复杂的,并且其效果从来都难以预期,但却是工作中最为重要的方面。

在很多大型项目中,需要在多个仓库中共享代码。这些代码可能是通信协议、公用代码库、第三方代码库等。如何管理共享代码是个头疼的问题,我们总不能在每个仓库中拷贝这些共享代码。git提供了submodule和subtree两种方案来方便我们管理。我们来进行对比,选择合适的方案加入到项目中。

git submodule

submodule是一个完全独立的仓库,引用submodule的父仓库和submodule的唯一联系,是保存在父仓库中子仓库某个commit-SHA值。我们通过在父仓库中执行addupdate来增加和更新submodule。

1
2
3
# test repository
$ git submodule add git://github.com/sub/sub.git thirdparty/sub
$ git submodule update --init --recursive

执行add操作后,在test目录下会产生一个.gitsubmodule文件,其内容如下:

1
2
3
4
$ cat .gitsubmodule
[submodule "sub"]
path = sub
url = git://github.com/sub/sub.git

针对于频繁更新的共享代码仓库,submodule的缺陷在于其管理的复杂性。其复杂性体现在三个方面:

1、更新代码的复杂性

如果我们需要在submodule中提交代码,我们需要执行四步:1、切换到开发分支;2、pull最新代码;3、提交代码到submodule仓库;4、从父仓库中提交submodule当前commit-SHA值。切换到开发分支是很重要的,否则我们很可能会丢掉代码。同时,如果我们存在多个父仓库,每个父仓库都要提交submodule的当前commit-SHA值。

如果我们需要获取最新的submodule,我们需要执行两步:1、拉取父仓库代码;2、运行git submodule update命令。

2、团队协作的复杂性

在更新代码时,如果团队成员忘记其中的任何一步,对其他成员将会存在一定的影响。举两个例子:

拉取最新代码时,如果我们忘记运行git submodule update命令,之后更新代码,很有可能在父仓库中提交了老的submodulecommit-SHA值。

如果团队成员对子模块做了一个本地的变更,但没有推送到公共公共服务器,然后他们提交了一个指向那个非公共状态的commit-SHA值,并推送到了父项目所在仓库。这时,其他开发者试图运行git submodule update就会提示找不到所引用的子模块提交。

3、代码合并的复杂性

在合并代码时,即使我们解决了submodule的冲突,还是需要执行git submodule update命令。

如果我们的共享代码仓库是稳定的第三方库,submodule是个极好的选择。然而,针对于频繁更新的共享代码仓库,使用submodule时无法避免其复杂性,但是可以通过一些手段,让我们不容易出错。

git submodule trick

约定

  • master分支上作为submodule发布分支(开发分支遵循checkout/merge原则)
  • master分支上最新的提交必须可以被父仓库引用

初次clone

使用recursive参数来自动clone submodule仓库。

1
2
3
4
5
6
7
# unpleasure method
$ git clone git://github.com/sub/sub.git
$ git submodule init
$ git submodule update
# elegant method
$ git clone --recursive git://github.com/sub/sub.git

更新代码

使用foreach将所有submodule切换到master分支、拉取最新代码,然后在父仓库提交submodule的commit-SHA值。

1
2
3
4
$ git submodule foreach --recursive git checkout master && git pull
# you can changes submodule repository here.
$ git commit -a -m "update all submodule to latest commit id"
$ git push

当我们需要更新仓库中的submodule,foreach可以简化我们的工作量。

如果某些submodule并没有使用master分支作为发布分支,我们采用下列命令:

1
2
3
4
git submodule foreach -q --recursive \
'git checkout \
$(git config -f $toplevel/.gitmodules submodule.$name.branch || echo master)' && \
`git pull`

采用这个命令的前提是在添加submodule时指明分支:

1
2
3
4
5
# add submodule to track master branch
git submodule add -b master [URL to Git repo];
# update your submodule, and update code to the latest of branch master
git submodule update --remote

提交代码

submodule代码会被多个父仓库引用,所以需要在每个父仓库中验证修改是否有效。在验证有效后,从开发分支merge到master分支。

错误提交补救

如果我们没有checkout master分支,却又提交了代码,可以使用cherry-pick命令提取错失的提交:

  • 执行git checkout master将HEAD从detached状态切换到master分支,记录git报出的warning中的commit-SHA
  • 执行git cherry-pick commit-SHA提取错失的提交到master分支;
  • 执行git push重新提交代码。

如果需要提交代码,必须按照使用foreach指令,确保每个submodule都在正确的分支、最新的提交。如果出现本节这种情况,说明开发者没有执行正确的使用步骤。

git subtree

subtree对于父仓库来说是完全透明的,所有开发人员看到的是一个项目中的普通目录,开发人员无须针对subtree做特殊的处理。只需要维护subtree的开发人员在合适的时候去执行代码同步操作。我们在父仓库中通过addpushpull来操作subtree:

1
2
3
4
$ git remote add -f sub git://github.com/sub/sub.git
$ git subtree add --prefix=sub sub master --squash
$ git subtree pull --prefix=sub sub master --squash
$ git subtree push --prefix=sub sub master --squash

subtree是git官方网站推荐使用的方案。subtree的优点在于简单,复杂度低。过于简单也是subtree的缺点:

  • 我们无法在subtree目录中查看对应的commit信息;
  • subtree的提交会污染父仓库的提交记录。

未完待续。

参考文献


本文作者:ZeroJiu
本文链接: http://www.freehacker.cn/tools/git-share-code/
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!