Gitをなんとなく使っていた僕がブランチ操作をちゃんと理解するまで

なんとなくGitを使っていました。
基本的にはgit add/commit/push/pull/cloneができれば、最低限は開発できたので、困るまでそれでいいかななんて思っていたのですが、
ブランチ操作でローカルレポジトリがよくわからないことになったので、ちゃんと勉強するか〜と思って勉強しました。

# 概要
Gitのブランチ操作は、HEADの位置をズラしているだけ。
別ディレクトリを切り出しているわけではない。

# ブランチ操作のときに起きた問題
ブランチを3つ作って、

- リモートレポジトリ同様のブランチ(develop)
- 実験ブランチ
- 必須要件ブランチ

という役割分担で運営してました。
ローカルのdevelopから、実験ブランチ→必須要件ブランチという順番にブランチを切っていきました。
必須要件ブランチをリモートリポジトリにｐushすっぞ、となったときに、リモートのバージョンが結構進んでいたので、pullして新しい差分をとりこむことにしました。
結構ガチャガチャやったら、なぜか**実験ブランチの修正内容が消えていました。**

原因はっきりわかっていないのですが、未commitのまま、ブランチを切り替えたので、
ワークディレクトリの内容が消えてしまったのだと思われます。
（消える危険があるときはcheckoutできないと思っていたのですが、できる場合もある？）

ブランチ運用を雰囲気でやっていたツケでした。
幸い実験ブランチはコード自体は微修正だったので、結局「git reset」で最終commit時まで戻して、コード内容だけをコピーして、
ブランチ自体を消してしまって、きれいにつくりなおすという原始的な手段を使いました。
ただリモートレポジトリに更新あるたびに、実験中のブランチめちゃくちゃになったら怖いです。

# ブランチを切ったときに本当に起きていること
ブランチを切る操作は、下記のコマンドをうちますね？

```bash:b1をつくる
git branch b1
```

```bash:b1にブランチを移動
git checkout b1
```

僕はこっちでやる派ですが、１つのコマンドでもできます。
（オプションを覚えられないんですよね……）

```bash:b1をつくって、移動
git checkout -b b1
```

僕は今まで、この操作で**別ディレクトリができているイメージ**でいました。
これは完全に間違い。
他のバージョン管理システムだと、ブランチを切る作業＝作業ディレクトリの丸コピーというものもあるみたいです。

Gitはlinus torvalds（Linux作ったフィンランド人）が、それまでのバージョン管理システム（VCS）にムカついて作ったものです。
僕自身はSubversionなどのバージョン管理システム使ったことなくて、いきなりgitだったので、branch操作が高速！　という感覚もなく、
それを当たり前として受け入れてしまいましたが、違うみたいです。

> Git のブランチモデルは、Git の機能の中でもっともすばらしいものだという人もいるほどです。 
そしてこの機能こそが Git を他の VCS とは一線を画すものとしています。 
何がそんなにすばらしいのでしょう? 
Git のブランチ機能は圧倒的に軽量です。
ブランチの作成はほぼ一瞬で完了しますし、ブランチの切り替えも高速に行えます。 
その他大勢の VCS とは異なり、Git では頻繁にブランチ作成とマージを繰り返すワークフローを推奨しています。 
一日に複数のブランチを切ることさえ珍しくありません。 
この機能を理解して身につけることで、あなたはパワフルで他に類を見ないツールを手に入れることになります。 
これは、あなたの開発手法を文字通り一変させてくれるでしょう。

[3.1 Git のブランチ機能 - ブランチとは](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81%E6%A9%9F%E8%83%BD-%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81%E3%81%A8%E3%81%AF)

でも作業ディレクトリコピーしないとしたら、どうやってGitはブランチ変更してるのか？　を説明します。
Gitのバージョン管理は、commitした時点のスナップショットによって行われます。
現在自分がどのスナップショットにいるのかを把握するために、「HEAD」というポインタを持っています。
commitを取り消したいときに

```bash
git reset --hard HEAD
```
というコマンドはうったことがありましたが、実はHEADが何かわかっていませんでした。
HEADは「レポジトリの中で現在どのコミット位置にいるか」を指すポインタなんですね。

commitだけではなく、branchもこのHEADを活用しています。

```bash:branch
git branch testing
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/333454/0fa8b387-8e19-6822-5dea-b2ef250d4cfa.png)

branchを切ったとき、ここではmaster→testingと切ったとしましょう。
その場合、見ているスナップショットの位置は一緒です。
更に**HEADはmasterを指しています。**

checkoutしてみましょう。

```bash:testingにブランチを移動
git checkout testing
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/333454/533511e8-8411-c0c2-06e1-d6ab81413685.png)

checkoutによって、HEADはtestingを指すようになります。
ただこの時点では、両branchともにスナップショットの中身が一緒なので、結局は同じバージョンを指しています。

このあとtestingで開発を進めて、commitすると、**branch内容が分岐します。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/333454/ccf3bb73-4d62-7207-e90f-c44b8f88eb49.png)

これ以降は、両ブランチは互いに分岐したものとして、それぞれの変更は、互いに影響しません。
実験ブランチであれば、作ってそのままにしておけばいいですし、本流に組み込みたければ、mergeを使います。
修正内容がかち合ったらconflictが発生するので、そこだけ修正すれば容易にmergeできるはずです。

# リモート追跡ブランチについて
これも書きたかったんですが、長くなったので、また別記事にします。

# 参考
[3.1 Git のブランチ機能 - ブランチとは](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81%E6%A9%9F%E8%83%BD-%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81%E3%81%A8%E3%81%AF)
記事中に出てくる画像もこちらからです。