章 ▾ 第2版

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

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

実世界で使うかもしれないワークフローでブランチとマージの簡単な例を見ていきましょう。次の手順に従います。

  1. ウェブサイトで作業を行う。

  2. 作業中の新しいユーザーストーリーのためにブランチを作成する。

  3. そのブランチで作業を行う。

この段階で、別の問題が致命的であり、ホットフィックスが必要だという連絡が入ります。次のことを行います。

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

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

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

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

基本的なブランチ操作

まず、プロジェクトで作業しており、すでにmasterブランチにいくつかのコミットがあるとしましょう。

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

会社が使用している課題追跡システムで、課題 #53 に取り組むことを決めたとします。新しいブランチを作成し、同時にそのブランチに切り替えるには、git checkoutコマンドに-bオプションを付けて実行します。

$ 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は作業ディレクトリを、そのブランチに最後にコミットしたときの状態に戻します。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をベースにしたホットフィックスブランチ

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

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

このマージで「fast-forward」(早送り)というフレーズに気づくでしょう。マージしたhotfixブランチが指すコミットC4が、現在いるコミットC2の真に先行していたため、Gitは単にポインタを先に進めるだけです。別の言い方をすると、あるコミットを、そのコミットの履歴を辿ることで到達できる別のコミットとマージしようとするとき、マージすべき分岐した作業がないため、Gitはポインタを先に進めることで処理を簡素化します。これを「fast-forward(早送り)」と呼びます。

これであなたの変更は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つのスナップショットと、それらの共通の祖先を使用して、単純な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")

マージの競合があり、解決されていないものはすべて「unmerged」(マージされていない)としてリストされます。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

これは、HEAD(マージコマンドを実行したときにチェックアウトしていた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