HOWTO · Git
Git パッチエラーのトラブルシューティング
この記事では、git パッチの適用に関連するいくつかの一般的なエラーのトラブルシューティング方法について説明します。
このページの内容
Git のパッチが適用できない主な原因は、パッチの形式に合わないコマンドを使っていること、パッチが想定するものとリポジトリのベースが異なること、またはパッチのコンテキストが変更されていることです。まずメール形式のパッチか diff かを見分け、適用先のブランチと作業ツリーを確認してから、適用できるかを調べ、復旧方法を選びます。diff パッチには git apply、メール形式のパッチには git am を使います。git am はコミットを作成します。
パッチの形式を見分け、使う Git コマンドを選ぶ
通常の diff パッチには diff --git で始まる行や、@@ で始まるファイルの差分が含まれます。これを作業ツリーに適用するには git apply を使います。git format-patch で作成されたパッチは、コミットのメタデータを含むメール形式のメッセージで、通常は git am で適用します。git am はコミットを作成し、作成者とコミットメッセージを保持します。この2つのコマンドは互換ではありません。ファイル形式がわからない場合は、適用する前に head で先頭行を確認し、メールヘッダーやコミットのメタデータがあるか調べてください。
すでに am の処理中であれば、続行する前に git status を確認します。現在のメールパッチの競合を解決した後、git am --continue で続行できます。git am --abort は処理を中止し、開始前の状態に戻します。git am --skip は現在のメールパッチをスキップするため、そのパッチを破棄すると決めた場合にのみ使ってください。通常、git apply のインデックスエラーを無視するためのコマンドではありません。
適用する前にリポジトリとパッチを確認する
まず git status --short --branch と git branch --show-current を実行します。現在のブランチが意図した適用先か確認し、追跡対象、ステージ済み、未追跡の変更をすべて把握してください。ローカルの変更が上書きされるおそれがある場合は、ブランチを切り替えないでください。先にコミットする、安全に退避するなど、変更を保持する方法を決めます。同じファイルが両方のブランチにあっても、別のコミットを基に作成されたパッチは現在のブランチに適用できないことがあります。
実行する前にパッチの内容を確認します。diff パッチの場合、git diff --stat -- my_patch.patch はパッチファイルの内容を調べるコマンドではありません。head、less、または git apply --stat my_patch.patch を使って、内容や変更対象のパスを確認してください。次に git apply --check my_patch.patch を実行します。これはファイルを変更せずに diff を適用できるか確認しますが、その変更が意図したものか、後の編集が正しいかまでは検証しません。メール形式のパッチは内容を確認し、通常の diff として扱わず、対応する git am の手順を使ってください。
Git がインストールされたシェルで、次の自己完結型の例を実行できます。この例では一時リポジトリを作成し、diff を生成して元のファイルを復元した後、同じベースに diff を適用します。
このセクションのコードと表示されるラベルや出力は英語のままです。
set -eu
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
repo="$tmp/repo"
mkdir "$repo"
git -C "$repo" init -q
git -C "$repo" config user.name Example
git -C "$repo" config user.email example@example.com
printf 'first line
' > "$repo/note.txt"
git -C "$repo" add note.txt
git -C "$repo" commit -qm 'Add note'
printf 'first line
second line
' > "$repo/note.txt"
git -C "$repo" diff > "$tmp/change.patch"
git -C "$repo" checkout -- note.txt
git -C "$repo" apply --check "$tmp/change.patch"
git -C "$repo" apply "$tmp/change.patch"
printf 'Applied content: '
cat "$repo/note.txt"
この例では git apply --check が事前確認を行い、その後の git apply が変更を適用します。自分のリポジトリで試す場合は、生成された diff のパスを手元のパッチのパスに置き換え、ステージする前にもう一度 git status を確認してください。
よくある Git パッチエラーを診断する
patch does not apply と表示される場合や hunk の適用に失敗する場合、Git は対象ファイル内に想定したコンテキストを見つけられていません。パッチが別のコミットを基に作られている、該当行が編集されている、または空白が異なるといった原因が考えられます。hunk の前後の行を現在のファイルと比較し、想定されたベースが何かを確認してください。空白だけが異なる場合は、--ignore-space-change や --ignore-whitespace などの設定が役立つことがありますが、ロジックの変更、ファイルの欠落、無関係な内容の競合は解決できません。
file already exists in index というメッセージは、インデックスまたはパスの衝突を示していますが、メッセージだけでは原因を特定できません。git status を確認し、git ls-files --stage -- path/to/file で該当パスを調べ、パッチの hunk も確認してください。変更がすでにステージされているのか、パッチが追跡済みのパスを追加しようとしているのか、別のリポジトリのベースを対象に作られたのかを判断します。意図した状態がわかるまでは、インデックスをリセットしたりファイルを削除したりしないでください。
error in file というメッセージも、それだけでは原因を特定できないほど一般的です。前後の Git 出力を読み、名前が示されたファイルと該当するパッチの hunk を調べ、ファイルパスの綴りと大文字・小文字が想定どおりか確認してください。パッチが壊れていると決めつけず、より具体的な失敗内容に基づいて修正方法を選びます。
パッチの適用に失敗したときの復旧方法を選ぶ
git apply --check でコンテキストの不一致が報告された場合は、まず適用先のベースを見直してください。正しいブランチやコミットを用意するか、現在のファイルに合わせてパッチを手作業で調整する必要があるかもしれません。ローカルの変更を確認して保持する前に、むやみにブランチを切り替えないでください。意図したベースを準備したら、もう一度チェックを実行します。
一部の hunk が独立していて、適用できるものだけを適用したい場合は、git apply --reject my_patch.patch を使うと、適用できる hunk が反映され、適用できなかった hunk は .rej ファイルに書き出されます。これは部分適用です。影響を受けたソースファイルと各 .rej ファイルをすべて確認し、却下された変更を手作業で反映するか、破棄してください。このコマンドは却下された内容を解決せず、パッチ全体の適用を完了するものでもありません。すべて適用するか適用しないかのどちらかにしたい場合は、チェックが成功してから通常の git apply を使ってください。
ベース情報が利用できるパッチには、git apply --3way my_patch.patch を使って Git に三者マージを試みさせる方法があります。必要なベースの blob が利用できなければ、このコマンドは失敗することがあります。三者マージが成功しても競合マーカーが残り、手作業での解決が必要になる場合があります。これはコンテキストが変わった場合に試せる方法であり、常に使える解決策ではありません。空白に関するオプションは別のものなので、調査の結果、障害が空白の違いだとわかった場合にだけ使ってください。
競合を解決し、結果を確認する
復旧を試みた後は git status --short と git diff -- を確認し、変更されたファイルと未ステージの内容を把握してください。--reject でパッチを適用した場合は .rej ファイルを探し、却下された各 hunk を現在のソースと比較します。三者適用で競合が残った場合は、競合マーカーを検索し、各ファイルを意図した最終内容に編集して、マーカーが残っていないことを確認してください。メール形式のパッチの処理中は、git status に表示される git am の状態に従って続行するか中止します。
ステージする前に、git diff --check で空白の問題を確認し、git diff で実際の変更を確認します。すでにステージ済みの変更がある場合は git diff --cached も確認してください。ファイルに意図した変更が含まれていることと、無関係なローカル作業が残っていることを確かめます。その確認を終えてから、ステージまたはコミットするかを選びます。git log で git am が作成したコミットを確認できますが、作業ツリーの変更をすべて表示するわけではありません。結果全体を検証するには、status と diff の出力も使ってください。