Git の mergetool で vimdiff を指定して Vim でマージコンフリクトを解決してみた
マージコンフリクトを解決する時に, git mergetool でいくつかあるうちの中かから一つのツールを指定することができますが, 今回は vimdiff を指定してみました. そしたらとても便利でした. Vimmer の方必見でござるよ.
マージコンフリクトを意図的に作る
まず git mergetool
を使うためには, マージコンフリクトが発生している必要がありますので, 次のようなマージコンフリクトを意図的に作ってみました.
ヒストリは次のようになります:
D <- hotfix
/
/
A -- B --- C <- master <- HEAD
そして A
から D
までのコミットで example.txt
を次のように編集してみました.
A
の example.txt
:
Alfa
B
の example.txt
:
Alfa
Bravo
C
の example.txt
:
Alpha & Bravo
Chirlie
D
の example.txt
:
Alfa
Bravo
Delta
そして次のコマンドで hotfix
ブランチを master
ブランチにマージさせると:
$ git merge hotfix
Auto-merging example.txt
CONFLICT (content): Merge conflict in example.txt
Automatic merge failed; fix conflicts and then commit the result.
このように example.txt
にマージコンフリクトが発生し, example.txt
は次のようになります:
<<<<<<< HEAD
Alfa & Bravo
Charlie
||||||| merged common ancestors
Alfa
Bravo
=======
Alfa
Bravo
Delta
>>>>>>> hotfix
このように, 競合しているところに <<<<<<<
, =======
, >>>>>>>
というマーカに加えて, 僕は merge.conflictStyle
の値を diff3
に設定しているので |||||||
という四種類のマーカが表示されます.
|||||||
というマーカによって, C
と D
の共通祖先 B
の example.txt
の内容を知ることができます. この場合は一行目が Alfa
で二行目が Bravo
だったと |||||||
と =======
の間の二行からわかります.
より詳しく |||||||
についてお知りになりたい方は, 前回の記事 “Git の merge.conflictStyle を merge から diff3 に変更したら, マージコンフリクトがより解決しやすくなった” をよろしければ参考にしていただければと思います.
という感じで, example.txt
のファイルを直接開くと, そのようなマーカが書き加えられているので, それを頼りにマージコンフリクトを解決することができます.
vimdiff
でマージコンフリクトを解決する
今度は git mergetool
というマージコンフリクトを解決するプログラムを起動するコマンドで vimdiff
を指定して先ほどの example.txt
のマージコンフリクトを解決してみたいと思います.
まだ先ほどのマージコンフリクトが残っている状態で:
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: example.txt
no changes added to commit (use "git add" and/or "git commit -a")
次のコマンドで, git mergetool
を通して vimdiff
を起動させます:
$ git mergetool -t vimdiff
Merging:
example.txt
Normal merge conflict for 'example.txt':
{local}: modified file
{remote}: modified file
4 files to edit
もし -t vimdiff
を省略したい場合, 僕も設定しているのですが, 次の設定をすると git mergetool
と入力するだけで vimdiff
を起動できるようになります:
git config --global merge.tool vimdiff
そして次のような画面が表示されます:
画面上半分の左からローカルの C
, 真ん中がベース (共通祖先) の B
, 右がリモートの D
となり, 画面下半分がマージコンフリクト解決中の example.txt
となります.
つまりは, 上半分の 3 つを参考にして, 下半分の example.txt
のマージコンフリクトを解決することができます.
このようにそれら 3 点のコミットの example.txt
の内容を直接見ることができるので, どのようにマージコンフリクトを解決するかという上でとても参考になります.
ちなみにどうして 3 点なのかというのは, Git のマージが 1 つのブランチを HEAD にマージさせる場合, 3-way マージとなり, マージするブランチの先端 2 つ (C
, D
) と, その 2 つの先端の共通祖先 (B
) の計 3 点のコミットが使われるためです.
ちなみに上半分の 3 つのファイルのそれぞれの 40 桁のチェックサム (SHA-1) は, マージコンフリクト発生中に次のコマンドで表示させることができます:
$ git ls-files -u
100644 90f9b648f654153a5f42619b8d2404f79b7122ca 1 example.txt
100644 ccbb6e7de8e70b118e4f663c836fdd800f7c0d35 2 example.txt
100644 807eec38ba1be39da929b95d0d6179fc82778915 3 example.txt
一行目がベースで, 二行目がローカルで, 三行目がリモートのファイルになります.
それぞれのコミット内容を直接みる場合は, 次のコマンドで見ることができます:
$ git show :1:example.txt
Alfa
Bravo
$ git show :2:example.txt
Alfa & Bravo
Charlie
$ git show :3:example.txt
Alfa
Bravo
Delta
ローカルとリモートは次のコマンドでも見ることができます:
$ git show HEAD
$ git show MERGE_HEAD
Vim の diff モードで使えるコマンド
で先ほどの vimdiff
の画面に戻りますが, vimdiff
は Vim の diff モードなので, マージコンフリクトを解決する時に便利なコマンドを使うことができます.
画面下半分の example.txt
の競合部分にカーソルを合わせている状態で:
1do
と打つとローカルの内容をその部分に持ってこれます:
2do
と打つとベースの内容をその部分に持ってこれます:
3do
と打つとリモートの内容をその部分に持ってこれます:
1do
, 2do
, 3do
のそれぞれは :diffget LOCAL
, :diffget BASE
, :diffget REMOTE
というコマンドでも同等のことができます.
というのも git mergetool -t vimdiff
による Vim の起動中に次のような一時的なファイルが作られるためです:
$ git status -s
UU example.txt
?? example_BACKUP_71903.txt
?? example_BASE_71903.txt
?? example_LOCAL_71903.txt
?? example_REMOTE_71903.txt
そして先ほどのコマンドに戻りますと, もしくはローカルの競合部分にカーソルを移動させ:
4dp
でそのローカルの内容を下半分の example.txt
に持ってこれます:
ベースの競合部分にカーソルを移動させ:
ベースの内容も 4dp
で持ってこれます:
リモートの競合部分にカーソルを移動させ:
リモートの内容も 4dp
で持ってこれます:
2 画面分割の diff モードだったら do
, dp
と数字を付けずにコマンドを打つことができるのですが, 4 画面分割なので 1
, 2
, 3
, 4
という数字を使ってバッファを指定する必要があります.
あと [c
や ]c
を使って, 前後の競合箇所にカーソルを移動させることもできます.
今回の場合は, 競合が発生している箇所が一箇所のみなので, [c
や ]c
の使用をお見せすることができないのですが , 1 つのファイル内で競合が二箇所以上発生している時にカーソル操作が楽になります.
Vim の diff モードに関するヘルプは, コマンドラインモードより :help diff.txt
で見ることができます.
そして例えば次のようにマージコンフリクトを解決し終えたら:
画面下半分の example.txt
を :w
で保存し, 4 画面全てを :qa
で終了させたら, 自動的に example.txt
がステージされるので git add example.txt
と入力する必要はありません:
$ git status
On branch master
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: example.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
example.txt.orig
あと Vim を終了させると, この場合は example.txt.orig
というファイルですが, .orig
という拡張子が付いたマージコンフリクト発生直後のファイルがバックアップ目的で作られるので, もう必要ない場合次のコマンドで削除します:
$ git clean -f
Removing example.txt.orig
ただこのコマンドは Git にトラックされていない全てのファイルを削除するので, 心配な方は次のように厳密に削除されることをオススメします:
rm example.txt.orig
もし git mergetool -t vimdiff
で Vim を起動させて, Vim を終了させた後, その .orig
ファイルを自動で作りたくない場合, 一例として次のコマンドでそのように設定できます:
git config --global mergetool.keepBackup false
あとは git commit
でマージコミットを作るだけとなります:
Merge branch 'hotfix'
# Conflicts:
# example.txt
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
# .git/MERGE_HEAD
# and try again.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
# modified: example.txt
#
コミットメッセージの確認ができたら, ZZ
でコミットして, マージが完了します.
まとめ
Vim をメインのテキストエディタとして使われている方は, ぜひ Git のマージコンフリクトを解決する際は git mergetool -t vimdiff
より vimdiff
を使われてみてはどうでしょうか.
Vim のポテンシャルを最大限発揮してマージコンフリクトを解決することができるので, Vim に慣れ親しまれている方はより快適にマージコンフリクトを解決することができます
僕は Vim が好きなので git mergetool -t vimdiff
によって起動される Vim の diff モードでマージコンフリクトを解決できると知ってから, そのコマンドを使って解決しています (
-t vimdiff
は省略していますが)
参考資料
関連記事
Git で変更されたファイルを部分的にステージする方法2018.11.01
git merge オプションの --ff, --no-ff, --ff-only の違い2018.08.15
Git でより良いコミットメッセージを書く方法2018.06.13
Git の 'detached HEAD' 状態とその注意点2018.05.31
Git の Reset, Checkout, Revert の違い2018.06.01