git merge よりも git rebase の勧め

以下、話を単純にするために、プルリクエストのようなことをやっておらず、チーム全体で1つのリモートレポジトリを共有し、各自でブランチを作って開発作業をするものとします。各自のブランチの名前は、Aさんは work/a などとします。

二人が別々の機能を平行して開発し始めたとします。

下の図は左から右にコミットグラフが伸びている様子です。

Aさんは開発の過程でリファクタリングにせまられ、ある定数の名前を変更してソースコードを改善しました。定数を参照しているところも全部書き換えてテストも問題ありません。

Bさんは別の機能の開発にその定数を参照する必要がありました。この時点ではBさんはAさんのブランチをmergeもrebaseもしてないので変更前の定数名で参照しています。

AさんとBさんはその後も開発を続けます。

Aさんが開発を完了し、masterブランチにマージしました。マージ後のテストも無事です。開発ブランチをmasterにrebaseをせずにmergeする場合は fast-forward にしないことが多いかと思いますので、下の図のようになります。

git fetch origin
git checkout master
git merge origin/master
git merge work/a --no-ff # not fast-forward
git push origin master

次にBさんが開発を完了し、テストも問題ないので、masterブランチにマージしました。 Aさんの修正したファイルと重なっていませんでしたので、衝突することなくマージできました。

git fetch origin
git checkout master
git merge origin/master
git merge work/b --no-ff # not fast-forward
git push origin master

しかし、マージ後のmasterブランチでテストにNGが出てしまいました。

マージ時に衝突が起きることがなかったので、マージそのものにまずい編集があったとも思えません。原因は2つの赤いコミットがお互いに不整合なものでそれをマージしてしまったことなのですが、この類の不整合は git merge では検出してくれません。この状態からバグ修正するにはどうしたらよいでしょうか。2つのコミットそれぞれ単独では問題ないのです。テストのNGの内容からバグを探すしかありません。Aさんがデバッグに取りかかれば、 Bさんのコードが古い定数名を参照していることが原因だと判断するかもしれません。しかしテストでNGを出したマージコミットはBさんによるものです。

したがって、Bさんが原因を探し始めることになると思います。 Bさんが調査を開始すると、定数を定義している箇所が間違っていると判断するかもしれません。その場合、定数の定義を元に戻してしまうかもしれません。そしてさらに新しい定数名で参照していた箇所がもしテストでNGになればそれらも戻してしまうかもしれません。テストで発見できなかった箇所はそのままになってしまうかもしれません。そうなると未発覚しないバグを潜めてしまうことになります。もちろんBさんがAさんのコミットログを読んでいれば、定数名を変更した意図を理解して、自分のコードのほうを変更し、うまくいくかもしれません。ただし、これもAさんがわかりやすいコミットログを残していればの話です。コミットログがいい加減で理解しづらいものであればやっぱりうまくいかないかもしれません。

いずれにせよ、問題の解決まで時間がかかりそうです。

同じ過程をrebaseを使う方法ですとどうなるでしょうか。以下の状態までは同じです。

Aさんが開発を完了し、masterブランチにマージしました。

git fetch origin
git rebase origin/master
git checkout master
git merge work/a  # do not create merge commit, but only update the branch pointer
git push origin master

次にBさんも開発を完了し、テストも問題ないので、まずはrebaseをします。

git fetch origin
git rebase origin/master

ここでテストを実行すると、この段階でNGが出ます。masterブランチにマージする前に異常が発覚しました。git bisect を使って原因となったコミットを自動で探すことができます。

git bisect start work/b origin/master
...

bisect すると、ただちにBさんの赤いコミットが原因だと断定されます。コミット当時は問題なかったのにrebaseした結果問題のあるコミットとなってしまった、ということはrebaseによって取り込まれたAさんの修正との間に不整合があったと判断できます。Aさんの修正はすでにmasterにマージされテストでも問題が出ていないので、Bさんのこの赤いコミットを修正すべきでしょう。 Bさんはすぐに定数名を変更してコミットを修正します。

# 問題のあるコミットがチェックアウトされている状態として
git checkout -b work/b2
git commit --amend
git cherry-pick ... # 赤いコミットより後ろのコミットを cherry-pick

Bさんは work/b2 ブランチで無事テストが通ることを確認して、 master にマージします。

git checkout master
git merge work/b2
git push origin master

問題発生から解決までとてもスムーズです。

このサイトは筆者(hydrocul)の個人メモの集合です。すべてのページは永遠に未完成です。
スポンサーリンク