はじめに
ある機能を追加したコミットの後、いくつか他の作業をしている間に、追加した機能の不具合を見つけて修正する、というようなことをすると、コミットログがカオスになってしまう。
これをきれいにするには、対話的リベースを実行し、必要に応じてスカッシュしたり、コミットメッセージの変更をしたりいうことをする。
これをきれいにする手順は、個々のリベース、スカッシュ、コミットメッセージ編集について説明した記事はあれど、なかなかまとめてある記事が見つからなかったので、自分用のメモとして記事にまとめた。
内容
新規機能Tを追加し、機能JをTに対応させた後、機能Tにちょっとしたバグを見つけて修正し、機能J対応も修正した、というコミットログ。
この一連のコミットを自分が行っており、まだローカルでのコミットにとどまっている。
abcdefg5 機能JのT対応修正
abcdefg4 Tを一部修正
abcdefg3 機能JをTに対応
abcdefg2 新規機能T追加
abcdefg1 YYYYY修正
abcdefg0 XXXXX修正
これを以下のように、あとで行った修正を最初のコミットと統合してコミットログをきれいにしたい。
abcdefg3 機能JをTに対応
abcdefg2 新規機能T追加
abcdefg1 YYYYY修正
abcdefg0 XXXXX修正
コミットを作り直すわけなので、リベースを行う。
リベースの起点となるのは、abcdefg1 YYYYY修正である。そこを起点として、その後のコミットを作り直したい。
コミット abcdefg1 は、現在のコミット(abcdefg5)から見ると4つ前のコミットとなる。
なので、リベースコマンドは次のようになる。
git rebase -i HEAD~4
リベース処理をエディタを介した対話型の処理としたいので、-iオプションを付けている。
すると、エディタが起動し、次のようなテキストが表示される。
pick abcdefg2 新規機能T追加
pick abcdefg3 機能JをTに対応
pick abcdefg4 Tを一部修正
pick abcdefg5 機能JのT対応修正
順序が、上から下へ向かって新しくなっている点に注意したい。
まずは順序を入れ替えてみる。
pick abcdefg2 新規機能T追加
pick abcdefg4 Tを一部修正
pick abcdefg3 機能JをTに対応
pick abcdefg5 機能JのT対応修正
これで、少しきれいになる。しかし、abcdefg4はabcdefg2にまとめ、abcdefg5はabcdefg3にまとめてしまいたい。
その場合は、pickの部分をs(squashの略)に変更する。
pick abcdefg2 新規機能T追加
s abcdefg4 Tを一部修正
pick abcdefg3 機能JをTに対応
s abcdefg5 機能JのT対応修正
この状態でエディタを保存し、エディタを閉じる。
すると、リベース処理が続行するが、まず最初の abcdefg2 と abcdefg4 のコミット統合について、コミットメッセージをどうするか確認する為、またエディタが開く。
# This is a combination of 2 commits.
# This is the 1st commit message:
新規機能T追加
# This is the commit message #2:
Tを一部修正
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Thu Jun 9 09:12:25 2022 +0900
#
# interactive rebase in progress; onto 0620d4c
# Last commands done (2 commands done):
# pick abcdefg2 新規機能T追加
# squash abcdefg4 Tを一部修正
# Next command to do (1 remaining command):
# pick abcdefg3 add 機能JをTに対応
# You are currently rebasing branch 'main' on '0620d4c'.
#
# Changes to be committed:
# modified: feature_t.cs
#
シャープから始まる行はただの説明行なので無視してよい。それ以外の部分がコミットメッセージになる。
現在は、元の2つのコミットメッセージが連結された状態になっているので、下部分を消す。
# This is a combination of 2 commits.
# This is the 1st commit message:
新規機能T追加
上部分の # 行を残しているのは特に意味はない。面倒なので消していないだけ。#行は無視されるので消しても消さなくても関係がない。
上記の状態でエディタを保存し、エディタを閉じる。
すると、リベースが進行し、次の2つのコミットを統合した後のコミットメッセージについて同様に聞かれる。
先ほどと同様に処理すると、リベースが完了する。
以上。
おまけ1: コミットメッセージを編集したい
amendで対応できない古いコミットメッセージも、rebase -i で対応できる。
その場合、エディタ上で、pick の部分を edit にしてエディタを閉じる(この時点ではコミットメッセージは編集しない)。
すると、一旦コマンドラインの処理が中断し、1つずつコミットメッセージを修正するモードに入るので、以下のコマンドで1つずつコミットメッセージを設定していく。
git commit --amend -m "新しいコミットメッセージ"
git rebase --continue
各コミットのメッセージを更新し終わると、リベースが完了する。
おまけ2: リベース失敗しちゃってやりなおしたい時
リベースがなんかうまくいかなくて、最初からやりなおしたいときのコマンド。
git reflog
上記コマンドで、最近gitで行ったリポジトリへの変更履歴が表示される。
38572ac6 HEAD@{0}: rebase (skip) (finish): returning to refs/heads/develop
38572ac6 HEAD@{1}: rebase: fast-forward
8143925a HEAD@{2}: rebase: fast-forward
90257dfb HEAD@{3}: rebase (start): checkout HEAD~5
b50993bf HEAD@{4}: rebase (abort): updating HEAD
b50993bf HEAD@{5}: rebase (abort): updating HEAD
b50993bf HEAD@{6}: rebase (abort): updating HEAD
b50993bf HEAD@{7}: rebase (abort): updating HEAD
abcdefg5 HEAD@{8}: commit: 機能JのT対応修正
abcdefg4 HEAD@{9}: commit: Tを一部修正
上記を見ると、HEAD@{8}が、懐かしいあの頃の最後のコミットである。
あの頃に戻りたい。
git reset --hard HEAD@{8}
これで無事戻れた。
やり直そう。