K Release
这条代码线用于Kitkat的版本发布。每一个Kitkat小版本的发布,都是基于
K Release
这条代码线。譬如,
4.4
发布后(对应
kitkcat-release
分支),OEM厂商基于
kitkat-release
分支来适配机型。
4.4
发布后,
K Release
代码线并没有停止向前,Google官方会持续做一些Bug修复,会从
Upstream Projects
选择代码更新,也会从
K Experimental
代码线选择代码,直到可以发布下一个新版本
4.4.1
。
每一次新版本发布,就意味着Android的一个正式版,伴随而来的是SDK版本和Android API版本的升级。
K Experimental
这条代码线用于实验最新的特性。AOSP会从Android社区选择一些新功能或是Bug修复放到该代码线上。
Android社区一般是由第三方合作厂商来贡献代码,譬如HTC、Moto、SAMSUNG等一些大厂;还有一些活跃的基于AOSP的第三方项目,譬如CyanogenMod。
当一个Android版本发布后,就会创建新的
Experimental
分支,譬如
4.4
发布,就会基于
4.4
的发布分支新建一个
K Experimental
分支。
当第三方合入
K Experimental
分支的代码经过一段时间达到稳定状态,被验证通过后,就会合入
K Release
中的分支,作为下一个发布版本
4.4.1
的新入代码。
L Release
这条代码线用于Lollipop的版本发布。在
K Release
代码线发布了
4.4
后,Google内部已经在运作Lollipop的
5.0
版本了(对应分支lollipop-release),
在
5.0
正式发布之前,
L Release
代码线都是非公开的,Google也会从
K Experimental
代码线选择性的合并代码。
直到
5.0
正式发布,
L Experimental
代码线构建,又重新进入了同样的开发流程。
总体而言,AOSP的代码管理涉及到以下几个方面:
多代码线并存:版本
N
的发布代码线和实验代码线,版本
N+1
的发布代码线,第三方开源代码线。同一条代码线可能存在多个多支,在一个多分支的环境下,就会存在”一个改动适应于哪些分支”的问题。
在
AOSP
的提交记录中(譬如:
lollipop-release分支
),
经常可以看到
DO NOT MERGE
关键字,为什么提交记录中会出现这种关键字?有什么用途呢?
We (Google) routinely develop on multiple branches at the same time. In order to make sure that the later branch (e.g. ics-mr1) contains all the new features and bugfixes developed in an older branch (e.g.ics-mr0), we have a server that automatically takes every commit made in ics-mr0 and merges it into ics-mr1.
However, sometimes the engineer making a change in ics-mr0 knows that this change doesn’t apply to ics-mr1, e.g. because a similar issue was fixed differently in ics-mr1 and the fix from ics-mr0 wasn’t necessary. In that case, the engineer includes the words “do not merge” in their change description, and the auto-merger performs a “git merge -s ours” instead of “git merge” when handling that change.
There’s a bit more complexity involved, but that’s the high-level view.
在downstream上使用rebase,表示要改变当前的基节点的位置,通过
rebase
到upstream,就意味着将基节点切换到upstream的最新提交
F
。
本来downstream和upstream公共的父节点是
B
, 使用完
rebase
后,则会将
C
和
D
两个提交记录挑出来,重新提交到
F
之后,
这同样会生成两个新的提交记录
C’
和
C’
,
Commit ID
与之前
C
和
D
的是不同的。
再进一步考虑,基于提交
D
拉出下游分支downstream2,新增提交
H
:
这时,继续使用
rebase
,将upstream合并到downstream2,会出现新的提交
C’’
,
D’’
,
H’‘
, 与之前的
Commit ID
都不同。
每次当上游分支有新的提交时,都需要重新
rebase
到上游分支最新的提交,所有下游分支公共父亲节点之后都需要被重新提交一次。
一旦中间某一次提交产生冲突,就会停下来,直到冲突解决完毕,才能继续
rebase
。
这种方式不适合代码自动流:
rebase
中断,需要人工参与解决冲突。当分支演进到一定程度时,中断的概率非常高,需要大量的人工参与,也就丧失了代码自动提交的意义。
downstream和downstream2两条分支虽然代码改动差不多,但实际上是两条差异很大的分支,因为它们的
Commit ID
不同。
如果要将这两条分支作为上、下游关系,面临着与
cherry-pick
同样窘境。
3.3 merge
在downstream上,使用merge,将upstream和提交合并到downstream
$ git merge upstream
upstream和downstream两个分支
merge
,会出现一个新的提交
M1
, 它的父提交有两个,分别是
D
和
F
。
如果产生冲突,则会一次性提示所有代码改动产生的冲突,这与
rebase
不一样,有一个很形象的比喻来形容
merge
和
rebase
进行代码合并的区别:
将一堆玩具整理到一个箱子中,
rebase
是一件一件挪,如果箱子满了(产生冲突),则需要整理一下箱子,腾出空间,再接着挪;
而
merge
是一股脑将玩具扔到箱子中,箱子满了,再一起整理。
一次
merge
操作不一定会生成新的合并提交
M1
, 默认情况下,
git merge
是采用
fast-forward
模式的,只是改变指针位置。
如果不出现冲突,则分支图中不会出现分叉的情况,还是保持所有的提交一条直线。这种情况下并不会产生新的提交记录,
E
和
F
还是保留了原来的
Commit ID
。
再进一步考虑,基于downstream的提交记录
D
又拉了新的分支downstream2 ,并增加了新的提交
H
, 仍然采用
merge
将upstream合并到downstream2:
这时产生了一个新的合并提交
M2
, 它的父提交是
F
和
H
。downstream和downstreanm2的公共父提交
F
。
这里有一个细节,upstream合并到downstream2,相当于
(B, F, H)
的三路合并,在此之前,将upstream合并到downstream,相当于
(B, F, D)
的三路合并。
E, F
与
C, D
合并可能会产生冲突,一旦冲突解决,则冲突解决的方法就被git记录下来了,再将
E, F
与
C, D, H
合并时,会利用之前解决的冲突,这样冲突数量会减少很多。
具体可以参见
git-rerere - Reuse recorded resolution of conflicted merges
机制。