git merge(マージ)とは何か?使い方を実例で解説|-mオプションでコミットメッセージを指定する方法

git-prograshi(プロぐらし)-kv git/github
記事内に広告が含まれていることがあります。

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
point

mergeしたiss53ブランチの「C3」と「C5」のコミットはmasterブランチのコミットログに移動していないことに注目してください。

masterブランチでは「C4」のコミットの後に、iss53ブランチのコミット内容を統合した「C6」という新しいコミットを1つだけ作成しています。


(補足)トピックブランチとは?

派生(枝分かれ)したブランチのことです。

トピックブランチから枝分かれしたブランチをサブトピックブランチと呼んだりもします。

------------- 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 log --oneline --graphが何をしているかについては下記をご参考ください。

【Git】git logを一行かつグラフで表示するオプション「–-oneline –-graph」で表示される内容を実例で解説


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つの対処法

コンフリクトの対処方法は何をするかによって変わります。

  1. 自分でコードをいじってコンフリクトを解決する。
  2. 今回のマージをキャンセルする。


自分でコードを編集してコンフリクトを解決する

最も王道の解決方法は「自分でコードを編集してコンフリクトを解決する」ことです。

実行手順

手順は次の3ステップです。

  1. コンフリクトが発生しているファイルの内容を修正する。
  2. git add <ファイル名> で対象のファイルをステージングする。
  3. 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 ChangeCurrent Changeの内容を残す(Incomming Changeや余計な記号は消す)
Accept Incoming ChangeIncomming 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
合わせて読みたい

git log --oneline --graphが何をしているかについては下記をご参考ください。

【Git】git logを一行かつグラフで表示するオプション「–-oneline –-graph」で表示される内容を実例で解説


リモートレポジトリにプッシュできない場合

コンフリクトを修正した後に、リモートレポジトリにプッシュしようとすると、エラーメッセージが表示されて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

比較的よく使うオプションです。

タイトルとURLをコピーしました