本当に怠惰だけど怒られたくない僕が普段Gitで意識していること・コマンドまとめ

## 概要・対象読者
- エンジニアとしての怠惰ではなく本当にただただ怠惰なひと
- 普段Gitを使って開発しているがCommit癖がないけど逐一commitするのはなんかダルいひと
- だけども「コミットが汚い！」とかGit関連で怒られたくもないひと

## TL;DR
以下の順番で説明します。
- git add -pを使おう
- git commit -m をつかうのはやめよう
- gitのcommit messageにはprefixをつけて"デキる人"感をつけよう
- git commit --amend --no-editを使おう
- git rebase -iを使おう

## 1. git add -pを使おう
例えば、次のようなコードが実装してあり、これはコミットされているものとします。
(forはたとえで、これがなんらか一つの処理だと思って下さい。)
```main.ts
...
...

for (const sample of samples){
  sample_func1()
  sample_func2()
  sample_func3()
}

...
...
```

それで、この下に、新たにもう一つ、別の処理をしたくなりました。


```main.ts
...
...

for (const sample of samples){
  sample_func1()
  sample_func2()
  sample_func3()
}
...

for (const sample2 of samples2){
  sample_func4()
  sample_func5()
  sample_func6()
}
...
...
```

まあこのくらいのコードだったら別に気になりませんが、
1つ1つの処理が大きいなら、別関数に切り出したくなるのがエンジニアの性です。

とりあえず、libsの中に切り出して、main.tsを整理したとしましょう。

```libs/hoge.ts
export const hoge = () => {
    for (const sample of samples){
      sample_func1()
      sample_func2()
      sample_func3()
    }
}

```
```libs/fuga.ts
export const fuga = () => {
    for (const sample2 of samples2){
      sample_func4()
      sample_func5()
      sample_func6()
    }
}

```


```main.ts
import { hoge } from "@/libs/hoge"
import { fuga } from "@/libs/fuga"
...

    hoge()
    fuga()

...
```

と切り出すことができました。さてコミットしましょう。
と、ここで、思います。

"""
もともとfugaを実装するはずだったけど、もともと実装してあったhogeを別関数に切り出しているな。
じゃあhogeを別関数に切り出すところはリファクタのコミットにしてfugaの実装とは別々のコミットにしたいな。
"""

と。

でもmain.tsのコードをみて思います。

```main.ts
import { hoge } from "@/libs/hoge"
import { fuga } from "@/libs/fuga"
...

    hoge()
    fuga()

...
```

これ、そのまんま`git add main.ts`しちゃうと`fuga()`とか`fuga()`のインポート文が入っちゃってやだな。
でもきりだしたいな。
一体どうすればいいんだ！？

こんな時に使うのが`git add -p`です。
**git add -pを使えば1ファイルの中を部分で分けてaddすることができます**

早速やってみましょう。
```
git add -p main.ts
```

そうするとこんな感じでインタラクティブなメッセージが表示されます。
```
@@ -1,6 +1,5 @@
+import { hoge } from "@/libs/hoge"
+import { fuga } from "@/libs/fuga"
 
-for (const sample of samples){
-  sample_func1()
-  sample_func2()
-  sample_func3()
-}
\ No newline at end of file
+hoge()
+fuga()
(1/1) Stage this hunk [y,n,q,a,d,s,e,?]? 
```

これは本当はある程度gitが自動的に1ファイルの中を区切ってくれるのですが、今回の例は少なかったのですべて1つのhunkとして扱われていますね。
コマンド押して何をするか決めてくれといってます。
怠惰な僕はこの中で3つのものしか覚えていません。
| オプション | 動作 | 
| --- | --- |
| y | 出力されている部分の変更をすべてaddする | 
| n | 出力されている部分の変更はaddしない | 
| e | 出力されている部分をさらに細かくaddする部分を決めたいからエディタを開きたい | 


今回は出力されたものをさらに細かくaddを指定したいのでeを押下しましょう。
エディタが立ち上がります。

```
# Manual hunk edit mode -- see bottom for a quick guide.
@@ -1,6 +1,5 @@
+import { hoge } from "@/libs/hoge"
+import { fuga } from "@/libs/fuga"
 
-for (const sample of samples){
-  sample_func1()
-  sample_func2()
-  sample_func3()
-}
\ No newline at end of file
+hoge()
+fuga()
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
# 
# If the patch applies cleanly, the edited hunk will immediately be
# marked for staging.
# If it does not apply cleanly, you will be given an opportunity to
# edit again.  If all lines of the hunk are removed, then the edit is
# aborted and the hunk is left unchanged.

```

これで、ファイルを"なりたい自分"に変更させてあげることができます。
今回はhoge()の切り出しの部分だけをaddしたいのでこんな感じにします。

```
# Manual hunk edit mode -- see bottom for a quick guide.
@@ -1,6 +1,5 @@
+import { hoge } from "@/libs/hoge"
 
-for (const sample of samples){
-  sample_func1()
-  sample_func2()
-  sample_func3()
-}
\ No newline at end of file
+hoge()
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
# 
# If the patch applies cleanly, the edited hunk will immediately be
# marked for staging.
# If it does not apply cleanly, you will be given an opportunity to
# edit again.  If all lines of the hunk are removed, then the edit is
# aborted and the hunk is left unchanged.

```

エディタを抜けて変更を保存します。

そうすると、指定した部分だけaddができています。
確認してみましょう。

```
git diff --staged src/main.ts

@@ -1,6 +1,3 @@
+import { hoge } from "@/libs/hoge"
 
-for (const sample of samples){
-  sample_func1()
-  sample_func2()
-  sample_func3()
-}
\ No newline at end of file
+hoge()

```

```
git diff src/main.ts


@@ -1,3 +1,5 @@
 import { hoge } from "@/libs/hoge"
+import { fuga } from "@/libs/fuga"
 
 hoge()
+fuga()

```

想定通り、分けてaddできました。あとは煮るなり焼くなり好きにしてcommitしてあげて下さい。

## 2. git commit -m をつかうのはやめよう
`git commit -m "commit message"`とすればこのコマンドだけでコミットができてとても便利なのですが、
個人的には使うのはおススメしません。

その理由は、**何をコミットするか最後の確認ができないから**です。

どういうことでしょうか？git commitを実行してみましょう。

```

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch main
# Changes to be committed:
#	modified:   src/main.ts
#
# Changes not staged for commit:
#	modified:   package-lock.json
#	modified:   package.json
#
# Untracked files:
#	src/components/
#	src/libs/
#

```

そうするとエディタが立ち上がり、こんな感じの画面が立ち上がりますよね。
これは`git status`を叩いたときと同じ出力です。

git commitで実行すれば、commitの直前にcommitするもの、commitしないものの確認ができるのです。
そのため、想定していないものがcommitされてしまっていたり、逆にcommitするべきものがされていなかったりすることを防げるのと同時に、
適切なコミットメッセージをつけることにも一役買えると個人的には思っています。

なので、いつも適当なコミットメッセージを投げてしまってレビューで怒られてしまう人、まずはgit commit -mから卒業しましょう。

## 3. gitのcommit messageにはprefixをつけて"デキる人"感を出そう

これはあくまで個人的な主観ですが、commit messageにfeat: とかfix: とかのprefixをつけているひとはやっぱりかっこいいですし、つよつよエンジニア感が漂ってきます。

(僕個人としてはprefixがついていてもついていなくてもコミットメッセージの読みやすさは大して変わらないですがなんかデキる人感がでるのでつける。これでデキる感がでるのであれば。)

僕が使っているprefixは以下の通りです。
グッとこらえてつけるようにしていたら1週間くらいしたらなんも考えずに自然とprefixつけれるようになります。

| prefix | 説明 | 
| --- | --- |
| feat: | 新機能追加 | 
| fix: | バグ修正 |
| refactor: | リファクタ |
| style: | コードフォーマット整形 | 
| docs: | ドキュメント編集 |
| chore: | その他(あんま使わん) | 

え？これだけ？と思うかもしれませんが、怠惰な僕には5個が限界です。
そして、6個で案外なんとかなります。

## 4. git commit --amend --no-editを使おう

例えば、次のようなコードをcommitしたとします。
```main.ts
console.log('Hello Wrold!')
```

そして、commitしてから、あ！Worldをtypoしてる！と気づくのです。
typoだけのコミットをするのもなぁ。そういう時に、使うのが`git commit --amend --no-edit`
を使うのです。

**--amend --no-editをつければ、HEADのコミットにコミットをまとめることができます。**

とりあえず、typo修正したコードをaddします。
```
git add src/main.ts
```

そして、
```
git commit --amend --no-edit
```

を実行するだけでまとめられます。

注意点としてはcommitはまとめられてしまうので、やっぱりナシ！ができない点です。
git status や git diffを使って、本当にまとめてもいいのか確認してから実行するようにしましょう。

## 5. git rebase -iを使ってなんとかしよう

最後です。僕はgit rebase -i を使えばpushしていないcommitをほとんどなんとかできると考えています。
commit messageの編集もそう、commitの統合もそう、commitの順番なんかもいじれます。最強です。

**ここで、最大の注意点です。**

ただし、最強なのはpushしていないcommitだけです。
pushして外の世界にでてしまったコミットはすでに僕だけのものではなく、みんなのものになってしまっています。
それをオレオレルールを適用してgit push -f を使いまくってコミット履歴を改変しまくるのはもう自己中超えて自己です。
わきまえましょう。

それでは例えば以下のようなコミットログになっているとします。
```
commit 1cc7d2f (HEAD -> develop)
Author: 
Date:   Sun Nov 12 14:25:07 2023 +0900

    feat: hogehoeg

commit fbbe3b6
Author: 
Date:   Sun Nov 12 14:24:56 2023 +0900

    fix: hoge2

commit 8bb114f
Author: 
Date:   Sun Nov 12 14:19:18 2023 +0900

    feat: hoge1

commit 261b729dfe
Author: 
Date:   Sun Nov 12 14:19:04 2023 +0900

    feat: hoge

```

これの`feat: hoge1`までのコミットをいじくっていきたいです。
そうしたら、一個深いcommitの`261b729dfe`を指定して、以下のコマンドを叩きます。

```
git rebase -i 261b729dfe
```
エディタが立ち上がります。

```
pick 8bb114f feat: hoge1
pick fbbe3b6 fix: hoge2
pick 1cc7d2f feat: hogehoeg

# Rebase 261b729..1cc7d2f onto 261b729 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

```

またコマンドがいっぱいでてきました。もちろん僕が全部覚えているはずありません。
覚えているのを共有します。

| オプション | 説明 | 
| --- | --- | 
| p | このコミットをそのまま使う | 
| r | このコミットを使いたいけどコミットメッセージは変更させて | 
| f | このコミット上のコミットにくっつけてくんね？ |

ぼくは3つしか使っていません。怠惰なので他を使おうとも思っていません。
そして、コミットの順番を入れ替えるにはただ並びかえればいいだけです。
それではエディタに表示されているものを以下のように修正してみます。

```
pick 8bb114f feat: hoge1
f fbbe3b6 fix: hoge2
r 1cc7d2f feat: hogehoeg

# Rebase 261b729..1cc7d2f onto 261b729 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

```

こうするとどうなるでしょうか？
- fbbe3b6のcommitは8bb114fと統合したい。
- 1cc7d2fのcommitはコミットメッセージの編集をしたい。

こんな感じですので、このエディタを保存終了すると、1cc7d2fのcommit message編集用のエディタが立ち上がります。

```
feat: hogehoeg

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
...
```

これを編集してあげればよいのです。
今回はhogehogeという文字のtypoを想定しているので、以下のように修正してみます。

```
feat: hogehoge

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
...
```

これで保存してみます。`Successfully rebased and updated <branch name>`と表示されて、
成功したことが通知されました。
`git log`をしてログを確認してみましょう。

```
git log

commit 6226d09a021ac37
Author: 
Date:   Sun Nov 12 14:25:07 2023 +0900

    feat: hogehoeg

commit 7afc8a708
Author: 
Date:   Sun Nov 12 14:19:18 2023 +0900

    feat: hoge1
```

しっかりコミット履歴を改修できました。
ちなみに、コミット履歴を入れ替えるとconflictが起こる可能性ももちろんあります。
よしなに対処してください。

## おわりに
怠惰でcommit癖が全然ない僕が最低限意識している箇所をまとめました。
少しでもお役に立てたら幸いです。

また、周りのつよつよエンジニアのgitあるあるをコメントに書いてくれるとうれしいです。
僕がガワだけ参考にします。