git rebaseでコンフリクトした際の「解消方法」や「取り消し方法」

git rebaseを実行した際に、同じファイルの同じ箇所を別々のブランチで変更していれば、コンフリクトが発生します。リベース(rebase)では、1つ1つのコミットに対してコンフリクトを検出し、コンフリクトがあればその都度コンフリクトを解消します。

この記事では「git rebaseでコンフリクトした際の解消方法」と「git rebaseの取り消し方法」について、図を用いて分かりやすく説明するように心掛けています。ご参考になれば幸いです。

git rebaseでコンフリクトした際の解消方法

git rebase実行後にコンフリクトが発生した場合、以下の手順でコンフリクトを解消することができます。

  • コンフリクトが発生しているファイルを修正する
  • ファイルの修正が終わったら、git addで対象のファイルをステージに追加する
  • git rebase --continueを実行する
  • コミットメッセージを作成する
  • 別コミットでもコンフリクトが発生していれば、1~4を繰り返す

ではこれから、意図的にコンフリクトを発生させて、コンフリクトを解消してみましょう。

意図的にコンフリクトを発生させる方法

意図的にコンフリクトを発生させる状況を作ります(mainブランチとfeatureブランチで同じファイルの同じ行を編集することで意図的にコンフリクトを発生させます)。

まず、プロジェクトディレクトリでgitを初期化します。カレントディレクトリがプロジェクトディレクトリであることを確認したら、以下のコマンドを実行してください。

$ git init
Initialized empty Git repository in C:/test/.git/

次に、mainブランチでtest01.txttest02.txtを作成して、コミットします。以下のコマンドを実行してください。

$ echo abc >> test01.txt
$ echo def >> test02.txt
$ git add .
$ git commit -m "[main]A"
[main (root-commit) d7e79fb] [main]A
 2 files changed, 2 insertions(+)
 create mode 100644 test01.txt
 create mode 100644 test02.txt

上記のコマンドの実行結果を見ると、コミットメッセージ[main]AのコミットIDがd7e79fb...であることが分かります。

次に、featureブランチを作成して切り替えます。以下のコマンドを実行してください。

$ git checkout -b feature
Switched to a new branch 'feature'

featureブランチでtest01.txtを変更して、コミットします。以下のコマンドを実行してください。

$ echo 123 >> test01.txt
$ git add .
$ git commit -m "[feature]B"
[feature 97a2337] [feature]B
 1 file changed, 1 insertion(+)

上記のコマンドの実行結果を見ると、コミットメッセージ[feature]BのコミットIDが97a2337...であることが分かります。

次に、featureブランチでtest02.txtを変更して、コミットします。以下のコマンドを実行してください。

$ echo 456 >> test02.txt
$ git add .
$ git commit -m "[feature]C"
[feature 336808d] [feature]C
 1 file changed, 1 insertion(+)

上記のコマンドの実行結果を見ると、コミットメッセージ[feature]CのコミットIDが336808d...であることが分かります。

次に、mainブランチに切り替えます。以下のコマンドを実行してください。

$ git checkout main
Switched to branch 'main'

mainブランチでtest01.txtを変更して、コミットします。以下のコマンドを実行してください。featureブランチとmainブランチで同じファイル(test01.txt)の同じ行を編集しているので、コンフリクトを発生させています。

$ echo ghi >> test01.txt
$ git add .
$ git commit -m "[main]D"
[main 09f6b76] [main]D
 1 file changed, 1 insertion(+)

上記のコマンドの実行結果を見ると、コミットメッセージ[main]DのコミットIDが09f6b76...であることが分かります。

次に、mainブランチでtest02.txtを変更して、コミットします。以下のコマンドを実行してください。featureブランチとmainブランチで同じファイル(test02.txt)の同じ行を編集しているので、コンフリクトを発生させています。

$ echo jkl >> test02.txt
$ git add .
$ git commit -m "[main]E"
[main d4aff40] [main]E
 1 file changed, 1 insertion(+)

上記のコマンドの実行結果を見ると、コミットメッセージ[main]EのコミットIDがd4aff40...であることが分かります。

次に、featureブランチに切り替えます。以下のコマンドを実行してください。

$ git checkout feature
Switched to branch 'feature'

これで事前準備終了です。この時点でログを確認すると以下のようになっています。

$ git log --oneline --all --graph
* d4aff40 (main) [main]E
* 09f6b76 [main]D
| * 336808d (HEAD -> feature) [feature]C
| * 97a2337 [feature]B
|/
* d7e79fb [main]A

上記の実行結果を図で表すと以下のようになります。

git rebaseでコンフリクトした際の解消方法01

では、実際にgit rebaseコマンドを実行してfeatureブランチをmainブランチにリベースしてみましょう。以下のコマンドを実行してください。

$ git rebase main
Auto-merging test01.txt
CONFLICT (content): Merge conflict in test01.txt # ← test01.txtでコンフリクトが発生
error: could not apply 97a2337... [feature]B
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 97a2337... [feature]B

今回、2つのコミットでコンフリクトが発生するようにしました。リベースでは各コミットごとにコンフリクトを解消する必要があります。なお、上記の実行結果を見ると、featureブランチの1回目のコミット(コミットメッセージ: [feature]B、コミットID: 97a2337...)でコンフリクトが発生しており、コンフリクトしているファイルがtest01.txtであることが分かります。

ではこれから、コンフリクトが発生している箇所を修正していきましょう。

コンフリクトが発生しているファイルを修正する

test01.txtを開くと、以下のようにコンフリクトが発生している箇所が表示されます。

abc
<<<<<<< HEAD
ghi
=======
123
>>>>>>> 97a2337 ([feature]A)

<<<<<<< HEAD から ======= までが、リベース先のブランチ(mainブランチ)で競合している箇所です。======= から >>>>>>> 97a2337 ([feature]A) までが、リベースしたいブランチ(featureブランチ)で競合している箇所です。

今回はmainブランチとfeatureブランチの両方の変更を取り込むことにして以下のように修正して保存します。

abc
ghi
123

ファイルの修正が終わったら、git addで対象のファイルをステージに追加する。

以下のコマンドを実行して、修正したtest01.txtをステージに追加します。

$ git add test01.txt

git rebase --continue を実行する

対象のファイルをステージに追加したら、以下のコマンドを実行します。

$ git rebase --continue

コミットメッセージを作成する

ステージに追加したtest01.txtをコミットするので、以下のようなコミットメッセージの編集画面が表示されます。コミットメッセージを編集するには、iキーを押してインサートモードに切り替えます。インサートモードでコミットメッセージの編集が終了したら esc キーを押し、:wqと入力し、Enterキーを押して保存します。1行目のデフォルトのコミットメッセージをそのまま使う場合は、インサートモードに切り替えずに:wqと入力し、Enterキーを押して保存します。

[feature]A

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# interactive rebase in progress; onto d4aff40
# Last command done (1 command done):
#    pick 97a2337 [feature]B
# Next command to do (1 remaining command):
#    pick 336808d [feature]C
# You are currently rebasing branch 'feature' on 'd4aff40'.
#
# Changes to be committed:
#       modified:   test01.txt
#

別コミットでもコンフリクトが発生していれば、1~4を繰り返す

1回目のコミット(コミットメッセージ: [feature]B、コミットID: 97a2337...)でのコンフリクトの解消は終わりましたが、今回は、コンフリクトが発生するコミットがもう1つ(コミットメッセージ: [feature]C、コミットID: 336808d...)あるので以下のようなレスポンスが表示されます。

$ git rebase --continue
[detached HEAD 06e0012] [feature]B
 1 file changed, 1 insertion(+)
Auto-merging test02.txt
CONFLICT (content): Merge conflict in test02.txt # ← test02.txtでコンフリクトが発生
error: could not apply 336808d... [feature]C
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 336808d... [feature]C

上記の実行結果を見ると、2回目のコミット(コミットメッセージ: [feature]C、コミットID: 336808d...)でコンフリクトが発生しており、コンフリクトしているファイルがtest02.txtであることが分かります。

test02.txtを開くと、以下のようにコンフリクトが発生している箇所が表示されます。

def
<<<<<<< HEAD
jkl
=======
456
>>>>>>> 336808d ([feature]C)

今回はmainブランチとfeatureブランチの両方の変更を取り込むことにして以下のように修正して保存します。

def
jkl
456

以下のコマンドを実行して修正したtest02.txtをステージに追加します。

$ git add test02.txt

対象のファイルをステージに追加したら、以下のコマンドを実行します。

$ git rebase --continue

ステージに追加したtest02.txtをコミットするので、コミットメッセージの編集画面が表示されますが、手順は同じなので省略します。

コミットメッセージの編集が完了したら、以下のようなレスポンスが表示されます。Successfully rebased and updatedと表示されればコンフリクトの解消が成功です。

$ git rebase --continue
[detached HEAD 60e7aa8] [feature]C
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/feature.

この時点でログを確認すると以下のようになり、リベースされていることが分かります。

$ git log --oneline --all --graph
* 60e7aa8 (HEAD -> feature) [feature]C
* 06e0012 [feature]A
* d4aff40 (main) [main]E
* 09f6b76 [main]D
* d7e79fb [main]A

上記の実行結果を図で表すと以下の様になります。

git rebaseでコンフリクトした際の解消方法02

git rebaseでコンフリクトを解消することができました。

なお、リベース(rebase)は、実際にはコミットを移動しているのではなく、既存のコミットを破棄して、新しいコミットを作成しています。新しく作成されたコミットは見た目は元のコミットと似ていますが、実際には別のコミットです。そのため、コミットのハッシュ値(コミットID)が変わります。上記のログを見ると、コミットメッセージ[feature]BのコミットIDはリベース前97a2337...だったのが、リベースすることによって06e0012...に変わっていることが分かります。

git rebaseの取り消し方法

コンフリクトの内容やコンフリクトの解消方法が分からない場合、リベースを取り消すことができます。以下のコマンドを実行すると、リベース操作が取り消され、リベース前の状態に戻ります。

git rebase --abort

本記事のまとめ

この記事では「git rebaseでコンフリクトした際の解消方法」と「git rebaseの取り消し方法」について、説明しました。

お読み頂きありがとうございました。