Gitリポジトリ破損エラーの解決方法と内部構造の理解

不適切な操作により、Gitリポジトリに重大な問題が発生しました。以下のようなエラーメッセージが表示されます:

error: object file .git/objects/3a/7f8b2c6d4e5a1b9c0d2e8f1a3b4c5d6e7f8a9b0 is empty
fatal: loose object 3a7f8b2c6d4e5a1b9c0d2e8f1a3b4c5d6e7f8a9b0 (stored in .git/objects/3a/7f8b2c6d4e5a1b9c0d2e8f1a3b4c5d6e7f8a9b0) is corrupt

特定のファイルが空であると警告しています。git loggit commitgit statusなどのコマンド実行時にこのエラーが発生します(ファイル名は異なる場合があります)。.gitディレクトリを削除して再初期化すれば、この問題を簡単かつ強制的に解決できます。しかし、これにより以前のバージョン情報がすべて失われ、望ましい結果ではありません。そこで、修復を試みることにしました。

まず、正しい解決方法のリンクを紹介します:https://stackoverflow.com/questions/11706215/how-to-fix-git-error-object-file-is-empty

理解が難しい場合は、最後に簡易版の解決策を記載します。

次に、私自身の試行錯誤の過程を説明します:

当時、問題のあるファイルを削除すればどうなるか考えました。しかし、その結果別のファイルが空であるというエラーが表示されました。つまり、さらに削除が必要になります。私は突然ひらめき、ログを変更するとどうなるか試してみました。ログファイルを確認しました:

cd .git
cd logs
vim HEAD

ログファイルの前半は以前のコミット情報でしたが、最後の行だけが化けていました。そこで、化けている行を削除しました。同時に、現在のディレクトリのサブディレクトリに移動しました:

cd refs
cd heads
vim main

ここでも最後の化けた行を削除しました。

その後、.gitの別のサブディレクトリrefsには、現在のバージョン情報のようなものが保存されていることに気づき、以前のHEADファイルを参考にして正常なバージョン番号に変更しました。この時点でgit logを試したところ、ログが正常に表示されました!次にaddを試みたところ、問題なく何も警告が表示されませんでした。警告が表示されないことは良いことだと聞いていました。しかし、コミット時に再び特定のファイルが空であるというエラーが表示されました。

どうやらどこか間違った操作をしたようです。

自分の試行錯誤は成功しませんでしたが、Gitリポジトリのファイル構造について少し理解できました:

[user@host ~/project]$>cd .git/
[user@host ~/project/.git]$>ls
branches/  COMMIT_EDITMSG  config  description  HEAD  hooks/  index  info/  logs/  objects/  refs/

一つずつ見ていきましょう。まずbranchesは空のファイルです(私の場合)。「枝」を意味し、おそらくブランチ関連のファイルですが、私はブランチを使用していないため、詳しくは分かりません。

COMMIT_EDITMSG、ファイル名から推測するとコミット時の編集情報で、推測通りでした:

[user@host ~/project/.git]$>cat COMMIT_EDITMSG 
辞書連想機能正常、マルチスレッドサービス対応可能

これはログファイルの最後の行と対応しています:

 42 e5085f07d6f8578bad1ae39d85bf88db6886c51d bd9a33f13603ef3b53184e3b9ce9408638b71fb4 user <user@example.com><br></br>    1480909458 +0800 commit: 辞書連想機能正常、マルチスレッドサービス対応可能

次にdescriptionファイルですが、何のためにあるのか分かりません(分からないものは省略、理解したら追記します)

HEADの中にはファイルパスが含まれています:

[user@host ~/project/.git]$>cat HEAD
ref: refs/heads/main
[user@host ~/project/.git]$>cat refs/heads/main
bd9a33f13603ef3b53184e3b9ce9408638b71fb4

ファイル内容はログファイルの最後の項目の大きな数字と同じです。したがって、現在のバージョン情報かもしれないと推測しました。

HEADを削除したり、中のパスを変更したりするとどうなるか:

削除の場合:

[user@host ~/project/.git]$>mv HEAD HEAD.bak
[user@host ~/project/.git]$>cd ..
[user@host ~/project]$>git log
fatal: Not a git repository (or any of the parent directories): .git

変更の場合:

[user@host ~/project]$>git log 
fatal: Not a git repository (or any of the parent directories): .git

Gitリポジトリがないと表示されます。

refs/heads/mainを以前のバージョン番号に手動で変更すると、git log時に以前のログ情報が表示されます。ログが巻き戻ったような状態ですが、ファイルはそのままです。この操作を行った後、git statusを実行すると多くの赤色の未コミットファイルが表示されます。addcommitも可能です。この操作を操作Aと名付けましょう。

ここでログファイルについて説明します。一部を以下に示します:

  1 0000000000000000000000000000000000000000 541115f2d1f08d2fe58a768e5a9d3a809bab1131 user <user@example.com>               1480061835 +0800 commit (initial): build git                                                                               
  2 541115f2d1f08d2fe58a768e5a9d3a809bab1131 31db0463027e42718de8e2bbd826586a89316723 user <user@example.com>               1480082783 +0800 commit: 日語、英語単語頻度統計、全角・半角記号を含まない
  3 31db0463027e42718de8e2bbd826586a89316723 1a5c8107af4852b0d8d36a76d988cb2a0b06cc10 user <user@example.com>               1480128209 +0800 commit: ストップワード除去
  4 1a5c8107af4852b0d8d36a76d988cb2a0b06cc10 959fa05c58dbcb6837d7b7a9062bf2f542a15a6b user <user@example.com>               1480176053 +0800 commit: グループ化完了

最初の2つの数字はどこからどこまでを示しているようで、次の行の開始は前の行の終わりです。refs/heads/mainには現在のバージョン番号が保存されているため、これを現在のバージョンを先頭とする連結リストと考えていました:

+=======+   +=======+         +========+   +=========+<br></br>|現在のバージョン|-->|前のバージョン|-->...-->|2番目のバージョン|-->|最初のバージョン|<br></br>+=======+   +=======+         +========+   +=========+

操作Aを行った後:

+=======+             +=============+         +===============+         +========+<br></br>|現在のバージョン|--+          |操作A前のバージョン|-->...+->|操作Aで設定したバージョン|-->...-->|最初のバージョン|<br></br>+=======+  |          +=============+      |  +===============+         +========+<br></br>           |                               |<br></br>           +-------------------------------+

このようになります。

objectsフォルダには、おそらく異なるバージョンのファイルが保存されており、何らかの暗号化アルゴリズムで処理されているので開くとほとんど読めません。

logsフォルダは前に述べたように、ログファイルが保存されています。

refsフォルダには現在のバージョン情報、「連結リストの先頭ノード」が保存されています。

以上、長々と説明しましたが、本題に戻りましょう。

最終的には、最初のリンクで見つけた方法で問題を解決しました:

最初に、念のため.gitフォルダをバックアップしておきます

まず、すべての空ファイルを削除します:

cd .git
find . -type f -empty -delete -print

次に、ログファイルの最後の2行を出力します:

tail -n 2 .git/logs/refs/heads/main

その後、特定のバージョンが正常か、つまり上記で出力した最初のものを確認します:

git show xxxx(バージョン番号)

その後、巻き戻します:

git update-ref HEAD xxxx(バージョン番号)

確認します:

git fsck

この段階で問題なく使用できるようになりました。リンクの後にはいくつかの追加処理がありますが、私はそこで停止しました(目的はリポジトリの修復だけなので、問題なく使えるならこれ以上やる必要ないと考え、さらに壊すのを恐れました)

リンク内の残りの操作:

rm .git/index
git reset
git fsck

しかし、この記事を書いている時点で、その方法はあまり効果がないようです。彼も続けたくないと言っていました(英語は苦手なので、おそらくこのような意味だと思います)。とりあえずこれで使おう--!

最後に修復後のログ情報を見ると、まるで「連結リストのノード削除」操作をしたような感じで、間違ったログ情報は依然として化けています。削除したくありません。また何か問題が発生するのが怖いです。

タグ: Git version-control repository corruption Troubleshooting

6月23日 22:38 投稿