すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

gitのmerge/rebaseのつかいかた

こんばんは,1000chです. gitをちょいちょい触ったりしている訳ですが, ぶっちゃけ開発とか機能作ろうとしてブランチ切ってマージして,くらいにしか使えていません. これでは「git使ってるぜどやー」とかできませんね.

最近チーム開発する機会が増えたので,一度gitのmergeとrebaseの違いを勉強してみます. 参考はこちらです.

おさるでもわかるはずなのでぼくでもだいじょうぶ!きっと!

じゅんび

merge,rebaseを使うのは,devブランチから派生したissueブランチである程度開発した後です. テストするために,まずはこんな感じでgitリポジトリを用意します.

$ mkdir test
$ cd test
$ git init
$ touch hoge0
$ ls
hoge0
$ git add .
$ git commit -m "touch hoge0"

さて,ではmasterブランチを統合元として,issueブランチを切って少し開発を進めます.

$ git checkout -b issue
$ touch hoge1
$ ls
hoge0 hoge1
$ git add .
$ git commit -m "touch hoge1"
$ echo hogehoge > hoge1
$ git commit -a -m "fix hoge1"

masterからissueブランチを切り,hoge1なるファイルをつくってコミット. さらにちょこっとテキスト内容を変更してからコミットしています.

git logすると...

* 36ed805 - (HEAD, issue1) fix hoge1 (4 minutes ago) <1000ch>
* f253639 - touch hoge1 (5 minutes ago) <1000ch>
* 6187e4b - (master) touch hoge0 (13 minutes ago) <1000ch>

masterブランチからissueブランチが2つ先行していることがわかりますね. 自分のgit logエイリアスは記事ラストに載せときます.

$ git checkout master
$ ls
hoge0
$ touch hoge_m
$ ls
hoge0 hoge_m
$ git add .
$ git commit -m "touch hoge_m"
$ git log
* 3d86c2e - (HEAD, master) touch hoge_m (34 seconds ago) <1000ch>
* 6187e4b - touch hoge0 (27 minutes ago) <1000ch>

masterが一歩進み,issueとは枝分かれしましたね.

ではここから,issueを取り込んでいきましょう.

mergeとrebase

それぞれの振る舞いをまとめます.

merge

ターゲットとなるブランチでの変更点を,1つのコミットとして現在のブランチに統合します. コミットログを見てみると,2つのブランチが,一つの点で結ばれるコトになります. 並列つなぎのイメージです. なので,ターゲットブランチがどちらであろうが結果はあまり変わらないです.

rebase

現在ブランチにあるコミットを,一つずつ新しいコミットとしてターゲットブランチの後ろにつなげていきます. この操作により,2つのブランチは1つの流れとして統合されます. イメージとしては直列つなぎですね.

この新しいコミットというところがポイントで,元のコミットの情報が消えてしまうコトに留意する必要があります. もしコンフリクトが存在した場合,各コミットは修正されたものだけが残ることになります. まぁ一本の流れになるんだからそりゃーコンフリクトするコード部分なんて必要ないですもんね.

実際にやってみる

というわけで,実際に先ほどのmasterブランチをmaster_merge,master_rebaseとしてログを見てみましょう.

まずはmergeから.

$ git checkout master
$ git checkout -b master_merge
$ git merge -m "merge issue" issue1
$ git log
*   156543a - (HEAD, master_merge) Merge branch 'issue1' into master_merge (11 seconds ago) <masa>
|\
| * 36ed805 - (issue1) fix hoge1 (60 minutes ago) <1000ch>
| * f253639 - touch hoge1 (61 minutes ago) <1000ch>
* | 3d86c2e - (master) touch hoge_m (42 minutes ago) <1000ch>
|/
* 6187e4b - touch hoge0 (69 minutes ago) <1000ch>

いかがでしょうか.一番下の"touch hoge0"というコミットからmaster,issue1に別れ,master_mergeは二つのブランチをまとめる点としてコミットされていますね.

きちんとブランチを切ってmergeを行うことで,"誰がどのブランチで開発を行ったか"がわかりやすくなると思います. 一方で開発者が増えるほどに枝分かれが多くなり,開発の流れ自体は追いにくくなるかもしれません.

ちなみに先ほど書いた通り,今回はmaster_mergeからgit mergeを行いましたが,issue1からgit mergeをしても結果はほとんど同じになります. mergeを行ったブランチが合流点を指すことになるので,個人的にはmasterからmergeするほうが良いのかなぁと思っています.

ではつぎに,rebaseを使ってみましょう.

$ git checkout master
$ git checkout -b master_rebase
$ git rebase issue1
$ git log
* 94aad61 - (HEAD, master_rebase) touch hoge_m (19 minutes ago) <1000ch>
* 36ed805 - fix hoge1 (85 minutes ago) <1000ch>
* f253639 - touch hoge1 (85 minutes ago) <1000ch>
* 6187e4b - touch hoge0 (2 hours ago) <1000ch>

先ほどとは異なり,2つあったブランチが1つにまとまりました. git rebaseを行ったmaster_rebaseのコミットが新しいものとしてissueブランチの後ろにくっついた形になります.

複数のブランチが一つにまとめられるため,開発の流れ自体は簡略化され,わかりやすくなります. しかし,どのブランチでどのような開発が行われたか,という情報は消えてしまいます. 大きい目的が同じブランチでのちょっとしたfixくらいであればrebaseしてしまったほうがいいと思いますが,別のissueに対するブランチを一つにまとめてしまうのはちょっと頂けないかなという印象です.

また先述の通り,コンフリクト時にコミットの改変が行われることがある点も,少し使いづらい印象を与えますね. もし一度でもリモートに登録したコミットであれば,それを改変してしまうのはよろしくないのではないでしょうか.

これらmergeとrebaseの使い分けとしては,mergeは新機能ブランチの統合rebaseは統合ブランチの更新に利用する,というのが一つの目安ではないでしょうか. 新しいブランチを切って機能を実装中,統合ブランチに改変が行われたとします. この情報を新しいブランチにも適用する際,mergeを行うと新機能コミットの中にバグフィックスのコミットが紛れ込んでしまいます.rebaseを使って新機能ブランチの分岐前バグフィックスコミットをinsertすることでこの問題は解決されますね.

とまぁこんな感じでいろいろ書いてきましたが,一番大切なのはチーム内でコミット,ブランチ切り等のルールを決めることだと思います. 個々人が何も考えずに自由に開発した時のnetworkをgithubでみると....なかなかに悲惨ですからねw

というわけで,これからもがんばってgitに慣れていきたいです!

おまけ

自分のgit logエイリアスです. ~/.gitconfigに追記すると,ログの表示がなかなか奇麗になりますよ. 参考

  log = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'