Island Life

< 近頃のらむ太 | 影響を受けた本 >

2010/12/05

git rebase

gitのワークフローgitメモを書いた頃はgit rebaseの使い方がいまいちわからなかったのだが、最近はupstreamからの取り込みにmergeではなく専らrebaseを使うようになった。

ひとつの理由は、パッチ統合のスピードアップのために、テストが完全に済んでいないけれど取り込み予定のパッチを先行して含んだunstableブランチがupstreamに作られたこと。パッチの正式取り込みは機能テストだけでなく性能テストを通る必要があり、それには日単位の時間がかかる。masterのHEADで通るパッチを提出しても、その時点でテストキューにあったパッチがmasterにマージされた後に自分のパッチが処理されるので、そこでconflictを起こしてrejectされる、ということが頻発した。なので基本的な機能テストを通ったものが先行してunstableブランチにマージされ、特にリリース近くなってキューが詰まりがちな時はunstableに対してパッチを作って提出する、という流れになった。

ところが、unstableのパッチはその後のテスト結果によってrejectされることがある。unstableから手元にmergeしてしまうと、rejectされたものを後で取り除くのがかなり面倒になる。というわけで、masterとのsyncにはrebase、ってことになった。

rebaseの動作はgitのマニュアルに図入りで説明されているとおりで、この原理はすぐにわかる。でも原理がわかっても実際の応用がうまく掴めなかった。特に、conflictが起きた時にどう解消すべきか、というところ。

手元のtopic branchは細かくコミットしているので、同じ箇所をいくつものコミットでいじってる場合がある。そういう場所でnon trivialなconflictが起きた場合、topic branchの最終形に引きずられて直してしまうと、rebaseを続行した時に同じところで何度もconflictを起こしてわけわからなくなることがあった。

  --o--o--O--o--o--X        <- master
           \
            a--b--c--d--e   <- topic branch

こんな形からtopic branchでgit rebase masterしたとする。c, d, eは同じ箇所をいじってるコミット。で、rebase中にcでconflictが起きたとする。

この時考えるべきは、「もし自分がXから作業を開始していたとしたら、cでどのような変更をしただろうか」ということ。これを頭に置いておくと間違えない。元々のcでの変更は実はbuggyで、それをdやeで直したかもしれないけれど、この時点でのconflict解決には、cで持ち込んだバグをそのまま残す。一旦過去の自分に戻って自分の作業をトレースする感じ。

rebaseは履歴が書き換わるから安全ではない、というけれど、自分の過去の作業を、出発点を変えてやり直す作業だと思えば、その性質も納得できる。

Tags: Programming, git

Past comment(s)

ukai (2010/12/06 13:29:31):

gopic branchを自分しかいじってない場合は、細かくcommitしてても、明らかにtopic branch上の以前のcommitを修正するような時は、git commit --amendか、git rebase -i で squashとかしておいて、わかりやすいcommit列にしておくほうが、git rebase masterする時にもやりやすいと思います。

shiro (2010/12/06 17:03:44):

なるほど、手元のブランチもいじっちゃうんですね。どうせ履歴をいじるわけですし、いずれパッチ提出の際にはsquashするのだから細かい履歴は取っとく必要無いか。

あーなるほど。gitはリビジョン管理というよりパッチ管理だ、という意味がわかったような。全ての履歴を保存しておくことが重要なんじゃなくて、作業をいかに差分の形で管理するかってことを考えるといいんですね。

Post a comment

Name: