Git を1からやり直す(リベース編)

Gitを1からやり直す(目次)

この記事(リベース編)はすべてローカルの話。プル・リベースについては次の記事(Git を1からやり直す(リモート編))。

チェリーピック

cherrypick とは「良いものだけつまみ食い」すること。Gitでは、任意のパッチを適用することを意味する。別のブランチの変更を取り込んだりできる。

コマンド 動作など
git cherry-pick コミット 指定したコミットのパッチをHEADに適用
git cherry-pick --abort コンフリクト発生時、cherry-pick を中止して元に戻す
git cherry-pick --continue コンフリクト発生時、コンフリクト解消 → add → continue で cherry-pick を続ける

Git - git-cherry-pick Documentation

パッチとはコミット間の差

$ git diff コミット1 コミット2のようにして、2つのコミット間の差を見ることができる。この差のことをパッチという。

$ git diff master^ master
diff --git a/README b/README
index ba02438..e9d5c76 100644
--- a/README
+++ b/README
@@ -4,4 +4,5 @@ This is a Git practice.
 Japanese.
 English.
-git101
+git101.
+git102.

master ブランチが下のようになっているとき、master 以外のブランチにいる状態で$ git cherry-pick masterで適用されるパッチは、D - C、つまりDCの差だ。

master  A - B - C - D (masterの先端)

チェリーピックを実際にやってみる

イメージ図

master  A - B - C - D (masterの先端)
             \
develop       P - Q (developの先端)

developブランチに、C(BとCの差分)を取り込んでみる。

$ git cherry-pick master^  # developブランチにmaster^、つまりCのパッチを当てる
[develop 4353a98] commit C
 Date: Thu Jul 9 21:20:29 2020 +0900
 1 file changed, 1 insertion(+)

$ git log --oneline --all --graph  # チェリーピック後
* 4353a98 (HEAD -> develop) commit C
* c6c0f75 commit Q
* f00f3f1 commit P
| * 0c1fd59 (master) commit D
| * 37c83c5 commit C
|/  
* 7eeed64 commit B
* 83e920c commit A

このように、変更を取り込んだ新しいコミットが作られる。コミットメッセージは、取り込むコミットのものが使われる。

リベース

リベースとは、ブランチの根元を移し替えること。

コマンド 動作など
git rebase コミット 今いるブランチの根元を指定したコミットに移し替える
git rebase --abort コンフリクト発生時、rebase を中止して元に戻す
git rebase --continue コンフリクト発生時、コンフリクト解消 → add → continue で rebase を続ける

リベースを実際にやってみる

developブランチを masterブランチにリベースする例を考える。developブランチの根元を masterブランチの先端に移し替える。

コマンド

$ git checkout develop  # developブランチに移動
$ git rebase master  # masterにリベース

イメージ図

# リベース前
master  A - B - C - D (masterの先端)
             \
develop       P - Q (developの先端)

# リベース後
master  A - B - C - D (masterの先端)
                     \
develop               P' - Q' (developの先端)

はじめ develop ブランチの根元はBだったが、リベース後はDになる。これなら早送りマージが可能だ。

リベースの中身は、チェリーピック(パッチを当てること)を繰り返すことだ。masterブランチDにまずPのパッチ(BとPの差分)を当ててP'を作り、次にQのパッチ(PとQの差分)を当ててQ'を作る。

実際にやってみた。

$ git log --oneline --all --graph  # リベース前(上のイメージ図と同じ分岐)
* 0c1fd59 (master) commit D
* 37c83c5 commit C
| * c6c0f75 (HEAD -> develop) commit Q
| * f00f3f1 commit P
|/  
* 7eeed64 commit B
* 83e920c commit A

$ git rebase master  # リベース実行(develop ブランチにいる状態)
First, rewinding head to replay your work on top of it...
Applying: commit P
Applying: commit Q

$ git log --oneline --all --graph  # リベース後(分岐がなくなって一直線に)
* be6c3eb (HEAD -> develop) commit Q  # PとQのハッシュは変わる
* d318693 commit P
* 0c1fd59 (master) commit D
* 37c83c5 commit C
* 7eeed64 commit B
* 83e920c commit A

リベース後、PQのハッシュが変わっていることが確認できる。リベースするとコミットは別物になる。

一度プッシュしたコミットはリベースしない!

このように、リベースするとコミットのハッシュ(コミットID)が変わる。そのため、一度プッシュしたコミットをリベースするとその後プッシュができなくなる点に注意しなければいけない。

ほんとうは怖いリベース

あぁ、このすばらしいリベース機能。しかし、残念ながら欠点もあります。その欠点はほんの一行でまとめることができます。

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


一般論として、両者のいいとこどりをしたければ、まだプッシュしていないローカルの変更だけをリベースするようにして、 歴史をきれいに保っておきましょう。プッシュ済みの変更は決してリベースしないようにすれば、問題はおきません。

Git - リベース

こちらのスライドもわかりやすい。こわくない Git

(11月11日追記)実際には、プッシュ後にもリベースしてる話。

masuyama13.hatenablog.com


(参考資料)わかる Git