git 最近感悟

这次 scrum 里面学到了一些 project management 的东西,暂记于此。部分内容来自这个 tutorial 和这个流程介绍

如果你有一个 release 周期的话…

曾经以为类似 gitflow 这类 workflow (见此讨论)对于稍微大一点的 team 才适用,其实某些原则对很小的 team 一样是 make sense 的。比如,devs 应该 push commits 到一个单独的 dev branch,而 release 的版本应该出现在另一个 branch 上(比如 release)。至于为什么,很多 CD 系统都有一个“截断”过程,比如可以 approve 某些 commit,之后的就会被拦截起来,那么看起来似乎就能够解决 release 的问题了,大家纷纷开发,然后某天 manager 说不错现在可以发布了,于是 approve 让当前版本进入 production。

这出现的基本问题就是一旦出现 bug,如何修正?如果只有一个 branch,那可好,万一有人 push 了新的 commit,那么 bug fix 这个 commit 就会在其后出现,因此进入 production 的是新的 commit + bug fix,这样新的 commit 又可能带入新的 bug… 这时你就意识到,一个 branch 是不够的了吧。因此 best practice 是 devs 只能 push to dev branch,manager 决定什么时候(往往是 feature complete 的时候)进行从 dev 到 release 的 merge。这样做几个好处:

  • devs 不会因为需要 release 而不能及时的 push(为了避免前面单线开发的那个情况势必需要等到下个 release 开始才适合 push)
  • manager 可以在 merge 的时候通过 non-fast-forward merge 的 commit message 记录一些事情,比如什么 feature 发布了等

Okay 如果你说我们就是单线的作风怎么办?那么取消他人的 commit 可以用 git revert 将 bug fix 前面的那么一两个 commit 去掉,等 bug fix 进入 production,再 revert 那个 revert 的操作就行了。

如果你有一个 code review 流程的话…

使用 git 很大的好处是可以很方便的进行 code review,通常你可以建立很多 local branch 以便你完成一项任务之后提交 CR 然后切换到另外的 branch 做另外的事情。一般性的原则有:

  • 短期开发使用 local branch 即可,如 bug fix、小 feature(几天的),注意不同类型的问题应该使用不同的 tracking branch,如 bug fix 应该对 release 或者专门的 hot fix branch 来做;而小 feature 应该考虑起简单明了的名称 track 最新的 dev branch
  • 长期开发使用单独的 remote tracking branch,这是因为 local commit 需要在远端进行备份以免丢失,而在这个 topic 完成之前应该避免进入 release 或者 dev 影响别的开发

往往 CR 意味着同一段代码上反复的迭代,这可以利用 git commit –amend 将头上尚未 push 的 commit 反复的修改,同时 git 也提供了重写 commit 的功能(见 magit),如果是一个 CR 多个 commits,开发者还有机会在 push 之前将整个 commits 逻辑调整到一般人容易理解的顺序。

某些 fork workflow 里面常提到的 pull request 和 CR 有点像,但是发生的环境往往是一个开发者完成了一个 feature 后希望 maintainer 从自己的 public repository 里面获取这个 feature。这种环境下是不能通过 git commit –amend 来纠正问题的(因为已经完成了 push),为此新的代码只能通过新的 commit 让对方看到。

merge 还是 rebase

从某种意义上来说 merge 和 rebase 的区分不是那么明显,似乎都是把一个 branch 的东西放进另一个,但是语义上是截然不同的,也应该尽量避免混用:

  • merge 的潜台词往往是在 trunk 上需要 merge 一个 feature branch 的新东西,merge 完之后 feature branch 就可以消失了,这个 merge 会导致 feature branch 的内容(区别于创建这个 feature branch 创建时 trunk 的内容)进入 trunk
  • rebase 的潜台词是,我在 feature branch 上,我觉得我的内容可能过时了,需要获得 trunk 上最新的内容(可能是因为我想 merge 到 trunk 了,可能是需要 trunk 上的新 feature 了,还可能是需要 bug fix 了,等等),rebase 之后我的 local commits 应该仍然出现在 rebase 的“新头”之后,这导致对应的 hash 也会被重写

发生 merge 的时候存在三种可能:

  • 当前的 head 和被 merge 进来的 branch 的曾经的 head 是一个,也就是说被 merge 进来的肯定是“新加的”内容时会进行 fast forward merge,这时可以理解成为简单的将被 merge 的 branch 里面的新 commit 加到当前 branch 的 head
  • 如果当前的 head 与被 merge 的 branch 的 head 不一样,这意味着当前 branch 又出现了新的 commit,因此无法“安全的”加入新的 commit,这时会做 recursive merge (3-way merge),这个 merge 如果成功就产生一个 merge commit message
  • 一旦不成功,也就是说两个 branch 至少对一段代码做出了不同的修改,那就需要 resolve conflict,这往往会在 git status 里面显示为 both modified,文件里面出现 >> == << 表示的不同版本,开发者需要选择其中一个或者重写这部分使得它合乎要求,之后 git add 并 git commit 得到 merge commit message

而发生 rebase 的时候类似也可能出现 conflict,这同样需要 resolve(编辑后 git add 即可),但是 rebase 同时提供了一个修改(message 甚至 commit 内容)的机会,某些 branch 上如果意料之中出现很多的 conflict 的话,应使用 git rebase -i 这样在 replay local commit 的时候可以判断哪些 commit message 需要改动,碰到某个 commit 出现 conflict 之后修改、git add 就可以 git rebase –continue。如果决定放弃 rebase 可以 git rebase –abort 回到 rebase 开始前的状态。

使用 rebase 最大的好处在于 commit history 会好看很多:比如大家都在 push 的 dev 上,如果大家选择的是简单的 merge,结果往往是 dev 的 history 显得错综复杂,而 rebase 会使得最后的 history 仍然是单线条。为什么?很简单,rebase 之后的 feature branch 里面的 commit 只会出现在头上,这意味着进行 merge 的时候一定是 fast-forward merge,而如果直接 git fetch/merge (从 tracking branch 或者当地的 trunk)到 feature branch 就会产生 merge commit,当这个部分 merge(push 到远端的 branch)时,这个 merge 产生的交错情况就会进入 commit history。

简化 rebase 最常见的就是直接 checkout -t -b feature_branch remote/dev 建立自己的 feature branch,之后 git pull –rebase 即可。

为什么每次我 rebase 都需要 resolve conflict?

什么时候你会碰到这个问题?其实短期的 local feature branch 不会碰到这个问题,因为你很可能就最后 merge 前进行 rebase,碰到 conflict 就 resolve 之后 push 了。因此碰到这个问题的一定是需要反复 rebase 的长期 topic branch。比如一个 topic branch 可能会开发一个季度,里面除了最后 rebase(如果你希望看到不错的 history 的话),中间还会有从 trunk 里面获得 fix、new feature 的各种情形,因此反复的 rebase 是不可避免的。

每次 rebase 的时候因为相当于是重放 local commits,那么势必可能多次(每次 rebase)与以前的编辑冲突,一个简化这类 conflict resolution 的手段是使用 git-rerere(reuse recorded resolution),这个会在 merge/rebase 等操作里面留下 record 的钩子,今后碰到一样的问题就会照葫芦画瓢了。

这里有个问题是如果你希望将这个 branch push 到另外的 remote branch 备份,那是否应该从 trunk 来做 rebase?感觉答案是否定的,因为 rebase 重放后 local commits 的 hash 就会与 remote branch 不同,merge 后就会出问题(出现重复的 commit),似乎这种情况下还是 merge 比较稳妥。

多个 branch 很要命吗?

其实初用 git 的人都会问这个问题,答案尽管是否定的,你可能还是不大敢创建一堆 branch 切换来切换去。但其实应该明白,只是新手不知道怎么处理切换时候碰到的问题:

  • 没啥修改,有 commit,那就放心吧,直接 checkout
  • 有修改,你有两个选择,或者 add 然后 commit 了,回来做 commit –amend 或者 git reset –soft HEAD^ 将这个 commit 取消掉或者直接 stash 回来再 stash pop

当然 stash 功能比较强大,你可以把 stash 在 branch 之间应用,比如你在某个 branch 里面多做了一些工作,临到提交 code review 了,这部分想继续下去,但不想 CR feedback 来了之后影响这部分工作的进度,那么可以将 CR 的部分 commit 之后 stash 其余的工作,然后建立新的 branch,在上面应用 stash,并且继续干活,顺便等 CR。

当然频繁的切换 branch 出现的问题是编辑器打开的文件怎么办?有的编辑器比较大胆,一旦文件被其他进程修改了,直接 reload;但是像 emacs 这种比较谨慎的编辑器就会问你是否 reread,设置 global-auto-revert-mode 是个不错的选择。我比较喜欢谨慎一点的,一次误操作可能让你硬盘上的文件丢失(git reset –hard 往往是万恶之源)那么 emacs buffer 里面的东西就是你最后的救命稻草。

最后一个小问题是创建的 branch 不用了就赶紧删掉吧 git branch -d 可以删掉,remote branch 可以用 push repos :branch_name 来删。

我想要非线性的 merge…

这个问题出现在比如我们使用 gitflow,已经准备 release 的情况下,突然决定把 dev 上的某个新完成的 feature 加入,或者 dev 上已经出现了当前 bug 的 fix 了,可是离那个 commit 中间还有好几个 commit 怎么办?这时有几种方法:

  • git cherry-pick 将对应的 commit 切出来加在本 branch 上,这会导致一个新的 commit
  • git format-patch + git am,前者构造 patch 后者写入当前 branch 并导致新的 commit
  • git format-patch 和 git apply,后者不产生新的 branch 而是要求编辑,之后手动 commit

一般说来 cherry pick 还是会导致重复记录(如果后来两个 branch 进行了 merge),慎用(经典的用例是 pick 之后,删掉被 pick 的 branch,也就是说一个 feature branch 几个 commit,但是有用的可能就一个,于是 pick 那一个之后这个 feature branch 就可以删掉了)!

—————–
And Jacob said unto Laban, Give me my wife, for my days are fulfilled, that I may go in unto her.

Advertisements
git 最近感悟

一个有关“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