ksaitoの日記

日々試したことの覚え書き

gitのrebaseでコミットを書き換える

git rebaseでコミットをきれいにする方法です。

使うタイミング

自分専用のトピックブランチで作業が完了して 元のブランチに戻す前 に行います。

練習用のリポジトリ作成

sampleというファイルに機能が3つインプリされているという想定のテスト用のリポジトリを作成します。

$ git init test
$ cd test
$ for i in `seq 1 3`; do printf "func%d\n\n" $i; done > sample
$ cat sample 
func1

func2

func3

$ git add sample; git commit -m "init"
[master (root-commit) c509fc6] init
 1 file changed, 6 insertions(+)
 create mode 100644 sample
$ 

トピックブランチを作って、3つの機能の全てに修正を加えてコミットしたと想定します。

$ git checkout -b topic
Switched to a new branch 'topic'
$ sed -ie 's/\(func.\)/\1+fix/' sample 
$ cat sample
func1+fix

func2+fix

func3+fix

$ git commit -a -m "fix"
[topic 77adf00] fix
 1 file changed, 3 insertions(+), 3 deletions(-)
$ 

この時点で、気がつけば、git reset HEADで戻すことも可能ですが、気が付いたときには、もう一つ機能が追加されていたとします。

$ echo func4 >> sample
$ git commit -am "これは、正しいコミットで修正したくない"
[topic e836500] これは、正しいコミットで修正したくない
 1 file changed, 1 insertion(+)
$ 

テスト用のリポジトリは、こんな感じになります。

$ git --no-pager log --pretty=oneline
e836500e25943c80e67fcbfdcfef10e0d8fc0040 これは、正しいコミットで修正したくない
77adf00234c23ceda93be668eef460a218944f5a fix
c509fc62f1a14a5d374c20203fecec670cf82a6f init
$ 

コミットを分割する

3つの機能の修正は、独立しているので、修正毎に3つのコミットにするべきだったと想定して、コミットを分割します。

対象のコミットを確認する。

git show-branchコマンドで、現在のtopicブランチに2つのコミットがありtopic^に修正したいコミットがあることがわかります。

$ git show-branch
! [master] init
 * [topic] これは、正しいコミットで修正したくない
--
 * [topic] これは、正しいコミットで修正したくない
 * [topic^] fix
+* [master] init
$ 

対象のコミットを修正

git rebase -iを使って対象のコミットを修正します。

対象は、topic^なので、その一つ前のmasterを指定します。

$ git rebase -i master

エディタが開いて対象のコミットが下記のように表示されます。

pickとなっている部分で[e]を選択してエディタを押します。

pick 77adf00 fix                                                                                                                                                                     
pick e836500 これは、正しいコミットで修正したくない                                                                                                                                  
                                                                                                                                                                                     
# Rebase c509fc6..e836500 onto c509fc6 (2 command(s))     

修正したいコミットのpickeditに変わったのを確認してエディタを修正します。

edit 77adf00 fix                                                                                                                                                                     
pick e836500 これは、正しいコミットで修正したくない                                                                                                                                  
                                                                                                                                                                                     
# Rebase c509fc6..e836500 onto c509fc6 (2 command(s))   

エディタが終了すると、こんなメッセージが表示されます。

Stopped at 77adf00234c23ceda93be668eef460a218944f5a... fix
You can amend the commit now, with

        git commit --amend 

Once you are satisfied with your changes, run

        git rebase --continue

$ 

git resetで修正を戻します。

$ git reset HEAD^
Unstaged changes after reset:
M       sample
$ 

修正内容を分割

git add -pを使って修正を分割します。

下記のように表示されるので、必要な修正まで[s]で分割していきます。

$ git add -p
diff --git a/sample b/sample
index bd073e0..b9dc832 100644
--- a/sample
+++ b/sample
@@ -1,6 +1,6 @@
-func1
+func1+fix
 
-func2
+func2+fix
 
-func3
+func3+fix
 
Stage this hunk [y,n,q,a,d,/,s,e,?]? 

[s]を押すと次のように、コミットが分割されます。

Stage this hunk [y,n,q,a,d,/,s,e,?]? s
Split into 3 hunks.
@@ -1,2 +1,2 @@
-func1
+func1+fix
 
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? 

[y]を押して、分割されたコミットがステージされた状態にします。

[q]を押して一旦、終了します。

Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? y
@@ -2,3 +2,3 @@
 
-func2
+func2+fix
 
Stage this hunk [y,n,q,a,d,/,K,j,J,g,e,?]? q

$ 

git statusで確認すると下記のようになります。

$ git status
interactive rebase in progress; onto c509fc6
Last command done (1 command done):
   edit 77adf00 fix
Next command to do (1 remaining command):
   pick e836500 これは、正しいコミットで修正したくない
  (use "git rebase --edit-todo" to view and edit)
You are currently splitting a commit while rebasing branch 'topic' on 'c509fc6'.
  (Once your working directory is clean, run "git rebase --continue")

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   sample

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

$ 

下記のように分割したコミットだけがステージされるので、これを、あと2回繰り返して、3つのコミットに分割します。

$ git --no-pager diff --staged
diff --git a/sample b/sample
index bd073e0..604342d 100644
--- a/sample
+++ b/sample
@@ -1,4 +1,4 @@
-func1
+func1+fix
 
 func2
 
$ git --no-pager diff
diff --git a/sample b/sample
index 604342d..b9dc832 100644
--- a/sample
+++ b/sample
@@ -1,6 +1,6 @@
 func1+fix
 
-func2
+func2+fix
 
-func3
+func3+fix
 
$ 

ログが分割されたことを確認します。

$ git --no-pager log --pretty=oneline
04d8da4ce4057583d6b917f0b1e481211fdb207c func3
e780e2cbd878d6d7bc2c9dd99cb1e0cefc9131dc func2
8263453e933119f5397b96267425c357731120e0 func1
c509fc62f1a14a5d374c20203fecec670cf82a6f init
$ 

git rebase --continueでログの修正を終了します。

$ git rebase --continue
Successfully rebased and updated refs/heads/topic.
$ 

これで、コミットが分割されました。

$ git show-branch
! [master] init
 * [topic] これは、正しいコミットで修正したくない
--
 * [topic] これは、正しいコミットで修正したくない
 * [topic^] func3
 * [topic~2] func2
 * [topic~3] func1
+* [master] init
$ 

以上