チャプター ▾ 第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がそれを指しているため)、iss53ブランチが前方に進みます。

$ 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をベースにしたホットフィックスブランチ

テストを実行し、ホットフィックスが意図したものであることを確認し、最後に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は単にポインタを前方に移動させます。別の言い方をすれば、最初のコミットの履歴をたどることで到達できるコミットと1つのコミットをマージしようとすると、マージする分岐した作業がないため、Gitはポインタを前方に移動させることで物事を単純化します。これを「fast-forward」と呼びます。

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

`master` is fast-forwarded to `hotfix`
図22. masterhotfixにfast-forward

非常に重要な修正が展開された後、中断される前にしていた作業に戻る準備ができました。しかし、まず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

これは、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はファイルをステージングして解決済みとしてマークします。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