Eyes, JAPAN Blog > 正しくコンフリクトを解消する技術

正しくコンフリクトを解消する技術

Yuki Ito

この記事は1年以上前に書かれたもので、内容が古い可能性がありますのでご注意ください。

一人で開発しているときには出会わない”コンフリクト”。

チーム開発で初めて出会うわけですが、実はかなり気をつけないと 高確率で痛い目にあってしまう のがコンフリクトの解消です。

よくあることとしては

  • 適当にマージした結果、 マージ後に削除したはずのコードが復活している
  • マージしたらいきなりバグが起こる
  • バグの調査をするも まさかマージコミットが原因だと思わず発見が遅れる

といった最悪の状況を簡単に引き起こしてしまうのがコンフリクトの解消というわけです。

では、どうしたら上記のような状況を防ぐことができるのでしょうか?

それにはしっかりと 正しい手順を踏んでコンフリクトを解消する ということが必要となります。

それではさっそく手順を見ていきますが、説明のためにリポジトリを一つ作って見ました。

acomagu/material-conflict

単によくあるコンフリクトを簡単に再現してあるだけですが、手元で触りながら読みたいとか、まだコンフリクトにあまり出会ったことのない方はcloneして見るといいかもしれません。しなくても大丈夫です。

以下、 masterブランチにfugaブランチをmergeしようとして、コンフリクトが発生する 状況で話を進めていきたいと思います。

コンフリクト解消の手順①: 深呼吸し、全体を見渡す

コンフリクトに出会ってしまった時、最も してはいけないこと はなんでしょうか?それは、 その場だけを見てチャチャッと解決してしまうこと です。

Mergeコミットはバグにつながりやすい上、多くの場合それがリリースに直結してしまうこともあります。まずは 深呼吸し、「俺はこれからコンフリクトに立ち向かうんだ」という覚悟を決めましょう。

そして次にやるべきことは、 全体を見渡し、コンフリクトの原因を把握すること です。大まかでいいのでブランチ単位でどことどこがコンフリクトしているのか、分岐点からどの程度離れているのか、そのMergeは正しいかを確認しましょう。この段階で そもそもMergeの操作を間違えていた ことに気づくなんてこともよくあります。

全体を見渡すのに便利なのは、 git log --oneline --graph --all コマンドでしょう。全てのブランチとコミットをツリー表示してくれるので、Merge元と先のブランチ名を探して どこの枝とどこの枝をMergeしようとしているか を確認すると良いでしょう。

これはサンプルリポジトリで実行した結果です。

SourceTreeやTigでも(多分)いいと思います。GitHub上だと全体を一度に表示する術がないので気をつけましょう。

コンフリクト解消の手順②: それぞれの差分を確認する

さて、全体を見渡すことができたら、今一度コンフリクトの該当部分を確認します。

サンプルリポジトリではこのようになっていますね:

#include <stdio.h>

int triangle(int n) {
  int i, ans = 0;
<<<<<<< HEAD
  for(i = 0; i < 100; i++) {
=======
  for(i = 0; i < n; i++) {
>>>>>>> fuga
    ans += i;
  }
  return ans;
}

int main() {
  int ans = triangle(10);
  printf("Answer: %d", ans);
  return 0;
}

Gitは5行目の for(i = 0; i < 100; i++) { という行を問題にしています。これを頭に置いて、まずマージする方である masterブランチで何が行われたか を確認していきます。

先ほど全体のツリー構造を見た際に、 masterブランチとfugaブランチの分岐元はコミット 6808fa9 だとわかります。なので、そこから現在のmasterブランチがどう変わったかは、 git diff 6808fa9 master でわかることになります。

(ちなみに分岐元を求める git merge-base という便利なコマンドもあり、 git diff $(git merge-base master fuga) master でも同様の結果を得られます)

どうやら 足し算のmax値を10から100に変更しただけ のようですね!

変更内容が分かったので、次はマージされる方である fugaブランチで何が行われたかを見ていきます。

うーん、いまいちわかりにくいですね…

このような時は、 git blame で該当するコミットだけ抜き出して見るとわかりやすくなることがあります。 (サンプルリポジトリの場合はコミットが一つしかないのでblameする必要もないですが、実際はいくつかのコミットをまとめてマージする場合がほとんどなので)

 ❯❯ git blame fuga -- ./triangle.c
6808fa90 (Yuki Ito 2017-09-06 11:07:45 +0900  1) #include <stdio.h>
6808fa90 (Yuki Ito 2017-09-06 11:07:45 +0900  2)
cbde45dc (Yuki Ito 2017-09-06 11:11:11 +0900  3) int triangle(int n) {
6808fa90 (Yuki Ito 2017-09-06 11:07:45 +0900  4)   int i, ans = 0;
cbde45dc (Yuki Ito 2017-09-06 11:11:11 +0900  5)   for(i = 0; i < n; i++) {
6808fa90 (Yuki Ito 2017-09-06 11:07:45 +0900  6)     ans += i;
6808fa90 (Yuki Ito 2017-09-06 11:07:45 +0900  7)   }
cbde45dc (Yuki Ito 2017-09-06 11:11:11 +0900  8)   return ans;
cbde45dc (Yuki Ito 2017-09-06 11:11:11 +0900  9) }
cbde45dc (Yuki Ito 2017-09-06 11:11:11 +0900 10)
cbde45dc (Yuki Ito 2017-09-06 11:11:11 +0900 11) int main() {
cbde45dc (Yuki Ito 2017-09-06 11:11:11 +0900 12)   int ans = triangle(10);
6808fa90 (Yuki Ito 2017-09-06 11:07:45 +0900 13)   printf("Answer: %d", ans);
6808fa90 (Yuki Ito 2017-09-06 11:07:45 +0900 14)   return 0;
6808fa90 (Yuki Ito 2017-09-06 11:07:45 +0900 15) }

このような結果が出ます。 問題の5行目はコミット cbde45dc で変更されたものであることがわかります。

早速そのコミットを見て見ます。

(この場合は)diffは変わっていませんが、 コメントから「関数に切り出した変更」であることがわかりました!

両方の変更が分かったので、最終的にApplyしていきます。

コンフリクト解消の手順③: 変更を整理し適用する

さて、情報が集まったところで、改めてコンフリクトを解消していきましょう。

上記からわかることは、

  • triangle関数に切り出す変更
  • 足し算のmax値を10から100にする変更

の二つの変更によってコンフリクトしているということでした。

これを 一つずつ適用していきます。

コンフリクトした状態は以下のような感じでした:

#include <stdio.h>

int triangle(int n) {
  int i, ans = 0;
<<<<<<< HEAD
  for(i = 0; i < 100; i++) {
=======
  for(i = 0; i < n; i++) {
>>>>>>> fuga
    ans += i;
  }
  return ans;
}

int main() {
  int ans = triangle(10);
  printf("Answer: %d", ans);
  return 0;
}

まず”triangle関数に切り出す変更”の結果、どのようになるか考えて見ましょう。

#include <stdio.h>

int triangle(int n) {
  int i, ans = 0;
  for(i = 0; i < n; i++) {
    ans += i;
  }
  return ans;
}

int main() {
  int ans = triangle(10);
  printf("Answer: %d", ans);
  return 0;
}

こうですね。fugaブランチの変更通り、もともと10だった部分を関数の引数nで受け取っています。

次に、ここに “足し算のmax値を10から100にする変更”を適用して見ます。

#include <stdio.h>

int triangle(int n) {
  int i, ans = 0;
  for(i = 0; i < n; i++) {
    ans += i;
  }
  return ans;
}

int main() {
  int ans = triangle(100);
  printf("Answer: %d", ans);
  return 0;
}

12行目が変更されました。

これで二つの変更が適用でき、無事コンフリクトが解消できました! 現場では、以上の主に②〜③をコンフリクトの数だけ繰り返していくことになります。

コンフリクト解消の手順④: 動作確認をする

全てのコンフリクトを解消したら、必ず動作確認をしましょう。自動テストがある環境ならそれを実行し、なければ手元で一度試すなりします。

今回はコンパイルして実行して見ることにします。

良さそうですね!

まとめ

いかがでしたでしょうか。私は経験を積みながら “コンフリクトの解消って、こんなに大変なものだったんだ…!” と思った覚えがあります。

このほかにも、”大きすぎる差分は分割してマージする”や”変更の小さい方に大きい方を適用していきコンフリクトを解消する”等色々とコンフリクト解消の技術はありますが、それはまたの機会にお話しできればと思います。

この記事が皆さんの 安全で効率的な コンフリクト解消の助けになれば幸いです!

Comments are closed.