Git
章 ▾ 第2版

3.1 Gitブランチ - ブランチ入門

ほぼすべてのバージョン管理システム(VCS)は、何らかの形のブランチサポートを持っています。ブランチとは、開発のメインラインから分岐し、メインラインを混乱させることなく作業を続けることを意味します。多くのVCSツールでは、これはややコストのかかるプロセスであり、多くの場合、ソースコードディレクトリの新しいコピーを作成する必要があるため、大規模なプロジェクトでは時間がかかります。

Gitのブランチモデルを「キラー機能」と呼ぶ人もいますが、それは確かにGitをVCSコミュニティの中で際立たせています。なぜこれほどまでに特別な機能なのでしょうか?Gitのブランチ作成方法は非常に軽量であるため、ブランチ操作はほぼ瞬時に行われ、ブランチ間の切り替えも同様に高速です。他の多くのVCSとは異なり、Gitは1日に複数回でも、頻繁にブランチを作成してマージするワークフローを推奨しています。この機能を理解し習得することで、強力で独自のツールが手に入り、開発方法を完全に変えることができます。

ブランチ入門

Gitのブランチ作成方法を本当に理解するには、一歩下がってGitがどのようにデータを保存しているかを調べることが必要です。

Gitとは何か?で説明したように、Gitは変更セットや差分としてデータを保存するのではなく、一連のスナップショットとしてデータを保存します。

コミットを作成すると、Gitはステージングしたコンテンツのスナップショットへのポインタを含むコミットオブジェクトを保存します。このオブジェクトには、作成者の名前とメールアドレス、入力したメッセージ、そしてこのコミットの直前にあるコミット(親コミット)へのポインタも含まれています。最初のコミットには親コミットが0個、通常のコミットには1個、2つ以上のブランチのマージによって生成されるコミットには複数個の親コミットがあります。

これを視覚化するために、3つのファイルを含むディレクトリがあり、それらをすべてステージングしてコミットすると仮定しましょう。ファイルをステージングすると、それぞれについてチェックサム(Gitとは何か?で説明したSHA-1ハッシュ)が計算され、そのファイルのバージョンがGitリポジトリに保存され(Gitではblobと呼ばれます)、そのチェックサムがステージングエリアに追加されます。

$ git add README test.rb LICENSE
$ git commit -m 'Initial commit'

git commitを実行してコミットを作成すると、Gitは各サブディレクトリ(この場合はルートプロジェクトディレクトリのみ)のチェックサムを計算し、Gitリポジトリにツリーオブジェクトとして保存します。次にGitは、メタデータとルートプロジェクトツリーへのポインタを持つコミットオブジェクトを作成します。これにより、必要に応じてそのスナップショットを再作成できます。

Gitリポジトリには、5つのオブジェクトが保存されます。3つのblob(それぞれ3つのファイルのいずれかのコンテンツを表す)、ディレクトリのコンテンツをリストし、どのファイル名がどのblobとして保存されているかを指定する1つのtree、そしてそのルートツリーとすべてのコミットメタデータへのポインタを持つ1つのcommitです。

A commit and its tree
図9. コミットとそのツリー

変更を加えて再度コミットすると、次のコミットはその直前にあったコミットへのポインタを保存します。

Commits and their parents
図10. コミットとその親コミット

Gitにおけるブランチは、単にこれらのコミットのいずれかへの軽量で移動可能なポインタです。Gitのデフォルトのブランチ名はmasterです。コミットを始めると、最後に作成したコミットを指すmasterブランチが与えられます。コミットするたびに、masterブランチポインタは自動的に前方に移動します。

注記

Gitの "master" ブランチは特別なブランチではありません。他のブランチと全く同じです。ほとんどのリポジトリにmasterブランチが存在する唯一の理由は、git initコマンドがデフォルトで作成し、ほとんどの人が変更しないためです。

A branch and its commit history
図11. ブランチとそのコミット履歴

新しいブランチの作成

新しいブランチを作成すると何が起こるのでしょうか?それは、移動するための新しいポインタを作成することです。例えば、testingという新しいブランチを作成したいとします。これはgit branchコマンドで行います。

$ git branch testing

これにより、現在いるのと同じコミットへの新しいポインタが作成されます。

Two branches pointing into the same series of commits
図12. 同じコミット系列を指す2つのブランチ

Gitは現在どのブランチにいるかをどのように認識するのでしょうか?それはHEADと呼ばれる特別なポインタを保持しています。これは、SubversionやCVSなど、あなたが慣れている他のVCSのHEADの概念とは大きく異なることに注意してください。Gitでは、これは現在いるローカルブランチへのポインタです。この場合、あなたはまだmasterブランチ上にいます。git branchコマンドは新しいブランチを作成しただけで、そのブランチに切り替えたわけではありません。

HEAD pointing to a branch
図13. ブランチを指すHEAD

これは、ブランチポインタがどこを指しているかを示す単純なgit logコマンドを実行することで簡単に確認できます。このオプションは--decorateと呼ばれます。

$ git log --oneline --decorate
f30ab (HEAD -> master, testing) Add feature #32 - ability to add new formats to the central interface
34ac2 Fix bug #1328 - stack overflow under certain conditions
98ca9 Initial commit

f30abコミットのすぐ隣にmastertestingブランチがあることがわかります。

ブランチの切り替え

既存のブランチに切り替えるには、git checkoutコマンドを実行します。新しいtestingブランチに切り替えてみましょう。

$ git checkout testing

これにより、HEADtestingブランチを指すように移動します。

HEAD points to the current branch
図14. HEADは現在のブランチを指しています

その意味は何でしょうか?では、もう一つのコミットを行いましょう。

$ vim test.rb
$ git commit -a -m 'Make a change'
The HEAD branch moves forward when a commit is made
図15. コミットが行われるとHEADブランチは前方に移動します

これは興味深いことです。なぜなら、これでtestingブランチは先に進みましたが、masterブランチはまだ、ブランチを切り替えるためにgit checkoutを実行したときのコミットを指しているからです。masterブランチに戻りましょう。

$ git checkout master
注記
git logは常にすべてのブランチをすべて表示するわけではありません

今すぐgit logを実行すると、作成したばかりの"testing"ブランチはどこに行ったのか疑問に思うかもしれません。出力に表示されないからです。

ブランチが消えたわけではありません。Gitは単に、あなたがそのブランチに関心を持っていることを知らず、あなたが関心を持っていると思っているものを表示しようとしているだけです。言い換えれば、デフォルトでは、git logはチェックアウトしたブランチ以下のコミット履歴のみを表示します。

目的のブランチのコミット履歴を表示するには、明示的に指定する必要があります: git log testing。すべてのブランチを表示するには、git logコマンドに--allを追加します。

HEAD moves when you checkout
図16. チェックアウトするとHEADが移動します

このコマンドは2つのことを行いました。HEADポインタをmasterブランチを指すように戻し、作業ディレクトリのファイルをmasterが指すスナップショットに戻しました。これはまた、この時点以降に行う変更は、プロジェクトの古いバージョンとは異なるようになることを意味します。本質的に、testingブランチで行った作業を巻き戻して、別の方向に進めることができます。

注記
ブランチを切り替えると、作業ディレクトリのファイルが変更されます

Gitでブランチを切り替えると、作業ディレクトリのファイルが変更されることに注意することが重要です。古いブランチに切り替えると、作業ディレクトリはそのブランチで最後にコミットしたときのように戻されます。Gitがきれいに実行できない場合、切り替えは許可されません。

いくつかの変更を加えて、再度コミットしてみましょう。

$ vim test.rb
$ git commit -a -m 'Make other changes'

これで、プロジェクトの履歴は分岐しました(分岐履歴を参照)。ブランチを作成して切り替え、そこで作業を行い、メインブランチに戻って別の作業を行いました。これらの変更は、それぞれ別のブランチに隔離されています。ブランチ間を自由に切り替えることができ、準備ができたらマージできます。そして、それらすべてを単純なbranchcheckoutcommitコマンドで行いました。

Divergent history
図17. 分岐履歴

git log --oneline --decorate --graph --allコマンドでも簡単に確認できます。このコマンドを実行すると、コミットの履歴が出力され、ブランチポインタの位置と履歴の分岐場所が表示されます。

$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) Make other changes
| * 87ab2 (testing) Make a change
|/
* f30ab Add feature #32 - ability to add new formats to the central interface
* 34ac2 Fix bug #1328 - stack overflow under certain conditions
* 98ca9 Initial commit of my project

Gitのブランチは実際には、それが指すコミットの40文字のSHA-1チェックサムを含む単純なファイルであるため、ブランチの作成と削除は安価です。新しいブランチの作成は、ファイルに41バイト(40文字と改行)を書くのと同じくらい迅速かつ簡単です。

これは、プロジェクトのすべてのファイルを第2のディレクトリにコピーすることを伴う、ほとんどの古いVCSツールがブランチを行う方法とは対照的です。プロジェクトのサイズによっては、数秒から数分かかる場合がありますが、Gitでは常に瞬間的です。また、コミット時に親を記録しているため、マージのための適切なマージベースの検索は自動的に行われ、一般的に非常に簡単に行えます。これらの機能は、開発者が頻繁にブランチを作成して使用することを促します。

その理由を見てみましょう。

注記
新しいブランチの作成と同時に切り替え

新しいブランチを作成し、同時にその新しいブランチに切り替えたいことは一般的です。これは、git checkout -b <newbranchname>で1つの操作で行うことができます。

注記

Gitバージョン2.23以降では、git checkoutの代わりにgit switchを使用して、

  • 既存のブランチに切り替えることができます: git switch testing-branch

  • 新しいブランチを作成して切り替えることができます: git switch -c new-branch-cフラグは作成を表し、--createという完全なフラグも使用できます。

  • 以前にチェックアウトしたブランチに戻ることができます: git switch -

scroll-to-top