minimize

事業拡大のため、新しい仲間を募集しています。
→詳しくはこちら

ブランチ

さて、ではいよいよ Git の中核機能、ブランチに入りましょう。
まずは現在のブランチを確認してみます。

$ git branch
* master

このように、最初はデフォルトで作成される master というブランチにいます。
では次に exp という名前でブランチを作成してみましょう。
※ 説明を簡単にするため、全ての変更をコミットしてある状態にしてから始めます

$ git branch exp
$ git branch
  exp
* master

できました。* は、現在作業中のブランチを表しています。
見ての通り、まだ現在のブランチは master のままです。
ではブランチを変更してみましょう。スイッチする、と言います。

$ git checkout exp
Switched to branch 'exp'

$ git branch
* exp
  master

checkout というコマンド名に違和感を感じるかもしれませんが、いずれ慣れるはずです。
これで、exp にスイッチしました。
ブランチを作ってすぐの状態なので、この時点はまだ master と exp ブランチの内容は全く同じです。

/path/prj1 (exp)
$ cat file1
hello world
hello world2
hello world3

シェルのコマンドプロンプトの上に、現在のブランチ名が表示されているのに気付いたでしょうか?
これは Git Bash が親切に付けてくれているのです。

では、ファイルを修正してみます。

$ echo "hello world exp" >> file1

そしたらコミットします。今回は説明を簡単にする都合上、commit -a を使います。

$ git commit -a -m "exp commit"

ここで、master ブランチに戻ってファイルの内容を確認してみます。

$ git checkout master
/path/prj1 (master)
$ cat file1
hello world
hello world2
hello world3

このように、先ほど修正した内容が反映されていません。
なぜなら先ほどの修正は exp ブランチで行ったものだからです。
master ブランチに戻った瞬間、file1 の内容は元の状態に戻ったのです。

checkout コマンドは「作業ディレクトリの全内容を特定のブランチの状態に切り替え」ます。
ですから checkout というコマンド名も納得できるのではないでしょうか。
蛇足ですが、前の章で説明したようにGit は常に「たった一つのカレントブランチ」を持ちます。
作業ディレクトリの一部だけカレントブランチを切り替えるようなことはできません。常に全体が切り替わります。

では、exp ブランチで行った変更を master ブランチに取り込みましょう。

$ git merge exp
Updating a8e6496..549cf9b
Fast-forward
 file1 |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

これで完了です。

/path/prj1 (master)
$ cat file1
hello world
hello world2
hello world3
hello world exp

この通りです。master ブランチと exp ブランチの内容は、この時点で同一になりました。
これがブランチで作業するときの基本的な流れです。

ブランチを作るとき、即座にそのブランチに移動したい場合は以下のコマンドがあります。

$ git checkout -b exp1

これは、以下と同じです。

$ git branch exp
$ git checkout exp1

マージ

では次に、二つのブランチで同一のファイルを変更してそれをマージしてみましょう。

作業前に master と exp の内容は同一で、変更中のファイルは存在しないものとします。
まずは master 側で、エディタを使って file1 を以下のような内容に変更します。

hello world1 master
hello world2
hello world3
hello world4

1行目に master の文字を追加しました。
ここでファイルをコミットし、exp にスイッチします。

$ git commit -a -m "master commit"
[master cf1af23] master commit
 1 files changed, 1 insertions(+), 1 deletions(-)
$ git checkout exp

今度は exp 側です。

hello world1
hello world2
hello world3
hello world4 exp

4行目に exp の文字を追加しました。
そしたらコミットし、master に戻ります。

$ git commit -a -m "exp commit"
$ git checkout master

ここでマージを実行しましょう。

$ git merge exp
Auto-merging file1
Merge made by the 'recursive' strategy.
 file1 |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

ファイルの内容を確認してみます。

$ cat file1
hello world1 master
hello world2
hello world3
hello world4 exp

見事に、両者の変更がマージされました。
最初のマージと比べて、出力結果が多少異なっていることに気付いたでしょうか。
これは Git が採用した「マージ方法」が異なるからです。
Git はいくつものマージ方法を持っていて、その場で最も適切なマージ方法を用いてマージを試みます。

実は最初のマージ処理時に表示された Fast-forward という処理、これは実はマージ処理ではありません。
処理の流れを追ってもらえばわかりますが、このときにした処理は exp で保存したファイルの内容を
ただそのまま master に取り込んだだけです。
内部的なポインタを進めたという意味で Fast-forward と呼ばれているようです。

しかしそれでも、マージが衝突(Conflict)することがあります。
そのときには手で衝突を解消する必要があります。

いずれ、exp ブランチも必要なくなるときが来るでしょう。
そしたら以下のコマンドでブランチを削除します。

$ git branch -d exp

変更中のファイルを残したままスイッチしたら

例えば exp で作業中、ファイルをコミットせずにブランチを master にスイッチしようとしたらどうなるでしょう。
ちょっとやってみます。

$ git checkout master
M       file1
Switched to branch 'master'

このように変更中のファイルが表示されます。
通常、ブランチをスイッチすると全てのファイルはスイッチ先のブランチの内容に置き換わります。
しかし、変更中のファイルにそれは成り立ちません。
変更中のファイルはそのままの内容で残り、スイッチは成功します。
これはファイルをステージに上げていても同じです。

上のような、サブブランチ→メインブランチへスイッチするときに
この動作は望ましくありません。
変更中のファイルが master に引き継がれたような形になってしまうからです。
もしこの段階で master ブランチ上でコミットを行うと、その変更は master ブランチに適用されてしまいます。

ですから、サブブランチ→メインブランチへスイッチするときには
「変更中の全てのファイルをコミットしてから」スイッチすることを徹底しましょう。
全ての変更されたファイルがコミットしてある状況のことを、「クリーンである」と言います。

$ git status
# On branch master
nothing to commit (working directory clean)

SVN などに慣れた人なら、こう言うかもしれません。
「まだこのファイル修正中だからコミットしたくないんだよなー」

コンパイルエラーが出る状態などでコミットしてはいけないという不文律は
通常のバージョン管理システムでは存在します。
しかし、Git ではいいのです。それがサブのプライベートブランチであるならば。
そのブランチはあなた以外の誰も使っていないのですから。

変更中のファイルを残したままスイッチしたら Part.2

では今度は、メインブランチ上で作業していることを想定してみます。

現在、改善Aに対応するために変更中のファイルがいくつかあります。
本来ならブランチAを作成してそこで作業していれば良かったのですが
うっかりメインブランチのままで作業してしまっています。
『まぁすぐに変更も終わってコミットできそうだし、このままでいいか』。

しかしここで、別の緊急対応が入りました!不具合Bを一刻も早く解決しなければなりません。
改善Aで変更しているファイルは一旦戻して、不具合B対応に移りたいのです。
『しまったーメインブランチで作業中だー。どうしよう…』

こんなときは、先ほどの特性を使うと便利です。
慌てず、新しいブランチを作成しましょう。

$ git checkout -b BRANCH_A
$ git commit -a -m "改善Aに対応中"

これで、変更中のファイルはAブランチに引き継がれ、無事コミットされました。
master ブランチは何も変更されていません。

$ git checkout MAIN_BRANCH

これで、メインブランチは最後にコミットしたときの状態でクリーンになりました。
後は安心して不具合B対応に取り掛かることができます。
※ 実はこういった場面ではもっと適切なソリューションもあります

マージしていないブランチ

例えば、exp でコミット後まだ master にマージされていない状態で exp ブランチを消そうとしたとします。

$ git branch -d exp
error: The branch 'exp' is not fully merged.
If you are sure you want to delete it, run 'git branch -D exp'.

このように、エラーが出てブランチは消せません。
この状態でブランチを消してしまうと、そのブランチ上で行ってきた変更が消えてしまうからです。

こういった変更を「到達不可能なコミット」と言います。
到達不可能なコミットを削除してしまうと、二度とそこには戻れません。
もしそれを承知でブランチを消す場合は、メッセージにも出ていますが -D オプションを使ってください。

ブランチを使おう

SVN では、リポジトリはサーバ上にありました。
よって、いくらシステム上簡単にブランチが作成できたとしても
「サーバ上にプライベートブランチを作るのはちょっと…」と躊躇う要因になり得ます。

例えば、別の人がリポジトリの内容を丸ごとチェックアウトした場合など
自分の作った(別の人には明らかに必要の無い)ブランチまでチェックアウトされてしまいます。
SVN でブランチは単なるディレクトリだからです。

また、プライベートブランチ上へのコミットもリビジョン番号をどんどん上げていきます。
SVN はツリー全体で共通の番号を持つからです。
これもやっぱり気になってしまいます。

Git では、ローカル上にリポジトリが存在します。
ですから、プライベートブランチは自分のローカル上にしか存在しません。
他の人はあなたのプライベートブランチを(探さない限りは)知ることがありません。

ブランチの使い方

実際の作業では、ブランチを使うと作業が捗る場面が度々あります。
上で挙げた例のように、作業時に別の作業に取り掛かるときです。

今やっているAという作業と、割り込みで入ってきたBという作業。
これらで変更するファイルがバッティングする場面ではもちろんですが、
そうでない場合ですらブランチを使いましょう。

Aで10個のファイルを修正して、Bで別の10個のファイルを修正したとしたら
あなたは即座にBの修正分だけコミットすることができますか?
もし自信を持って YES と言えるのなら、何も言うことはありません。

A と B を別のブランチに分け、その中で作業しましょう。
先ほども言いましたが、このときは master ではないブランチを二つ用意します。
そして A ⇔ B にブランチをスイッチするときにはその前に一旦全ファイルをコミットしておきましょう。