Gitを学び始めた初学者にとって恐ろしいのがコンフリクトです。
コンフリクトが起こるとコンソールに CONFLICT
や error
といった文字が並ぶのでドキッとしてしまいますよね。
実際、職業プログラマでも稀にコンフリクトの解消に失敗してデグレしてしまうこともあります。
しかし、コンフリクトがなぜ起こるのか、起こった場合にどう対処すれば良いのか、もしデグレした場合に何が起こるのかをしっかり理解しておけば、過度に恐れる必要はありません。
そこでこの記事では、コンフリクトをわざと発生させ、コンフリクトがなぜ発生するのか、発生した場合にどう対処すれば良いのかを学びます。
ブランチ操作、コミットなど基本的な Git 操作はできる人が対象です。
コンフリクトを発生させてみる題材として easy-notion-blog というOSSを使用します。
まず最初に、適当なディレクトリに移動してリポジトリをクローンします。
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を使ってコミットを cherry-pick
で取り込んでみます。
git cherry-pick <コピーしたコミットID>
# 例) git cherry-pick c48ed11
コンフリクトが発生しました。
さて、コンフリクトが発生するメカニズムは何となくおわかりいただけたでしょうか。
コンフリクトは、2つのコミットが同じ箇所に対して異なる修正をしている場合に発生します。
Gitがどちらの修正を採用すべきか判断できないため、開発者にコンフリクトが起こったことを知らせて対応させるために起こるのです。
ここまでわかったことを図にまとめました。
それでは次に、コンフリクトの解消を実践してみましょう。
コンフリクトの発生理由がわかったので、解消するために何をすれば良いかはある程度想像がつくかもしれません。
コンフリクトの解消とは、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)
<<<<<<< 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.tsx
を git add
してコミットします。
git add src/pages/index.tsx
git commit
これでコンフリクトを解消することができました。おつかれさまでした。
Gitのコンフリクトは、慣れないうちは何か恐ろしいことが起こったように感じてしまいます。
しかし、2つのコミットの差分が衝突したこと、最終的な差分を作ってあげれば良いことを理解しておけば、過度に恐れる必要はありません。
仮にコンフリクトの解消に失敗したとしても、多くの場合、アプリケーションがエラーで止まったり、テストが通らなかったりして気付けるチャンスはあります。
1番怖いのはこれらの段階で気付けないままリリースされ、後になって障害が発生することですが、個人開発の場合は影響範囲も限定されているのでそれほど心配することもないでしょう。
以上です。
この記事では、コンフリクトをわざと発生させ、コンフリクトがなぜ発生するのか、発生した場合にどう対処すれば良いのかを学びました。
Git初学者の人にとって少しでも学びになれば幸いです。
コメントを送る
コメントはブログオーナーのみ閲覧できます