2014年5月11日日曜日

git のmergeとrebaseを考える

いま会社では二人だけの小さなプロジェクトでgitを使っている。

で、僕が管理者なので、もう一人からpull request的なお知らせが来て、
変更したコードを確認して(いわゆるコードレビュー)、
で、その変更を取り込む、という流れな訳だけど。

gitを使い始めた当初はrebaseを使って取り込むようにしていた。
なぜかというと、mergeコマンドの動作を勘違いしていたから、です。

rebaseしてからmergeする

簡単にいうと、修正していたブランチに本筋のブランチ(ここではmaster)の修正点をまず取り込んでおいてから、本筋のブランチにマージする、というやり方。

こんな状態だったとする

例えば、まず以下のブランチがあったとする。
$ git branch
  branch_fix_A
* master

それぞれのログは以下とする。
$ git checkout master
$ git log --graph --oneline --decorate
* 8d03efb (HEAD master) add B.
* 2dcd836 (tag: v_1.0.0) initial commit.
$ git checkout branch_fix_A
$ git log --graph --oneline --decorate
* ac292d9 (HEAD, branch_fix_A) fix A1.
* 5a7c805 fix A.
* 2dcd836 (tag: v_1.0.0) initial commit.
つまり、branch_fix_Aはmasterのタグv_1.0.0からブランチを作成して、
二つのコミットをした状態。
masterの方は、その後に一つのコミットをした状態。

branch_fix_Aをmasterに取り込もうとしている、という状況。

ここでまず、branch_fix_Aでmasterをrebaseする

$ git rebase -i master
そうすると、branch_fix_Aで行ったコミットを取り込むのか、すっ飛ばすのか、などなどを選べる。
pick 5a7c805 fix A.
pick ac292d9 fix A1.
# Rebase 8d03efb..ac292d9 onto 8d03efb
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
今回はこのまま取り込む。

・・・おっと、同じファイルを編集してたので、conflictしてるよ。
error: could not apply 5a7c805... fix A.
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

Could not apply 5a7c80521adfa971cdda95b942cbf962f83e7419... fix A.
てことで、conflictを解消してrebaseを継続する。
$ git add readme.txt
$ git rebase --continue
現状のログを見ると以下となる。
$ git log --graph --oneline --decorate
* 67b268c (HEAD, branch_fix_A) fix A1.
* ef0bb01 fix A.
* 8d03efb (master) add B.
* 2dcd836 (tag: v_1.0.0) initial commit.

で、masterに戻ってmerge

$ git checkout master
$ git merge --no-ff branch_fix_A
Merge made by the 'recursive' strategy.
readme.txt | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
でOK。

$ git log --graph --oneline --decorate
*   f639397 (HEAD, master) Merge branch 'branch_fix_A'
|\ 
| * 67b268c (branch_fix_A) fix A1.
| * ef0bb01 fix A.
|/
* 8d03efb add B.
* 2dcd836 (tag: v_1.0.0) initial commit.
こんな感じで、取り込んだコミットだけ見れて、masterブランチから見て、どのタイミングで取り込んだのかがわかっていいかなぁと思ってたのですが。

実はこれ、branch_fix_Aの二つのコミットのIDが変わっちゃってます。
つまり、修正したときの元の完全な状態に戻せない、ということですね。。

それはまずいかなぁと思って、昨日から(^^; 普通にマージするようにしました。

単純にmergeする

上述と同じ状況でmasterでmergeするだけ

$ git checkout master
$ git merge --no-ff branch_fix_A
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
て、conflictするので解消してcommitする。
$ git commit -m "fix confilict on merge with branch_fix_A."
$ git log --graph --oneline --decorate
*   3aa835d (HEAD, master) fix confilict on merge with branch_fix_A.
|\
| * ac292d9 (branch_fix_A) fix A1.
| * 5a7c805 fix A.
* | 8d03efb add B.
|/
* 2dcd836 (tag: v_1.0.0) initial commit.
これがすっきりしててよいのでしょうね。


で、git mergeのなにを勘違いしていたのかというと、
マージしたい二つのソースがあったときに、手動で差分をとってソースを取り込む、という動作を想像してしまったんです。

えっと、ややこしいですね。。

ブランチが分岐した後のmasterに入ったコミットが、マージのときにややこしくなるんじゃないか?と思ってたんですよね。

人間が手動でやる場合には上述のことを考慮しなくちゃいけないけど、
gitはブランチの分岐点を知ってるので、差分だけをマージしてくれるのです!
もちろんコンフリクトするソースは手動で直すのですが。


てことで、ソース取り込みも手間が少なくなりました。


0 件のコメント:

コメントを投稿