Git
Chapters ▾ 2nd Edition

3.1 Git のブランチ機能 - ブランチとは

ほぼすべてと言っていいほどの VCS が、何らかの形式でブランチ機能に対応しています。 ブランチとは、開発の本流から分岐し、本流の開発を邪魔することなく作業を続ける機能のことです。 多くの VCS ツールでは、これは多少コストのかかる処理になっています。 ソースコードディレクトリを新たに作る必要があるなど、巨大なプロジェクトでは非常に時間がかかってしまうことがよくあります。

Git のブランチモデルは、Git の機能の中でもっともすばらしいものだという人もいるほどです。 そしてこの機能こそが Git を他の VCS とは一線を画すものとしています。 何がそんなにすばらしいのでしょう? Git のブランチ機能は圧倒的に軽量です。ブランチの作成はほぼ一瞬で完了しますし、ブランチの切り替えも高速に行えます。 その他大勢の VCS とは異なり、Git では頻繁にブランチ作成とマージを繰り返すワークフローを推奨しています。 一日に複数のブランチを切ることさえ珍しくありません。 この機能を理解して身につけることで、あなたはパワフルで他に類を見ないツールを手に入れることになります。 これは、あなたの開発手法を文字通り一変させてくれるでしょう。

ブランチとは

Git のブランチの仕組みについてきちんと理解するには、少し後戻りして Git がデータを格納する方法を知っておく必要があります。

使い始める で説明したように、Git はチェンジセットや差分としてデータを保持しているのではありません。そうではなく、スナップショットとして保持しています。

Git にコミットすると、Git はコミットオブジェクトを作成して格納します。このオブジェクトには、あなたがステージしたスナップショットへのポインタや作者・メッセージのメタデータ、そしてそのコミットの直接の親となるコミットへのポインタが含まれています。最初のコミットの場合は親はいません。通常のコミットの場合は親がひとつ存在します。複数のブランチからマージした場合は、親も複数となります。

これを視覚化して考えるために、ここに 3 つのファイルを含むディレクトリがあると仮定しましょう。3 つのファイルをすべてステージしてコミットしたところです。ステージしたファイルについてチェックサム (使い始める で説明した SHA-1 ハッシュ) を計算し、そのバージョンのファイルを Git ディレクトリに格納し (Git はファイルを blob として扱います)、そしてそのチェックサムをステージングエリアに追加します。

$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'

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

この時点で、Git リポジトリには 5 つのオブジェクトが含まれています。3 つのファイルそれぞれの中身をあらわす blob オブジェクト、ディレクトリの中身の一覧とどのファイルがどの blob に対応するかをあらわすツリーオブジェクト、そしてそのルートツリーおよびすべてのメタデータへのポインタを含むコミットオブジェクトです。

コミットおよびそのツリー
Figure 9. コミットおよびそのツリー

なんらかの変更を終えて再びコミットすると、次のコミットには直近のコミットへのポインタが格納されます。

コミットおよびその親
Figure 10. コミットおよびその親

Git におけるブランチとは、単にこれら三つのコミットを指す軽量なポインタに過ぎません。Git のデフォルトのブランチ名は master です。最初にコミットした時点で、直近のコミットを指す master ブランチが作られます。その後コミットを繰り返すたびに、このポインタは自動的に進んでいきます。

Note

Git の “master” ブランチは、特別なブランチというわけではありません。 その他のブランチと、何ら変わるところのないものです。 ほぼすべてのリポジトリが “master” ブランチを持っているたったひとつの理由は、 git init コマンドがデフォルトで作るブランチが “master” である (そして、ほとんどの人はわざわざそれを変更しようとは思わない) というだけのことです。

ブランチおよびそのコミットの歴史
Figure 11. ブランチおよびそのコミットの歴史

新しいブランチの作成

新しいブランチを作成したら、いったいどうなるのでしょうか? 単に新たな移動先を指す新しいポインタが作られるだけです。 では、新しい testing ブランチを作ってみましょう。 次の git branch コマンドを実行します。

$ git branch testing

これで、新しいポインタが作られます。 現時点ではふたつのポインタは同じ位置を指しています。

ふたつのブランチが同じ一連のコミットを指す
Figure 12. ふたつのブランチが同じ一連のコミットを指す

Git は、あなたが今どのブランチで作業しているのかをどうやって知るのでしょうか? それを保持する特別なポインタが HEAD と呼ばれるものです。 これは、Subversion や CVS といった他の VCS における HEAD の概念とはかなり違うものであることに注意しましょう。 Git では、HEAD はあなたが作業しているローカルブランチへのポインタとなります。 今回の場合は、あなたはまだ master ブランチにいます。 git branch コマンドは新たにブランチを作成するだけであり、 そのブランチに切り替えるわけではありません。

ブランチを指す HEAD
Figure 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 Fixed bug #1328 - stack overflow under certain conditions
98ca9 The initial commit of my project

“master” と “testing” の両ブランチが、コミット f30ab の横に表示されていることがわかります。

ブランチの切り替え

ブランチを切り替えるには git checkout コマンドを実行します。 それでは、新しい testing ブランチに移動してみましょう。

$ git checkout testing

これで、HEADtesting ブランチを指すようになります。

HEAD は現在のブランチを指す
Figure 14. HEAD は現在のブランチを指す

それがどうしたって? では、ここで別のコミットをしてみましょう。

$ vim test.rb
$ git commit -a -m 'made a change'
HEAD が指すブランチが、コミットによって移動する
Figure 15. HEAD が指すブランチが、コミットによって移動する

興味深いことに、testing ブランチはひとつ進みましたが master ブランチは変わっていません。 git checkout でブランチを切り替えたときの状態のままです。それでは master ブランチに戻ってみましょう。

$ git checkout master
チェックアウトによって HEAD が移動する
Figure 16. チェックアウトによって HEAD が移動する

このコマンドは二つの作業をしています。 まず HEAD ポインタが指す先を master ブランチに戻し、そして作業ディレクトリ内のファイルを master が指すスナップショットの状態に戻します。 つまり、この時点以降に行った変更は、これまでのプロジェクトから分岐した状態になるということです。 これは、testing ブランチで一時的に行った作業を巻き戻したことになります。 ここから改めて別の方向に進めるということになります。

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

気をつけておくべき重要なこととして、Git でブランチを切り替えると、作業ディレクトリのファイルが変更されることを知っておきましょう。 古いブランチに切り替えると、作業ディレクトリ内のファイルは、最後にそのブランチ上でコミットした時点の状態まで戻ってしまいます。 Git がこの処理をうまくできない場合は、ブランチの切り替えができません。

それでは、ふたたび変更を加えてコミットしてみましょう。

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

これで、プロジェクトの歴史が二つに分かれました (分裂した歴史 を参照ください)。 新たなブランチを作成してそちらに切り替え、何らかの作業を行い、メインブランチに戻って別の作業をした状態です。 どちらの変更も、ブランチごとに分離しています。ブランチを切り替えつつそれぞれの作業を進め、必要に応じてマージすることができます。 これらをすべて、シンプルに branch コマンドと checkout コマンドそして commit コマンドで行えるのです。

分裂した歴史
Figure 17. 分裂した歴史

この状況を git log コマンドで確認することもできます。 git log --oneline --decorate --graph --all を実行すると、コミットの歴史を表示するだけではなく、 ブランチポインタがどのコミットを指しているのかや、歴史がどこで分裂したのかも表示します。

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

Git におけるブランチとは、実際のところ特定のコミットを指す 40 文字の SHA-1 チェックサムだけを記録したシンプルなファイルです。 したがって、ブランチを作成したり破棄したりするのは非常にコストの低い作業となります。 新たなブランチの作成は、単に 41 バイト (40 文字と改行文字) のデータをファイルに書き込むのと同じくらい高速に行えます。

これが他の大半の VCS ツールのブランチと対照的なところです。 他のツールでは、プロジェクトのすべてのファイルを新たなディレクトリにコピーしたりすることになります。 プロジェクトの規模にもよりますが、これには数秒から数分の時間がかかることでしょう。 Git ならこの処理はほぼ瞬時に行えます。 また、コミットの時点で親オブジェクトを記録しているので、マージの際にもどこを基準にすればよいのかを自動的に判断してくれます。 そのためマージを行うのも非常に簡単です。 これらの機能のおかげで、開発者が気軽にブランチを作成して使えるようになっています。

では、なぜブランチを切るべきなのかについて見ていきましょう。