1. はじめに
当記事は全5回(+α)からなる Git入門シリーズ の第5回です。
今回は ブランチ について説明します。
2. ブランチとは?
ブランチ はコミットを分岐させることができる機能であり、分岐したコミットの流れの最新のコミットを指し示すポインタでもあります。
3. コミットの流れ
これまでの解説では触れてきませんでしたが、Gitには コミットの流れ とでも表現すべき概念が存在します。
例えば、あるリポジトリに対して、3回コミットを行ってみます。その後、コミット履歴を確認するためのlogコマンドを --graphオプション と組み合わせて実行します。
PS C:\Users\AGadget> git log --graph
* commit 814b2e6bf4a13dd1c46a7c6314a616fe2798e872 (HEAD -> master)
| Author: AGadget <AGadget@gmail.com>
| Date: Sat Aug 1 12:03:00 2020 +0900
|
| 3回目のコミットです。
|
* commit 8dd64f12e3b69ec76430bbdfb99e2b9f8675842a
| Author: AGadget <AGadget@gmail.com>
| Date: Sat Aug 1 12:02:00 2020 +0900
|
| 2回目のコミットです。
|
* commit bbf4ff0b9ba40cbbf15c6fb8fa4aa6ca0a3f6661
Author: AGadget <AGadget@gmail.com>
Date: Sat Aug 1 12:01:00 2020 +0900
1回目のコミットです。
--graphオプションについては説明していないので上記実行結果を見るのは初めてだと思います。--graphオプションを付けなかったときに出力される内容に加えて、アスタリスク記号と縦棒記号が出力されています。この第1回コミットから、第3回コミットに向かって伸びるような表現がコミットの流れを表しています。
さらに少しコミットを足してみます。
PS C:\Users\AGadget> git log --graph
* commit dd9d3f8ae6d44f6bd8979ef4a95bae144e5fb1c0 (HEAD -> master)
| Author: AGadget <AGadget@gmail.com>
| Date: Sat Aug 1 12:05:00 2020 +0900
|
| 5回目のコミットです。
|
| * commit a0e701a07cc170b5d04ba5a45d3a04621db01e40 (bugfix)
|/ Author: AGadget <AGadget@gmail.com>
| Date: Sat Aug 1 12:04:00 2020 +0900
|
| 4回目のコミットです。
|
* commit 61fab30c63cc55dd64dd8677f7c7591e68a6a06b
| Author: AGadget <AGadget@gmail.com>
| Date: Sat Aug 1 12:03:00 2020 +0900
|
| 3回目のコミットです。
|
* commit e5afb32a598403fc14e4c43537b11bff44521b14
| Author: AGadget <AGadget@gmail.com>
| Date: Sat Aug 1 12:02:00 2020 +0900
|
| 2回目のコミットです。
|
* commit 6beb18ed081c010ca5bad0d305b6c831571f01a9
Author: AGadget <AGadget@gmail.com>
Date: Sat Aug 1 12:01:00 2020 +0900
1回目のコミットです。
何やら奇妙な内容になりました。まるで、コミットが分岐したような、複数のコミットの流れができたような出力結果です。
また、コミットの流れの、それぞれの先頭にあたる第4回コミットと第5回コミットに、それぞれ master と bugfix という記号が付いているのも確認できます。masterは以前からありましたがbugfixとは何なのでしょうか。そして、これら記号は何を意味しているのでしょうか。
4. ブランチの正体
ブランチの正体は、このmasterとbugfixのような 任意の名前を付けることができるポインタ です。先ほどの例で見せたbugfixというポインタは裏で作成していおいた自作のブランチです。ちなみにbugfixという名前に意味はありません。何となくbugfixという名前にしました。
masterブランチは初めてコミットすると自動的に作成されるブランチです。正直、初めてコミットする前にブランチの作成を要求する仕様にしたが良いと思うのですが、Gitでは初コミット時にmasterブランチが自動作成される仕様になっているので仕方ありません。
4-1. タグとの違い
このように説明すると前回紹介した タグ と似ているようと思われるかもしれません。
実際、ブランチもタグも、その実態はただのポインタであり、特定のコミットを指し示すだけのものです。
ただ、ブランチはHEADのように(基本的に)コミットするたびに最新のコミットに移動するという性質があります。これは、あるコミットを指し示し続けるタグとは異なる性質です。
また、上記例でも確認できたようにブランチは分岐します。ブランチはかなりの独立性があり、あるブランチで作業した内容は基本的に他のブランチに影響を与えません。そのため多くの開発現場では完成品用ブランチ・開発用ブランチ・バグ修正用ブランチなど、複数のブランチを用意する傾向にあります。
5. ブランチの操作
ブランチの作成や削除には branchコマンド を、ブランチの切り替えには switchコマンド を使用するのをオススメします。
また、色々なことができるコマンドとして checkoutコマンド がありますが、ちょっと多機能すぎるので使いにくいと思います。
5-1. 確認
作成されたブランチの一覧を確認するには branchコマンド を単体で使用します。現在有効になっているブランチ名の先頭にはアスタリスク記号が付きます。
PS C:\Users\AGadget> git branch
あるいは --show-currentオプション を付けることで現在有効となっているブランチだけを表示させることができます。
PS C:\Users\AGadget> git branch --show-current
5-2. 作成
ブランチを作成することを ブランチを切る と言います。何故そのような言い回しをするのかは知りませんが、ともかく「切る」と表現するのです。
ブランチを切るにはbranchコマンドの引数にブランチ名を指定します。既に存在しているブランチを新たに切ることはできませんので注意してください。このコマンドを実行するとHEADの位置に新たなブランチが作成されます。
PS C:\Users\AGadget> git branch [ブランチ]
HEADではない――過去のコミットにブランチを新規作成したいときは第2引数に付与したコミットを指定します。
PS C:\Users\AGadget> git branch [ブランチ] [コミット]
5-3. 切り替え
有効なブランチを切り替えるには switchコマンド を使用してください。
PS C:\Users\AGadget> git switch [ブランチ]
5-4. リネーム
既に作成されたブランチをリネームするときはbranchコマンドと --moveオプション を組み合わせてください。
PS C:\Users\AGadget> git branch --move [旧ブランチ] [新ブランチ]
5-5. 削除
ブランチを削除するときはbranchコマンドと --deleteオプション を使います。
PS C:\Users\AGadget> git branch --delete [ブランチ]
6. コミットの全体像を確認
logコマンドに --branchesオプション と --graphオプション を付けることで、全てのブランチのコミットがグラフィカルに表示されます。個人的に、よく使うコマンドなのでオススメします。
PS C:\Users\AGadget> git log --branches --graph
7. マージ
ブランチは作ったら作りっぱなし、分岐したら分岐させっぱなし、ということはありません。不要になり破棄するケースはあるでしょうが、有効な(有用な)ブランチは、いずれかのタイミングで マージ するのが基本です。
7-1. マージとは?
マージ は異なる2つのブランチの状態・内容を1つに統合させる操作・機能のことです。
例えば、masterブランチに最初の完成版となるv1.0.0のコミットを保存したとします。
PS C:\Users\AGadget> git commit --message="最初の完成版となるv1.0.0です"
一旦完成したということで、Aさんはv1.0.0のバグ修正を、Bさんは新機能の実装を、それぞれ分担して行うことにしました。そこで、お互い専用のブランチを切って作業することにしました。
PS C:\Users\AGadget> git branch A/fix
PS C:\Users\AGadget> git branch B/feature
数日後、AさんもBさんも作業を完遂しました。この時点ではmasterブランチにはv1.0.0の状態が保存されており、A/fixブランチにはv1.0.0のバグ修正を行ったプログラムが、B/featureにはv1.0.0をベースに新機能を追加したプログラムが保存されています。
さて、この状況でAさん・Bさんが加えた修正をv1.0.0のソースと統合するにはどうすればよいでしょうか。1つには、何らかの方法で差分を確認し、masterブランチに保存されたソースを差分のとおりに書き直してコミットするというのも手でしょう。ただ、マージを使えばもっとスマートかつセクシーに統合を行うことができます。
7-2. mergeコマンド
マージを行うときは マージ元となるブランチ と マージ先となるブランチ という視点が必要です。それぞれプログラミングでいう親クラス・子クラスのような関係と言えます。先の例ですとmasterブランチが前者、A/fixブランチとB/featureブランチが後者となります。
マージするときは、まず、マージ先となるブランチに移動します。
PS C:\Users\AGadget> git switch master
そして mergeコマンド の引数にマージさせたいブランチを指定します。
PS C:\Users\AGadget> git merge A/fix
PS C:\Users\AGadget> git merge B/feature
マージ処理が成功すると統合されたコミットが作成され、リポジトリに保存されます。
8. コンフリクト
基本的にマージは成功する 頼むから成功してほしい のですが、時折 コンフリクト が生じることがあります。コンフリクトとはマージを実行したときに何らかの理由から統合に失敗すること、また、その状態を指します。
例を挙げましょう。まず、以下のようなファイルを用意します。
Hello world.
これをmasterブランチにコミットし、新たにsubブランチを切って、masterブランチとsubブランチの双方で変更を加えます。
Hello world.
master
Hello world.
sub
このように、元のファイルの同じ個所に変更を加えたファイル同士をマージした場合、コンフリクトが発生し、マージ処理が失敗します。statusコマンドを実行してみると Unmerged paths: や both modified: という記述があり、やはりコンフリクト状態になっていることが分かります。
PS C:\Users\AGadget> git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: sample.txt
no changes added to commit (use "git add" and/or "git commit -a")
8-1. マージを取り消す
コンフリクト状態になった場合、何らかのリアクションを行い、コンフリクトを解消せねばなりません。
最も安全なのはマージ処理を取り消すことです。
コンフリクト発生後、コンフリクト解消のためにファイルの編集などを行っていない場合はmergeコマンドと --abortオプション を組み合わせることでマージ処理を取り消すことができます。
PS C:\Users\AGadget> git merge --abort
コンフリクト解消のために何らかの編集を行った場合は resetコマンド を使用し、最初の状態に復元しましょう。
PS C:\Users\AGadget> git reset --hard HEAD
8-2. 手動マージ
取り消さないならば手動でマージを行う必要があります。
まず、statusコマンドなどで問題が発生したファイルを開きましょう。すると問題発生個所が次のように書き換えられていることが分かります。
Hello world.
<<<<<<< HEAD
master
=======
sub
>>>>>>> sub
元の内容に加え、謎の記号が書き込まれています。見方は何となく分かると思いますが <<<<<<< HEAD** と **>>>>>>> sub の間が問題のあった部分です。そして、それぞれのブランチの内容が ======= で区切られています。
この範囲を手動で修正し、ステージング・コミットします。今回は次のように修正してみました。
Hello world.
sub
master
修正できましたらステージング・コミットして、2つのコミットの内容をマージしたコミットを作成します。
PS C:\Users\AGadget> git add .\sample.txt
PS C:\Users\AGadget> git status
On branch master
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: sample.txt
PS C:\Users\AGadget> git commit .\sample.txt
hint: Waiting for your editor to close the file...
[master 0675cab] Merge branch 'sub' into master