【やらかし別】commit履歴を整理するためのGitコマンド



#ケース別commit履歴の整理法

正直な話、たった一人でプロダクトを開発していると、僕みたいな人間はcommitの粒度などに無頓着になってくるのですが、最近は開発したプロダクトのコードレビューをしていただく機会が多くなったこともあり、気をつけ始めています。
相手としても読みにくいだろうし、見せるこちらとしてもやはり恥ずかしいですもんね。。。

**この記事は、ローカルのcommit履歴を整理するための手順をまとめたものです。**
僕自身の備忘録でもあり、おそらく今後もよく見返すであろう手順をここにまとめています。

僕は驚くほど無知なので、もし何かより良い提案などございましたら、ぜひ教えていただければ幸いです。

（テンションおかしいのは無視してください）

##ケース１「あっ。直前のcommitを修正したい...」
やだーーー！！
fugas_controllerのhogeアクションとviewのhoge.html.erbを消して「delete fugas#hoge」とcommitしたのに、ルーティングの設定が残ったままじゃん！


このcommitを取り消そう。
commitを取り消すにしても、commitを間違えた歴史は残したくないから、`revert`じゃなくて、`reset`ですね。
（[参考記事：Git commit 取り消したい]( https://qiita.com/maejimayuto/items/30bfab250186a00ae884) ）

indexとワーキングツリーはそのままでいいから、`--hard`じゃなくて、`--soft`ですね。
（[参考記事：[git reset (--hard/--soft)]ワーキングツリー、インデックス、HEADを使いこなす方法](https://qiita.com/shuntaro_tamura/items/db1aef9cf9d78db50ffe) ）

取り消すのは直前のcommitなので、`HEAD^`ですね。


```terminal:
git reset --soft HEAD^
```

routes.rbを変更したから、indexに追加しよう。

```terminal:
git add config/routes.rb
```

変更はちゃんと全部indexに追加されてるかなぁ〜〜〜？？？
`git status`で確認しよう。

```terminal:
git status
```

どれどれ〜？？

```terminal:出力結果
 modified:   app/controllers/fugas_controller.rb
 deleted:    app/views/fugas/hoge.html.erb
 modified:   config/routes.rb
```
問題ないですね。

よし、commitしよう。

```terminal:
git commit -m "delete fuga#hoge" -m "fugaコントローラーのhogeアクションを削除した。"
```

完了！！


##ケース２「げっ！いくつか前のcommitを修正したい...」
やだーーー！！
fugas_（ry


それもいくつか前のcommitだなぁ...。修正しなきゃ〜...。

で？修正したいのは、どれくらい前のcommitだっけ？
`git log`で確認や！
修正内容まで見なくても、コミットメッセージだけ表示してくれればわかるので、`--oneline`つけとこう。

```terminal:
git log --oneline
```
どれどれ？？

```terminal:出力結果
 dc77776b (HEAD -> refactoring) modify footer
 3fb04106 delete fugas#hoge
 0764bcaf delete landing_page
 29970ee1 fix pagenate bug
```

"delete fugas#hoge"は２つ前のcommitか。
了解了解。
``q`` を入力して離脱しよう。

さて、ここからがちょっと面倒くさい。
commit履歴が少し過去にまで遡るので、`delete fugas#hoge`のコミットまでgit resetしてしまうと、`modify footer`のコミットまで消えてしまいます。

だからここで使うコマンドは、`git rebase -i <変更したいコミットの「１つ前」のハッシュ値>`です。

これを使えば、ピンポイントで特定のcommitの内容だけを修正できる。
注意すべきは、rebase -iで指定するのは、**「変更したいコミットの１つ前のコミットのハッシュ値」**だということ。
ここではつまり、`delete landing_page`のハッシュ値になる。
だから打つべきコマンドは、下になる。

```terminal:
git rebase -i 0764bcaf
```
すると次の入力画面が現れる。

```terminal:出力結果（変更前）
 pick 3fb04106 delete fugas#hoge
 pick dc77776b modify footer

# Rebase 0764bcaf..dc77776b onto 0764bcaf (2 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
```

さて、過去のcommitを書き換えるには、この画面の１行目の`pick 3fb04106 delete fugas#hoge`を、`edit 3fb04106 delete fugas#hoge`に書き換えなくてはならない。
しかし、初心者にとっては、ここが意外にも難所だったりします。
ここで表示される入力画面は、普段触る機会の少ない『Vim』と呼ばれるクセのあるエディターだからです。
Vimの使い方については、下の記事が詳しいです。
[（参考記事：知識0から始めるVim講座）](https://qiita.com/JpnLavender/items/fabcc79b4ab0d52e1f6d)
　
読む暇もないという方は、これから僕が唱える呪文を順番に打ってください。
表示されたばかりの状態では文字入力はできないので、まずは`i`のキーを叩くことで、文字入力できる状態にします。
叩いた？文字入力できるようになった？
では次に、１行目の`pick 3fb04106 delete fugas#hoge`を、`edit 3fb04106 delete fugas#hoge`に書き換えます。
書き換えられた？
次に、`escキー`を押します。
押した？また文字入力できなくなった？
次に、`:wq`を押して、保存して終了します。
終了した？
もし下の画面が出力されたら、とりあえず成功です。

```terminal:出力結果
 Stopped at 3fb04106...  delete fugas#hoge
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue

```

そしたら、`delete fugas#hoge`のコミットに追加したいファイルなり変更なりをstagingしましょう。

```terminal:
git add config/routes.rb
```

そしていつものようにcommmitするのですが、ここでちょっと注意。
オプションは`--amend`にするのだ。

```terminal:
git commit --amend 
```
commitした？？
すると、またこんな感じでVimの画面が表示されたと思う。

```terminal:出力結果
delete fugas#hoge
  
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Thu Jun 25 15:06:15 2020 +0900
#
# interactive rebase in progress; onto 7339e063
# Last command done (1 command done):
#    edit 3fb04106 delete fugas#hoge
# Next commands to do (2 remaining commands):
#    pick dc77776b modify footer

```
とくにコミットメッセージなど変更するつもりがないなら、`:wq`で編集画面を終了しましょう。
メッセージを修正したいなら、さっき紹介した`i`から始まる手順を踏めばいい。
　
さて、これでcommitできたわけだけれど、ここで安心するのはまだ早い。
まだrebase作業は終わっていない。
最後に下のコマンドを入力しましょう。

```terminal:
git rebase --continue
```

これで終わり！！！
お疲れ様！！

[（参考記事：rebase -i でコミットを修正する）](https://backlog.com/ja/git-tutorial/stepup/33/)






##ケース３「あ〜。１つのファイルに複数の変更を加えてstagingしちゃったけど、変更はそれぞれ分けてcommitしたいな...」
とりあえず、`git diff`でどのへんを変更したのか確認しよう。
もうデータはstagingしちゃったので、変更前との差分を見たいなら`--staged`をつけよう。
（[参考記事：git 差分を見る](https://qiita.com/ikenji/items/42248085c4f4b55660d6)）

```terminal:
git diff --staged
```
どれどれ？？

```terminal:出力結果
 diff --git a/config/routes.rb b/config/routes.rb
index 02fcf8e4..ebcf4fa1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -18,14 +18,14 @@ Rails.application.routes.draw do
 
   #get  "how_to_use",     to: "static_pages#how_to_use"
   #
-  #get '/landing_page', to: 'static_pages#landing_page'
index 02fcf8e4..ebcf4fa1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -18,14 +18,14 @@ Rails.application.routes.draw do
 
   #get  "how_to_use",     to: "static_pages#how_to_use"
   #
-  #get '/landing_page', to: 'static_pages#landing_page'

```

なるほどね。
まずはconfig/routes.rbをindexから外さないとね。
indexからファイルをunstagingするには、`git reset <ファイル名>`か`git rm --cached <ファイル名>`ですね。
[（参考記事：git add の取り消し方法と、関連コマンドまとめ）
](http://www-creators.com/archives/1282#git_add-2)

```terminal:
git reset config/routes
```

さて、unstagingしたこのファイル変更箇所の一部分だけをstagingし直すには...。
ヘェ〜、`git add -p <ファイル名>`なんてあるのか。なるほど。
[（参考記事：Git 変更のあるファイルの一部だけをコミットしたい。）](https://chaika.hatenablog.com/entry/2017/06/28/120000)

```terminal:
git add -p config/routes.rb
```

どれどれ？

```terminal:出力結果
 diff --git a/config/routes.rb b/config/routes.rb
index 02fcf8e4..ebcf4fa1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -18,14 +18,14 @@ Rails.application.routes.draw do
 
   #get  "how_to_use",     to: "static_pages#how_to_use"
   #
-  #get '/landing_page', to: 'static_pages#landing_page'
+
 
   get "/select_plan", to: "static_pages#select_plan"
   get "/update", to: "static_pages#update"
 
   get "/invitation_or_premium", to: "static_pages#invitation_or_premium"
 
-  get "/new", to: "static_pages#new"
+
 
 
(1/1) Stage this hunk [y,n,q,a,d,s,e,?]? 
```

hunk?
あぁ、stagingする単位のことか。
って、変更が全部１つにまとまっちゃってんじゃ〜ん！
変更を分割するには、`s`を入力してenter。

```terminal:出力結果
 (1/1) Stage this hunk [y,n,q,a,d,s,e,?]? s
Split into 2 hunks.
@@ -18,10 +18,10 @@
 
   #get  "how_to_use",     to: "static_pages#how_to_use"
   #
-  #get '/landing_page', to: 'static_pages#landing_page'
+
 
   get "/select_plan", to: "static_pages#select_plan"
   get "/update", to: "static_pages#update"
 
   get "/invitation_or_premium", to: "static_pages#invitation_or_premium"
 
(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?
```
この変更をstagingしよう。
`y`を入力して、enter。

```terminal:出力結果
 (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? y
@@ -22,10 +22,10 @@
 
   get "/select_plan", to: "static_pages#select_plan"
   get "/update", to: "static_pages#update"
 
   get "/invitation_or_premium", to: "static_pages#invitation_or_premium"
 
-  get "/new", to: "static_pages#new"
+
 
 
(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]?
```

この変更はあとでcommitするので、今はstagingしない。
`n`を入力してスキップ！
OK、終了終了。
　
よし、これで１つのファイルの２つの変更のうち１つだけstagingできたぞ〜！
できてるよね？？
不安なので、`git status`でindexを確認しよう。

```terminal:
git status
```
どれどれ〜？

```terminal:出力結果
 On branch fix_bug
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   config/routes.rb

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   config/routes.rb

```

よしよし。ちゃんとconfig/routesrbが分割でstagingされてる。


じゃあ、まずは今stagingしたファイルをcommitしよう。

```terminal:
git commit -m "delete landing_page_path" -m "ランディングページのルーティングを削除"
```

それから残りの変更もstagingして、

```terminal:
git add config/routes.rb
```
commit！

```terminal:
git commit -m "delete new_path" -m "static_pages#newのルーティングを削除"
```

終わり！！！


##ケース４「あぁ〜、いくつか前のcommit、粒度粗いし分割したい〜〜〜」

過去のcommitの分割は、「ケース１」「ケース２」「ケース３」を合わせた知識でできます。


`rebase -i`で、過去のcommitに移動したあと、
**そのcommitを`git reset --soft HEAD^`で取り消す。**

その後は、すでにindexにある分割してcommitしたいファイルを、`git reset <ファイル名>` でunstagingしたり、
「ケース３」で紹介した手順で、１つのファイルの複数の変更を分割してstagingして、何回かに分けてcommitするなどすれば、commitの粒度を細かくできます。

**このときのcommitは、`commit --amend`ではなく、`commit -m "コミットメッセージ"`を使います。**

最後に `git rebase --continue`を打ってrebaseを終了し、
`git log --oneline`で確認すれば、
きちんとcommitが分割されていることがわかります。



##宣伝

Terminalの英語が読めない僕とあなたのために、[BooQsという学習の科学に基づいた英単語学習サービス](https://www.booqs.net/)を開発しています。
BooQsの中でもとくに人気のあるコンテンツは、[NGSL（New General Service List）と呼ばれる、一般的な英文の９割を網羅した英単語帳](https://note.com/kawanjin01/n/na861d9264699)です。
よろしければぜひ！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">BooQs（ブックス） は、<br>楽しく効率的に英単語を覚えられるサービスです。<br><br>現在、『基礎英単語』『学術英単語』『TOEIC英単語』『ビジネス英単語』を学ぶことができます。<br><br>ぜひご活用ください！！<a href="https://twitter.com/hashtag/BooQs?src=hash&amp;ref_src=twsrc%5Etfw">#BooQs</a><a href="https://t.co/Ygvf3QeZKL">https://t.co/Ygvf3QeZKL</a></p>&mdash; 挫折させない英単語サービス『BooQs』 (@BooQs_net) <a href="https://twitter.com/BooQs_net/status/1240537593806086145?ref_src=twsrc%5Etfw">March 19, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>