オライリー・ジャパン『実用Git』を最近読み終えたので、
マージについて、実用的な部分をつらつらとまとめておく。
時間があればマージだけでなく、コミットの変更方法についても後日書くかも。
#マージ
##マージ方法
マージするにはマージされる(他ブランチのコードを取り込む)側のブランチをチェックアウトして、
マージ対象(他のブランチにコードを取り込まれる)ブランチを指定して実行。
マージする時には余計なトラブルを避ける為に、未コミットのものがある状態でのマージは避ける。
git checkout ${マージされる側}
git merge ${マージ対象ブランチ}
マージ対象複のブランチは数指定可能。
# topic1,topic2のブランチがmasterにないコミットを含んでいる事を確認
$ git show-branch
* [master] a
! [topic1] b
! [topic2] c
---
+ [topic2] c
+ [topic1] b
*++ [master] a
#マージ実行
$ git merge topic1 topic2
Fast-forwarding to: topic1
Trying simple merge with topic2
Merge made by the 'octopus' strategy.
b | 1 +
c | 1 +
2 files changed, 2 insertions(+)
create mode 100644 b
create mode 100644 c
#マージ後にブランチの状態を確認すると、topic1,2の変更がマージされている事がわかる
$ git show-branch
* [master] Merge branches 'topic1' and 'topic2'
! [topic1] b
! [topic2] c
---
- [master] Merge branches 'topic1' and 'topic2'
* + [topic2] c
*+ [topic1] b
*++ [topic2^] a
##競合のあるマージ
例としてmasterにファイルhello.txtを用意し、topicブランチ作成後hello.txtに両ブランチで文言を追加し、masterにマージして競合を発生させる。
$ mkdir hello
$ cd hello/
$ git init
$ echo "hello from master" > hello.txt
$ git add hello.txt
$ git commit -m "hello from master"
$ git checkout -b topic
#topicブランチで編集
$ echo "hello from topic" >> hello.txt
$ git commit -am "hello from topic"
$ git checkout -
#masterブランチでも編集
$ echo "hello from master" >> hello.txt
$ git commit -am "hello from master again"
#同じファイルに対して、マージする、される側両方で編集していることを確認
$ git show-branch
* [master] hello from master again
! [topic] hello from topic
--
* [master] hello from master again
+ [topic] hello from topic
*+ [master^] hello from master
$ git merge topic
Auto-merging hello.txt
CONFLICT (content): Merge conflict in hello.txt
Automatic merge failed; fix conflicts and then commit the result.
#競合した
$ cat hello.txt
hello from master
<<<<<<< HEAD
hello from master
=======
hello from topic
>>>>>>> topic
#diffをとってみる
$ git diff
diff --cc hello.txt
index 308c441,2f4e5d4..0000000
--- a/hello.txt
+++ b/hello.txt
@@@ -1,2 -1,2 +1,6 @@@
hello from master
++<<<<<<< HEAD
+hello from master
++=======
+ hello from topic
++>>>>>>> topic
競合後、git diffをとると++<<<<<<< HEADのように+が二重でかかっている。(場合によってはもっと増えることもある)左側がHEAD(マージされる側)に対するdiffで右側がtopic(取り込まれる側)に対してつけられたものなので注意。ちなみに、マージされる側の参照はMERGE_HEADで、マージ後に参照可能。
また、git diff HEADはgit diff --ours、MERGE_HEADは--theirsで、マージの起点からは--baseでdiffをとれる。
$ git diff HEAD
diff --git a/hello.txt b/hello.txt
index 308c441..9b4ba13 100644
--- a/hello.txt
+++ b/hello.txt
@@ -1,2 +1,6 @@
hello from master
+<<<<<<< HEAD
hello from master
+=======
+hello from topic
+>>>>>>> topic
$ git diff MERGE_HEAD
diff --git a/hello.txt b/hello.txt
index 2f4e5d4..9b4ba13 100644
--- a/hello.txt
+++ b/hello.txt
@@ -1,2 +1,6 @@
hello from master
+<<<<<<< HEAD
+hello from master
+=======
hello from topic
+>>>>>>> topic
$git diff --base
* Unmerged path hello.txt
diff --git a/hello.txt b/hello.txt
index 653f0d3..9b4ba13 100644
--- a/hello.txt
+++ b/hello.txt
@@ -1 +1,6 @@
hello from master
+<<<<<<< HEAD
+hello from master
+=======
+hello from topic
+>>>>>>> topic
注意として、競合解決後オプション無しのgit diffでは、どちらかの候補を選ぶだけの解決をするとdiffがでなくなる仕様になっています。
$ git diff
diff --cc hello.txt
index 308c441,2f4e5d4..0000000
--- a/hello.txt
+++ b/hello.txt
@@@ -1,2 -1,2 +1,6 @@@
hello from master
++<<<<<<< HEAD
+hello from master
++=======
+ hello from topic
++>>>>>>> topic
#HEADを採用
$ vi hello.txt
$ cat hello.txt
hello from master
hello from master
#diffが出ないので注意
$ git diff
diff --cc hello.txt
index 308c441,2f4e5d4..0000000
--- a/hello.txt
+++ b/hello.txt
競合がどこにあるかをチェックする場合は、git statusかgit ls-files -uを行う。
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: hello.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git ls-files -u
100644 653f0d3a57048c82d25251ba8802d42bf2dcbd0e 1 hello.txt
100644 308c441c01f3e2a5d4fa5147ed5261d782af009f 2 hello.txt
100644 2f4e5d4f8d84256cecd64825ce364c2e3ca97da7 3 hello.txt
git ls-files -uをする事で競合のあったファイルのステージ番号がでる。123と番号があるのはそれぞれ以下の通り。
- マージの起点(base)
- マージ対象(ourバージョン)
- マージされる側(theirバージョン)
それぞれgit cat-file や git diff で参照できる。
$ git cat-file -p :1:hello.txt
$ git cat-file -p :2:hello.txt
$ git cat-file -p :3:hello.txt
$ git diff :2:hello.txt :3:hello.txt
##競合解決
通常であれば、競合したファイルを編集後、ステージ(git add)してcommitすれば良い。(マージマーカなどが残った状態でもこの手順でgitには競合を解決したとして、commit可能になるので注意。
また、修正方法がourバージョンかtheirバージョンに統一するだけであれば
$ git checkout --ours hello.txt
$ git checkout --theirs hello.txt
としてcommitすることも可能。
##マージ中断
マージ後(ただし、マージ完了をコミットしていない状態で)マージ前の状態に戻したい場合は
$ git reset --hard HEAD
マージ後(マージ完了をコミットした状態で)マージ前に戻したい時は以下のようにする
$ git reset --hard ORIG_HEAD