バージョン管理ツール「Git」でローカルリポジトリをいじろう！

プログラムを作る上で、ぜひ活用したいのが「バージョン管理ツール」です。

新しく機能を追加したら、うまく動かなくなったから元に戻したい……
過去のバージョンを別ファイルで保存しているけど、どれがどれだか分からない……
共同で開発するときに、いちいち変更点を探して合わせるのが面倒……

このような問題を「バージョン管理ツール」を使えば、解決できます！

今回は、バージョン管理ツール「Git」のローカルリポジトリの操作方法をご紹介します。

ローカルリポジトリとは、自分のパソコンなどの端末上に置いておくリポジトリのことです。
リポジトリは、Gitの操作を行うディレクトリのようなものです。

# ローカルリポジトリの作成
新しくローカルリポジトリを作成するときは、terminalで次の操作を行います。

1. mkdir ディレクトリ名
2. cd ディレクトリ名
3. git init 
4. git status　

`git init`は、プロジェクトの初期化をして、新しくGitインスタンスを作成するコマンドです。

ついでに`git status`で現在の状態も確認しておきましょう。

```
On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)
```
まだ何もコミットしてないので、まっさらな状態です。
`On branch master`は、現在「masterブランチにいるよ」ということです。
ブランチについては後程説明するので、「そんなもんか」程度に理解しておきましょう。

# コミットとは？
「コミット」とは、簡単に言うと現在の管理しているファイルたちの現在のバージョンを保存するような操作です。
もう少し詳しく説明すると、前の状態から変更点をローカルリポジトリに保存する操作です。

## ファイル作成とコミット
リポジトリを作ったので、早速プログラムファイルを作ってみましょう！
今回は簡単のためにreadme.txtを作ってみます。

```:readme.txt
Hello World
```
これをローカルリポジトリにコミットします！


1. git add ファイル名
2. git commit -m "message" 

`git add`でコミットしたいファイルを指定します。
全てのファイルをコミットしたい場合は、引数に`.`を指定します。
これで変更点がありコミットしたいファイルをインデックスに保存できます。

`git commit`でインデックスに入っているファイルをコミットします。オプション`-m`を付けると、今回のコミットで変更する部分を説明するメッセージを付けることができます。
今回であれば、メッセージは"create readme.txt"のようにすると分かりやすいですね。

### コマンドの省略
「毎回2回もコマンド打つの面倒」
そんな面倒くさがりさんも大丈夫！
実は変更したファイルが1つだけなら、1回でaddとcommitできるんです！
`git commit -a ファイル名`
ただし、1回でコミットするファイルが複数ある場合は、この方法は使えません。
ちゃんと2回実行しましょう。

# ブランチをつくろう

さて、つくったアプリに機能を追加したいとしましょう。
機能の開発中は、不具合が起る可能性もありますし、きちんと動作するか分からないのに、現在のバージョンの上にコミットしたくないですよね。

そこで便利なのが「ブランチ」です。

## ブランチとは？

「ブランチ」は英語で「枝」です。
その名の通り、あるバージョンから枝分かれしたものをさします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/298418/cb98d072-f2ae-1ecf-f9b2-ecdf14b97f47.png)

最初ローカルリポジトリを作った時点でmasterブランチが作られます。
そしてそのままコミットする度にmasterブランチが伸びて、コミットの履歴〇ができます。

## ブランチの操作
ここに次の操作をすることで上の図のような状態になります。

1. git branch ブランチ名
2. git checkout ブランチ名
3. git add ファイル名
4. git commit ファイル名

まず1で新しくブランチを作成します。
次に2、操作するブランチを新しく作ったブランチ"new option"に変更します。

因みに`git branch`で、ブランチの一覧が表示できます。

```
*master
new option
```
`*`がついているブランチが現在チェックアウトしているブランチです。
2を実行することで、

```
master
*new option
```
となり、現在のブランチをnew option変更できます。

そして3，4のコミットの操作を行うと、ブランチnew optionの枝が伸びて、新しいコミットができるのです。

### コマンドを省略
「4回もコマンド打つの面倒」
面倒くさがりでも、そうでなくてもできるだけスマートに作業をすませたいですよね。
ちゃんとスマートに操作する方法もあります。

`git checkout -b ブランチ名`

これで、ブランチ作成`git branch`とブランチの切替`git checkout`が同時にできちゃうんです。

# 前のコミットに戻りたい
例えば、プログラムを書いていて、何か不具合が起きとき、前のバージョンから書き直したいと考えたとしましょう。
以前のコミットにさかのぼるには？
`git checkout`
`git branch`
がここでも使えるのです！

## git checkoutでHEADを操作
git checkout は、さっきブランチの切替でも使いましたね。
これはcheckoutの機能の1つでしかありません。
git checkoutは本来HEADの位置を操作する役割があるのです。

"HEAD"とは、現在チェックアウトしているコミットの場所を指すものです。
デフォルトでは、masterブランチが指すコミット（masterブランチの最新のコミット）と一致していますが、
`git checkout コミット名`
とすることで、ブランチmasterと分離し、指定したコミットにチェックアウトできます。

例えば

```
$git checkout master
$git checkout master^
```
とすると、ブランチからHEADが分離し、次のような状態になります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/298418/77e2fd38-3d46-b14a-daaa-38229c7474d9.png)

master^はmasterブランチが指すコミットの1つ前のコミットをさしています。
このような相対リファレンスについては次で説明します。

## 相対リファレンス
`git checkout コミット名`
で指定したコミットにHEADを移動できることを説明しましたが、
そもそもコミット名ってどうやって指定すればいいのでしょう？

その方法の1つが相対リファレンスです。

- `ブランチ名/コミットのハッシュ値/HEAD`に`^`を１つ付けるたびに`ブランチ名/コミットのハッシュ値/HEAD`の１つ前に戻る。
- `ブランチ名/コミットのハッシュ値/HEAD`に`~<num>`をつけると指定した数だけ`ブランチ名/コミットのハッシュ値/HEAD`を戻る。
- `ブランチ名/コミットのハッシュ値/HEAD`に`^<num>`をつけると`ブランチ名/コミットのハッシュ値/HEAD`の親コミットのうち指定した番号のコミットに戻る。

## 直接指定する
コミットを指定する方法に、直接コミットを指定する方法もあります。
まず`git log`でHEAD以前のコミットを確認します。

```
$ git log
commit a9f49cd8dbf88c078160ff160a0616eafca4e6c9 (HEAD -> master)
Merge: 63b63ca 91addb9
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:30:01 2018 +0900

    Merge branch 'new_option2'

commit 91addb9f3ed5d8d77f20ec1b55ecf0d52ff7d029 (new_option2)
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:28:07 2018 +0900

    create new_option2

commit 63b63ca043719931965d98eed4cb398f1deedf25 (new_option)
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:22:10 2018 +0900

    create new_option.txt
```
commitの横にかかれた暗号みたいなのが「コミットののハッシュ値」です。
またハッシュ値の横の（）には、そのコミットをさしているブランチが書いてあります。
例えば、この状態では現在**HEADはmasterにくっついていて、`commit a9f49…`をさしている**ので
（HEAD -> master）が横に書かれていますね。

git checkoutでは、引数に**ハッシュ値の「上4文字」**を指定することで、そのコミットにHEADを移動できます。


試しに、ブランチnew_option2があるコミットに移動してみましょう。
`git commit 91add`

```
commit 91addb9f3ed5d8d77f20ec1b55ecf0d52ff7d029 (HEAD, new_option2)
Author: YukakoNishimura <***@gmail.com>
Date:   Sun Dec 23 21:28:07 2018 +0900

    create new_option2
```
`git log`で確認してみると、確かにHEADがmasterから分離してnew_option2と同じコミットをさしていますね。

## ブランチを移動する
HEADを分離せずにbranchを移動したいときは
`git branch -f ブランチ名　コミットの場所`
で移動できます。

```
$ git log
commit a9f49cd8dbf88c078160ff160a0616eafca4e6c9 (HEAD -> master)
Merge: 63b63ca 91addb9
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:30:01 2018 +0900

    Merge branch 'new_option2'

commit 91addb9f3ed5d8d77f20ec1b55ecf0d52ff7d029 (new_option2)
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:28:07 2018 +0900

    create new_option2

commit 63b63ca043719931965d98eed4cb398f1deedf25 (new_option)
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:22:10 2018 +0900

    create new_option.txt
```

この状態から、new_option2ブランチをハッシュ値`63b6…`のコミットに移動してみましょう。


`$ git branch -f master 63b6`

```
$ git log
commit a9f49cd8dbf88c078160ff160a0616eafca4e6c9 (HEAD -> master)
Merge: 63b63ca 91addb9
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:30:01 2018 +0900

    Merge branch 'new_option2'

commit 91addb9f3ed5d8d77f20ec1b55ecf0d52ff7d029 
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:28:07 2018 +0900

    create new_option2

commit 63b63ca043719931965d98eed4cb398f1deedf25 (new_option2,new_option)
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:22:10 2018 +0900

    create new_option.txt
```
確かに移動していますね！

# タグの設定
いちいちハッシュ値調べるの面倒…
確かによく使うコミットをいちいちハッシュ値で指定するのってスマートじゃないですね。
そんな時に使えるのが**タグ**です！

`git tag タグ名 コミットの場所`

これで今度からタグ名でそのコミットを選択できます。
試しにハッシュ値が`91ad…`のコミットをタグ付けしてみましょう！

`git tag OPTION 91ad`

```
$ git log
commit a9f49cd8dbf88c078160ff160a0616eafca4e6c9 (HEAD -> master)
Merge: 63b63ca 91addb9
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:30:01 2018 +0900

    Merge branch 'new_option2'

commit 91addb9f3ed5d8d77f20ec1b55ecf0d52ff7d029 (tag: OPTION)
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:28:07 2018 +0900

    create new_option2

commit 63b63ca043719931965d98eed4cb398f1deedf25 (new_option2,new_option)
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:22:10 2018 +0900

    create new_option.txt
```
ちゃんと横にタグ名OPTIONがついていますね！
では、さっき移動したブランチをタグ名OPTIONをつかって元の位置に戻してみます。

`git branch -f new_option2 OPTION`

```
$ git log
commit a9f49cd8dbf88c078160ff160a0616eafca4e6c9 (HEAD -> master)
Merge: 63b63ca 91addb9
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:30:01 2018 +0900

    Merge branch 'new_option2'

commit 91addb9f3ed5d8d77f20ec1b55ecf0d52ff7d029 (tag: OPTION,new_option2)
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:28:07 2018 +0900

    create new_option2

commit 63b63ca043719931965d98eed4cb398f1deedf25 (new_option)
Author: YukakoNishimura <****@gmail.com>
Date:   Sun Dec 23 21:22:10 2018 +0900

    create new_option.txt
```
ちゃんと元の位置に戻りましたね！

# 現在地の確認
コミットしているうちに自分が今どこを操作しているのか分からなくなることもあるでしょう。
そこで役に立つのが`git describe`です。

`git describe --tags`
を実行すると
`<タグ名>_<コミット数>_g<現在のコミットハッシュ>`
を返してくれます。

例えば、
`OPTION-2-ga9f49cd`
という結果が返ってきたら、
「今HEADはOPTIONタグの2つ先のコミット、つまりハッシュ値が`a9f49cd`で始まるコミットにいるよ」
ということが分かることで現在地を確認できるのです！