Open5

git rebase理解するマン

takamin55takamin55

git rebase使う機会がなくていつまでたってもふわっとした概念しか覚えられなかったが、本日ついに使う機会が来た。
当然使い方がわからずボロボロになったので改めて書く。

公式:
https://git-scm.com/book/ja/v2/Git-のブランチ機能-リベース

ざっと読んだ結果、やはり
コミット履歴を改ざんする危険なヤツというイメージしか持てない。

友達になるために、もう少し具体的なイメージにする。
親コミット変えるという言い方も良いのかもしれない。

コミットには親コミットのハッシュ情報が含まれているのを知っているので、なんかしっくりくる。
さらにイメージを深めるために、ユースケースを調べてみる。

takamin55takamin55

rebaseのユースケース

  • コミットログを綺麗に保つためにrebaseを使う

まぁこれだ。
じゃあ汚いコミットログってどんなもの?と言われると以下のような形だろう。

1. コミットが多すぎる

  • hu804 [feat] A修正
  • hioriu4 [feat] A修正漏れ
  • habnkj [feat] B修正
  • hfih4a [feat] C修正    ← ここで一つの文脈が完了

イメージはこれだ。
「A修正漏れ」はいらないし、A,B,Cで一つの文脈を形成するのであれば、A,B,Cのコミットはまとめられたほうが良い。

2. 分岐と合流が多すぎる

無理だ。
おそらく、少ないブランチかつ、生存期間の短いブランチだったらこんな事は起きないんだろう。


rebaseはこれらを解決する。

  • 複数のコミットをまとめることができる
  • 親コミットを改変するので、やろうと思えば一直線のコミット履歴を作成できる

じゃあやってみるか。

takamin55takamin55

rebaseを実施してみる

ローカルで遊んでも実践的ではない。組織のリポジトリでやるぞ ←←←
と言いたいが流石に辞めておく。

というわけで微妙な履歴を作ってみた。

叶えたいことは、

  1. aaa, bbb, cccをまとめて1つのコミットにする
  2. 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

意図通りのコミットが作れた。

takamin55takamin55

ざっと読んで気になったことを更に調べる

1. squash mergeとはどういう関係なのか

さきほど、squash mergeでも同じことを実現できた。どのような違いがあるのか。

git merge feature-1 --squashをすると、feature-1ブランチのコミットは圧縮され潰され1つにまとめられ他状態で、mainブランチに取り込まれる(コミットはされないので、後で明示的にcommitが必要)。

この意味で、確かに最終成果物は--squashrebase -iも同じ。

ただし、--squashをするとfeature-1ブランチの履歴は変わらない。
つまり、

main
  スカッシュしたやつ
  created main.go
  init

feature-1
  ccc
  bbb
  aaa
  init

という履歴になってしまう。このあとfeature-1で開発を続けると、それをmainにマージするときにとんでもないことになる。

対してrebaseの場合は履歴が揃う。
当たり前で、feature-1をいじってからそれをmainにmergeしているためだ。

こういう違いがあるので、最終成果物は一緒に見えるかもしれないけど注意。

takamin55takamin55

git pull --rebaseについて

もう想像がつくが、これはローカルブランチの親を最新リモートブランチにするということになる。

ローカルのブランチが自分だけのものなのであれば、いくらでも使って構わない。
マージコミットを避けたいなら積極的に使ってもいいと思う。