git rebase
(リベース)でconflict(コンフリクト)が発生してしまった時の対処法について実例を用いてわかりやすく解説しています。
エラーの内容
git rebaseでエラーが発生した場合次のようなエラーが内容されます。
$ git rebase origin/master
First, rewinding head to replay your work on top of it...
Applying: [A]article show
Using index info to reconstruct a base tree...
M db/schema.rb
Falling back to patching base and 3-way merge...
Auto-merging db/schema.rb
CONFLICT (content): Merge conflict in db/schema.rb
error: Failed to merge in the changes.
Patch failed at 0001 [A]article show
hint: Use 'git am --show-current-patch' to see the failed patch
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
エラーの内容はやたらと長くて複雑に見えますが、重要な部分は次の2つです。
CONFLICT (content): Merge conflict in db/schema.rb
error: Failed to merge in the changes.
マージする際に、db/schema.rbというファイルでコンフリクトが発生したという内容です。
Merge conflict in <ファイル名>
のファイル名に注目してください。
次に重要なのは最後の4行です。ここにコンフリクトの解決方法が3つ記されています。
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
詳しくは次の章で解説します。
コンフリクトの3つの対処方法
上記3つの対処方法は何をするかによって変わります。
- 自分でコードをいじってコンフリクトを解決する。
- 今回のリベースをスキップする。
- 今回のリベースをキャンセルする。
自分でコードを編集してコンフリクトを解決する
最も王道の解決方法は「自分でコードを編集してコンフリクトを解決する」ことです。
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
実行手順
手順は次の3ステップです。
- コンフリクトが発生しているファイルの内容を修正する。
git add <ファイル名>
で対象のファイルをステージングする。git rebase --continue
を実行する。
対象ファイルの修正
まずはコンフリクトが発生しているファイルを開きます。VSCodeを使っている場合はわかりやすくカラーで何が衝突しているかを教えてくれます。
<<<<<<と====と>>>>>>の間に囲まれている部分がポイントです。
<<<<<<<< HEAD (Current Change)
現在取り込もうとしている内容
=====
自分の変更内容
>>>>>>>> <コミットメッセージ> (Incoming Change)
上側のCurrent Changeの部分が取り込もうとしている内容です。自分のローカルとは違ってレポジトリの内容になるためCurrent Change(現在の変更内容)と言われています。
下側が自分のコミットです。新たに入れ込もうとしているのでIncomming Changeとなっています。
Current Changeの方が正しければ、下側や<<<<<<<といった不要な記号を消して、Current Changeの内容にします。Incomming Changeの場合はCurrent Changeを消します。
両方とも必要な場合はどちらも残します。
要は、編集を加えて最終形にすればOKです。
上記の例ではレポジトリの内容(上側)が正しいので、Current Changeを残して他を削除します。
VSCodeの便利オプション
VSCodeではコンフリクトが発生した時に便利なオプションが用意されています。
コンフリクトしている内容の上に表示されているオプションを選択すると、コードを自動編集してくれます。
オプション | 内容 |
---|---|
Accept Current Change | Current Changeの内容を残す(Incomming Changeや余計な記号は消す) |
Accept Incoming Change | Incomming Changeの内容を残す(Current Changeや余計な記号は消す) |
Accept Both Change | 両方の内容を残す(余計な記号は消す) |
Compare Changes | 左右のウィンドウで変更点を比較する |
それぞれ選択すると次のようになります。
コンフリクト中のコード
<<<<<<< HEAD
ActiveRecord::Schema.define(version: 2021_15_08_103381) do
=======
ActiveRecord::Schema.define(version: 2021_15_08_103372) do
>>>>>>> [A]article events
Accept Current Change
ActiveRecord::Schema.define(version: 2021_15_08_103381) do
Accept Incomming Change
ActiveRecord::Schema.define(version: 2021_15_08_103372) do
Accept Incomming Change
ActiveRecord::Schema.define(version: 2021_15_08_103381) do
ActiveRecord::Schema.define(version: 2021_15_08_103372) do
Comapre Changes
右側のウィンドウを閉じると、元のコンフリクト状態の画面に戻ります。
変更後のファイルをステージングする
ファイルの内容が正しく修正できたら、git add <ファイルパス>
を行います。
$ git add db/schema.rb
この状態でgit status
で状態を確認するとrebaseが進行中であることが表示されます。
$ git st
rebase in progress; onto a170a57c2
You are currently rebasing branch 'test' on 'a170a57c2'.
(all conflicts fixed: run "git rebase --continue")
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
ステージングしたファイルの一覧がずらっと並ぶ
git rebase –continue を実行する
最後にgit rebase --continue
を実行して完了となります。
$ git rebase --continue
Applying: [A]article events
Applying: [WIP]article yearly pv
Applying: [F]article_years model and job
git log でログを確認すれば、まだ取り込んでいなかったコミットが追加されていることが確認できます。
リモートレポジトリにプッシュできない場合
コンフリクトを修正した後に、リモートレポジトリにプッシュしようとすると、エラーメッセージが表示されてpushできない場合があります。
▼エラーメッセージの例
$ git push origin test
To github.com:xxx/yyy.git
! [rejected] test -> test (non-fast-forward)
error: failed to push some refs to 'git@github.com:xxx/yyy.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
これは既にリモートレポジトリに過去のファイルをプッシュしてしまった場合に発生します。
※基本的にコンフリクトを注意されるのは、リモートレポジトリにプッシュした後なので、上記のメッセージがでます。
対処法は、現在ローカルで行った修正内容が正しいので -f オプションを使って強制的にプッシュします。
$ git push origin test -f
Enumerating objects: 65, done.
Counting objects: 100% (65/65), done.
Delta compression using up to 12 threads
Compressing objects: 100% (50/50), done.
Writing objects: 100% (50/50), 7.32 KiB | 1.46 MiB/s, done.
Total 50 (delta 41), reused 0 (delta 0)
remote: Resolving deltas: 100% (41/41), completed with 15 local objects.
To github.com:xxx/yyy.git
+ 69751d9b1...6e98e6c92 test -> test (forced update)
以上で完了です。
最後にgit log --oneline
でコミットログを確認すると、自分のローカルとリモートレポジトリが、取り込もうとしていたブランチ(以下では origin/master)より上に来ていることがわかります。
$ git log --oneline
6e98e6c92 (HEAD -> test, origin/test) [F]article_years model and job
8dbb9dcb5 [WIP]article year pv
11ee776ea [A]article events
a170a57c2 (origin/master) Merge pull request #1422 from xxxx/zzz
今回のリベースをスキップする
コンフリクトした際に表示される2つ目のオプションはgit rebase --skip
です。
これを使うとコミットをスキップできます。
You can instead skip this commit: run "git rebase --skip".
あまり使うことがないオプションかなと思います。
今回のリベースをキャンセルする
コンフリクトした際に表示される3つ目のオプションはgit rebase --abort
です。
git rebase自体を無かったことにできる便利コマンドです。コンフリクトの内容や修正方法がわからない場合はとりあえずgit rebase --abort
をしておくと、無難にコンフリクトなしの状態に戻せます。
To abort and get back to the state before "git rebase", run "git rebase --abort".
比較的よく使うオプションです。