Git入門 - 4

## 1. はじめに

当記事は全5回（+α）からなる **Git入門シリーズ** の第4回です。

今回は **保存したデータの活用方法** と **必須ではないが覚えておくと便利な操作** について説明します。

- [1回: 概要](https://qiita.com/AGadget/items/a5f3addbe334db97d990)
- [2回: 導入手順とGitコマンド](https://qiita.com/AGadget/items/24c655fcbf9fc58625f0)
- [3回: データの保存](https://qiita.com/AGadget/items/6b1ccc036d682f5475a0)
- **4回: データの活用と便利な操作**
- [5回: ブランチ](https://qiita.com/AGadget/items/b48983b7da16099eeba0)
- [番外編: GitHub](https://qiita.com/AGadget/items/1edc9f1f5da1dafe8325)





## 2. 差分を確認

ソースファイルを編集していると、最後に保存したときから、どれだけ変更を加えたのか分からなくなるときがあります。

**statusコマンド** を使うことで、どのファイルに、どのような変更が加えられたのか――新規作成されたのか、変更されたのか、削除されたのか、リネームされたのかなどは分かります。

```
PS C:\Users\AGadget> git status
```

ただ、内容の変化までは分かりません。内容を知りたいときは **diffコマンド** を使用します。オプションも引数も無しに実行すると、インデックスと比較して変更のあった全てファイルの変更が表示されます。

```
PS C:\Users\AGadget> git diff
```

これはこれで便利ですが、実際に需要があるのは、ある特定のファイルに限った差分表示でしょう。diffコマンドの引数にファイルを指定することで、そのファイルのみチェック対象にとることができます。

```
PS C:\Users\AGadget> git diff [ファイル]
```





### 2-1. インデックスを対象に

**--cachedオプション** を付けると、インデックスに保存されたファイルと最新コミットとの差分を確認することができます。

```
PS C:\Users\AGadget> git diff --cached [ファイル]
```

「ステージングしたファイルのなかに、どれだけ変更を加えたか覚えていないファイルがある」といったときに便利です。





## 3. あるコミットの状態に復元

**restoreコマンド** を使うと、任意のファイルを過去保存したコミットの状態に戻すことができます。

基本的には最後に保存したコミットの状態に戻ります。

```
PS C:\Users\AGadget> git restore [復元したいファイル]
```

任意のコミットの復元したいときは **--sourceオプション** を使用してください。

```
PS C:\Users\AGadget> git restore --source=[コミット] [復元したいファイル]
```





## 4. ステージングを取り消す

作業していると誤ってステージングすることがあると思います。そんなときは **restoreコマンド** と **--stagedオプション** を組み合わせることで、ステージングの取り消し処理が実現できます。

そもそも「ステージングを取り消す」とは、どういった結果になればステージングを取り消したことになるのでしょう。人によっては異なる意見があるでしょうが、概ね **最後にコミットしたときの状態に戻す** ことがステージングを取り消すということになるのではないでしょうか。

状態の復元を行うrestoreコマンドと、復元範囲をワークツリーからインデックスに切り替える--stagedオプションを使うことで、そのような結果を得ることができます。

```
PS C:\Users\AGadget> git restore --staged [ファイルパス]
```





### 4-1. コミットがないとき

1度もコミットしたことがない場合はrestoreコマンドは使えません。何故ならばrestoreコマンドは、あるコミットの状態に戻すコマンドであるため、そもそもコミットがされていない場合は戻し先がないのです。

そのときは緊急的（？）に **rmコマンド** と **--stagedオプション** の組み合わせで対応します。

```
PS C:\Users\AGadget> git restore --staged [ファイルパス]
```

後述しますがrmコマンドはワークツリーにあるファイル――厳密には1度でもコミットされたことがあるファイルを削除するためのコマンドです。そして--stagedオプションは削除範囲をワークツリーからインデックスに変更する効果があります。

個人的には、ステージングを取り消すのには原則としてrestoreコマンドを使うべきだと思います。





## 5. ファイルを削除する

先述したようにファイルを削除するには **rmコマンド** を使用します。

基本的に、ファイルを削除するときはシェルやファイルマネージャーではなく、rmコマンドを使って削除するようにしてください。特に **捕捉** されたファイルに対してはrmコマンドの使用を推奨します。

```
PS C:\Users\AGadget> git rm [ファイルパス]
```





### 5-1. 捕捉

Gitには **捕捉** という状態・概念があります。リポジトリに保存されたファイルは捕捉状態になります。捕捉状態になると、不要なファイルを削除したいとき（あるいは削除したとき）Gitに削除した旨を通知する必要が生じます。

これまで、ファイルを新規作成したり、変更したりした場合は、その都度ステージング・コミットを実行してきました。それらと同様に、削除した場合もステージング・コミットを実行して、Gitに削除したという記録を残す必要があるのです。

```:こんな感じにGitに削除記録を残す
PS C:\Users\AGadget> Remove-Item .\samaple.ps1

PS C:\Users\AGadget> git add .\samaple.ps1

PS C:\Users\AGadget> git commit --message="○○の理由から、sample.ps1は不要になったので削除しました。" .\samaple.ps1
```

ただ、それは捕捉状態に限った話です。捕捉状態にないファイルは消したところで **そもそも、そのようなファイルは最初から存在しなかった** と認識されるのでステージング・コミットの必要はありません。





### 5-2. rmコマンドを推奨する理由

ファイルを削除した場合、ステージングとコミットを行う必要があるという話ですが、rmコマンドは削除とステージングをまとめて行ってくれます。

```:ちょっと操作が減る
PS C:\Users\AGadget> git rm .\samaple.ps1

PS C:\Users\AGadget> git commit --message="○○の理由から、sample.ps1は不要になったので削除しました。" .\samaple.ps1
```

シェルやファイルマネージャーから削除した場合、ステージングを忘れる可能性があるのでミス防止のためにrmコマンドの使用を推奨します。

また、そもGitにファイル削除用のコマンドがあるのですから、できることならコマンドを使って操作したほうが **綺麗・丁寧** だと思うのです。





## 6. ファイルの移動とリネーム

ファイルを移動させたり、リネームするときは **mvコマンド** を使用してください。

```
PS C:\Users\AGadget> git mv [旧ファイル] [新ファイル]
```





## 7. コミットをやり直す

コミットを間違えたときは **commitコマンド** と **--amendオプション** の組み合わせで直前のコミット処理をやり直すことができます。このコマンドを実行すると最新コミットが上書きされます。

```
PS C:\Users\AGadget> git commit --amend [コミットしたファイル]
```





## 8. resetコマンドとHEADポインタ

便利なコマンドの1つに **resetコマンド** があります。resetコマンドの本質は **HEADを変更すること** です。副次的な効果として、インデックス・ワークツリーの状態を、変更先のコミットの状態にすることができます。

```:文法
PS C:\Users\AGadget> git reset [影響規模を指定するオプション] [HEAD変更先のコミット]
```

```:例文
PS C:\Users\AGadget> git reset --mixed HEAD^
```





### 8-1. HEADとは？

HEADとは **現在選択中のブランチにおいて参照中・作業中のコミットを表すポインタ** です。ただ、この説明を理解するには **ブランチ** という概念・機能を知らねばなりません。そのため現在のところは **最新のコミットを表すポインタ** と理解しておいてください。

これまで、何らかの理由で特定のコミットを指定したいときは、コミットのハッシュ値を使う必要がありました。ただ、ハッシュ値をいちいち入力するのは手間ですし、そもそもlogコマンドなどを使ってハッシュ値を調べる必要があります。古いコミットを参照するならともかく、最新のコミットや最新の1つ前くらいのコミットぐらいは、もっと簡単に参照したいものです。そんなときに役立つのがHEADです。

基本的に、HEADはcommitコマンドが実行されるたびに移動し、常に最新のコミットを参照します。そのため以下のような感じで最新のコミットを参照することができます。長ったらしいハッシュ値を打ち込むよりも、はるかに短くて済みます。

```
PS C:\Users\AGadget> git [何らかのコマンド] HEAD
```

また、HEADには特殊な記法が用意されており、柔軟な利用が可能になっています。

```:最新のコミットの、1つ前のコミットを参照
PS C:\Users\AGadget> git [何らかのコマンド] HEAD^

PS C:\Users\AGadget> git [何らかのコマンド] HEAD~1
```

```:最新のコミットの、2つ前のコミットを参照
PS C:\Users\AGadget> git [何らかのコマンド] HEAD^^

PS C:\Users\AGadget> git [何らかのコマンド] HEAD~2
```

ところで基本的に最新のコミットを参照するHEADですが、状況によっては最新以外のコミットを指す場合があります。そのような状態を **detached HEAD** と言います。普通に操作していれば発生しませんし、発生したからといって必ずしも悪いわけではないので、特別気にする必要はありませんが一応覚えておいてください。





### 8-2. 改めてresetコマンドについて解説

HEADについて理解できたところで、改めてresetコマンドの挙動について説明します。

resetコマンドは引数に指定されたコミットにHEADを移動させるためのコマンドです。その副次的な効果として、インデックスとワークツリーに、移動先のコミットの状態を反映させたりさせなかったりすることができます。

まず、HEADの移動だけ行いたい場合は **--softオプション** を指定してください。インデックスとワークツリーの状態は最後にコミットしたときの状態のままですので、直前のコミットをやり直すときなどに便利です。

```
PS C:\Users\AGadget> git reset --soft [コミット]
```

**--mixedオプション** を付けると移動先のコミットの状態をインデックスに反映させます。--softオプションと似ていますが、インデックスまで変化するので、コミットをやり直すのに際してステージングするファイルを大きく変化させたいときなどに便利です。

```
PS C:\Users\AGadget> git reset --mixed [コミット]
```

インデックスとワークツリーのどちらにも影響を与えたいときは **--hardオプション** を使ってください。個人的には--hardオプションが一番使う機会が多い気がします。

```
PS C:\Users\AGadget> git reset --hard [コミット]
```

ちなみに、この3オプションのいずれかを指定しなかった場合、--mixedオプションが自動的に適用されます。





## 9. タグ

**タグ** とはコミットに付けることができるポインタ・記号・目印のことです。

Gitには、コミットの内容・意図を説明するコミットメッセージ機能が付いています。ただ、そのコミットが何であるか――つまり、バージョン情報とかをコミットに紐づけたいときは、その旨をコミットメッセージ内に含める必要がありました。

そこでタグの出番です。タグはコミットメッセージから独立しており、コミットの状態を表現するのに適しています。また、コミットを指定するときにハッシュ値やHEADとともに使用することができます。





### 9-1. 作成されたタグを一覧表示

タグを操作するには **tagコマンド** を使用します。

コマンド単体で実行するとタグの一覧を確認することができます。

```
PS C:\Users\AGadget> git tag
```





### 9-2. タグを作成・付与

タグを作成したいときは引数にタグ名を指定してください。作成されたタグは、作成されると同時に最新コミットに付与されます。

```
PS C:\Users\AGadget> git tag [タグ名]
```

任意のコミットにタグを付けたいときは、第2引数に付与したいコミットを指定してください。

```
PS C:\Users\AGadget> git tag [タグ名] [コミット]
```

タグは重複して使用することはできません。つまり、あるタグを複数のコミットに付けることはできないわけです。逆に、1つのコミットに複数のタグを付けることは可能です。

既に作成されているタグを、現在付いているコミットから別のコミットに変更したいときは **--forceオプション** を指定します。

```
PS C:\Users\AGadget> git tag --force [タグ名] [コミット]
```





### 9-3. タグを削除

タグを削除したい場合は **--deleteオプション** を使用します。

```
PS C:\Users\AGadget> git tag --delete [タグ名]
```





### 9-4. 注釈付きのタグ

**--annotateオプション** と **--messageオプション** 使うことで、タグに注釈を付けることができます。

```:文法
PS C:\Users\AGadget> git tag --annotate --message=[注釈] [タグ名]
```

```:例文
PS C:\Users\AGadget> git tag --annotate --message="バグ修正完了版です。" v1.0.1
```