Git
目次 ▾ 第2版

3.2 Gitブランチ - 基本的なブランチとマージ

基本的なブランチとマージ

現実世界のワークフローにおけるブランチとマージの簡単な例を見てみましょう。以下の手順に従います。

  1. ウェブサイトで作業を行います。

  2. 作業中の新しいユーザーストーリー用のブランチを作成します。

  3. そのブランチで作業を行います。

この段階で、別の問題が緊急でホットフィックスが必要であるという連絡を受けたとします。次の手順を実行します。

  1. 本番ブランチに切り替えます。

  2. ホットフィックスを追加するためのブランチを作成します。

  3. テスト後、ホットフィックスブランチをマージして本番環境にプッシュします。

  4. 元のユーザーストーリーに戻り、作業を続けます。

基本的なブランチ操作

まず、プロジェクトに取り組んでおり、`master`ブランチにいくつかのコミットがあるとします。

A simple commit history
図18. 簡単なコミット履歴

会社の課題管理システムで、課題番号53に取り組むことにしました。新しいブランチを作成して同時に切り替えるには、`-b`スイッチ付きの`git checkout`コマンドを実行します。

$ git checkout -b iss53
Switched to a new branch "iss53"

これは以下のコマンドの省略形です。

$ git branch iss53
$ git checkout iss53
Creating a new branch pointer
図19. 新しいブランチポインタの作成

ウェブサイトで作業を行い、いくつかのコミットを行います。これにより、`iss53`ブランチが前進します。なぜなら、チェックアウトされている(つまり、`HEAD`がそれを指している)からです。

$ vim index.html
$ git commit -a -m 'Create new footer [issue 53]'
The `iss53` branch has moved forward with your work
図20. 作業によって`iss53`ブランチが進みました。

ウェブサイトに問題が発生し、すぐに修正する必要があるという連絡を受けたとします。Gitを使用すれば、行ったiss53の変更と一緒に修正プログラムをデプロイする必要がなく、修正プログラムを本番環境に適用する前にそれらの変更を元に戻すために多くの労力を費やす必要もありません。やるべきことは、masterブランチに切り替えるだけです。

ただし、その前に、作業ディレクトリまたはステージングエリアに、チェックアウトするブランチと競合するコミットされていない変更がある場合、Gitはブランチの切り替えを許可しません。ブランチを切り替える際には、クリーンな作業状態にするのが最善です。これを回避する方法(つまり、スタッシュとコミットの修正)については、後でスタッシュとクリーンアップで説明します。ここでは、すべての変更をコミット済みと仮定し、masterブランチに戻るとします。

$ git checkout master
Switched to branch 'master'

この時点で、プロジェクトの作業ディレクトリは、問題#53に取り組み始める前とまったく同じ状態になり、ホットフィックスに集中できます。これは重要な点です。ブランチを切り替えると、Gitは作業ディレクトリを、そのブランチに最後にコミットした時の状態にリセットします。ファイルの追加、削除、変更を自動的に行い、作業コピーが前回のコミット時のブランチの状態と一致するようにします。

次に、ホットフィックスを作成する必要があります。完了するまで作業するhotfixブランチを作成しましょう。

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'Fix broken email address'
[hotfix 1fb7853] Fix broken email address
 1 file changed, 2 insertions(+)
Hotfix branch based on `master`
図21. masterに基づくホットフィックスブランチ

テストを実行し、ホットフィックスが意図したものであることを確認し、最後にgit mergeコマンドを使用してhotfixブランチをmasterブランチにマージして本番環境にデプロイします。

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

そのマージで「高速転送」というフレーズに気付くでしょう。マージしたhotfixブランチが指すコミットC4は、現在いるコミットC2の直接の先祖であったため、Gitは単にポインタを前方に移動します。言い換えると、あるコミットを、最初のコミットの履歴に従って到達できるコミットとマージしようとすると、Gitはマージする分岐した作業がないため、ポインタを前方に移動することで処理を簡素化します。これを「高速転送」と言います。

変更は、masterブランチが指すコミットのスナップショットに含まれるようになり、修正プログラムをデプロイできます。

`master` is fast-forwarded to `hotfix`
図22. masterhotfixに高速転送されます。

非常に重要な修正プログラムをデプロイしたら、中断される前に実行していた作業に戻ることができます。ただし、最初にhotfixブランチは不要になったため(masterブランチが同じ場所を指しているため)、削除します。git branch-dオプションを付けて削除できます。

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

これで、問題#53の作業中のブランチに戻って作業を続けることができます。

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'Finish the new footer [issue 53]'
[iss53 ad82d7a] Finish the new footer [issue 53]
1 file changed, 1 insertion(+)
Work continues on `iss53`
図23. iss53での作業継続

ここで注意すべき点は、hotfixブランチで行った作業は、iss53ブランチのファイルに含まれていないことです。取り込む必要がある場合は、git merge masterを実行してmasterブランチをiss53ブランチにマージするか、後でiss53ブランチをmasterブランチにプルバックすることにするまで、これらの変更の統合を待つことができます。

基本的なマージ

問題#53の作業が完了し、masterブランチにマージする準備ができたとします。そのためには、先ほどhotfixブランチをマージしたように、iss53ブランチをmasterブランチにマージします。やるべきことは、マージ先のブランチをチェックアウトし、git mergeコマンドを実行することだけです。

$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

これは、先ほど行ったhotfixのマージとは少し異なります。この場合、開発履歴は以前のある時点から分岐しています。現在いるブランチのコミットがマージするブランチの直接の先祖ではないため、Gitは作業を行う必要があります。この場合、Gitは、ブランチの先端が指す2つのスナップショットと、その2つの共通の祖先を使用して、単純な3方向マージを実行します。

Three snapshots used in a typical merge
図24. 通常のマージで使用される3つのスナップショット

ブランチポインタを単純に前方に移動するのではなく、Gitはこの3方向マージの結果として新しいスナップショットを作成し、それを指す新しいコミットを自動的に作成します。これはマージコミットと呼ばれ、複数の親を持つ特殊なコミットです。

A merge commit
図25. マージコミット

作業がマージされたので、iss53ブランチは不要になりました。問題追跡システムで問題を閉じ、ブランチを削除できます。

$ git branch -d iss53

基本的なマージ競合

時々、このプロセスはスムーズに進みません。マージする2つのブランチで同じファイルの同じ部分を異なる方法で変更した場合、Gitはそれらをクリーンにマージできません。問題#53の修正が、hotfixブランチと同じファイルの同じ部分を変更した場合、次のようなマージ競合が発生します。

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

Gitは新しいマージコミットを自動的に作成していません。競合を解決している間にプロセスを一時停止しています。マージ競合が発生した後に、どのファイルがマージされていないかを調べるには、git statusを実行できます。

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")

マージ競合があり、解決されていないものはすべて、マージされていないものとしてリストされます。Gitは、競合のあるファイルに標準的な競合解決マーカーを追加するため、それらを手動で開き、競合を解決できます。ファイルには次のようなセクションが含まれています。

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

これは、HEADgit mergeコマンドを実行したときにチェックアウトしていたmasterブランチ)のバージョンがそのブロックの上部(=======の上のすべて)であり、iss53ブランチのバージョンが下部のすべてであることを意味します。競合を解決するには、どちらか一方を選択するか、内容を自分でマージする必要があります。たとえば、この競合を次のように置き換えることで解決できます。

<div id="footer">
please contact us at email.support@github.com
</div>

この解決策には各セクションの一部が含まれており、<<<<<<<=======>>>>>>>行は完全に削除されています。各競合ファイルのこれらのセクションをすべて解決したら、各ファイルに対してgit addを実行して、解決済みとしてマークします。ファイルをステージングすると、Gitで解決済みとしてマークされます。

グラフィカルツールを使用してこれらの問題を解決する場合は、git mergetoolを実行できます。これにより、適切なビジュアルマージツールが起動し、競合を案内します。

$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

デフォルト以外のマージツールを使用する場合は(このコマンドはmacOSで実行されたため、Gitはopendiffを選択しました)、 「one of the following tools」の後にリストされているすべてのサポートされているツールを確認できます。代わりに使用したいツールの名前を入力するだけです。

注記

複雑なマージ競合を解決するための高度なツールが必要な場合は、高度なマージでマージについて詳しく説明しています。

マージツールを終了すると、Gitはマージが成功したかどうかを尋ねます。スクリプトに成功したことを伝えると、ファイルがステージングされて解決済みとしてマークされます。git statusを再度実行して、すべての競合が解決されたことを確認できます。

$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

    modified:   index.html

それで満足していて、競合があったものがすべてステージングされていることを確認したら、git commitと入力してマージコミットを確定できます。コミットメッセージはデフォルトで次のようになります。

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#	.git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#	modified:   index.html
#

将来このマージを見る人に役立つと思われる場合は、マージを解決した方法の詳細をこのコミットメッセージに追加し、変更が明らかでない場合は行った変更の説明を追加できます。

scroll-to-top