git rebase理解するマン

git rebase
使う機会がなくていつまでたってもふわっとした概念しか覚えられなかったが、本日ついに使う機会が来た。
当然使い方がわからずボロボロになったので改めて書く。
公式:
ざっと読んだ結果、やはり
コミット履歴を改ざんする危険なヤツというイメージしか持てない。
友達になるために、もう少し具体的なイメージにする。
親コミット変えるという言い方も良いのかもしれない。
コミットには親コミットのハッシュ情報が含まれているのを知っているので、なんかしっくりくる。
さらにイメージを深めるために、ユースケースを調べてみる。

rebaseのユースケース
- コミットログを綺麗に保つためにrebaseを使う
まぁこれだ。
じゃあ汚いコミットログってどんなもの?と言われると以下のような形だろう。
1. コミットが多すぎる
- hu804 [feat] A修正
- hioriu4 [feat] A修正漏れ
- habnkj [feat] B修正
- hfih4a [feat] C修正 ← ここで一つの文脈が完了
イメージはこれだ。
「A修正漏れ」はいらないし、A,B,Cで一つの文脈を形成するのであれば、A,B,Cのコミットはまとめられたほうが良い。
2. 分岐と合流が多すぎる
無理だ。
おそらく、少ないブランチかつ、生存期間の短いブランチだったらこんな事は起きないんだろう。
rebase
はこれらを解決する。
- 複数のコミットをまとめることができる
- 親コミットを改変するので、やろうと思えば一直線のコミット履歴を作成できる
じゃあやってみるか。

rebaseを実施してみる
ローカルで遊んでも実践的ではない。組織のリポジトリでやるぞ ←←←
と言いたいが流石に辞めておく。
というわけで微妙な履歴を作ってみた。
叶えたいことは、
- aaa, bbb, cccをまとめて1つのコミットにする
- 1つのコミットを
created main.go
の後ろにつける
普通にマージすると…
git merge feature-1
まぁこうなる。
merge --squashすると…
git merge feature-1 --squash
その後コミット
おや。叶えたいことができている。
git rebaseすると
git rebase main
git merge feature-1
一直線にはなるが、まとまっていない。
git rebase -iでまとめる
git rebase -i main
こんな感じのエディタが出てくる。
-i
は別にまとめてくれるオプションではなく、rebase
を対話的に進めてくれるオプション。
mainにリベースするにあたり、コミットをどうしながらリベースするのかをココで書く。
例えば、
- 直近コミットのcccは落としたい
- 残りは1つにまとめたい
となると、以下のように指示を与えると良い。
pick 5e44207 aaa commit
squash a924e51 bbb commit
drop 12d53f6 ccc commit
すると、上の条件を満たしてい新たに生成されるコミットのメッセージを指示する画面が出てくる。
好きなように変更する。
# This is a combination of 2 commits.
# This is the 1st commit message:
# This is the commit message #2:
aaa, bbb commit
意図通りのコミットが作れた。

ざっと読んで気になったことを更に調べる
1. squash mergeとはどういう関係なのか
さきほど、squash merge
でも同じことを実現できた。どのような違いがあるのか。
git merge feature-1 --squash
をすると、feature-1ブランチのコミットは圧縮され潰され1つにまとめられ他状態で、mainブランチに取り込まれる(コミットはされないので、後で明示的にcommit
が必要)。
この意味で、確かに最終成果物は--squash
もrebase -i
も同じ。
ただし、--squash
をするとfeature-1
ブランチの履歴は変わらない。
つまり、
main
スカッシュしたやつ
created main.go
init
feature-1
ccc
bbb
aaa
init
という履歴になってしまう。このあとfeature-1で開発を続けると、それをmainにマージするときにとんでもないことになる。
対してrebaseの場合は履歴が揃う。
当たり前で、feature-1をいじってからそれをmainにmergeしているためだ。
こういう違いがあるので、最終成果物は一緒に見えるかもしれないけど注意。

git pull --rebaseについて
もう想像がつくが、これはローカルブランチの親を最新リモートブランチにするということになる。
ローカルのブランチが自分だけのものなのであれば、いくらでも使って構わない。
マージコミットを避けたいなら積極的に使ってもいいと思う。