git 开发一例

对于不善于事前搞清楚一个任务如何分割的程序员来说,git 真是太方便了。比如我要解决一个事情,但是却很难将这个宏观的问题直接与 code base 里面的文件直接关联上或者说很难清楚的认识到每个文件需要做一些什么修改,因为他们彼此还存在先后关系,一般这种情况下,程序员或者能把整个逻辑想清楚再动手,这样每步干啥都很明确,但是更多的情况下,写程序也有一点“试错”的感觉,就是我想到大概这里需要加一点,那边需要改一点,还有些什么协调起来看还差一点,最后你的 workspace 充斥着各种修改,但是你还是不满意,不过经过几个尝试,你逐渐的明白了其中哪些可以分割成更小的能 manage 的任务,比如你的流程里面还有 code review,将一堆修改无头无脑的提交怕是让 reviewer 最头疼的事情了。这里讲一些“试错”过程的 git 技巧,可以帮助你解决这个过程中的反复。

混沌

我把第一个阶段称为混沌,也就是程序员什么也没想清楚的试错阶段。

分割

从混沌走出来的第一步就是分割,这个时候你大致清楚将整个问题如何划分成可以被 review 的部分了,那么面对 workspace 里面乱糟糟的修改,我们第一件事情就是分割任务。分割任务最简单的策略就是利用 branch 和 stash,在当前 branch 将可以一个子任务搞定的东西 git add 甚至 git add -p 加入到一个 commit,然后 stash 剩下的。基于此,如果你有一个相关的任务,就基于此 branch 创建新的 branch,否则可以创建单独的 tracking branch(假定你一般都需要 track 远端的 origin/master),在新的 branch 里面应用 stash,然后再剥离出来一个子任务,如此反复下去,任务就被各个 local branch 的 HEAD 所 track 了。

s - a
     \- b
         \- c

各个击破

这个阶段每个子任务可以单独的解决,更新(如完成或者提交 review 根据 feedback 修改)之后有两种选择:

  • 新的修改进入到新的 commit 里面
  • 新的修改通过 git commit –amend 融合到前面一个 commit 里面

这两种策略都会在你有一个“树”状的 branch 结构下出现一些困难,比如说我们的 local branch 在分割之后有 A 之上创建了 B,B 之上创建了 C,记现在 ABC 的 head 分别为 abc。

新的修改进入新的 commit,比如 A 上你完成了结果,多一个 commit a1,提交了 CR 之后你进入 B,然后开始在 B 里面干活。你发现你需要一个来自 A 的 working feature,这时你就得 rebase B onto A。比如 B 很简单解决了之后(添加 b1)你进入了 C,

s - a - a1
     \- b - b1
         \- c

此时 C 本质上还是 abc 三个 commit,很可能你得 rebase C onto B,这样 C 上才会有 a、a1、b、b1 和 c。问题出现在如果在 B 的 rebase 阶段出现了 conflict(比如 a1 和 b 存在冲突),那么在 rebase C 的时候一样会出现类似的 conflict,因为本质上 B 那时是 a-a1-b-b1,C 是 a-b-c 其中因为 B 的 rebase conflict,两个 b 已经不一样了,这时 rebase 的 common head 是 a,最后 C 试图将 b-c 应用到 a-a1-b-b1 上,这样之前 b 和 a1 的冲突就会重现。

使用 amend 可以减少每个 branch 的 commit 数目,但是一旦 A 进行了 amend,比如 A: a’,那么 B 和 C 已经与其有差距了,B 的 rebase 需要 resolve a 与 a’ 的差异,而 b 一旦修改,那么 C 的 rebase 又会重复 a、b 产生的 conflict。

s - a'
 \- a - b
 \- a - b - c

一个比较“假”的解决方案是使用 git rerere,他会录入每次 merge/rebase 的 resolution,在碰到相似的情况下自动的应用。比如前一种策略下,解决 B 的 rebase conflict 之后,可以顺带将 C 也按 A 进行 rebase,这时的 conflict 就会重复从而自动的解决。注意这里并不能将 C 对 B 进行 rebase,因为 B 完成 rebase 后头上的 commit 已经变化了,如此 rebase 反而不能利用 rerere。注意这里的结果很有意思,本来 B 是 A 的子树,C 是 B 的子树,B 和 C 对 A 进行 rebase 后 B、C 就分道扬镳了。

s - a'
     \- b' - b1
     \- b' - c

一种可能的解决方案是寻求一个 branch subtree 的 rebase,这样 rebase B 的时候也能顺带解决 C 的问题就更好了。一般来说是没有终极解决方案的,因为一旦 subbranch 各自又有新的 update,那是不可能通过一次 rebase 稳妥的解决的,但是如果他们尚未被更新,理论上是有保持这个结构的可能的,因为这等价于先把 C 当作 B 的 commit,rebase 之后再将 C 那个  commit 剥离到新的 branch 里面去,这种思路可以解决 linear graph,如果 C 不是 B 的唯一子 branch,这个大概也是行不通的。

看样子 rebase 要真正跟人们编辑的习惯对应起来有一定的距离啊。

—————–
And Leah said, A troop cometh: and she called his name Gad.

Advertisements
git 开发一例

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s