Git編:一歩踏み出すフロントエンド入門

初心者でもわかる!リベースの使い方を解説します


初心者でもわかる!リベースの使い方を解説します
(編集部注*2014年5月7日に公開された記事を再編集したものです。)

こんにちは、エンジニアの王です。今回は、Git初心者を悩ませるリベースについて解説してみたいと思います。

  • リベースが初耳
  • リベースを聞いたことはあるけど、使っていない
  • 不安を抱えながらも、リベースをなんとなく使っている

上記に当てはまる方は、ぜひ読んでくださいね。

▼目次

リベースで何ができる?

コミットが綺麗になる! 以上です! この一言に尽きる!

具体的にどのように綺麗になるかというと……

  • コミット履歴がわかりやすくなる
  • コミットメッセージを後から変える
  • コミットの順序を後から変える
  • 2つ以上のコミットを1個に統合する
  • 一度コミットした内容を編集する

といった具合でしょうか? 整理整頓が好きな方は、ぜひリベースを使いこなしていただきたいと思います!

マージとリベース

2つのブランチの変更点を統合するとき、Gitの最も一般的なやり方は、マージとリベースを使うことです。マージは初回で説明したので、今回は「リベース」を掘り下げていきましょう。

 
仮に下記のようなコミット履歴を持っているレポジトリがあるとします。

p1

masterをメインブランチにして、コミットCの時点で、「topic」ブランチを切って、masterと平行して、新機能を開発する、というシチュエーションにしましょう。

マージの場合

p2
checkout master && git merge topic

topicブランチのほうで新機能開発が完了して、マージを使って、masterブランチに取り込むと履歴図はこうなります。

リベースの場合

一方、リベースを使うとこうなります。

p4
checkout topic && git rebase master

履歴図を見てわかる通り、ここでは、最終的には全く同じ統合結果となります。しかし、履歴の残り方が違いますよね。リベースはマージと比べて、もっと単純明快な直線的な履歴になるので、開発履歴がとても読みやすくなるというメリットがあります!

上記の例だと極めて単純化しているので、特にメリットを感じてもらえないかもしれませんが、複数人で同時開発するときに力を発揮します!

cherry-pick

リベースを理解する前に、「cherry-pick」というリベースの簡易版のようなコマンドを知っておくと理解しやすくなると思いますので、先にcherry-pickというコマンドを覗いてみましょう。

「cherrypick」の元々の意味は、熟したサクランボとそうでないものを選んでわけることで、そこから転じて、「いいとこ取り」「気に入ったものだけを選ぶ」という意味になっています。

Gitでは、サクランボではなく特定のコミットだけをピックアップして、現在いるところ(HEAD)に適用するという意味になります。

 
以下の例を見てみましょう。

p5

HEADが[F]にいる状態で、git cherry-pick Eを実行すると、コミットEのコピーが作られ(仮にE`と記述しましょう)……

 
p6

[F]のすぐ後に追加することが可能です。

 
p7

もし、topicの全ての変更点をmasterに取り込もうと思うなら、git cherry-pick D E Jと打てば、上図のようになります。

 
p8

用済みになったtopicブランチをgit branch -D topicで削除すると、あたかもtopicというブランチが存在しなかったかのように履歴が一直線になって、スッキリします。

リベース

さて、上記例の作業をおこなうとき、リベースを使うともっとパワフルかつ簡単にできるのです!

と言うのも、HEADがtopicにいることを確認してgit rebase masterと打てば、git cherry-pick D E Jと同等の結果が得られます。

※HEADがtopicにいないなら、git checkout topic でチェックアウトしておくこと。

リベースで何が起きた?

では、git rebase master で一体何が起きたのか、おこなわれた処理を順に見てみましょう。

p9

  1. 現在のブランチ(D,E,Jコミット)でおこなわれた変更を一時的に保存
  2. 移行先のブランチ(master)にリセットする(git reset --hard master
  3. 1. で一時的に保存したコミットを順番に適用していく(親が違うので、コミットIDも変わる)

つまり、リベース(rebase)とは、「re+base」でベースとなるブランチを変えるということですね。

 
p10

git checkout master && git merge topic
最後に、masterのほうでfast-forwardマージをおこなえば、綺麗な履歴となります。

※いわゆる「fast-forwardマージ」というのは、マージコミットを作らずに、HEADを移動するだけでのマージのことです。

リベースコマンドの基本形

git rebase BaseBranch
<BaseBranch> に、現在いるブランチでおこなった全てのコミットを適用します。

※BaseBranchを指定してない場合、branch.<name>.remotebranch.<name>.merge の値を使ってください。

git rebase BaseBranch Branch
<Branch> が指定されている場合、まずブランチにcheckoutをおこなってからリベースします。

つまり、以下のコードと同等の意味になります。

git checkout <Branch> && git rebase <BaseBranch>

git rebase --onto

基本形は以上で紹介しましたが、もうちょっとトリッキーな事例を考えてみましょう。

p11

subtopicブランチでおこなったコミット(H,I,G)をmasterに取り入れたら下図のようになりますよね。

p12

これを実現するためには --onto オプションを使います:

git rebase --onto master topic subtopic

どういう意味かというと、

  1. subtopic ブランチにチェックアウト。
  2. topicとsubtopicの共通祖先(D)までのコミット(H,I,G)を一時保存。
  3. masterブランチにリセットする。
  4. 2. で一時保存したコミットを順番に適用。

ということになりますね。

 
p13

ここで間違えて、git rebase master subtopicと実行すると、コミットDまで含まれることになっちゃうので、気をつけましょう。

リベースのinteractiveモード

オプションをつけると、いわゆる「interactiveモード」なリベースがおこなえます。

rebase --interactive or rebase -i

このinteractiveモードを使うと、いろんなことが簡単にできちゃいます!

「rebase -i」で何ができる?

実例で説明しましょう。

 
p14

現在のコミット履歴は上図のとおり。git rebase -i <commit>で、現在のブランチにある<commit>以降のコミット(コミットとマージコミットを含まない)を取り上げて、エディタが立ち上がります。

 
15

git rebase -i 1e2df67 を実行したすると、画面が上図のようになります。

1行目に「pick faf09e9 B」とあるように、コミットIDの前にコマンドが書かれています。デフォルトでは「pick」コマンドになっていますよね。コメントのところにある説明を見てもわかるように、以下のコマンドが使えます。

  • pick:コミットを採用
  • reword:コミットを採用するがコミットメッセージを変更
  • edit:コミットを採用するがファイルを修正する
  • squash:一個前のコミットと合体させる
  • fixup:コミットメッセージを変更しない点以外squashと同じ
  • exec:shellでコマンドを実行する

reword:コミットメッセージを変える

ご存知のとおり、直前のコミットメッセージを変えるのにはgit commit --amendが使えます。ただ、あくまでも「直前のコミット」という条件付きのみに有効です。

3個くらい前のコミットメッセージを変えたい場合は、リベースを使います。

 
16

コミットメッセージを変更したいコミットの先頭をpickから「reword」あるいは「r」に変えます。

 
17

保存するとまたエディタが立ち上がるので、ここで先ほど選んだコミットのメッセージを編集します。

 
18

logしてみて、結果を確認します。

squash:一個前のコミットと合体させる

例えば、直近2つのコミットを1個のコミットにしたい場合、git reset --soft HEAD~1 && git commit -m "合体コミット!"reset --softを使って実現することができますよね。

しかし、rebase -iを使うと、もっとわかりやすい、かつ柔軟で簡単にコミットを統合できます!

19

今度はコミットの先頭をsquash(s)にしてみます。squashが付けられたコミットは、上図が示すように、1つ前のコミットと結合するようになります。

保存したら、コミットメッセージの確認画面が立ち上がりますので、そこでコミットメッセージを変えられます。

 

20

終わってlogで確認すると、ちゃんと統合されているのがわかります。

うまくリベースできないときに使うコマンド

リベースで衝突が起きたりすると、リベース作業が一旦中止されます。このとき、下記のコマンドが使えるようになります。

  • git rebase --continue:衝突などを解決したあとに実行してリベースを続行する
  • git rebase --skip:エラーを無視する
  • git rebase --abort:リベースをやめる

リベースを使う上での注意点

では、最後に注意点だけご説明しておきたいと思います。

パブリックレポジトリのコミットはリベースしちゃだめ!

パブリックレポジトリにプッシュしたコミットをリベースしてはいけません!

1人で作業する場合はともかく、複数人での作業の場合、リモートにあるレポジトリを無理矢理リベースしたら、全く同じコミットが重複する可能性大です。

リベースは特定のコミットをどこか別のコミットの後ろに移動するだけの作業に見えるかもしれませんが、実際は移動ではなく、新規コミットを作っているので、当然コミットIDも変わります。したがって、他の開発者のローカルにあるコミットをリベースしちゃうと、コミットIDが変わり、すでに存在している同じコミットと区別がつかなくなり、重複してしまいますよね。

ですから、他人のコミットを決してリベースしてはいけないのです。それでも、どうしてもリベースしたい場合は、事前に相手と相談しておくようにしましょう!

リベース操作を元に戻す方法

リベースに限った話ではありませんが、Gitでおこなったあらゆる操作をundoできる、いわばタイムマシンみたいな万能の機構「reflog」があります。Gitでおこなったあらゆる操作を履歴に残してくれているので、リベースだろうがリセットだろうが「reflog」を使えばどんなコマンドでも確実に戻せます。万が一リベースでしくじっても、怖くないのだ!

git reflogで操作歴を見て、戻したいポイントを見つけたらgit reset --hardでリセット! これでおしまいです。

※reflogが導入されるまでgit reset --hard ORIG_HEADを使って元に戻すのが一般的でした。ただし、ORIG_HEADはリベースコマンドの直前を指しているため、リベースのあとでさらにコミットすると使えなくなります。

まとめ

以上、Gitのリベースについて、ざっと説明しました!
コード潔癖症じゃなくても、共同開発のときに他の開発メンバーが履歴を辿りやすいように、履歴は綺麗に残したいものですよね。マージも便利ですが、場合に応じてリベースを活用するとぐっと履歴が読みやすくなりますよ。

次回は1週間後、リセットについて解説していきます。お楽しみに!


この記事を書いた人

王
バックエンドエンジニア 2012年入社
LIGの王です。ウェブの全てを学ぶ為、中国は四川省より日本にやってきました。王という名に恥じぬよう、ウェブ業界のKINGとなるべく日々頑張っております。よろしくお願いいたします。