logo

アルパカログ

Git 実践コンフリクト解消「まだあわてるような時間じゃない」

Gitを学び始めた初学者にとって恐ろしいのがコンフリクトです。

コンフリクトが起こるとコンソールに CONFLICTerror といった文字が並ぶのでドキッとしてしまいますよね。

画像が読み込まれない場合はページを更新してみてください。
コンフリクトは初学者にとって心臓に悪い

実際、職業プログラマでも稀にコンフリクトの解消に失敗してデグレしてしまうこともあります。

しかし、コンフリクトがなぜ起こるのか、起こった場合にどう対処すれば良いのか、もしデグレした場合に何が起こるのかをしっかり理解しておけば、過度に恐れる必要はありません。

そこでこの記事では、コンフリクトをわざと発生させ、コンフリクトがなぜ発生するのか、発生した場合にどう対処すれば良いのかを学びます。

📝
修正したはずの不具合が何らかの原因で再発してしまうことをデグレ(degrade: デグレード)と言います。
対象となる読者

ブランチ操作、コミットなど基本的な Git 操作はできる人が対象です。

ℹ️
まず Git の基礎知識を身に付けたい人は Git 図解でわかるブランチとコミットGitカテゴリーの記事 をご覧ください。
1. コンフリクトを発生させてみよう

コンフリクトを発生させてみる題材として easy-notion-blog というOSSを使用します。

ℹ️
「easy-notion-blog とは何?」という方は Notion Blog が簡単に始められる easy-notion-blog の紹介 をご覧ください。

まず最初に、適当なディレクトリに移動してリポジトリをクローンします。

git clone git@github.com:otoyo/easy-notion-blog.git
cd easy-notion-blog

ブランチをチェックアウトします。ブランチ名は conflict-branch-1 としておきます。

git checkout -b conflict-branch-1 origin/main

src/pages/index.tsx を開き、L10付近の Welcome!Thank you for visiting! に変更してみましょう。

-       <h2>Welcome!</h2>
+       <h2>Thank you for visiting!</h2>

変更したら保存してコミットしておきましょう。

git add src/pages/index.tsx
git commit -m 'Change title'

ブランチをもうひとつチェックアウトします。今度はブランチ名を conflict-branch-2 としておきます。

git checkout -b conflict-branch-2 origin/main

また src/pages/index.tsx を開き、今度は Welcome!Welcome again! に変更してみます。

ついでにその下に水平線 <hr /> も入れておきましょう。

-       <h2>Welcome!</h2>
+       <h2>Welcom again!</h2>
+       <hr />

変更したら保存してコミットします。

git add src/pages/index.tsx
git commit -m 'Change title and add hr'

これで準備が整いました。2つのブランチに作成したそれぞれのコミットが同じ箇所を編集しています。

それではここから実際にコンフリクトを発生させてみます。まず git log でコミットIDを確認します。

git log --oneline conflict-branch-1

コミットの履歴がずらっと表示されるので、1番上の Change title となっているコミットのコミットID(先頭7文字のハッシュ)をコピーします。

画像が読み込まれない場合はページを更新してみてください。
黄色部分のコミットIDをコピーする

コミットIDを使ってコミットを cherry-pick で取り込んでみます。

git cherry-pick <コピーしたコミットID>
# 例) git cherry-pick c48ed11

コンフリクトが発生しました。

画像が読み込まれない場合はページを更新してみてください。
わざとコンフリクトを起こすことができた

さて、コンフリクトが発生するメカニズムは何となくおわかりいただけたでしょうか。

コンフリクトは、2つのコミットが同じ箇所に対して異なる修正をしている場合に発生します。

Gitがどちらの修正を採用すべきか判断できないため、開発者にコンフリクトが起こったことを知らせて対応させるために起こるのです。

ここまでわかったことを図にまとめました。

画像が読み込まれない場合はページを更新してみてください。
コンフリクトが発生する理由

それでは次に、コンフリクトの解消を実践してみましょう。

2. コンフリクトを解消しよう

コンフリクトの発生理由がわかったので、解消するために何をすれば良いかはある程度想像がつくかもしれません。

コンフリクトの解消とは、Gitが反映できなかった2つのコミットの差分を人間が見て最終的な差分を作ってあげることです。

コンフリクトが発生したら、まずはどのファイルでコンフリクトが起きているのかを確認します。

確認するには git status を使います。

easy-notion-blog % git status
On branch conflict-branch-2
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

You are currently cherry-picking commit c48ed11.
  (fix conflicts and run "git cherry-pick --continue")
  (use "git cherry-pick --skip" to skip this patch)
  (use "git cherry-pick --abort" to cancel the cherry-pick operation)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   src/pages/index.tsx

no changes added to commit (use "git add" and/or "git commit -a")

both modified に表示されているのがコンフリクトが発生したファイルです。

今回はわざと起こしたので明らかですが src/pages/index.tsx がコンフリクトしています。

それではコンフリクトを解消するために src/pages/index.tsx をエディタで開きます。

コンフリクト箇所が下記のようになっているはずです。エディタによっては左右に分割して表示されるかもしれません。

<<<<<<< HEAD
      <h2>Welcome again!</h2>
      <hr />
=======
      <h2>Thank you for visiting!</h2>
>>>>>>> c48ed11 (Change title)
src/pages/index.tsx にコンフリクトマーカーが表示されている

<<<<<<< HEAD======= のように見慣れない記号が並んでいます。

これはコンフリクトが発生した箇所を表すもので、コンフリクトマーカーと言います。

意味としては、<<<<<<< HEAD から ======= までが今いるブランチの差分、 ======= から >>>>>>> c48ed11 (Change title) までが、取り込もうとしたコミットによって発生した差分を表しています。

<<<<<<< HEAD
  今いるブランチの差分
=======
  取り込もうとして発生した差分
>>>>>>>
コンフリクトマーカーの意味

コンフリクトを解消するとは、具体的にはコンフリクトマーカーを全て削除して最終的な差分を作ることになります。

それでは実際にコンフリクトマーカーを削除して、最終的な差分を作ってみましょう。

今回は見出しを <h2>Thank you for visiting!</h2> にしつつ、 <hr /> も入れたいとします。

src/pages/index.tsx を次のように編集します。

- <<<<<<< HEAD
-       <h2>Welcome again!</h2>
+       <h2>Thank you for visiting!</h2>
        <hr />
- =======
-       <h2>Thank you for visiting!</h2>
- >>>>>>> c48ed11 (Change title)
コンフリクトマーカーを削除して最終的な差分を作る

src/pages/index.tsx のコンフリクト箇所は最終的に次のようになりました。

      <h2>Thank you for visiting!</h2>
      <hr />
最終的な差分ができた

普段の開発では、ここでテストを実行したり動作確認します(今回はGitの練習なのでスキップします)。

問題なければ、コンフリクトしていた src/pages/index.tsxgit add してコミットします。

git add src/pages/index.tsx
git commit

これでコンフリクトを解消することができました。おつかれさまでした。

おわりに

Gitのコンフリクトは、慣れないうちは何か恐ろしいことが起こったように感じてしまいます。

しかし、2つのコミットの差分が衝突したこと、最終的な差分を作ってあげれば良いことを理解しておけば、過度に恐れる必要はありません。

仮にコンフリクトの解消に失敗したとしても、多くの場合、アプリケーションがエラーで止まったり、テストが通らなかったりして気付けるチャンスはあります。

1番怖いのはこれらの段階で気付けないままリリースされ、後になって障害が発生することですが、個人開発の場合は影響範囲も限定されているのでそれほど心配することもないでしょう。

💁‍♂️
仕事ではそのコードを書いた人にコードレビューを依頼することが多いです。それでも稀にコンフリクト解消に失敗したままリリースされてしまうことはあります。

以上です。

この記事では、コンフリクトをわざと発生させ、コンフリクトがなぜ発生するのか、発生した場合にどう対処すれば良いのかを学びました。

Git初学者の人にとって少しでも学びになれば幸いです。