章 ▾ 第2版

3.1 Git ブランチ - ブランチの概要

ほとんどすべてのVCSには、何らかの形のブランチサポートがあります。ブランチとは、開発のメインラインから分岐し、メインラインを乱すことなく作業を続けることを意味します。多くのVCSツールでは、これはある程度コストのかかるプロセスであり、多くの場合、ソースコードディレクトリの新しいコピーを作成する必要があり、大規模なプロジェクトでは時間がかかることがあります。

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

ブランチの概要

Gitがどのようにブランチを扱うのかを本当に理解するためには、一歩引いて、Gitがデータをどのように保存しているかを検討する必要があります。

Gitとは?で述べたように、Gitはデータを変更セットや差分として保存するのではなく、スナップショットのシリーズとして保存します。

コミットを作成すると、Gitはステージングしたコンテンツのスナップショットへのポインタを含むコミットオブジェクトを保存します。このオブジェクトには、作者の名前とメールアドレス、入力したメッセージ、およびこのコミットの直前のコミット(その親または複数の親)へのポインタも含まれます。最初のコミットには親がなく、通常のコミットには親が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つのファイルのうちの1つの内容を表す)、ディレクトリの内容をリストし、どのファイル名がどの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`コミットのすぐ隣に`master`と`testing`ブランチがあるのがわかります。

ブランチの切り替え

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

$ git checkout testing

これにより、`HEAD`は`testing`ブランチを指すように移動します。

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'

これであなたのプロジェクト履歴は分岐しました(履歴の分岐を参照)。あなたはブランチを作成してそれに切り替え、そこでいくつかの作業を行い、その後メインブランチに戻って別の作業を行いました。これらの変更はどちらも別々のブランチに隔離されています。準備ができたらブランチ間を行き来して、それらをマージすることができます。そして、これらすべてをシンプルな`branch`、`checkout`、`commit`コマンドで行いました。

Divergent history
図17. 履歴の分岐

これは`git log`コマンドでも簡単に確認できます。`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文字と改行)をファイルに書き込むのと同じくらい迅速かつ簡単です。

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

なぜそうすべきなのかを見てみましょう。

注記
新しいブランチを同時に作成して切り替える

新しいブランチを作成し、同時にその新しいブランチに切り替えたいというのはよくあることで、これは`git checkout -b `という1つの操作で実行できます。

注記

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

  • 既存のブランチに切り替える: `git switch testing-branch`。

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

  • 以前チェックアウトしたブランチに戻る: `git switch -`。

scroll-to-top