Git

Gitマージの基本 マージ・競合・競合解決・マージしなおし

More than 3 years have passed since last update.

オライリー・ジャパン『実用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と番号があるのはそれぞれ以下の通り。

  1. マージの起点(base)
  2. マージ対象(ourバージョン)
  3. マージされる側(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