🛤

初めて git rebase してみた

2022/05/01に公開

はじめに

こちらの記事では、今まで GitHub Desktop に頼り切っており、最近漸く Git のコマンドに手を出した私が、rebase について勉強したこと・実際に rebase を実施した結果をメモとして残します。

rebase とは

Git の歴史を改変するコマンド

  • 改変例
    • コミットメッセージを変更(reword)
    • コミットを修正(edit)
    • コミットを前のコミットとまとめて、コミットメッセージを変更(squash)

メリット

rebase コマンドのオプションの一例

$ rebase -i

  • rebase を開始
  • 各コミットに対して、どのコマンド(pick、reword、edit、squash など)を実行するか選択

$ rebase --continue

  • rebase を継続
  • 使用例
    1. rebase 時にコンフリクトが発生し、rebase が中断
    2. コンフリクトを解決
    3. add と commit を実行し、次に作るべきコミットを生成
    4. $ rebase --continue を実行し、rebase を再開

$ rebase --abort

  • rebase を中止
  • $ rebase -i の実行前に戻る

rebase するときの注意点

push 済みのブランチをリベースしない(特に、複数人で1つのブランチを共有して開発を進めている場合)

試しに rebase してみる

こちらの記事がとても腹落ちしたので、記事の内容を自分で再現してみます。

rabase するための準備

準備の完成形

ex1-1

各ブランチとコミットの作成

main ブランチ

  • root コミットを作成
    • $ echo "# rebase_test" > README.md
  • push する

main から feat/1/機能A ブランチを作成

  • c_1 コミットを作成
    • $ echo "func_A_1" > feat_A.md
  • c_2 コミットを作成
    • $ echo "call feat_A" > call.md
  • push する

main から feat/2/機能B ブランチを作成

  • c_3 コミットを作成
    • $ echo "func_B_1" > feat_B.md
    • $ echo "call feat_B" > call.md
  • push する

feat/1/機能Amain にマージ

  • c_4 コミットを作成
    • feat/1/機能A からプルリクエストを作成し、main にマージ

rabase してみる

  1. feat/2/機能B に移動し、$ git fetch$ git rebase -i main を実行
    • 今回は pick を選択して終了
  2. HEADfeat/2/機能B でコンフリクトが発生
    • call.md を修正
    • 今回は call feat_Acall feat_B の両方を残す
  3. c_5 コミットを作成
    • ここまでで、何かに失敗してしまっていたら、$ git rebase --abort を実行して1に戻る
  4. git rebase --continue を実行して rebase を再開し、rebase を完了させる
  5. 1 ~ 4 の結果を確認
    ex1-2

テスト(今回はスキップ)

実際の開発では、機能Aが存在する状態で機能Bが正常に動作するか、確認する必要があると思います。

完成した feat/2/機能B をマージしてもらう

push で失敗してみる

feat/2/機能B を push したいところですが、実際にはエラーが発生すると思います。
これは、push 済みのブランチをリベースしているからです。
このエラーが発生する原因について、もう少し説明します。

まず、現状では、下の画像のようにリモートの feat/2/機能B は、ローカルの origin/feat/2/機能B と同じコミットを指しています。
また、push では、リモートの feat/2/機能B を fast-forward merge で更新します。
しかし、下の画像から分かるように、リモートの feat/2/機能B が指すコミット(今回は、ローカルの origin/feat/2/機能B が指すコミットと同義)から、新しく追加したい feat/2/機能B が指すコミットまでは、親→子の順で辿ることができません。そのため、push に失敗します。
このような状況では、force push を実行する必要があります。
ex1-2

force push してみる

push に --force-with-lease を追加して実行します。
下の画像のように、ローカルの feat/2/機能B と ローカルの origin/feat/2/機能B が指すコミットが一致すると思います。
ex1-3

feat/2/機能B からプルリクエストを作成してマージしてもらう

c_6 コミットを作成すると、下の画像のような結果になると思います。
確かに歴史が綺麗。
ex1-4

実際に rebase するときに気になったこと

rebase により発生したコンフリクトを解決した後の $ git diff の表示結果

予想していた表示結果

  • リベース先のブランチのインデックスと、コンフリクト解決後のワーキングツリーの差分を表示
  • diff --git a/XXXXX.xxx b/XXXXX.xxx

実際の表示結果

  • コンフリクトを解決するために適用した全ての修正を表示
  • diff --cc XXXXX.xxx
    • -cc とは?

調べて分かったこと

  • rebase によるマージを実行したとき、このような表示になる
    • 参考:Combined diff format
      • -cc$ git diff でマージを表示するときのデフォルトのオプション
      • 結合された差分を生成
  • リベース先のブランチの最新のコミット(インデックス)と、コンフリクト解決後の結果(ワーキングツリー)の差分を表示

    • 上を実現するためには、$ git diff HEAD^ HEAD を実行

おわりに

この記事では、最近漸く Git のコマンドに手を出した私が、rebase について勉強したこと・実際に rebase を実施した結果をメモとして残しました。
間違い等があれば、ご指摘いだけると助かります。
初めて git rebase してみる人の参考になればと思います。

Discussion