git rebase -i でコミットをまとめる

実務でgit rebase -i を使う必要がありそうだったので、予習のために使ってみました。

シチュエーション

  • リモートにpush済み
  • コードレビューをしてもらった後に修正コミットがたくさんできている

この状況でコミットをまとめていきます。
ただ、リモートにpush済みのコミットをgit rebase -iでまとめるのは本来NGです。しかし、自分が作業をしているブランチが「絶対に自分しかいじらない」のであればコミットをまとめても大丈夫な場合があります。例えば私の職場では、コードレビュー後にコミットを1つにまとめるという文化がありますが、コミットをまとめないで欲しいというところもあるようです。コミットをまとめたいと思った時は、チームに確認してからにしましょう。

それでは、実際にやっていきたいと思います。

コミットlogを確認

まずはコミットlogを確認します。

$ git log --oneline

2fd949a (HEAD -> create_list, origin/create_list) リスト1を修正
968362c リスト2を修正
d196455 リスト1、リスト2を作成
f6bd217 (origin/title) titleを記載

「リスト2を修正」と「リスト1を修正」が修正コミットです。この2つの修正コミットと「リスト1、リスト2を作成」を1つにまとめます。

git rebase -i でまとめたいコミットを指定

git rebase -i でまとめたいコミットを指定します。その際二つの指定方法があるので紹介します。

git rebase -i HEAD~<数字>

数字の部分にはまとめたいコミットの数を指定します。今回であれば3つのコミットをまとめたいので、3を指定します。

$ git rebase -i HEAD~3
git rebase -i <コミットのハッシュ値>

こちらはコミットのハッシュ値を指定します。しかし、この指定方法には少し癖があります。HEADを使って指定する場合は「まとめたいコミットの数」とわかりやすいですが、コミットのハッシュ値を指定する場合は「まとめたいコミットの一つ前のハッシュ値」を指定します。例えば今回の場合だと、「リスト1を修正」〜「リスト1、リスト2を作成」までをまとめたいので、その前のコミットである「titleを記載」のハッシュ値を指定します。

$ git rebase -i f6bd217

git rebase -i を実行してコミットをまとめる

git rebase -i を実行します。

$ git rebase -i HEAD~3  もしくは  git rabase -i f6bd217

すると、以下のようにエディタが起動します。

pick d196455 リスト1、リスト2を作成
pick 968362c リスト2を修正
pick d196455 リスト1を修正 # Rebase 3c1cd16..d196455 onto 3c1cd16 (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 botto

インサートモードに切り替えて、修正コミットの「pick」を「s」に変更・保存します。エディタにも書いてありますが、この「s」はsquashのsです。

pick d196455 リスト1、リスト2を作成
s 968362c リスト2を修正    ⬅︎ 変更
s d196455 リスト1を修正    ⬅︎ 変更

# Rebase 3c1cd16..d196455 onto 3c1cd16 (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 botto

するとコミットメッセージを編集するためのエディタが開きます。

# This is a combination of 3 commits.
# This is the 1st commit message:

リスト1、リスト2を作成 # This is the commit message #2:

リスト2を修正 # This is the commit message #3:

リスト1を修正 # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # Date: Wed Dec 11 23:52:55 2019 +0900 # # interactive rebase in progress; onto f53176d # Last commands done (3 commands done): # squash 968362c リスト2を修正 # squash d196455 リスト1を修正 # No commands remaining.

コミットメッセージは「リスト1、リスト2を作成」だけあればいいので、他の2つは削除します。

# This is a combination of 3 commits.
# This is the 1st commit message:

リスト1、リスト2を作成 # This is the commit message #2:

             ⬅︎ 削除 # This is the commit message #3:

             ⬅︎ 削除 # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # Date: Wed Dec 11 23:52:55 2019 +0900 # # interactive rebase in progress; onto f53176d # Last commands done (3 commands done): # squash 968362c リスト2を修正 # squash d196455 リスト1を修正 # No commands remaining.

この変更を保存するとrebaseが完了です。以下のように表示されれば問題なくrebaseできています。

Successfully rebased and updated refs/heads/create_list.

git log --oneline でlogを見てみると、コミットが1つにまとまっているのがわかると思います。

最後に、リモートにpushをします。ここで要注意なのが、rebaseをしたことでコミットのハッシュ値が変わっているので、pushができなくなっています。この場合、git push に-f オプションを付けて、強制的にpushすることができます。

$ git push -f origin create_list:create_list

これでGitHubを確認すると、コミットが1つにまとまっていると思います。

補足

git push -fなのですが、リモートの内容を破壊的に書き換えてしまうため、実はあまり多用することはおすすめできません。冒頭で、リモートのコミットをまとめるのが本来NGと書いたのは、実はこのgit push -f をする必要があるからでした。なので、git push -f を実行する時は、「絶対に自分以外いじらないブランチ」で作業をしているという確証のもと、実行するようにしてください。