Gitを使った分散開発管理8 – ブランチのマージ

2011.08.01

ブランチのマージと競合

前回ブランチやタグの基本をご紹介しました。今回はブランチ間のマージと競合時の対処を試してみます。
そもそもブランチを使用するということは、それぞれ別の変更を行いますし、その変更を共有するケースもあります。
ブランチ間のマージ方法はいくつかあり、今回は通常のマージとブランチの複数コミットを1つのコミットに圧縮する方法をご紹介します。
また、ブランチをマージするということは、同じファイルの同じ箇所を同時に変更してしまう可能性があるということです。
そこで、各ブランチで同じ箇所を変更した場合の対処方法についてもご紹介します。

マージを行ってみる

1.直接マージ
まずは、いちばん単純なマージ方法です。ブランチの履歴全体をマージ先のブランチへ取り込みます。
マージの確認のため、ブランチを1つ作成してそこへ2つコミットを行いましょう。

$ git checkout -b merge_test
Switched to a new branch 'merge_test'
$ git branch
  master
* merge_test

ファイルを2度修正しコミットします。まずはhello.htmlにリンクを1つ追加しました。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Hello Git!</title>
</head>
<body>
  <h2 id="toc-hello-git">Hello Git!</h2>
  <a href="http://git-scm.com/">Git Home.</a>
  </br>  ←追加
  <a href="http://mercurial.selenic.com/">Mercurial Home.</a>  ←追加
</body>
</html>

ファイルをステージングして1度目のコミットを行います。

$ git add hello.html 
$ git commit -m "add Mercurial link."
[merge_test cccf0c5] add Mercurial link.
 1 files changed, 2 insertions(+), 0 deletions(-)

コミット後にもう一度リンクを追加します。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Hello Git!</title>
</head>
<body>
  <h2 id="toc-hello-git1">Hello Git!</h2>
  <a href="http://git-scm.com/">Git Home.</a>
  </br>
  <a href="http://mercurial.selenic.com/">Mercurial Home.</a>
  </br> ←追加
  <a href="http://bazaar.canonical.com/en/">Bazaar Home.</a> ←追加
</body>
</html>

もう一度ステージングとコミットを行いましょう。

$ git add hello.html 
$ git commit -m "add Bazaar link."
[merge_test 0f35230] add Bazaar link.
 1 files changed, 2 insertions(+), 0 deletions(-)

これでmerge_testブランチはmasterブランチにはないコミットが2つ追加されました。
ではmasterブランチに対してマージをしてみましょう。マージをするにはまずマージ先にしたいブランチに切り替えます。

$ git checkout master
Switched to branch 'master'

masterブランチには先程のリンクを追加した2つのコミットはありません。
ここにmerge_testブランチの変更すべてをマージします。

$ git merge merge_test
Updating 1346355..0f35230
Fast-forward
 hello.html |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

git meager <マージしたいブランチ名>とすれば、そのブランチをマージすることができます。
マージ後にgit logで履歴みると、コミット履歴がそのままmasterブランチに入っているのがわかります。

2.圧縮コミット
次に、ブランチの複数の変更を1つのコミットとしてマージする、圧縮コミットをためしてみます。
まずは、さきほどのマージを取り消しましょう。

$ git reset --hard HEAD^^

これでmasterブランチはマージ前に戻りました。では、merge_testブランチの変更を1つのコミットとしてマージします。
まず、マージ対象のブランチを選択します。

$ git checkout master
Switched to branch 'master'

次に、--squashパラメータをつけてmergeコマンドを実行します。

$ git merge --squash merge_test
Updating 1346355..0f35230
Fast-forward
Squash commit -- not updating HEAD
 hello.html |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

この時点で、merge_testブランチの変更がすべてmasterブランチの作業ツリーへ適用され、ステージングされています。
まだコミットはされていません。コミットをしましょう。

$ git commit -m "add Bazaar and Mercurial links."

これで、merge_testブランチの変更が1つのコミットとして追加されました。
これ以外にも、任意のコミットだけを選択してマージする「チェリーピック」というマージ手法もあります。
詳しくはGitのリファレンスを参照してみてください。

競合への対処

最後に、それぞれのブランチで同じ箇所を変更して、競合がおこった場合の対象方法を説明します。
まずは先ほどと同じように、masterブランチの変更を元にもどしましょう。

$ git reset --hard HEAD^

そして、masterブランチファイルを下記のように変更します。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Hello Git!</title>
</head>
<body>
  <h2 id="toc-hello-git2">Hello Git!</h2>
  <a href="http://git-scm.com/">Git Home.</a>
  </br> ←追加
 <a href="http://subversion.apache.org/">Subversion Home.</a> ←追加
</body>
</html>

masterブランチでは2つ目のリンクとして、Subversionのリンクを追加しました。
ステージングしてコミットしましょう。そしてマージを行ってみます。

$ git merge merge_test
Auto-merging hello.html
CONFLICT (content): Merge conflict in hello.html
Automatic merge failed; fix conflicts and then commit the result.

hello.htmlでCONFLICT(競合)と言われてしまいました。
hello.htmlを開いてみると、下記のようになっています。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Hello Git!</title>
</head>
<body>
  <h2 id="toc-hello-git3">Hello Git!</h2>
  <a href="http://git-scm.com/">Git Home.</a>
  </br>
<<<<<<< HEAD
 <a href="http://subversion.apache.org/">Subversion Home.</a> 
=======
  <a href="http://mercurial.selenic.com/">Mercurial Home.</a>
  </br>
  <a href="http://bazaar.canonical.com/en/">Bazaar Home.</a>
>>>>>>> merge_test
</body>
</html>

<<<の後のコードは、現在選択されているブランチ(master)のコードです。
競合している他のブランチのコードは===の後に表示されており、>>>で終わることを示しています。
そして、現在選択されているmasterブランチのHEADとmerge_testブランチが競合しているのがわかります。
今回のようなシンプルな例の場合は、直接ファイルを編集してしまいましょう。
それぞれのブランチで追加されたリンクをすべて表示するようにしました。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Hello Git!</title>
</head>
<body>
  <h2 id="toc-hello-git4">Hello Git!</h2>
  <a href="http://git-scm.com/">Git Home.</a>
  </br>
  <a href="http://subversion.apache.org/">Subversion Home.</a> 
  </br>  
  <a href="http://mercurial.selenic.com/">Mercurial Home.</a>
  </br>
  <a href="http://bazaar.canonical.com/en/">Bazaar Home.</a>
</body>
</html>

修正がおわったら、変更をステージングしてコミットします。これで競合時のマージが完了しました。

まとめ

さて、今回は直接マージや圧縮コミットなど、マージの手法についてご紹介しました。
必要に応じて適切なマージ方法を選択してください。
また、競合の対処方法についてもご紹介しました。
競合がおこった場合、今回は手動で修正しましたが、git mergetoolと入力すれば、GUIのファイルマージ用ツールを使用できます。
こちらも必要に応じて使いやすいものを使用してください。
次回はgitの履歴変更についてやっていく予定です。