• トップ
  • ブログ一覧
  • 【Git】rebaseについて解説
  • 【Git】rebaseについて解説

    かじー(エンジニア)かじー(エンジニア)
    2023.07.03

    IT技術

    目次

    rebaseとは

    Gitに用意されているコマンドの一つになります。指定ブランチに連なっているコミットを別ブランチの最新コミットに続く形でつけかえる(rebase)ことができます。指定ブランチの内容を取り込むという意味ではgit mergeコマンドに似ていますが、rebaseにはmergeにないメリットがあるため使い分けられています。ただmergeと比べるとややこしい部分があるため、細かく解説していこうと思います。

    解説

    まずrebaseを実行した際にどういった変更がブランチに加わるのか図で見ていきます。
    以下構成のブランチがあるとします。

    1	      E---F---G feature
    2	     /
    3    A---B---C---D develop

    developブランチ(以下develop)のBコミットから派生したfeatureブランチ(以下feature)のEコミットがあり、それにF、Gというコミットが連なっている状態です。

    カレントブランチをfeatureとした状態で以下を実行します。

    1git rebase develop

    もしカレントブランチがdevelopになっている場合は以下のコマンドでも上記コマンドと同様の結果にすることができます。(featureにチェックアウトした後rebaseを実行してくれる)

    1git rebase develop feature

    するとブランチ構成は以下のように変更されます。

    1                  E'---F'---G' feature
    2                 /
    3    A---B---C---D develop

    派生の起点がBコミットからDコミット(developの最新コミット)に移動したことがわかります。
    また、featureブランチのコミットにシングルクォーテーションがついたことに注目してください。
    これはコミット番号が変わったことを表現しており、rebase前のブランチとは履歴上別コミットとして扱われます。

    mergeとの違い

    先程と同じrebase前のブランチ構成でmergeコマンドを実行してみましょう。

    1          E---F---G feature
    2         /
    3    A---B---C---D developカレントブランチをfeatureにした状態で実行。
    1git merge develop

    すると以下のようになります。

    1	      E---F---G---H feature
    2	     /         /
    3    A---B---C---D develop

    rebaseとの違いがはっきりわかりますね。mergeの場合、developのコミットを含めた新たなコミットHがfeatureに作成されます。また、B→Eの分岐点は変わっておらずE, F, Gのコミット番号はそのままになります。

    コンフリクトの解消方法

    コンフリクトの解消方法においてもrebaseとmergeで少し動きが異なります。コンフリクト箇所を修正するという点は共通ですが、rebaseの場合各コミットごとに適用していくことになります。具体的には、以下のブランチ構成だったとしてE, Fコミットはdevelopでの修正箇所と被っているとします。

    1	      E---F---G feature (カレントブランチ)
    2	     /
    3    A---B---C---D develop

    この状況でgit rebase developを実行するとまずEコミットがdevelopと競合がないか検証されます。今回Eコミットはdevelopと修正箇所が被っているというシチュエーションのためコンフリクトが発生します。

    1               E(コンフリクト)---F---G feature
    2             /
    3A---B---C---D develop

    コンフリクトを解消するための修正を加えた後、対象のファイルをgit add することでステージングします。これで一区切りになっており、次のFコミットをdevelopに適用するには以下のコマンドを実行します。

    1git rebase --continue

    developに適用されていない残りのコミットがコンフリクトしなければここでrebaseが完了となりますが、今回Fコミットはdevelopと競合すると言う条件なので再びコンフリクトします。

    1	               E'---F(コンフリクト)---G feature
    2	             /
    3    A---B---C---D develop

    Eコミットのときと同じように対応し、コンフリクトを解消しステージングします。そして再度git rebase --continueを実行することでrebaseが完了となります。(Gコミットはdevelopと競合していないため対応不要)

    1	               E'---F'---G' feature
    2	             /
    3    A---B---C---D develop

    これでコミット履歴が整理されましたが、rebaseにはコミットをまとめる機能が存在します。
    これを利用し履歴を更に整理していきます。履歴がきれいになるイメージをしやすくするために上記記載のrebase後のブランチを再現してみました。

    rebase したことにより、developの最新コミットであるCommitDの後に続く形でCommitE, F, Gが存在します。きれいな一直線になっていますね。ここからさらにCommitE, F, Gを1つにまとめることで、より一層履歴を見やすいものにすることができます。

    そのためには以下のコマンドを実行します。

    1git rebase -i HEAD~3

    HEADの部分はgit log --onelineコマンドを実行した際のHEADとなっているコミットから3つ分のコミットを示しています。ここで指定したコミット(E, F, G)をこれから1つにまとめていきます。

    1git log --oneline      
    2f15ce0a (HEAD -> feature) Commit_G_プルリク指摘箇所修正
    3e8989b7 Commit_F_リファクタリング
    45f1bb0a Commit_E_新規実装

    rebaseコマンドの-iオプションを実行したことによりvimが開きます。

    1  1 pick 5f1bb0a Commit_E_新規実装
    2  2 pick e8989b7 Commit_F_リファクタリング
    3  3 pick f15ce0a Commit_G_プルリク指摘箇所修正
    4  4 
    5  5 # Rebase 6e558cf..f15ce0a onto 6e558cf (3 commands)
    6  6 #
    7  7 # Commands:
    8  8 # p, pick <commit> = use commit
    9  9 # r, reword <commit> = use commit, but edit the commit message
    10 10 # e, edit <commit> = use commit, but stop for amending
    11 11 # s, squash <commit> = use commit, but meld into previous commit
    12 12 # f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
    13 13 #                    commit's log message, unless -C is used, in which case
    14 14 #                    keep only this commit's message; -c is same as -C but
    15 15 #                    opens the editor
    16以下省略

    1行目から3行目を見てみると先程指定した3つのコミットが記載されており、先頭には「pick」というワードがあります。この「pick」を指定したコミットが、rebase先に適用する際に使用されます。今回はコミットをまとめたいのでコミットE以外は「pick」ではなく「fixup」を指定します。「fixup」は指定したコミットを直前のコミットに統合します。つまりCommitEにFとGが統合されひとまとめになります。

    11 pick 5f1bb0a Commit_E_新規実装 
    22 fixup e8989b7 Commit_F_リファクタリング 
    33 fixup f15ce0a Commit_G_プルリク指摘箇所修正

    同じようなコマンドとして「squash」があります。これもコミットを統合するコマンドですが、「fixup」との違いは統合する際のコミットメッセージの扱いです。「fixup」を指定した場合はコミットF, Gのメッセージは無視され、コミットEのメッセージが採用されます。「squash」は特に自分でいじらなければ元々のコミットメッセージが統合されます。

    fixupした場合とsquashした場合のコミットログを抜粋したものが以下になります。`squash`の方は統合前のコミットメッセージが残っていますね。

    1// fixup
    2Commit_E_hoge機能追加対応
    3
    4// squash
    5Commit_E_hoge機能追加対応
    6
    7Commit_F_hoge機能追加対応
    8
    9Commit_G_hoge機能追加対応

    コミットを統合しているという意味はfixupsquashも同じなので、プロジェクトの方針やお好みに合わせて使い分ければいいと思います。

    これでコミットを整理することができました。ログを確認するとdevelopの最新コミットであるコミットDの後に、先程統合したコミットEが並んでる形になっています。

    1git log --oneline   
    29c10639 (HEAD -> feature) Commit_E_新規実装
    36e558cf (develop) Commit_D_hoge機能追加対応

    まとめ

    今回はrebaseの使い方について紹介しました。今までmergeしか使ってこなかったので、最初は手こずりましたが慣れるとコミットログが整理されて見やすいため便利に感じました。ただ後々ログを見返すことがない場合やプロジェクトの方針で特に使用する必要がない場合はmergeの方が楽なのでどちらを使うかはケースバイケースかなと思います。

    かじー(エンジニア)

    かじー(エンジニア)

    おすすめ記事