NITICNoobsによるGit/GitHub講座 第4回 〜並行世界を創世する〜

フライさん(loxygen)です。[限界開発鯖](https://approvers.dev/)でのだのだ言っています。~~フライさんにおまかせなのだ！~~
説明はあまり得意ではありません。

**※この記事は茨城高専生向けに書かれたものです。**

# お説明

ここからちょっと難しくなってきます。対戦よろしくおねがいします。
今回は「**ブランチ**」について解説していきます。このお話は、プログラマーを***世界を操る神***として例えるとしやすい(当社比)ので、***神***になりきって読んでくださると読みやすいかと思います。

## ブランチってどんなの？

ブランチというのは、**履歴を分岐して記録していくため**の機能です。噛み砕くと**並行世界**のようなものでしょうか。
ブランチを新しく作成することを「**ブランチを切る**」といいます。
「**ブランチを切る**」のは「**並行世界を新しく創る**」と同義ですね。

### どういうタイミングで切っていくんや？

大体は、**一つの機能ごと**、または**機能の小さな集まりごと**にブランチを切っていきます。テキストエディタを例に取るなら、

- **テキストを編集する**機能を実装するブランチ
- **ファイルを読み込み・保存する**機能を実装するブランチ

もちろん他にもいろいろなやり方があります。

### masterブランチ

リポジトリを作成すると、必ず一つ、**`master`という名前**がついたブランチが生成されています。いわゆる**現実世界**です。
masterブランチが**メインのブランチ**になります。プログラムの完成品を置いていく場所ですね。

### マージ

並行世界でたくさん変更を加えたあとは、**まとめて現実世界にその変更を反映させます**。並行世界だけに変更を加えても現実世界は何の変化もないですからね。
この「**まとめて現実世界にその変更を反映させる**」ことを**マージ(merge)**と言います。

## ブランチの存在意義

### 変更履歴が必然的に美しくなる

ブランチを切らないで、何も考えずに実装していったとしましょう。
以下の図はコミット履歴を表した図です。
一つの○が一つのコミットで、線がコミット同士のつながりです。同じ色のコミットは同じ機能を実装しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413967/a62734a3-8d36-141c-f2f7-57247f401932.png)

複数の機能の実装が入り組んでしまっていて、**履歴がわかりにくく**なっています。
今は問題ないかも知れませんが、後から見たり、また他の人が見たりしたときに「:thinking:」となる可能性は十分あります。

ここで**ブランチ**を使用してみましょう。


一つ、または機能のまとまりごとにブランチを切っていくと説明しましたね。機能(＝同じ色)ごとにブランチを切ってみましょう。
具体的には、こんな感じになります:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413967/311a1999-1116-2bc8-c25b-c1ced2369906.png)
masterブランチの編集履歴が**単純化**されていますね。複数の機能が混じることなく、機能実装ごとにコミットが分かれています。
上の図と比べたら大分キレイになっていますね。
あまり詳しく考えずに「嗚呼確かにそうかもな」ってだけ思って頂ければ幸いです。

### 品質を保証できる

ブランチがあることのもうひとつの利点は、**`master`ブランチ上のプログラムの品質を保証できる**ことです。
現実世界でバグが起こったら嫌ですよね。SCP[^1]どころの騒ぎじゃなくなるかもしれません。
そこで、各々のブランチ(＝並行世界)でバグを潰したあとに`master`ブランチにマージします。

するとどうなるでしょうか。**`master`ブランチ(＝現実世界)のバグはかなり抑えられた状態になります**。つまりこれは、**`master`ブランチ上のプログラムの品質が保証されている**ということになりますね。
これがもう一つの利点です。


# キーボードかたかたしよう

そろそろ用語の解説に飽きてきたので、ブランチ切ってコミットしてマージしてみましょう。
作業したいフォルダでGit Bashを開いておいてくださいね。


## リポジトリを生成してInitial Commitする

リポジトリがないとマジで何も始まらないので作ります。


以下のコマンドを入力してEnterを押してください。
こるくも言っていましたが、**`$`は入力しないでください**ね。

```bash:Bash
$ git init (任意のリポジトリ名)
```

「`(任意のリポジトリ名)`」は、お好きな名前にしてください。

Enterを押すと

```
Initialized empty Git repository in /path/to/your/selection/(任意のリポジトリ名)/.git/
```

と表示されます。新しいリポジトリが生成されました。
リポジトリに移動しましょう。

```bash:Bash
$ cd (任意のリポジトリ名)
```


ブランチは**コミットを単位に**切っていきます。つまりコミットがないとブランチを切ることができません。というわけでInitial Commitをしちゃいましょう。

リポジトリに`hello.txt`という名前で以下のファイルを生成して保存してください。

```text:hello.txt
I am the smartest person.
```

そうしたらInitial commitします。
`Add`を忘れないでくださいね。筆者は忘れてしまいました。

```bash:Bash
$ git add --all
```

`git status`を確認する癖をつけましょう。つけて困ることはありません。

```bash:Bash
$ git status
```

すると以下の出力を得ることができます。

```bash:Bash
On branch add-new-func
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   hello.txt
```

「緑色で`new file:   hello.txt`」 …「次のコミットで `hello.txt`が新しいファイルとして登録されるで！」と出力されていますね。新しく`hello.txt`というファイルを作ったので正常です。

もし`hello.txt`が赤く表示されていれば**`add`し忘れています**。しましょう。
このままコミットして問題なさそうなのでコミットしちゃいましょう。

```bash:Bash
$ git commit -m "Initial Commit"
```

以下のように表示されます。

```
[master (root-commit) 1234567] Initial Commit
 1 file changed, 1 insertion(+)
 create mode 100644 hello.txt
```

これでこのリポジトリのコミット履歴はこうなりました：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413967/3824239b-e4eb-2720-f62d-d453c972a33e.png)
Initial Commitだけがある状態です。HEAD(≒現在位置)もInitial Commitにいます。

## ブランチを切る

**これまで入力と出力を分けて書いていましたが、ここから一緒に書いてしまいます。**
入力する際は、**`$`から行末まで**を入力してください。

ここで並行世界を創世しましょう。新しいコマンドのお出ましです。
以下のコマンドを入力してEnterを押してください。

```bash:Bash
$ git branch add-new-func
```

このコマンドを叩くと、**`add-new-func`というブランチが切られ**ます。新しく**並行世界を創世する**わけです。
並行世界を創世しただけじゃ何も始まらないので、創世した並行世界に移動しましょう。

```bash:Bash
$ git checkout add-new-func
Switched to branch 'add-new-func'
```

このコマンドを叩くと**`add-new-func`ブランチに移動します**。

ちなみに以下のコマンドを打つと上記の2つの操作を同時に行うことができます。

```bash:Bash
$ git checkout -b add-new-func
Switched to a new branch 'add-new-func'
```

通常はこちらを使います。一行で済んで楽ですからね。

試しに`git status`を叩いてみましょう。

```bash:Bash
$ git status
On branch add-new-func
nothing to commit, working tree clean
```

`On branch add-new-func`、つまり「`add-new-func`ブランチにいますよ」と表示されていますね。

さて、一連のコマンドを打った後、コミット履歴はこうなりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413967/e87e572a-2b28-206a-7acd-ebe02f539399.png)

**・・・？**なんも変わってないやん。
なんか`add-new-funcブランチ`って文字列は認識できるけどそれ以外はなんも変わってないやん。　

この時点ではまだコミット履歴上の変化はありません。それではここにコミットをしてみましょう。

## 並行世界に歴史を刻む

先程作ったファイルを以下のように変更してください。

```text:hello.txt
I am the kindest human.
```

そしてコミットします。コミットメッセージは何でも構いません。

```bash:Bash
$ git add --all

$ git status
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   hello.txt

$ git commit -m "Edit hello.txt"
[add-new-func c48284a] Edit hello.txt
 1 file changed, 1 insertion(+), 1 deletion(-)
```

これでコミット履歴はこうなっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413967/239f2628-cb52-2649-b0c1-311e117c0a30.png)

**あっ！？なんか縦にズレとる！**
そうです。**並行世界で変更を加えたのでちょっとズレました**。

## 現実世界はどうなってるん？

並行世界(＝`master`から切ったブランチ)で変更を加えましたが、現実世界(＝`master`ブランチ)はどうなってるんでしょうか。一旦`master`に戻ってどんなもんか見てみましょう。

```bash:Bash
$ git checkout master
Switched to brach 'master'
```

このコマンドで**現実世界に帰る**ことができます。**`-b`はいらない**ので注意してください。
ちなみに、ブランチ間を移動することを「**チェックアウト**」と呼びます。

ここで、`hello.txt`の中身を見てみましょう。「`cat`」というコマンドを使用してみます。
`cat`コマンドは、**ファイルの中身を見る**コマンドです。
全てGit Bashで完結させたかったので使っていますが、もちろんお好みのエディタで大丈夫です。以降についても同様です。

```bash:Bash
$ cat hello.txt
I am the smartest person.
```

`cat`のあとにファイル名を入力して`Enter`すると、そのファイルの中身が表示されます。
お気づきかもしれませんが、**並行世界で加えた変更がありません**。
でも消し飛ばされたわけではありません。並行世界に帰ってみましょうか。

```bash:Bash
$ git checkout add-new-func
Switched to branch 'add-new-func'
```

同じコマンドを打って`hello.txt`を見てみます。

```bash:Bash
$ cat hello.txt
I am the kindest person.
```

**変更が帰ってきました**。

そうです。**並行世界で変更を加えても、現実世界には反映されないようになってる**んです。
そりゃそうだろと言えばそりゃそうなんですが、なんだかすごいですよね。本当に世界を行き来してるみたいじゃありませんか？

# お練習

一旦masterブランチに帰りましょう。

```bash:Bash
$ git checkout master
Switched to branch 'master'.
```

そうしたら、以下の操作をしてみてください。

```
・add-new-func-2ブランチを切る。
・hello.txtの中身を以下のように変更する:
  I am the greatest people.
・自由なコミットメッセージでコミットする。
```
**[第1回](https://qiita.com/Colk-tech/items/8d4b28b9001b862eeb99)** インストール編
**[第2回](https://qiita.com/Colk-tech/items/f0817df7c6fdc9a7f283)** 設定編
**[第3回](https://qiita.com/Colk-tech/items/e75c61146cabc887ad29)** 実際に使ってみよう-基本技能編
以下に答えを書いておきます。行き詰まったり、正解を確認したいときに見てくださいね。

<details><summary>お答え</summary><div>


### Answer

まずブランチを切りましょう。

```bash:Bash
$ git checkout -b add-new-func-2
Switched to a new branch 'add-new-func-2'.
```

そしたらファイルの中身を指示されたとおりに変更します。お好みの方法でどうぞ。

保存したらコミットします。

```bash:Bash
$ git add --all

$ git status
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   hello.txt

$ git commit -m "(何かしらメッセージをどうぞ)"
[add-new-func d2ad39f] (入力したメッセージ)
 1 file changed, 1 insertion(+), 1 deletion(-)
```

これで完成です！

</div></details>

このルーチンは生涯で$N$回 ($N \geqq 任意の数字$)回やるので、なんとしてでも身につけておきましょう。

# マージする

並行世界に変更を加えただけでは現実世界(`master`ブランチ)に変更は反映されません。というわけでマージもしてみましょう。

## マージの流れ

1. マージしたい**先**のブランチにチェックアウトします。
2. `git merge <マージするブランチ>`をします。
3. **上手く行けば**マージ完了です！

この流れでマージします。上手く行かなかった場合については、次回説明します。

## 実際にマージしてみる。

今のところ、コミット履歴は以下のようになっているはずです:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413967/3e578eaa-f19a-7b71-c1dc-773bc8e7ed22.png)
`master`ブランチから`add-new-func`ブランチと`add-new-func-2`ブランチが生えている状態です。`HEAD`は動かすのでどこでも大丈夫です。
それでは並行世界の歴史を現実世界に反映していきましょう。

まず、マージする**先**のブランチにチェックアウトします。
今回はmasterブランチにMergeしたいので、masterブランチにチェックアウトしましょう。

```bash:Bash
$ git checkout master
Switched to branch 'master'
```

一応ここで`hello.txt`を見ておきます。

```bash:Bash
$ cat hello.txt
I am the smartest person.
```

並行世界上では変わっていますが、現実世界上では変わっていませんね。***smartest person***のままです。

そうしたら実際にマージしてみます。

```bash:Bash
$ git merge add-new-func
Updating 1234567..890abcd
Fast-forward
 hello.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
```

なんかいろいろ出てきましたね。エラーは見た感じ出てないみたいです。
`hello.txt`を見てみましょう。

```bash:Bash
$ cat hello.txt
I am the kindest person.
```

**…おっ?**中身が変わりましたね。***smartest person***が***kindest person***になっています。中身が**`add-new-func`ブランチのものと同一**になりました。

ここで、コミット履歴は以下のようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413967/698adff5-cd03-9484-8e8b-45ee3d2bf2a3.png)
**ァ！なんかくっついてる！**[^2]
はい。mergeをしたことによって**`add-new-func`ブランチの歴史が`master`ブランチの歴史に併合しました**。
こうして並行世界の変更が現実世界にも適用されました。

## コンフリクト

第5回で詳しく説明しますが、`add-new-func-2` ブランチは「コンフリクト」というエラーが発生して**マージすることができません**。
マージしないまま第5回に進んでくださいね。

#### マージする前の確認作業

上記のエラーを防ぐために、マージ作業を執り行う前にある**確認作業**をするとお作法がいいです。[^3]これも一緒に第5回で説明しますので、ぜひ頭に置いておいてください。

# おまとめ

- ブランチは**並行世界のようなもの**
- masterブランチは**現実世界のようなもの**
- ブランチの利点は:
  - 変更履歴が必然的にキレイになる
  - masterブランチの品質を保証できる
- ブランチの操作には以下のコマンドを使用する:
  - `git checkout -b <ブランチ名>` …ブランチを切る<br />
    中身は`git branch <ブランチ名>` → `git checkout <ブランチ名>`
  - `git checkout <ブランチ名>` …ブランチ間を移動(**チェックアウト**)
  - `git merge <ブランチ名>` …ブランチを**マージする**

ここまでお疲れ様でした。**が、**ブランチの話にはあと少しだけ続きがあります。
第4回はかなり膨れてしまったのでここで切り上げますが、第5回で同じリポジトリを使用するので取っておいてくださいね。

# おリンク
**[第1回](https://qiita.com/Colk-tech/items/e75c61146cabc887ad29)** インストール編
**[第2回](https://qiita.com/Colk-tech/items/f0817df7c6fdc9a7f283)** 設定編
**[第3回](https://qiita.com/Colk-tech/items/8d4b28b9001b862eeb99)** 実際に使ってみよう-基本技能編
**第4回 いまここ**
**[第5回](https://qiita.com/Flisan/items/6afd47345fdca42f1c3c)** ブランチ(平行世界)編-2
**[第6回](https://qiita.com/ko_5/items/c0c45c80f12e0072e51e)** GitHub入門編
**[第7回](https://qiita.com/isso0424/items/a7a0816d9a7f0caf1de6)** fetch/pull編
**[第8回](https://qiita.com/isso0424/items/6fc09a68bf0011dfc922)** issue/Pull Request編

## 執筆者

- [いっそう](https://twitter.com/kousou4129)
- [こ](https://twitter.com/ko_CottonLove)
- [こるく](https://twitter.com/Colk_)
- [フライさん](https://twitter.com/loxygenK)

[^1]: いわゆる超常現象を引き起こす物たちです。いきなり精神犯し始めたりします。
[^2]: 今回は**噛み砕いて説明する**ことを意識しているので分かりやすく書きましたが、今回のケースでは実際は**Fast-forward**という方法が自動的に選択されてマージされています。<br />(Fast-forwardマージが選択された場合は、図のようなコミット履歴にはなりません(`master`ブランチでのHEADが`add-new-func`ブランチのHEADに移動するだけです)。)<br />興味のある方は調べてみてください。
[^3]: ちなみに筆者は知りませんでした。お作法が悪いですね。