Edited at

スッキリ! Git Commit Log

More than 3 years have passed since last update.


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


  • コミット履歴がスッキリしていないと気になって仕方がないという人

  • 1つのコミットは1つの修正(typo修正、バグ修正、機能追加)に対応していないと落ち着かないという人

  • けどわりとよくルールを無視した修正やコミットをしてしまい後で直したくなる人


注意点


  • 下記文中ではコミットログの改変を行なっています

  • 複数人で共有リポジトリを使っている場合、共有済みのコミットログに対する改変は行わないようにしましょう

  • まだ共有リポジトリにpushしていなければOK!

  • 途中でgit rebaseを利用している箇所がありますが、rebaseそのものついての説明は省略しています


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

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

git stashが使えます。

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

$ git 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を実行すると、ローカルの変更をいったん保留できます。

$ 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")

$ git stash

Saved working directory and index state WIP on master: d4be763 初めてのHello

HEAD is now at d4be763 初めてのHello

$ git status

On branch master

nothing to commit, working directory clean

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

$ git commit -a -m'typo修正'

[master b81a089] typo修正

1 file changed, 1 insertion(+), 1 deletion(-)

$ 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)

$ git 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が使えます。

$ git log --pretty=oneline --abbrev-commit

d53e8bc クラスメソッド化

b2f0cdd typoを修正
6ca3ee3 初めてのHello

$ 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を指定しますが、変更したいコミットの一つ前を指定する必要がある点に注意。

$ 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が使えます。

$ git log --pretty=oneline --abbrev-commit

c4138dc Ignore vim temporary files

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

$ 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化

...

$ git log --pretty=oneline --abbrev-commit

fbdf7de Module化

b5efab3 強調オプションを追加
809c985 クラスメソッドに変更
e7b95e0 typo修正
4cc86ad 初めてのHello
f3d53d1 Ignore vim temporary files

並び替えできました。


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

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

git rebase -iが使えます。

$ git log --pretty=oneline --abbrev-commit

6d4385a Ignore emacs temporary files

8b81bcb Ignore vim temporary files

$ 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
...

$ git log --pretty=oneline --abbrev-commit

23799b4 Ignore editor temporary files

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

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

$ git add . && git commit -m WIP

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

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


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

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

git add -pが使えます。

$ git 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を対話的に選択することができます。

$ git add -p hello.rb

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を入力します。

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されている状態です。

$ git diff --cached

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

$ git 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

$ 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します。

$ git commit -m 'テキストを共通化'

[master 4380f0b] テキストを共通化

1 file changed, 6 insertions(+), 2 deletions(-)

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

$ git status -s

 M hello.rb

$ git add hello.rb

$ git commit -m 'Ciaoクラスを追加'

[master 8d8414d] Ciaoクラスを追加

1 file changed, 7 insertions(+)

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

$ git log --pretty=oneline --abbrev-commit

8d8414d Ciaoクラスを追加

4380f0b テキストを共通化


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

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


範囲を分割する

$ git add -p hello.rb

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)を入力すると、範囲を分割して再度提示してくれます。

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)を入力します。

$ git add -p hello.rb

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

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

# 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!の部分にはパッチを適用しないようにします。

# 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
...

編集終了すると再度プロンプトが表示されるのでパッチ作業を再開します。

そうすると指定したパッチのみが適用されています。

$ git diff --cached

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のままです。

$ git 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が使えます。

$ git show 2145b50

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します。

$ 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

$ git log --pretty=oneline --abbrev-commit

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

62fbef9 初めてのHello
24b6d40 Ignore editor temporary files

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

$ git reset HEAD^

Unstaged changes after reset:

M hello.rb

$ git 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で部分的にパッチを適用します。

$ git add -p hello.rb

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

$ git ci -m 'typoを修正'

[detached HEAD 3cba2ca] typoを修正

1 file changed, 1 insertion(+), 1 deletion(-)

$ git ci -a -m 'クラスメソッドに変更'

[detached HEAD 4f731bc] クラスメソッドに変更

1 file changed, 1 insertion(+), 1 deletion(-)

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

$ git log --pretty=oneline --abbrev-commit 

4f731bc クラスメソッドに変更

3cba2ca typoを修正
62fbef9 初めてのHello
24b6d40 Ignore editor temporary files

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

$ 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します。

$ git rebase --continue

Successfully rebased and updated refs/heads/master.

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

$ 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: