Gitの中でもよく使うコマンドの一つに「git merge」があります。git mergeは指定したブランチの内容を取り込む(マージする)コマンドです。
ただし、何が起こっているかを理解していないとコンフリクトが発生したり、事故ったりします。
ここではgit mergeとは何か?どんな処理をしているのか?や実際の使い方、コンフリクトが発生したときの対処法についてまとめてます。
git mergeとは何か?
git mergeは元のブランチとトピックブランチ(分岐したブランチ)を統合した新しいコミットを作成します。
git rebaseとの違いは、トピックブランチ(分岐したブランチ)のコミットをとってこないことです。
このため、git merge後に生成されるコミットは1つのみです。
Githubを使ったプロジェクトでプルリクしそれが承認されたときに実行されるのが、git mergeです。
git mergeのイメージ図
例えば以下の図で、masterブランチの最新のコミットがC4だとします。そして、masterから分岐したブランチiss53の最新のコミットがC5だとします。
この状態で、iss53ブランチのコミットをmasterブランチにマージすると以下のようになります。
$ git merge iss53
(補足)トピックブランチとは?
派生(枝分かれ)したブランチのことです。
トピックブランチから枝分かれしたブランチをサブトピックブランチと呼んだりもします。
------------- master
\_________ topic
\____ subtopic
git mergeの使い方
git mergeを使う場合は、mergeしたいブランチを指定します。
git merge <ブランチ名>
実行すると、マージ後のコミットには「Merge branch ‘指定したブランチ名’ into 実行したブランチ名」というコミットメッセージが入ります。
実例
例えば、testブランチでaaブランチの内容を取り込みたい場合は次のようにします。
#testブランチに移動
git checkout test
#aaブランチの内容をマージ
$ git merge aa
Merge made by the 'recursive' strategy.
app/controllers/api/pj1/clients_controller.rb | 49 +++++++++--
app/javascript/app.vue | 62 +++++++++----
app/javascript/components/Modal.vue | 106 +++++++++++++++++++++++
(省略)
17 files changed, 550 insertions(+), 36 deletions(-)
create mode 100644 app/javascript/components/Modal.vue
create mode 100644 app/javascript/components/Top.vue
すると、git mergeの下に処理内容が表示されgit mergeが実行されます。
ログ履歴を確認すると、分岐したブランチが統合されているのがわかります。
$ git log --oneline --graph
* 3eefcfb (HEAD -> test) Merge branch 'aa' into test
|\
| * 0701d9d (tag: v3.0, origin/aa, aa) [U]docker-compose add webpack port 3035
| * a90d4ef (tag: v2.0) [U]content-security-policy(CSP) enable webpack-dev-server
マージ後の新しいコミット「3eefcfb」が一つだけ生成されて、「Merge branch ‘aa’ into test」というコミットメッセージが入力されています。
git mergeでオリジナルのコミットメッセージを作成する方法
git mergeを使った場合に、デフォルトの状態だと「Merge branch ‘指定したブランチ名’ into 実行したブランチ名」といったメッセージが自動で入ります。
-mオプションを使えば、これをオリジナルのコミットメッセージにすることもできます。
git merge <ブランチ名> -m "コミットメッセージの内容"
なお-mは--message
のショートオプションです。
実例
例えば、testブランチでaaブランチの内容を独自のコミットメッセージを付けて取り込みたい場合は次のようにします。
#testブランチに移動
git checkout test
#aaブランチの内容をマージ
$ git merge -m "オリジナルのコミットメッセージ" aa
Merge made by the 'recursive' strategy.
app/controllers/api/pj1/clients_controller.rb | 49 +++++++++--
app/javascript/app.vue | 62 +++++++++----
app/javascript/components/Modal.vue | 106 +++++++++++++++++++++++
(省略)
17 files changed, 550 insertions(+), 36 deletions(-)
create mode 100644 app/javascript/components/Modal.vue
create mode 100644 app/javascript/components/Top.vue
qgit mergeの下に処理内容が表示されgit mergeが実行されます。
ログ履歴を確認すると、分岐したブランチが統合され指定したコミットメッセージになっていることがわかります。
$ git log --oneline --graph
* 0341d3c (HEAD -> test) オリジナルのコミットメッセージ
|\
| * 0701d9d (tag: v3.0, origin/aa, aa) [U]docker-compose add webpack port 3035
| * a90d4ef (tag: v2.0) [U]content-security-policy(CSP) enable webpack-dev-server
git conflictが発生した場合の対処法
git mergeで他のブランチの情報を取り込む際にコンフリクト(conflict)が発生して処理を実行できない場合があります。
例えば次のようなエラーが発生します。
エラーの内容
$ git merge aa
Auto-merging db/schema.rb
CONFLICT (content): Merge conflict in db/schema.rb
Automatic merge failed; fix conflicts and then commit the result.
エラーの内容はやたらと長くて複雑に見えますが、重要な部分は次の部分です。
CONFLICT (content): Merge conflict in db/schema.rb
マージする際に、db/schema.rbというファイルでコンフリクトが発生したという内容です。
Merge conflict in <ファイル名>
のファイル名に注目してください。
コンフリクトの2つの対処法
コンフリクトの対処方法は何をするかによって変わります。
- 自分でコードをいじってコンフリクトを解決する。
- 今回のマージをキャンセルする。
自分でコードを編集してコンフリクトを解決する
最も王道の解決方法は「自分でコードを編集してコンフリクトを解決する」ことです。
実行手順
手順は次の3ステップです。
- コンフリクトが発生しているファイルの内容を修正する。
git add <ファイル名>
で対象のファイルをステージングする。git commit <ファイル名>
で対象のファイルをコミットする。
対象ファイルの修正
まずはコンフリクトが発生しているファイルを開きます。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
で状態を確認するとgit mergeが進行中であることが表示されます。
On branch test
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
ステージングしたファイルの一覧
変更後のファイルをコミットする
ファイルの内容が正しく修正できたら、git commit <ファイルパス>
を行います。
今回はコミットメッセージを「[F]conflict」とします。([F]はfixの意味で、コンフリクトを修正しましたという内容です)
$ git commit db/schema.rb -m "[F]conflict"
以上でコンフリクトの修正は完了です。
コミット履歴を確認すると、分岐していたブランチがmergeされて一本になっていることがわかります。
$ git log --oneline --graph
* 06d6762 (HEAD -> test) [F]conflict
|\
| * ec9e8b6 (aa) [F]add div
| * 0701d9d (origin/aa) [U]docker-compose add webpack port 3035
リモートレポジトリにプッシュできない場合
コンフリクトを修正した後に、リモートレポジトリにプッシュしようとすると、エラーメッセージが表示されて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 merge --abort
です。
git merge自体を無かったことにできる便利コマンドです。
コンフリクトの内容や修正方法がわからない場合はとりあえずgit merge --merge
をしておくと、無難にコンフリクトなしの状態に戻せます。
use "git merge --abort" to abort the merge
比較的よく使うオプションです。