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