これだけ知っておけば幸せになれるGitコマンドたち

# この記事なんやねん
これだけ知っておけば現場での開発では困らないだろうGitコマンドをつらつらと載せていきます。
マニアックなコマンドは載せないので、内容的には初級者向けです。

`add`、`commit`などの超基本的なコマンドはリッチテキストエディタならGUIから操作できるので省略してます。

## 用語解説
この記事で使う用語解説もついでに載せておきます。
因みに、[サルでもわかるGit入門](https://backlog.com/ja/git-tutorial/)が入門チュートリアルとしてはオススメなので完全にGit触るのが初めてならこちらも確認してください。

### リモートリポジトリ
Gitは分散型バージョン管理システムなのでリモートリポジトリとローカルリポジトリが存在します。リモートリポジトリは複数人で作業する時に使う共有リポジトリで、例えばGitHub上で作った自分のリポジトリなどのことです。

### ローカルリポジトリ
自分専用のリポジトリです。ローカル環境にあるのでオフラインでも編集内容を登録できます。例えば、リモートリポジトリから`git clone`すると作業ディレクトリがローカルにできますが、これがローカルリポジトリになります。

### ブランチ
リポジトリの中には更にブランチという履歴毎の分岐が存在しています。要するに、ブランチ毎にソースが管理されています。ブランチ同士は独立した履歴を持っているので影響を受けません。
例えば、AブランチとBブランチがあった時に、Aブランチで作業を進めたとしても、Bブランチでは一切ソースは変更されません。
ブランチがある事により、同一リポジトリ内で複数の修正作業を同時に進める事ができます。

### Trackedファイル
Git管理下にあるファイルのことです。トラッキングされてればファイルの内容を変更するとGitが教えてくれます。

### Untrackedファイル
Git管理外にあるファイルのことです。新規に作ったファイルはコミットしない限りトラッキングされずに、UntrackedファイルとしてGitに認識されます。

### add
修正した内容をコミット対象に含める事です。具体的には、ワークツリーからインデックスに移動します。

### コミット
修正した内容をローカルリポジトリに反映(登録)する事です。これによりGitの履歴に修正内容がコミットログとして反映され、UntrackedだったファイルもGit管理下になります。

### プッシュ
ローカルリポジトリでのコミット内容をリモートリポジトリにも反映する事です。これにより自分の修正が他のチームメンバーにも共有可能になります。

### ワークツリー
作業領域のことです。Git管理下のファイルや新規でファイルを追加するとワークツリーに反映されます。

### インデックス(ステージ)
コミットする対象ファイルを配置する領域です。Gitの操作は基本的にコミット単位で行われるので、コミットを意味のある単位でまとめる事が重要になります。
そのため、ワークツリーとインデックスという2つの領域が存在していて、修正したファイルを全てコミットしなくてもいいようになっています。これにより柔軟なコミットが可能になっています。

**ローカルリポジトリの状態についてまとめると下記のようになります**

```shell
% git status
# ローカルリポジトリにコミットされてまだプッシュされてないコミット
# 今回だと3つがまだプッシュされていない
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
  (use "git push" to publish your local commits)

# インデックスに登録されてコミットする準備ができてるファイル
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   hoge.txt

# ワークツリーのファイルでインデックスに登録されていないファイル
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   sample.txt

# git管理外の新規ファイル
Untracked files:
  (use "git add <file>..." to include in what will be committed)

	foo.txt
```

ローカルリポジトリとリポートリポジトリの差分をログから確認すると下記のようになってる。

```shell
% git log --oneline master
# ローカルリポジトリでは3つのコミットが追加されてるのが分かる
fa18ec5 (HEAD -> master, foo, bugfix) remove sample 03
9f2b27d fix sample
f354df0 reject push master
# リモートリポジトリの最新はここまで
ff78f49 (origin/master, origin/HEAD) add hoge
3be6061 Add Hoge
```


# コマンド集
早速ここからコマンドを紹介していきます。ユースケース毎に紹介していこうと思います。

## ブランチの作成と切り替えを同時に行う
`git checkout`でブランチの作成と切り替えを同時に行う事ができます。

`git checkout -b hoge`

## ブランチを削除したい
ローカルリポジトリが増えてくると整理したくなりますよね。`git branch`に`-d`オプションを付与することでブランチを削除する事ができます。

`git branch -d hoge`

削除したいブランチを複数指定することも可能。

`git branch -d hoge foo`

強制削除したい時は`-D`オプションを指定。

`git branch -D hoge`

## コミットログを1行単位で出力したい
正直デフォルトだと見辛いですよね。そこで`--oneline`オプションを使うと1行で表示できてめっちゃスッキリします。

`git log --oneline`

## コミット間の差分を表示したい
`diff`コマンドを使用すると差分が確認できます。差分箇所とファイル名などの詳細情報を表示します。
差分を確認したいハッシュ値を指定してください。

`git diff fa18ec5 9f2b27d`

1つ指定したら今いるブランチの最新コミットとの比較になります。

`git diff fa18ec5`

## ブランチ間の差分を表示したい
ブランチ間の差分を表示したい場合はブランチ名を指定します。

`git diff origin/master`

別ブランチ同士の比較もできます。

`git diff hoge origin/master`

## 差分のあるファイル名のみ表示したい

差分のあるファイル名のみ表示したい場合は`--name-only`を付与します。

`git diff --name-only fa18ec5 9f2b27d`
`git diff --name-only origin/master`

## 特定のファイル・ディレクトリの差分が知りたい
特定のファイル・ディレクトリの差分が知りたい場合はファイルパスを指定します。

`git diff hoge sample.txt`

## 作業中に別ブランチに切り替えたい
`stash`コマンドを使うと作業中の修正を一旦保存してくれます。作業状態の保存は複数登録できます。

保存する時は `git stash`,

取り出す時は `git stash pop` を使用してください。

`git stash list`でstashしたリストも見れます。

## 一時保存を指定して取り出したい
一時保存の一覧 `git stash list` で確認してから、左端のインデックスを指定して取り出しましょう。

```shell
% git stash list
stash@{0}: WIP on foo: 9f2b27d fix sample
stash@{1}: WIP on foo: 9f2b27d fix sample
```

`stash@{1}`を取り出したい場合は下記のようにします。

`git stash pop stash@{1}`

## masterの最新を作業中のブランチに反映したい
`git rebase`を使うと、ブランチで作業した内容の履歴より前にmasterの最新を反映できるので、コミットログが綺麗になって便利です。

実行前にフェッチするのを忘れずに。

`git rebase origin/master`

## コミットを修正したい
`git rebase`に`-i`オプションをつけることでコミット履歴を修正できます。かなり便利なコマンドで使用頻度も高いです。

まずは、コミットログを確認しましょう。

```shell
% git log --oneline
ba829e6 (HEAD -> hoge) add hoge 02
16ffcff add hoge 01
fa18ec5 (master, foo, bugfix) remove sample 03
```

`add hoge 01` のコミットを修正したい場合は**1つ前のコミットハッシュ値を指定**してください。

`git rebase -i fa18ec5`

次に、エディタが開くので、各コミットにコマンドを指定して編集してください。それぞれよく使うのはこんな感じ。

- `pick` : コミットをそのまま使う。
- `r` : コミットは使うけど、コミットメッセージは変える。
- `s` : 上のコミットにまとめる。コミットメッセージも変える。
- `f` : 上のコミットにまとめるけど、コミットメッセージは上のコミットのものを使う。
- `d` : コミットを破棄する。

編集画面はこんな感じです。コミットのハッシュ値の左にコマンドを指定してコミットを編集してください。今回の例だと、`add hoge 01`コミットに`add hoge 02`コミットを統合してコミットメッセージはそのままにしていますね。

```shell
  1 pick 16ffcff add hoge 01
  2 f ba829e6 add hoge 02
  3 
  4 # Rebase fa18ec5..ba829e6 onto fa18ec5 (2 commands)
  5 #
  6 # Commands:
  7 # p, pick = use commit
  8 # r, reword = use commit, but edit the commit message
  9 # e, edit = use commit, but stop for amending
 10 # s, squash = use commit, but meld into previous commit
 11 # f, fixup = like "squash", but discard this commit's log message
 12 # x, exec = run command (the rest of the line) using shell
 13 # d, drop = remove commit
```

## ローカルリポジトリでリモートリポジトリを上書きしたい
`git push`に`-f`をつけると強制プッシュできます。
既にリモートリポジトリにプッシュ済みのコミットを`rebase`で編集した時に`non-fast-forward`のためプッシュできなかったりします。
もしブランチで作業しているのが自分だけであれば強制プッシュするのが手っ取り早いです。

`git push origin/hoge -f`

ただし、強制プッシュはその他のメンバーの開発に影響を与えるので、実行する際は慎重にしてください。自分のみ作業しているブランチ以外で実行する場合は、チームメンバーの了解を得るようにしましょう。

## リモートリポジトリでローカルリポジトリを上書きしたい
上記の逆パターンですね。`reset`コマンドを使いましょう。
反映させたいリモートリポジトリを指定してください。

`git reset --hard origin/master`

## 特定のコミットをブランチに取り込みたい
`cherry-pick`を使うと別ブランチのコミットを作業してるブランチなどに取り込む事ができます。
取り込みたいハッシュ値を指定すれば内容が反映されます。

`git cherry-pick 7e4bfd0`

## 特定のコミットまで戻したい
`reset`コマンドを使いましょう。
戻したいコミットのハッシュ値を指定してください。

`git reset --hard 9f2b27d`

もしくは最新のコミット(HEAD)から指定してもいいです。

`git reset --hard HEAD`   ← 作業中の内容を破棄したい時
`git reset --hard HEAD^`  ← 1つ前のコミットまで戻したい時
`git reset --hard HEAD^^` ← 2つ前のコミットまで戻したい時

## gitの操作を取り消したい時
`reflag`で作業内容を確認して同じく`reset`コマンドで戻す事ができます。よく使うパターンは`reset`コマンドでミスした時などですかね。
因みに、`reflag`で出てくるログはローカルリポジトリでのリポジトリ操作に関する参照ログなので、コミットログとは違います。

```shell
# たくさん出てくるの直近5件のログを指定してます
% git reflog -n 5
f354df0 (HEAD -> master) HEAD@{0}: reset: moving to HEAD^
9f2b27d HEAD@{1}: reset: moving to 9f2b27d
fa18ec5 (foo, bugfix) HEAD@{2}: reset: moving to fa18ec5
9f2b27d HEAD@{3}: reset: moving to 9f2b27d
fa18ec5 (foo, bugfix) HEAD@{4}: checkout: moving from hoge to master
```

例えば、`reset: moving to 9f2b27d` にて`reset`した内容を取り消したい場合。

`git reset --hard HEAD@{1}`

## コミットを取り消したい
コミットを取り消すやり方をケース毎に紹介します。

### リモートにはプッシュしていない時
`git reset`か`git rebase -i`の`drop`コマンドを使うのが簡単です。
それぞれの使い方は下記の項目を参照してください。

[reset](https://qiita.com/belion_freee/items/a30b5bba3dcf4a2c0e86#%E7%89%B9%E5%AE%9A%E3%81%AE%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88%E3%81%BE%E3%81%A7%E6%88%BB%E3%81%97%E3%81%9F%E3%81%84)
[rebase](https://qiita.com/belion_freee/items/a30b5bba3dcf4a2c0e86#%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88%E3%82%92%E4%BF%AE%E6%AD%A3%E3%81%97%E3%81%9F%E3%81%84)

## 競合が発生した
競合はマージする際に同じ箇所を同じタイミングで修正した場合に発生します。
競合が発生した場合は基本的にはエディタを開いて適用したい履歴を選択してください。

```shell
% git merge origin/fix02
# 競合が発生！
Auto-merging sample.txt
CONFLICT (content): Merge conflict in sample.txt
Automatic merge failed; fix conflicts and then commit the result.
% cat sample.txt
<<<<<<< HEAD
=======
Hoge Hoge
>>>>>>> origin/fix02
```

上記のログで説明すると、`HEAD`で囲まれてる部分がmerge先のブランチの状態で、

```
<<<<<<< HEAD
=======
```

`origin/fix02`で囲まれてる部分がmergeしようとしてるブランチの状態です。
若干、見辛いですが、エディタを使ってればもっと見易くなります。

```
=======
Hoge Hoge
>>>>>>> origin/fix02
```

このコンフリクトを解消する場合は、単純に適用したい方のソースコードを残して、あとのコードは消してください。当たり前ですが、`<<<<<<< HEAD`などの記載も消してください。
Atomなどのエディタを使ってれば標準でボタン押すだけでコンフリクト解消できます。

### 競合する前の状態に戻したい
もし、競合する前の状態に戻したい、要するに、マージを取り消したければ、`--abort`オプションを使いましょう。

`git merge --abort`

rebaseでも使えます。　`git rebase --abort`

### 既にリモートにプッシュした時
いくつかやり方がありますが、推奨されてるやり方、というか簡単なのは`revert`です。
`revert`はコミット自体を消すのではなくて、コミットを打ち消すコミットを作成します。
つまり、コミットそのものを消す破壊的な`reset`や`rebase -i`とは異なり、コミットログには履歴が残り続けます。

取り消したいコミットのハッシュ値を指定します。

`git revert f354df0`

マージコミットを取り消したい場合は`-m 1`オプションをつけます。これは親番号を指定するもので、マージコミットには親が2つ以上(masterなどの本線と派生ブランチ)存在するため、どちらを親にするか指定する必要があります。普通の開発フローなら`-m 1`で大丈夫です。

`g revert -m 1 e3d2c6e`

## 特定のファイルを修正前の状態に戻したい
`git checkout`で特定のコミットまで戻すことができます。
使い方は、 `git checkout <コミットID> <戻したいファイルパス>` です。

`git checkout f354df0 hoge.txt`

## Untrackedファイルを削除したい
新規で追加したファイルを削除したい時は`git clean`を使います。

`git clean -n` でGit管理外のファイルを確認,
`git clean -f` でファイルを削除。