チャプター ▾ 第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つのファイルのうち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ツールがブランチを行う方法とは大きく対照的です。古いVCSツールでは、プロジェクトのすべてのファイルを別のディレクトリにコピーするため、プロジェクトのサイズによっては数秒から数分かかることがありますが、Gitでは常に瞬時に処理が完了します。また、コミット時に親を記録するため、マージのための適切なマージベースの検索は自動的に行われ、通常は非常に簡単です。これらの機能は、開発者が頻繁にブランチを作成して使用することを奨励するのに役立ちます。

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

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

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

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

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

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

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

scroll-to-top