あるファイルに大量のコンフリクトが発生し解決が面倒なとき、パッチを使ってファイルに1コミットずつ変更を適用する方法を示す。この方法のメリットは:
- ファイルへの変更を1コミットずつ適用・コンフリクト解決することができる
- それぞれのコミットを適用する前に、コミットをパッチファイルの形で編集できる
- 注目するファイル以外への変更をいったん無視し、そのファイルに関係する変更に集中できる
の3点である。複数コミットの変更が混ざった大量のコンフリクトマーカーを手作業で消すような状況に陥ったとき、この方法を使えばいくぶんかは楽にマージ作業を進められる。
概要
マージ中に特定のファイルに大量のコンフリクトが起きたら、マージを中止する。一時作業用ブランチを作り、そのファイルに1コミットずつパッチを当てて編集する。パッチを当て終わったらマージをやり直し、コンフリクト解決作業中に、コンフリクトしたファイルを一時作業用ブランチからチェックアウトしたファイルで置き換える。
やりかた
次のようにマージを実行したとき、 Foo.h, Foo.m に多くのコンフリクトが発生したとする。
$ git checkout master
$ git merge dev
# Foo.h, Foo.m に大量のコンフリクトが発生
# 他のファイルには比較的簡単なコンフリクトが発生
まず merge を中止する。
$ git merge --abort
現在のブランチから新しい作業用ブランチを切り、そのブランチに移る。
$ git checkout -b work
取り込もうとしているブランチから Foo.h, Foo.m に関係するコミットだけをパッチファイルに書き出す。 (補足:以下のコマンドで、 ..dev
は「dev
から到達可能だが HEAD
から到達可能でないコミット」、言い換えれば「両者が分岐した以降、 dev
にのみ含まれるコミット」を表す。 --histogram
は diff アルゴリズムを histogram diff に切り替える。場合によるが、デフォルトのアルゴリズムより読みやすいパッチを生成する)
$ git format-patch --histogram ..dev -- Foo.h Foo.m
0001-Edit-Foo.patch
0002-Another-change-to-Foo.patch
必要なら適用する前にパッチを手作業で編集する。ここで不要な変更を取り除くなどしておく。
$ vim *.patch
git apply -3
でパッチを順に適用する。1つ前のステップでパッチの行数が変わるような編集を行ったなら、git apply
に --recount
オプションを与える。作業用ブランチは後で削除するのでコミットメッセージは適当でよい。(補足: git apply
に -3
オプションを与えると 3-way merge を実行する。 --recount
オプションを与えると、パッチに含まれる行数のヒントを無視する)
$ git apply -3 0001-Edit-Foo.patch
# コンフリクトを解決
$ git commit -m "Tmp 1"
$ git apply -3 --recount 0002-Another-change-to-Foo.patch
# コンフリクトを解決
$ git commit -m "Tmp 2"
すべてのパッチを適用した時点で、作業用ブランチ内の Foo.h, Foo.m は必要な変更をすべて取り込んだ状態になる。
パッチファイルを削除してから、先ほど中止したマージをやり直す。
$ rm *.patch
$ git checkout master
$ git merge dev
# ここではまだ Foo.h, Foo.m にコンフリクトが発生する
作業用ブランチからコンフリクト解決済みの Foo.h, Foo.m をチェックアウトする。チェックアウトしたら作業用ブランチは削除してよい。
$ git checkout work -- Foo.h Foo.m
$ git add Foo.h Foo.m
$ git branch -D work
適宜その他のコンフリクトを解決し、マージを完了する。
# コンフリクトを解決
$ git add -u
$ git commit
Tips
上では「現在のブランチから作業用ブランチを切り、その上に取り込もうとしているブランチの変更を適用」するとしたが、変更の量によっては「取り込もうとしているブランチから作業用ブランチを切り、その上に現在のブランチの変更を適用」してもよい。注目するファイルに対して変更量が多い・変更の内容が複雑なブランチをベースに作業用ブランチを作ると、取り込むべきパッチの量が減り、作業が楽になる。