Git
git rebase

git rebaseコマンド: --continue、--abort、cancel、--interactive、squash

この記事のテーマは git rebase コマンドです。 git rebase コマンドは git merge コマンドと並んで、複数のブランチをマージするための主要な手段です。 このコマンドの基本的な使い方と、よく使われるオプションについて説明しましょう。

1. git rebase を正しく使う

前回の git merge の記事で紹介したように、git rebase コマンドを使って複数のブランチをマージすると、コミットの履歴をそのまま残すことができます。 rebase がどのように動作するかの図を以下に示します。

git 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

こんな感じです。

git rebase の実際の実行結果

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

どのような場面で使えるのか、実際の実行結果を見てみましょう。

git rebase --abort 実行結果

ログメッセージを見ると、1.md ファイルが 2 つのブランチで変更されていることがわかります。 この状況では、リベースコマンドを実行したときに競合が検出され、リベースがしばらく中断されました。 --abort オプションでリベースを中止すると、元の状態に戻りました。

3. コンフリクト発生時に --continue オプションでリベースを完了する [#3].

次に、--continue オプションを使ってコンフリクトを解決し、リベースを完了させる方法を説明します。 コンフリクトの解決方法は git merge と同じです。 コンフリクトしているファイルをエディタで開いて選択する必要があります。 詳細は git merge post を参照してください。

ファイルを修正したら、git add コマンド を使ってステージします。 そして、マージのように新しいコミットを行うのではなく、rebase コマンドオプションを使ってリベースの処理を続けます。

次のように使用します。

$ git rebase --continue

実際のクラッシュケースで実行した結果を見てみましょう。

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 コマンドを実行します。 消えてしまった変更を作業ツリーに残しておきたい場合は --mixed オプションを、すべてを消して戻りたい場合は --hard オプションを使用します。

以下は --mixed オプションを使用した結果です。

git reset --mixed コマンドで git rebase を元に戻した結果

削除したコミットの変更がリベース後も作業ツリーに残っていることもわかります。

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

新しい情報を必要とするアクション、例えば rewordeditsquash を実行すると、Git は必要な数の新しいエディタを開いて必要な情報を要求します。

git rebase -i による reword の実行

上の画面は、reword コマンドに続いて新しいコミットメッセージを求めるエディタウィンドウを示しています。 下のコメントは、現在 reword を実行中であることを示しています (Last command done)、 そして、次に fixup を実行する予定であることを示しています (Next command to do)。

git インタラクティブセッションの結果

結果を見てみると、上で書いたコマンドによってコミットメッセージの変更と直前のコミットのマージが完了していることがわかります。

前のコマンド git commit --amend で見たように、単純なメッセージの変更であっても、Git は新しいコミットを作成してそれを置き換えるという方法を使います。したがって、共同作業を行う際には、rebase のすべてのコマンドを注意深く使う必要があります。

6. 最後に

git rebase コマンドには、Git の不文律のひとつであるコミットの履歴を変更する機能があります。 このコマンドは強力ですが、共同作業中に予期せぬ混乱を引き起こすこともあります。

このため、git rebase はチームの規約に従って常に公開で使うようにしましょう。

copyright for git rebase

© 2023 All rights reserved.