クラウドMac CIでGitオブジェクト破損を診断し安全に復旧する

クラウドMac CIでGitオブジェクト破損を診断し安全に復旧する

checkout の直後にパイプラインが突然 bad objectmissing blobpack checksum mismatch を出して失敗し、再実行すると復旧することもあれば、より多くのジョブが同時に失敗することもあります。多くの場合、原因はアプリケーションコードではなく、作業領域、オブジェクトキャッシュ、またはGitのpackファイルの不整合です。正しい対応順序は、すぐにキャッシュを削除することではありません。まず書き込みを止めて現状を保存し、破損が単一リポジトリ内にあるのか、共有レイヤーにあるのかを切り分けます。

ネットワーク障害とオブジェクト破損を最初に切り分ける

ネットワーク障害は取得処理中に発生することが多く、ログには通常、接続の切断、リモート側による早期切断、転送の未完了などが記録されます。一方、オブジェクト破損はチェックアウト、マージ、履歴の読み取り、アーカイブ生成などで表面化しやすく、具体的なオブジェクトハッシュを伴います。失敗したコマンド、終了コード、コミットID、作業領域のパス、キャッシュの世代を最初に記録してください。パイプラインログの末尾数十行だけを残すのでは不十分です。

まずは読み取り専用の検査を実行できます。

git status --porcelain=v2
git rev-parse --show-toplevel
git rev-parse HEAD
git fsck --full --strict --no-dangling

git fsckmissing blob を報告した場合、参照から到達可能なファイルオブジェクトが存在しないことを意味します。missing tree は通常、ディレクトリのチェックアウトを妨げます。invalid sha1 pointer は、参照先のオブジェクトを読み取れないことを示します。danglingオブジェクトだけが検出された場合は、直ちにリポジトリ破損と判断してはいけません。リベース、圧縮、置き換えられたコミットによって生じた可能性があります。

診断段階の目的は、現在のコマンドを一時的に成功させることではなく、破損範囲を特定することです。オブジェクトデータベースを書き換える操作は、現状の保存が完了するまで延期してください。

作業領域を凍結して証拠を保存する

最初に、同じディレクトリへ書き込むrunner、定期取得処理、キャッシュ更新ジョブを停止します。元のディレクトリで git gcgit prune、再パック、再帰的な削除を実行しないでください。これらの操作はオブジェクト配置を変え、当初の異常を再現できなくする可能性があります。

少なくとも、ジョブの完全なログ、.git/HEAD.git/config.git/packed-refs.git/refs.git/logs、現在の状態、未コミットの差分を保存します。ソースコードの作業領域には未追跡ファイルが含まれる場合があります。アーカイブする前に一覧を確認し、認証情報や大容量のビルド成果物を誤って含めないようにしてください。

incident="$HOME/git-incidents/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$incident"
git status --porcelain=v2 > "$incident/status.txt"
git show-ref --head > "$incident/refs.txt"
git reflog show --all --date=iso > "$incident/reflog.txt"
git diff --binary > "$incident/worktree.patch"
git diff --cached --binary > "$incident/index.patch"
git fsck --full --strict > "$incident/fsck.txt" 2>&1 || true

CIによって作業領域がいつでも再作成される可能性がある場合、元の場所で修復するのではなく、ディレクトリ全体をリネームして隔離する方法が最も安全です。隔離したディレクトリは読み取り専用にし、対応するジョブとキャッシュの取得元を記録します。

ルーズオブジェクト、packファイル、共有キャッシュを特定する

Gitオブジェクトはルーズファイルとして存在する場合も、.pack に格納されている場合もあります。エラーにハッシュが含まれている場合は、まずオブジェクトの種類を確認します。

object="0123456789abcdef0123456789abcdef01234567"
git cat-file -t "$object"
git cat-file -s "$object"

コマンドが失敗する場合は、.git/objects/${object:0:2}/${object:2} が存在するか確認します。ファイルが存在するのに読み取れない場合、一般には内容の切り詰め、チェックサム不一致、下位レイヤーでのコピー未完了が疑われます。信頼性を確認できていない別の取得元から同名ファイルをコピーし、現状のファイルを上書きしてはいけません。

packファイルについては、インデックスを1つずつ検証します。

for index in .git/objects/pack/*.idx; do
  git verify-pack -v "$index" >/dev/null || printf '%s\n' "$index"
done

リポジトリでalternatesを設定している場合は、.git/objects/info/alternates も確認します。複数のジョブが同じ書き込み可能なオブジェクトディレクトリを共有していると、書き込みが一度中断されただけで、そのディレクトリを参照するすべての作業領域に影響する可能性があります。この場合、特定のリポジトリだけを再クローンしても根本原因は解消されません。該当するキャッシュ世代の公開を停止する必要があります。

破損を安全に再現できるか確認する

隔離したディレクトリをコピーしてから検査を再現し、ファイルシステムの空き容量、プロセスの終了方法、同じ時間帯に実行されていたジョブ数を記録します。毎回同じオブジェクトで失敗する場合は、オブジェクトの取得元とキャッシュを優先的に調査します。失敗するオブジェクトが毎回変わる場合は、ディスク容量の逼迫、同時書き込み、作業領域のクリーンアップ処理を確認してください。

その場で修復せずクリーンクローンで復旧する

手作業による変更がないCI作業領域では、新しいディレクトリを作成し、対象コミットを完全に取得して検証した後、古いディレクトリをアトミックに置き換える方法が一般に安全です。新しいクローンでは、健全性が未確認のオブジェクトキャッシュを再利用しないでください。

root="$HOME/ci-workspaces"
next="$root/project.next"
active="$root/project"
failed="$root/project.failed"

rm -rf "$next"
git clone --no-local "$REPOSITORY_PATH" "$next"
git -C "$next" checkout --detach "$EXPECTED_COMMIT"
git -C "$next" fsck --full --strict
test "$(git -C "$next" rev-parse HEAD)" = "$EXPECTED_COMMIT"
mv "$active" "$failed"
mv "$next" "$active"

置き換え前には、プロジェクトの最小限の受け入れ確認も実行します。たとえば、プロジェクトの解析、ビルドエントリーポイントの列挙、短時間で完了するテスト一式の実行などです。隔離したリポジトリに未プッシュのコミットがある場合、再クローンで復元できるとは考えないでください。保存した参照とreflogを使い、コピー上で読み取りを試みます。オブジェクトが実際に失われている場合は、信頼できるリモート、別の完全なクローン、または元の作業成果から復元するしかありません。

オブジェクト検証をキャッシュ公開フローに組み込む

共有Gitキャッシュに、すべてのジョブが同時に書き込む構成は避けるべきです。より安定するのは「生成、検証、公開」のモデルです。ジョブは一時ディレクトリに新しいキャッシュを構築し、取得完了後に git fsck を実行します。検証に成功した場合に限り、ディレクトリを新しい読み取り専用世代としてリネームします。実行中のジョブはそれぞれに割り当てられた世代を固定して使用し、途中で更新先へ切り替えないようにします。

日常的な確認項目は4つに集約できます。キャッシュ公開前にオブジェクトを検証すること、ジョブの作業領域を共有ベースラインから分離すること、失敗時にオブジェクトハッシュとキャッシュ世代を保存すること、クリーンアップスクリプトでは参照されなくなった完全な世代だけを削除することです。これにより、単一のダウンロード失敗やプロセス終了によって不完全な書き込みが発生しても、影響を未公開の一時ディレクトリ内に限定できます。

最後に、復旧手順を実行可能なスクリプトとして用意し、業務データを含まないテスト用リポジトリで検証します。信頼できる復旧演習では、少なくとも次の点を実証する必要があります。破損したキャッシュが配布され続けないこと、対象コミットをクリーンな取得元から再構築できること、未コミット差分に独立した保存経路があること、置き換え中に2つのジョブが同じ作業領域へ同時に書き込まないことです。

よくある質問

.git/objects内の異常なファイルをすぐ削除してもよいですか?

削除すべきではありません。先に書き込みを止め、ログ、参照、作業ツリーの変更を保存します。直接削除すると別の参照まで壊れ、原因調査も難しくなります。

git gcで破損したオブジェクトデータベースを修復できますか?

git gcは修復コマンドではありません。オブジェクトの再編成と削除を行うため、障害の痕跡を失う可能性があります。CI用コピーは正常なクローンへ交換する方が安全です。

壊れたGitキャッシュが後続ジョブへ広がるのを防ぐ方法は?

キャッシュを変更不可の世代として公開します。別ディレクトリで作成してgit fsckを実行し、合格した世代だけをディレクトリ名の原子的変更で有効化します。

専有物理ノード

継続的インテグレーションにクラウドMacを選ぶ

SDKMac M4とSDKMac M4 Proの構成、リージョン、4種類の料金サイクルを比較して、注文を作成できます。

レンタルプランを選ぶ