おひとり

できる限りひとりで楽しむための情報やプログラミング情報など。

【git rebaseハンズオン】他のブランチの変更を統合、コンフリクト(衝突)の解決、複数のコミットを1つにまとめる方法

f:id:hitoridehitode:20210601115020p:plain

git rebaseは既存の「歴史」を書き換えるコマンドです。
慣れないとややとっつきにくいコマンド。。。
とっつきにくいものは実際に手を動かしてみるのが一番効果的ですよね。

そこで今回は、代表的な以下のユースケースでgit rebaseを試してみましょう。

  • 他のブランチの内容を作業中のブランチに適用する。(更新されたmainの内容を取り込みたいとき)(コンフリクトの解決)
  • 複数のcommitをまとめる(pushする前に整理したいとき)

開発中にこのような処理を行って、「歴史」を整えたい時がありますね。
実際にgit rebaseを使ってこれらを実現しましょう。

今回はHTMLを編集するようなストーリーで試してみましょう。
このハンズオンを通じて

  • rebaseはどんなコマンドなの?
  • rebaseで発生したコンフリクト解決するには?
  • どんなときに便利なの?
  • 使う上で注意するポイントは?

を知ることができます。
その過程で、以下のコマンドを使います。

  • git rebase
  • git rebase --abort
  • git rebase --continue
  • git rebase -i

※予めブランチなどの概念を知っておく必要があります。

他のブランチの内容を作業中のブランチに適用する。(更新されたmainの内容を取り込みたいとき)

今回は企業情報のサイトを作成するイメージでやってみます。

  1. index.htmlを作成し、初期化してcommit
  2. topic_companyブランチを作成(checkoutはしない)
  3. mainブランチでindex.htmlに何か変更してcommit
  4. topic_companyブランチでindex.htmlを編集&commit
  5. mainブランチで行った変更をtopic_companyブランチにrebase (コンフリクトを解決します。)
  6. mainブランチにもどり、topic_companyの変更内容をmerge(Fast-forward merge)

ではやっていきましょう!

まずは、適当に作業用のディレクトリを作成し、そこでGitを初期化します。

$ mkdir rebase-hanson

$ cd rebase-hanson

$ git init
Initialized empty Git repository in /*****/rebase-hanson/.git/

続いて、index.htmlを作成しましょう。

$ touch index.html

そして、index.htmlに以下の内容を書き込んで保存します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>企業情報</title>
</head>
<body>
    <article>
        <section id="message"></section>
        <section id="company"></section>
    </article>
</body>
</html>

最初のコミットをしましょう。

$ git add index.html

$ git commit -m "Initial commit"

ここで、topic_companyブランチを作成しておきましょう。
まだチェックアウトしないので、branchコマンドで作成します。

$ git branch topic_company

index.htmlを以下のように、修正します。messageセクションを編集します。

...
<body>
    <article>
        <section id="message">
            とても良い会社です!
        </section>
        <section id="company"></section>
    </article>
</body>
...

commitします。

$ git add index.html

$ git commit -m "add message"

さて、続いてtopic_companyブランチで作業します。

$ git checkout topic_company

では、ここで以下の変更を行ってそれぞれ commitしましょう。(2つのコミットを作成してください。)

  1. #companyのセクションに資本金情報を追加する。
...
<body>
    <article>
        <section id="message"></section>
        <section id="company">
            <dl>
                <dt>資本金</dt>
                <dd>1000万円</dd>
            </dl>
        </section>
    </article>
</body>
...

commitします。

$ git add index.html

$ git commit -m "add capital"

つづいて、先ほどのセクションに従業員数を追加します。

...
        <section id="company">
            <dl>
                <dt>資本金</dt>
                <dd>1000万円</dd>
                <dt>従業員数</dt>
                <dd>100人</dd>
            </dl>
        </section>
...

commitします。

$ git add index.html

$ git commit -m "add employees"

さて、ここからいよいよmainブランチの変更をtopic_companyブランチにrebaseします。 その前に、現在の状態をおさらいしておきましょう。

f:id:hitoridehitode:20210604163418p:plain
mainブランチとtopic_companyブランチがある

では、git rebaseを使っていきます。

$ git rebase main

残念ながら、コンフリクトが発生してしまいます。。。

すぐにコンフリクトの解決が難しい(他人と相談が必要な場合など)場合は、--abortrebaseをキャンセルし実行前の状態に戻せます。

$ git rebase --abort

今回はコンフリクトを解決しましょう!

index.htmlを開いて以下の内容になるようにマーカーを削除しましょう。
add capitalのコミット内容とのコンフリクトである点を覚えておきましょう。

<<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>企業情報</title>
</head>
<body>
    <article>
        <section id="message">
            とても良い会社です!
        </section>
        <section id="company">
            <dl>
                <dt>資本金</dt>
                <dd>1000万円</dd>
            </dl>
        </section>
    </article>
</body>
</html>

コンフリクトを解決したので、addします。

$ git add index.html

以下のコマンドを実行すると、rebaseを継続します。

$ git rebase --continue

※エディタが起動しますので、コミットメッセージを修正(orそのまま)して保存しましょう。
Vimの場合は保存した段階でrebaseが完了します。

リポジトリの状態は以下のようになります。

f:id:hitoridehitode:20210604174240p:plain
topic_companyのコミット1つずつmainに適用される。

rebaseでは、差分となるコミットを1つずつ適用していきます。
今回はmainの先に対して以下の順で適用されました。

  1. add capital のコミット
  2. add employeesのコミット

コンフリクトが発生した時を思い出してください。
今回のコンフリクトは「1」の段階で発生しました。
※実際に解決するとき、add employeesの内容は含まれてませんでしたよね。
コンフリクトが解決されたあと、つまり、--continueを実行したあとに「2」のadd employeesのコミットが適用されたということになります。

f:id:hitoridehitode:20210604180010p:plain
rebaseがどのように働くか確認

rebaseはコミットを適用し直すため、コミットのハッシュが変わります。

f:id:hitoridehitode:20210604181215p:plain
rebaseではcommitのハッシュが変わる(新しくcommit作られるため)

ではmainブランチにもどってtopic_companyをマージしてみましょう。

$ git check main

$ git merge topic_company
Updating 0f36ab7..7450d56
Fast-forward
 index.html | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

以下のようになります。

f:id:hitoridehitode:20210604181901p:plain
mainの参照先を返るだけでマージ完了

また、ログにFast-forwardと出ていますね。
Fast-forwardのマージとは、今回のように、「merge commitを作る必要がなく、単純なポインタの差し替えだけでmergeが完了した」ことを意味しています。
うまく使えば、コミットログをシンプルに保つことにつながります。

最後に、不要になったtopic_companyを削除しましょう。

$ git branch -d topic_company

コミットログが一直線になったのできれいになりました。

f:id:hitoridehitode:20210604182423p:plain
一直線のコミットログ

複数のコミットを1つのコミットにまとめる

つづいて、複数のコミットを1つのコミットにまとめて見ましょう。
今回はadd capitaladd employeesを1つのコミットadd company infoにまとめましょう。

コミットをまとめるには-iオプションを使います。

$ git rebase -i HEAD~~

※今回はHEADから2つ分まとめるのでHEAD~~を使っています。もっとたくさんある場合はコミットのハッシュで指定することもできます。

すると、エディタが開きます。

pick cad164a add capital
pick 7450d56 add employees

ここで、add employeesのコミットをpick -> squashに変更しましょう。(省略してsでもOK)

pick cad164a add capital
squash 7450d56 add employees

保存すると、もう一度エディタが開きます。
2つのコミットをまとめるので、新しいコミットメッセージを入力する必要があります。
ここではadd company infoというメッセージにしましょう。
あとは保存するとrebase完了です。

1つのコミットにまとまっているはずです。

f:id:hitoridehitode:20210604183711p:plain
rebase -iで複数のコミットを1つにまとめることができる

もちろん、この操作を行うとコミットのハッシュが変わります。
また、3つなど、2つ以上のコミットもまとめることができます。

さらに、もっとたくさんの「まとめ」コミットを1度に作成できます。
例えば、以下のように1度に複数の「まとめ」コミットを作成します。

pick *** A
squash *** B
pick **** C
squash *** D

この場合は、2つのまとめコミットができます。

f:id:hitoridehitode:20210604184419p:plain
2つのまとめコミットを作成できる

rebaseの注意点

rebaseはとても有用な機能ですが、1つ重大な注意点があります。
それは、「すでにremoteにpushしたコミットには使わない。」ということです。

Gitの中ではブランチなどはハッシュの参照として実装されています。
そのため、rebaseしてハッシュが変わってしまうと、参照先が消失してしまいます。
他の開発者がrebase前の状態をcheckoutしている場合、非常に面倒なことになります。
これは複数人で作業しているときに大きな影響があります。

「よく分からないよ」という場合は、公式サイトにもあるように、「rebaseはまだpushしていないローカルの変更だけに対して使う」ことを心がけましょう。

Gitの公式サイトには以下のように厳しいことが書いてあります。
絶対に気をつけましょう。

公開リポジトリにプッシュしたコミットをリベースしてはいけない

この指針に従っている限り、すべてはうまく進みます。 もしこれを守らなければ、あなたは嫌われ者となり、友人や家族からも軽蔑されることになるでしょう。

Git - リベース

まとめ

git rebaseを使えば

  • 履歴をキレイにしつつ、他のブランチのコミットを適用できる
  • 複数のコミットを1つにまとめる

というメリットがあります。
しかし、「すでにremoteにpushしたコミットには使わない。」というルールを守って使いましょう。

参考リンク

git-scm.com

Icon Licence

f:id:hitoridehitode:20210601115020p:plain

Git Icon by Icon Mafia on Iconscout