スッキリ！ Git Commit Log

# 以下のような人向けの記事です

* コミット履歴がスッキリしていないと気になって仕方がないという人
* 1つのコミットは1つの修正(typo修正、バグ修正、機能追加)に対応していないと落ち着かないという人
* けどわりとよくルールを無視した修正やコミットをしてしまい後で直したくなる人

# 注意点

* 下記文中ではコミットログの改変を行なっています
* 複数人で共有リポジトリを使っている場合、共有済みのコミットログに対する改変は行わないようにしましょう
* まだ共有リポジトリにpushしていなければOK!
* 途中で``git rebase``を利用している箇所がありますが、rebaseそのものついての説明は省略しています

# 今の作業を一時的に中断して別の作業をやりたい

機能追加の修正を行なっている最中にtypoを見つけてしまったけど、機能追加が終わるのを待ってたら忘れてしまうかもしれないし・・・というようなケースの場合。

``git stash``が使えます。

インスタンスメソッドからクラスメソッドに変更しようとして、

```bash
$ git diff
```
```diff
diff --git a/hello.rb b/hello.rb
index 071b93c..fb46af8 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,5 +1,5 @@
 class Hello
-  def say
+  def self.say
     # 挨拶する
     puts "hallo"
   end
```
はたと``hallo``というtypoに気づいてしまった場合、
``git stash``を実行すると、ローカルの変更をいったん保留できます。

```bash
$ git status
```
```
On branch master
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:   hello.rb

no changes added to commit (use "git add" and/or "git commit -a")
```

```bash
$ git stash
```
```
Saved working directory and index state WIP on master: d4be763 初めてのHello
HEAD is now at d4be763 初めてのHello
```

```bash
$ git status
```
```
On branch master
nothing to commit, working directory clean
```

その後typoを修正し、``git stash pop``で作業を再開します。

```bash
$ git commit -a -m'typo修正'
```
```
[master b81a089] typo修正
 1 file changed, 1 insertion(+), 1 deletion(-)
```
```bash
$ git stash pop
```
```
Auto-merging hello.rb
On branch master
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:   hello.rb

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (0d64fd669dd28e891ce3edbd0c859e9c75d4eaf3)
```

```bash
$ git diff
```
```diff
diff --git a/hello.rb b/hello.rb
index 5463e06..5a3288e 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,5 +1,5 @@
 class Hello
-  def say
+  def self.say
     # 挨拶する
     puts "hello"
   end
```

# 直前のコミットメッセージを変更したい

今さっきコミットしたけど、コミットメッセージにtypoが見つかった、表現を微妙に変更したい、というようなケースの場合。

``git commit --amend``が使えます。

```bash
$ git log --pretty=oneline --abbrev-commit
```
```
d53e8bc クラスメソッド化
b2f0cdd typoを修正
6ca3ee3 初めてのHello
```

```bash
$ git ci --amend -m 'クラスメソッドに変更'
```
```
[master 6be1a54] クラスメソッドに変更
 Date: Thu Jan 22 13:39:11 2015 +0900
 1 file changed, 1 insertion(+), 1 deletion(-)
```

# 過去のコミットメッセージを変更したい

少し前にコミットしたメッセージを見てみたらtypoが見つかった（以下略）というようなケースの場合。

``git rebase -i``が使えます。

コミットIDを指定しますが、変更したいコミットの一つ前を指定する必要がある点に注意。

```bash
$ git rebase -i c093377^
```

を実行すると、エディタが起動して以下のような編集状態になります。

```
pick c093377 typo修正
pick ede2ecf クラスメソッドに変更

# Rebase 6ca3ee3..ede2ecf onto 6ca3ee3
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
...
```

``#``で始まる行はコメントですが、説明が表示されています。
今回はコミットメッセージの変更なので、``reword`` を行います。

``pick c093377 typo修正``
の行を
``r c093377 typo修正``
に編集した後エディタを終了します。

すると、再度エディタが起動して以下のような編集状態になります。

```
typo修正

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Tue Jan 20 20:11:07 2015 +0900
#
# rebase in progress; onto 6ca3ee3
# You are currently editing a commit while rebasing branch 'master' on '6ca3ee3'.
#
# Changes to be committed:
# modified:   hello.rb
#
```

これはいつものコミットメッセージ入力と同じなので、適当に編集してエディタを終了します。

```
[detached HEAD b2f0cdd] typoを修正
 Date: Tue Jan 20 20:11:07 2015 +0900
 1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/master.
```

するとrebase処理が走り、正常に終了すればコミットメッセージが変更されています。

# コミットの順序が気になるので並び替えたい

コミットログを見直してみると設定変更と機能追加とが入り混じってたので、前者を先頭に揃えたい、というようなケースの場合。

``git rebase -i``が使えます。

```bash
$ git log --pretty=oneline --abbrev-commit
```
```
c4138dc Ignore vim temporary files
c4d27e1 Module化
6d14f26 強調オプションを追加
1b4a3b4 クラスメソッドに変更
2edc7f6 typo修正
92d0e63 初めてのHello
```
```bash
$ git rebase -i --root
```

Note: 最後``git rebase``にコミットIDじゃなく``--root``を渡しているのは、 first commitに変更を加えたい場合はこうする必要があるため

そうすると例によって以下の編集画面が起動します。

```
pick 92d0e63 初めてのHello
pick 2edc7f6 typo修正
pick 1b4a3b4 クラスメソッドに変更
pick 6d14f26 強調オプションを追加
pick c4d27e1 Module化
pick c4138dc Ignore vim temporary files

...
# These lines can be re-ordered; they are executed from top to bottom.
...
```

re-orderできるみたいなことを書いているので、行を入れ替えて保存終了します。

```
pick c4138dc Ignore vim temporary files
pick 92d0e63 初めてのHello
pick 2edc7f6 typo修正
pick 1b4a3b4 クラスメソッドに変更
pick 6d14f26 強調オプションを追加
pick c4d27e1 Module化

...
```

```bash
$ git log --pretty=oneline --abbrev-commit
```
```
fbdf7de Module化
b5efab3 強調オプションを追加
809c985 クラスメソッドに変更
e7b95e0 typo修正
4cc86ad 初めてのHello
f3d53d1 Ignore vim temporary files
```

並び替えできました。

# 複数のコミットを結合したい

似たようなコミットが何個もあるので1つにまとめたい、というようなケースの場合。

``git rebase -i``が使えます。

```bash
$ git log --pretty=oneline --abbrev-commit
```
```
6d4385a Ignore emacs temporary files
8b81bcb Ignore vim temporary files
```
```bash
$ git rebase -i 8b81bcb^
```

以下の編集画面になります。

```
pick 8b81bcb Ignore vim temporary files
pick 6d4385a Ignore emacs temporary files

...
#  s, squash = use commit, but meld into previous commit
...
```

今回は``squash``を使います。以下の編集を行い、保存終了します。

```
pick 8b81bcb Ignore vim temporary files
s 6d4385a Ignore emacs temporary files

...
```

すると再度編集画面になります。

```
# This is a combination of 2 commits.
# The first commit's message is:

Ignore vim temporary files

# This is the 2nd commit message:

Ignore emacs temporary files

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Tue Jan 27 15:01:12 2015 +0900
#
# rebase in progress; onto 06b5d8c
# You are currently editing a commit while rebasing branch 'master' on '06b5d8c'.
#
#
# Initial commit
#
# Changes to be committed:
# new file:   .gitignore
#
```

``squash``の場合、直前のコミットと結合して、かつコミットメッセージを変更することができます。
上記例では2つのコミットが結合しているので、2つのメッセージが含まれています。

適当に編集して保存します。

```
# This is a combination of 2 commits.
# The first commit's message is:
Ignore editor temporary files

- vim
- emacs
...
```

```bash
$ git log --pretty=oneline --abbrev-commit
```
```
23799b4 Ignore editor temporary files
```

なお``squash``の代わりに``fixup``を指定すると、同じように直前のコミットと結合しますが、コミットメッセージを破棄する点が違います。

自分の場合、一時的にブランチを切り替える時とかに``git stash``だと後で忘れたりするのがイヤで、

```bash
$ git add . && git commit -m WIP
```

などで適当にコミットしておくことがよくあります。

そんな感じでいくつかできたWIP用コミットを最終的に結合する際に``fixup``を用いることが多いです。

# 複数の修正をしてしまったのでそれぞれ別個にコミットしたい

まだコミットはしていない状態で、気がついたらtypo修正と機能追加とを両方行なっていたが、それぞれ別のコミットにしたい、というようなケースの場合。

``git add -p``が使えます。

```bash
$ git diff
```
```diff
diff --git a/hello.rb b/hello.rb
index 013d56d..221d59f 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,15 @@
 module Greeting
+  def message
+    "hello"
+  end
+
   def say options={}
     if options[:emphasise]
       # 元気よく挨拶する
-      puts "hello!"
+      puts "#{message}!"
     else
       # 挨拶する
-      puts "hello"
+      puts "#{message}"
     end
   end
 end
@@ -13,3 +17,10 @@ end
 class Hello
   extend Greeting
 end
+
+class Ciao
+  extend Greeting
+  def self.message
+    "ciao"
+  end
+end
```

勢いあまって、テキスト文字列の共通化とCiaoクラスの追加とを同時に行なってしまっていますが、それぞれ個別のコミットにします。

``git add -p``すると、対話的な画面になります。
``-p``は``--patch``のことで、適用するpatch hunkを対話的に選択することができます。

```bash
$ git add -p hello.rb
```

```diff
diff --git a/hello.rb b/hello.rb
index 013d56d..221d59f 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,15 @@
 module Greeting
+  def message
+    "hello"
+  end
+
   def say options={}
     if options[:emphasise]
       # 元気よく挨拶する
-      puts "hello!"
+      puts "#{message}!"
     else
       # 挨拶する
-      puts "hello"
+      puts "#{message}"
     end
   end
 end
Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? 
```

このhunkをstageするか、と聞いてきます。stageするかというのはつまり取り込むかどうか、ということです。
今回は、まずテキストの共通化を行いたいのでstageします。stageするには``y``を入力します。

```diff
Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? y
@@ -13,3 +17,10 @@ end
 class Hello
   extend Greeting
 end
+
+class Ciao
+  extend Greeting
+  def self.message
+    "ciao"
+  end
+end
Stage this hunk [y,n,q,a,d,/,K,g,e,?]? 
```

すると続けて、別のhunkが提示され、同じようなプロンプトが表示されます。
今回はCiaoクラスの追加は含めたくないので``n``を入力します。

```
Stage this hunk [y,n,q,a,d,/,K,g,e,?]? n
```

add処理が終了していますが、部分的にstageされている状態です。

```bash
$ git diff --cached
```
```diff
diff --git a/hello.rb b/hello.rb
index 013d56d..6b42e0a 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,15 @@
 module Greeting
+  def message
+    "hello"
+  end
+
   def say options={}
     if options[:emphasise]
       # 元気よく挨拶する
-      puts "hello!"
+      puts "#{message}!"
     else
       # 挨拶する
-      puts "hello"
+      puts "#{message}"
     end
   end
 end
```
```bash
$ git diff
```
```diff
diff --git a/hello.rb b/hello.rb
index 6b42e0a..221d59f 100644
--- a/hello.rb
+++ b/hello.rb
@@ -17,3 +17,10 @@ end
 class Hello
   extend Greeting
 end
+
+class Ciao
+  extend Greeting
+  def self.message
+    "ciao"
+  end
+end
```
```bash
$ git status
```
```
On branch master
Your branch and 'origin/master' have diverged,
and have 6 and 5 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   hello.rb

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:   hello.rb
```

1つめの修正を``git commit``します。

```bash
$ git commit -m 'テキストを共通化'
```
```
[master 4380f0b] テキストを共通化
 1 file changed, 6 insertions(+), 2 deletions(-)
```

しかし、まだstageされていない修正があるので、再度addします。

```bash
$ git status -s
```
```
 M hello.rb
```
```bash
$ git add hello.rb
$ git commit -m 'Ciaoクラスを追加'
```
```
[master 8d8414d] Ciaoクラスを追加
 1 file changed, 7 insertions(+)
```

2つのコミットに分割することができました。

```bash
$ git log --pretty=oneline --abbrev-commit
```
```
8d8414d Ciaoクラスを追加
4380f0b テキストを共通化
```

## ``git add -p``についてもう少し

対話的にパッチを適用する際に、必ずしも自分の必要とする範囲を提示してくれるとは限りません。

### 範囲を分割する

```bash
$ git add -p hello.rb
```
```diff
diff --git a/hello.rb b/hello.rb
index 013d56d..221d59f 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,15 @@
 module Greeting
+  def message
+    "hello"
+  end
+
   def say options={}
     if options[:emphasise]
       # 元気よく挨拶する
-      puts "hello!"
+      puts "#{message}!"
     else
       # 挨拶する
-      puts "hello"
+      puts "#{message}"
     end
   end
 end
Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? 
```

``s(split the current hunk into smaller hunks)``を入力すると、範囲を分割して再度提示してくれます。

```diff
Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? s
Split into 3 hunks.
@@ -1,4 +1,8 @@
 module Greeting
+  def message
+    "hello"
+  end
+
   def say options={}
     if options[:emphasise]
       # 元気よく挨拶する
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? 
```

hunkが分割された状態になり、再度プロンプトが表示されるので、パッチ適用作業を再開します。

### 自分でパッチを編集する

範囲分割が利用不可能な場合や、さらに細かい範囲を指定したい場合、パッチファイルを自分で編集することができます。

``e(manually edit the current hunk)``を入力します。

```bash
$ git add -p hello.rb
```
```diff
diff --git a/hello.rb b/hello.rb
index 013d56d..221d59f 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,15 @@
 module Greeting
+  def message
+    "hello"
+  end
+
   def say options={}
     if options[:emphasise]
       # 元気よく挨拶する
-      puts "hello!"
+      puts "#{message}!"
     else
       # 挨拶する
-      puts "hello"
+      puts "#{message}"
     end
   end
 end
Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? e
```

すると、編集画面が起動します。

```diff
# Manual hunk edit mode -- see bottom for a quick guide
@@ -1,11 +1,15 @@
 module Greeting
+  def message
+    "hello"
+  end
+
   def say options={}
     if options[:emphasise]
       # 元気よく挨拶する
-      puts "hello!"
+      puts "#{message}!"
     else
       # 挨拶する
-      puts "hello"
+      puts "#{message}"
     end
   end
 end
# ---
# 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,
```

説明を読むと、

* 削除行である``'-'``行を除外するには、``' '``行に置換する
* 追加行である``'+'``行を除外するには、その行を削除する

とあるので、以下のように編集します。
ここでは、hello!の部分にはパッチを適用しないようにします。

```diff
# Manual hunk edit mode -- see bottom for a quick guide
@@ -1,11 +1,15 @@
 module Greeting
+  def message
+    "hello"
+  end
+
   def say options={}
     if options[:emphasise]
       # 元気よく挨拶する
       puts "hello!"
     else
       # 挨拶する
-      puts "hello"
+      puts "#{message}"
     end
   end
 end
...
```

編集終了すると再度プロンプトが表示されるのでパッチ作業を再開します。
そうすると指定したパッチのみが適用されています。

```bash
$ git diff --cached
```
```diff
diff --git a/hello.rb b/hello.rb
index 013d56d..f48c393 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,15 @@
 module Greeting
+  def message
+    "hello"
+  end
+
   def say options={}
     if options[:emphasise]
       # 元気よく挨拶する
       puts "hello!"
     else
       # 挨拶する
-      puts "hello"
+      puts "#{message}"
     end
   end
 end
```

当然、適用していないパッチ部分はunstageのままです。

```bash
$ git diff
```
```diff
diff --git a/hello.rb b/hello.rb
index f48c393..221d59f 100644
--- a/hello.rb
+++ b/hello.rb
@@ -6,7 +6,7 @@ module Greeting
   def say options={}
     if options[:emphasise]
       # 元気よく挨拶する
-      puts "hello!"
+      puts "#{message}!"
     else
       # 挨拶する
       puts "#{message}"
@@ -17,3 +17,10 @@ end
 class Hello
   extend Greeting
 end
+
+class Ciao
+  extend Greeting
+  def self.message
+    "ciao"
+  end
+end
```

# 複数の修正が含まれるコミットを分割したい

コミットログを見直してみると、typo修正と機能追加とを1つのコミットにしていたが、それぞれ別のコミットにしたい、というようなケースの場合。

``git rebase -i``, ``git reset``, ``git-add -p``が使えます。

```bash
$ git show 2145b50
```
```diff
commit 2145b5006b467d04009b772815f5cd53115f0fef
Author: Akira Uchihara <uchihara@tab.do>
Date:   Fri Jan 23 11:56:37 2015 +0900

    クラスメソッドに変更
    
    ついでにtypo修正

diff --git a/hello.rb b/hello.rb
index 071b93c..5a3288e 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,6 +1,6 @@
 class Hello
-  def say
+  def self.say
     # 挨拶する
-    puts "hallo"
+    puts "hello"
   end
 end
```

``git rebase -i``します。

```bash
$ git rebase -i 2145b50^
```

```
pick 2145b50 クラスメソッドに変更
...
#  e, edit = use commit, but stop for amending
```

``edit``を使います。これはコミットを適用した後、rebaseを中断した状態で停止します。

```
e 2145b50 クラスメソッドに変更
...
```

保存終了すると、rebase中で停止していることが分かります。

```
Stopped at 2145b5006b467d04009b772815f5cd53115f0fef... クラスメソッドに変更
You can amend the commit now, with

	git commit --amend 

Once you are satisfied with your changes, run

	git rebase --continue

```
```bash
$ git log --pretty=oneline --abbrev-commit
```
```
2145b50 クラスメソッドに変更
62fbef9 初めてのHello
24b6d40 Ignore editor temporary files
```

``git reset``して、コミットを取り消します。

```bash
$ git reset HEAD^
```
```
Unstaged changes after reset:
M	hello.rb
```
```bash
$ git diff
```
```diff
diff --git a/hello.rb b/hello.rb
index 071b93c..5a3288e 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,6 +1,6 @@
 class Hello
-  def say
+  def self.say
     # 挨拶する
-    puts "hallo"
+    puts "hello"
   end
 end
```

``git add -p``で部分的にパッチを適用します。

```bash
$ git add -p hello.rb
```
```diff
diff --git a/hello.rb b/hello.rb
index 071b93c..5a3288e 100644
--- a/hello.rb
+++ b/hello.rb
@@ -1,6 +1,6 @@
 class Hello
-  def say
+  def self.say
     # 挨拶する
-    puts "hallo"
+    puts "hello"
   end
 end
Stage this hunk [y,n,q,a,d,/,s,e,?]? s
Split into 2 hunks.
@@ -1,3 +1,3 @@
 class Hello
-  def say
+  def self.say
     # 挨拶する
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? n
@@ -3,4 +3,4 @@
     # 挨拶する
-    puts "hallo"
+    puts "hello"
   end
 end
Stage this hunk [y,n,q,a,d,/,K,g,e,?]? y
```
```bash
$ git ci -m 'typoを修正'
```
```
[detached HEAD 3cba2ca] typoを修正
 1 file changed, 1 insertion(+), 1 deletion(-)
```
```bash
$ git ci -a -m 'クラスメソッドに変更'
```
```
[detached HEAD 4f731bc] クラスメソッドに変更
 1 file changed, 1 insertion(+), 1 deletion(-)
```

コミットが分割できました。

```bash
$ git log --pretty=oneline --abbrev-commit 
```
```
4f731bc クラスメソッドに変更
3cba2ca typoを修正
62fbef9 初めてのHello
24b6d40 Ignore editor temporary files
```

繰り返しますが、この状態はまだrebase中です。

```bash
$ git status
```
```
rebase in progress; onto 62fbef9
You are currently editing a commit while rebasing branch 'master' on '62fbef9'.
  (use "git commit --amend" to amend the current commit)
  (use "git rebase --continue" once you are satisfied with your changes)

nothing to commit, working directory clean
```

rebase作業を続ける必要があるので、``git rebase --continue``します。

```bash
$ git rebase --continue
```
```
Successfully rebased and updated refs/heads/master.
```

コミットが分割されていることが分かります。

```bash
$ git log --pretty=oneline --abbrev-commit
```
```
4dd22e2 Ciaoクラスを追加
177e872 テキストを共通化
39f5b2f Module化
b341538 強調オプションを追加
4f731bc クラスメソッドに変更
3cba2ca typoを修正
62fbef9 初めてのHello
24b6d40 Ignore editor temporary files
```

# おわり

ステキなGit Commit Logライフを！ :laughing: