submodule切换分⽀_Git管理实践(⼀):多分⽀⼦模块依赖
管理
编辑推荐:
本⽂来⾃hahack,⽂章介绍了⾯向复杂⼯程的简单化
Git 分⽀依赖管理⽅案等相关知识。
需求描述
我们尝试使⽤ Git 来维护⼀个项⽬的代码。这个项⽬的结构⽐较复杂:
项⽬包含由多个⼦模块,每个⼦模块是⼀个独⽴的 Git 仓库,⼦模块还允许继续嵌套包含⼦模块。
例如,主⼯程依赖 common、framework、react_native 等多个⼦模块,⽽ react_native
⼦模块⼜依赖 node_modules、HFCommon、HFModules 等多个嵌套⼦模块。
[-] app_android/
嫌恶的读音|-[+] HFUIKit
|-[+] channel
|-[+] common
|-[+] framework
|-[+] hybrid
|-[+] messagecenter
|-[-] react_native
|-[+] HFCommon
|-[+] HFModules
|-[+] node_modules
主⼯程和⼦模块允许存在多个分⽀,且相互之间有依赖关系。例如,主⼯程的
jilin 分⽀同时依赖 common ⼦模块的 master 分⽀,以及 framework ⼦模块的
jilin 分⽀。
Git submodule 的问题
Git 提供了 submodule 来⽀持⼦模块的需求,使⽤它可以很⽅便的将多个独⽴仓库包含到同⼀个主⼯程中:
$ git init
Git submodule 还⽀持嵌套添加⼦模块:
$ git submodule
$ cd react_native
通过⼦模块,这些⼦模块既可以各⾃独⽴的修改和提交代码,⼜可以将改动作⽤到依赖它的⽗⼯程。这听起来是个很棒的特性,然⽽
Git submodule 也存在着⼀些让⼈抓狂的坑。
⾸先,主⼯程并不直接跟踪⼦模块的代码,⽽仅仅只跟踪⼦模块的 commit
id 的改动。在执⾏ git submodule update 更新⼦模块代码时,Git 就是根据主⼯程所维护的
commit id 来更新⼦模块到指定状态的。
bash-3.2$ git
diff react_native
diff --git a/react_native b/react_native
index 3a9c5b1..ad68a28 160000
--- a/react_native
+++ b/react_native
@@ -1 +1 @@
-Subproject commit 3a9c5b14c45b199e2e6863d2b6da22dabc2a54f5
+Subproject commit ad68a28c13d4196df531c7df8523d0735*******
(END)
因此,如果你只在⼦模块中修改并提交了代码,⽽没有到主⼯程上⾯再把⼦模块的
commit id 提交⼀下,其他⼈拉取⼯程代码的时候会发现⼦模块的代码依然停留在⽼的 commit
id 所指向的状态。对于嵌套⼦模块,这种⼯作尤为繁琐,提交代码后要逐层往上提交 commit id
,否则其他⼈⽆法正确更新代码。liwu
其次,如前⾯所说,使⽤ git submodule update 更新⼦模块后,⼦模块将被切换到⼀个指向⽗⼯程维护的
commit id 所指定的游离状态:
bash-3.2$ git
submodule update react_native中秋快乐的英文
bash-3.2$ cd react_native
bash-3.2$ git branch
* (detached from 3a9c5b1)
master
jilin
TaiShan
⼀旦代码处于游离分⽀,你就要时刻警惕在游离分⽀上的提交有没有即时合并到⾮游离分⽀上。如果你直接在游离分⽀上开发并提交了代码,之后在⽗⼯程⾥再次
git submodule update ,你所有未合并的提交都会丢失!
最后还有⼀个⾮常⿇烦,但也极容易出现的问题:如果团队⾥有⼈只提交了主⼯程该⼦模块的
commit id ,却忘了进⼊该模块提交模块真正的代码,那么当推送到中央仓库之后,其他⼈就会因为找不到与该
commit id 对应的代码⽽⽆法正确更新代码:
bash-3.2$ git
submodule update
成都欢迎您error: pathspec 'ad68a28c13d4196df531c7df8523d0735*******'
did not match any file(s) known to git.
Did you forget to 'git add'?
Unable to checkout 'ad68a28c13d4196df531c7df8523d0735*******'
in submodule path 'react_native'
对于熟练的⽤户,这些坑⾃然可以轻松越过。但考虑到团队⾥⼤都是 Git
新⼿,我们发现⼦模块的引⼊对他们造成了很⼤的负担,频繁出现⼦模块代码没有更新到最新状态,或者更新出错的情况。简单解决⽅案
经过考虑,我们决定对⼦模块的使⽤做些简化:
toko
所有⼦模块不再根据⽗⼯程的 commit id 更新代码,⽽是直接更新到主⼯程所依赖的分⽀的最新⼀次提交;
由于 commit id 不再⽤来更新代码,因此可以禁⽌直接提交⼦模块的
commit id ,避免出现只提交⼦模块 commit id ⽽忘记提交⼦模块代码的情况。
造个轮⼦:fmanager
为了达到第⼀个⽬的,我们⾃⼰写了个专⽤的管理⼯具 fmanager
。⽬前它⼀共⽀持如下⼏个功能,并且在不断扩展中:
1 fmanager pull #更新当前分⽀的主⼯程,并将每个⼦模块的代码更新到指定分⽀的最新状态。
2 fmanager update # ./fmanager pull
的别名
3 fmanager checkout #
切换到某个主⼯程分⽀,同时完成⼦模块的代码切换。
4 fmanager submodule update
# 更新指定⼦模块的代码到所处分⽀的最新状。
5 fmanager showbranch # 查看当前主⼯程和所有⼦模块的所属分⽀。
6 fmanager status # 查看当前主⼯程和所有⼦模块的修改状态。
7 fmanager log # 查看当前主⼯程和所有⼦模块的当前分⽀/标签和最新提交。
8 fmanager cherry-pick
id> cherry-pick 某个 commit id 到分⽀列表。
9 fmanager cherry-push
id> cherry-pick 某个 commit id 到分⽀列表,并推送这些分⽀。
这个⼯具直接内置在主⼯程的根路径,并且接受⼀个 json 格式的配置⽂件
modules.json ,该配置⽂件⼤概长这样:
{
"sub":
{
"app": {"branch": "master_dev"},
"common": {"branch": "master_dev"},
"fmall": {"branch": "master"},
"framework": {"branch": "jilin"},
"fund": {"branch": "master_dev"},
"hybrid": {"branch": "master_dev"},
"messagecenter": {"branch":
"master"},
"property":
{"branch": "master"},
"safetykeyboardnew": {"branch":
"master"},
"scores": {"branch":
"master"},
"thirdparty":
{"branch": "master_dev"},
"react_native": {"branch":
"jilin"},
"react_native/HFModules":
{"branch": "jilin"},深圳电脑培训
"react_native/HFCommon":
{"branch": "master_dev"},
"react_native/node_modules": {"branch":
"master"}
}
}
不同的主⼯程分⽀,modules.json 配置⽂件的内容允许不同,且每个模块都允许指定不同分⽀。对于嵌套⼦模块,我们通过加上⽗模块前缀来做标识。
当使⽤ fmanager 切换分⽀时,fmanager 将⾸先完成主⼯程的分⽀切换,然后读⼊该分⽀下的
modules.json ,再根据 modules.json 的配置逐个切换到各⾃模块的指定分⽀。
使⽤ fmanager 更新⼯程和切换分⽀相似,只是顺便完成了⼦模块的
git pull 操作。
这样的⼦模块管理策略看起来有点“激进”:永远使⽤分⽀最新的代码状态。为了保证稳定性,我们还⽀持在
modules.json 中使⽤ tag :
{
"sub":
{
"app": {"tag": "2.0.1"},
"common": {"tag": "2.0.1"},
"fmall": {"tag": "2.0.1"},
"framework": {"tag": "jilin-2.0.1"},
"fund": {"tag": "2.0.1"},
换行符"hybrid": {"tag": "2.0.1"},
"messagecenter": {"tag": "2.0.1"},
英孚教育网站"property": {"tag": "2.0.1"},
"safetykeyboardnew": {"tag":
"2.0.1"},
"scores": {"tag":
"2.0.1"},
"thirdparty":
{"tag": "2.0.1"},
"react_native":
{"tag": "jilin-2.0.1"},
"react_native/HFModules": {"tag":
free rice
"jilin-2.0.1"},
有内涵的英文网名
"react_native/HFCommon":
{"tag": "2.0.1"},
"react_native/node_modules":
{"tag": "2.0.1"}
}
}
在项⽬后期,可以通过引⽤标签来保持整个⼯程的稳定性。同时,负责打包的机器每次打包时,都会顺便执⾏fmanager log 产出⼀份包含当前所有⼦模块所处分⽀和最新⼀次 commit 的记录,⽅便追查问题。
加个钩⼦:pre-commit
要达到第⼆个⽬的,可以通过编写本地钩⼦ pre-commit 来实现。该钩⼦可以⽤来在
commit 前进⾏⼀些检查⼯作,并拒绝⼀些不合法的提交。针对我们的需求,可以写⼀个脚本检查提交中是否包含commit id 的修改,如果有,就先重置那些修改再提交剩下的内容。
#!/usr/bin/python