git rebaseコマンド: --continue、--abort、cancel、--interactive、squash
この記事のテーマは git rebase
コマンドです。
git rebase
コマンドは git merge
コマンドと並んで、複数のブランチをマージするための主要な手段です。
このコマンドの基本的な使い方と、よく使われるオプションについて説明しましょう。
1. git rebase を正しく使う
前回の git merge
の記事で紹介したように、git rebase
コマンドを使って複数のブランチをマージすると、コミットの履歴をそのまま残すことができます。
rebase がどのように動作するかの図を以下に示します。
たとえば、feature-a
ブランチから main
ブランチへのリベースを行いたいとしましょう。
Git は feature-a
ブランチのコミットのうち、ふたつのブランチの共通の先祖からブランチしたコミットをすべて main
ブランチの最後のコミットにマージします。
同時に、古い feature-a
ブランチのコミットのうち、2 つのブランチにまたがっていたものはすべて削除されます。
その結果、 main
ブランチの最後のブランチから始まる新しい feature-a
ブランチが作成されます。
これにより、コミット履歴が 2 つから 1 つに変更されます。
このために使用するコマンドは
git merge
コマンドとは異なり、source branch
に移動して target branch
に対してコマンドを実行します。
上の例では、次のようになります。
$ git switch feature-a
$ git rebase main
こんな感じです。
rebase は develop
ブランチと main
ブランチをマージしました。
その結果、main
から分岐した新しい develop
ブランチが作成され、古い develop
ブランチは消滅しました。
リベース中にコンフリクトが発生すると、次のようなメッセージが表示されます。
$ git rebase main
結果
Auto-merging 1.md
CONFLICT (content): Merge conflict in 1.md
error: could not apply 7fc042b... 1.md edited in develop
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm ", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 7fc042b... 1.md edited in develop
リベースをここで終わらせたい場合は、git add コマンドでコンフリクトを解決し、ファイルをステージしてください。 それからオプションを使用してリベース処理を続行します。
リベース処理を続行するためのオプションは次のとおりです。
$ git rebase --continue
コンフリクトを表示してリベースを元に戻したい場合は、以下のコマンドを入力してリベース前の状態に戻す。
$ git rebase --abort
それぞれのオプションについては、後のセクションで詳しく説明する。
2. コンフリクトが発生したら --abort でリベースを中断する
Git がリベースの途中でコンフリクトを発見すると、一時停止します。
そして、コンフリクトを解決してリベースを続行するか、リベースを中断するかの選択肢が表示されます。
この時点で中止したい場合は、--abort
オプションを使います。
このオプションを使うと、タスクツリーはリベースを実行する前の状態に戻ります。 これはリベースを続行できないような衝突や問題に遭遇したときに便利である。
このコマンドは以下のように使う
$ git rebase --abort
どのような場面で使えるのか、実際の実行結果を見てみましょう。
ログメッセージを見ると、1.md
ファイルが 2 つのブランチで変更されていることがわかります。
この状況では、リベースコマンドを実行したときに競合が検出され、リベースがしばらく中断されました。
--abort
オプションでリベースを中止すると、元の状態に戻りました。
3. コンフリクト発生時に --continue オプションでリベースを完了する [#3].
次に、--continue
オプションを使ってコンフリクトを解決し、リベースを完了させる方法を説明します。
コンフリクトの解決方法は git merge
と同じです。
コンフリクトしているファイルをエディタで開いて選択する必要があります。
詳細は git merge post を参照してください。
ファイルを修正したら、git add コマンド を使ってステージします。 そして、マージのように新しいコミットを行うのではなく、rebase コマンドオプションを使ってリベースの処理を続けます。
次のように使用します。
$ git rebase --continue
実際のクラッシュケースで実行した結果を見てみましょう。
ログメッセージを見ると、両方のブランチで 1.md
ファイルが変更されていることがわかります。
rebase を実行するとコンフリクトが確認され、Git は rebase の処理を一時停止します。
コンフリクトしている行をエディタでクリーンアップしてステージし、--continue
オプションを使いました。
その結果、リベースが正常に完了したことがわかります。
4. git reset によるリベースの取り消し
すでに実行したリベースを元に戻したい場合は、git reflog
コマンドと git reset
コマンドを使う必要があります。
git commit post](/git/commit) で見たように、通常のコミットも git log
コマンドが出力するコミット ID を使って取り消したり削除したりすることができます。
しかし、git rebase
を実行した場合はすでに既存のコミットに変更を加えたことになるので、現在のコミット履歴の情報を元に戻すことはできません。
git reflog
コマンドは、ブランチやタグなどの ref
として保存されているすべての値に対して行ったことのログを表示します。
これは、デバッグ用のより詳細な git log
だと考えることができます。
このおかげで、たとえ git rebase
コマンドを実行した後でも以前のコミットに戻ることができます。
以下は、セクション 3 で実行した rebase に関連する git reflog
コマンドが出力したログの一部です。
$ git reflog
結果:
69fd9e0 (HEAD -> develop) HEAD@{0}: rebase (finish): returning to refs/heads/develop
69fd9e0 (HEAD -> develop) HEAD@{1}: rebase (continue): 1.md edited in develop
5e110f7 (main) HEAD@{2}: rebase (start): checkout main
7fc042b HEAD@{3}: rebase (abort): returning to refs/heads/develop
ログから、先ほど実行したリベースの開始位置は HEAD@{2}
であり、その前に戻るには HEAD@{3}
に戻る必要があることがわかります。
どこに戻ればいいかがわかったところで、git reset
コマンドを実行します。
消えてしまった変更を作業ツリーに残しておきたい場合は --mixe
d オプションを、すべてを消して戻りたい場合は --hard
オプションを使用します。
以下は --mixed
オプションを使用した結果です。
削除したコミットの変更がリベース後も作業ツリーに残っていることもわかります。
5. --interactive オプションでコミット履歴を自由に変更する (feat. squash)
git rebase
コマンドの --interactive
または -i
オプションは、対話型のリベースセッションを実行するためのオプションです。
このセッションでは、現在のブランチのコミットを変更したり、スクワッシュしたり、順番を入れ替えたりすることができます。
その結果、現在のブランチのコミット履歴が変更されます。
先ほど見た git rebase
コマンドとは異なり、操作するのはひとつのブランチのコミットだけであることに注意しましょう。
では、実際にその結果を見てみましょう。
セッションを実行するには、操作したいコミットの最後のコミット ID を次のように入力します。
$ git rebase -i [commit-hash]
現在のコミット履歴は次のようになっています。
ここで、上のコマンドを実行すると下のようなウィンドウが開きます。
ここで、#
で始まるコメントはすべて指示です。
一番上のコメントされていない部分が、このリベースセッションの対象となるコミットの一覧です。
pick 7fc042b 1.md edited in develop
pick 6d80db8 1.md edited in develop 2
この例ではこの部分だ。pick
の部分を他のコマンドに置き換えて保存し、好きなように使うことができる。
5.1. キーコマンドとその使い方
pick
: 現在の状態を保持するreword
: コミットメッセージのみを変更するedit
: 作者や日付などの詳細を変更するsquash
: 新しいコミットメッセージを作成し、前のコミットとマージするfixup
: 最後のコミットメッセージを使用する: 最後のコミットメッセージを使用し、前のコミットとマージするdrop
: コミットを削除する
例えば、7fd0
のコミットのメッセージを変更し、6d80
のコミットを前のコミットとマージするには、以下のように変更して保存し、エディタを閉じます。
reword 7fc042b 1.md edited in develop
fixup 6d80db8 1.md edited in develop 2
新しい情報を必要とするアクション、例えば reword
、edit
、squash
を実行すると、Git は必要な数の新しいエディタを開いて必要な情報を要求します。
上の画面は、reword コマンドに続いて新しいコミットメッセージを求めるエディタウィンドウを示しています。
下のコメントは、現在 reword を実行中であることを示しています (Last command done
)、
そして、次に fixup を実行する予定であることを示しています (Next command to do
)。
結果を見てみると、上で書いたコマンドによってコミットメッセージの変更と直前のコミットのマージが完了していることがわかります。
前のコマンド git commit --amend で見たように、単純なメッセージの変更であっても、Git は新しいコミットを作成してそれを置き換えるという方法を使います。したがって、共同作業を行う際には、rebase のすべてのコマンドを注意深く使う必要があります。
6. 最後に
git rebase
コマンドには、Git の不文律のひとつであるコミットの履歴を変更する機能があります。
このコマンドは強力ですが、共同作業中に予期せぬ混乱を引き起こすこともあります。
このため、git rebase
はチームの規約に従って常に公開で使うようにしましょう。
