よもやま話β版

よもやま話を書きます

困難は分割せよ(diffを減らしてmergeしていく話)

超小規模エンジニアチーム*1とはいえ、ときにはでかいリリースの必要性に迫られることがあります。 今回取り組んだ機能実装では、3つのmigration、4つのcontrolller、たくさんのviewとテストのファイルを新規作成しました。

ぼっちでやるにあたって、でかいプルリクはでかいというだけでリスクです。見直しにも集中力が必要でしんどいです。 ちょっとでもまともにリリースするため、自分は全体像が完成したら、切り出せそうなところを抽出して先にリリースするようにしています。困難は分割せよという言葉が合いそうだなぁと思います。

多種多様な意見がありますし、将来の自分が見たら考えが変わっていて後ろから刺される可能性もありますが、現時点での自分のやり方を挙げてみます。

まず master ブランチからfeatureブランチを生やします。featureブランチを育てるだけ育てます。このときcommit名とかにはあんまり気を配りません。とにかく80~90%くらいの完成度を目指してcommitを積みまくります。 ほぼほぼ完成に近づいたら全体を眺めます。全体の中から既存の機能に対する変更や、単体で更新できそうな機能(child_a, child_bとします)を考えます。

masterブランチから改めてchild_a, child_bを生やし、抽出できる機能を移植します。 移植には $ git checkout ブランチ -- ファイル名 を使うと便利です。

$ git checkout -b feature master
$ git commit -m "バリバリ開発"
...
$ git checkout -b child_a master

こうするとfeatureブランチから app/models/hoge.rb をとってこれる。
とってきたデータはステージされている状態となっている。

$ git checkout feature -- app/models/hoge.rb
$ git status
On branch feature
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   app/models/hoge.rb

$ git commit -m "hoge.rbだけ取り出して先にリリースするぞ"

この時点でchild_aのdiffを眺めて、もうちょっと良くできないか考えます。 大抵の場合、膨れ上がったfeatureブランチでは見つからなかった問題点や不足していたテストなどに気づき、この時点で手を入れます。 その後、納得できたらmasterに先にmergeします。

$ git commit -m "さらにリファクタしたぞ"
$ git checkout master
$ git merge child_a

ここで child_a は feature に含まれるものよりもだいぶ変わっている状態です。 変更を取り込むとき、merge派とrebase派がいるような気がするんですが、自分はrebase派です。 何回も繰り返してコンフリクトを修正するのが面倒なので、自分はfeature ブランチに積み上がったコミット群をひとまとめにしてからmasterにrebaseします。

# featureに移動して10件のcommitをひとまとめにしてしまう
$ git checkout feature
$ git rebase -i HEAD~10
# 全部squashする。歴史はあとで改変する。
# ひとつにまとまったらmasterへrebaseする
$ git rebase master
# おそらくコンフリクトするので、うまく修正する。

これをfeatureブランチを機能が分解できなくなるまで繰り返し、最後にfeatureブランチの歴史を整えなおして全機能のリリースが完了します。

一通り書き出したけどあんまり分かりやすいとは言えない説明になった。 うーん、図解とかが入れば良かったかもしれない。 Gitで歴史をどうにかするごにょごにょは別途記事にしようかな。

*1:チームといいつつぼっちでやっています。2018年秋の時点ではリファラルで仲間を探しています。