1. ホーム
  2. git

[解決済み] Git は Blob の SHA-1 衝突をどのように処理するのでしょうか?

2022-03-18 03:38:15

質問

gitのリポジトリを持っていて、コミットをしたとき、とても運が悪いことに、あるBlobがすでにリポジトリにある別のBlobと同じSHA-1になってしまったとします。問題は、Gitはこれをどう扱うかです。単に失敗するのでしょうか?2つのblobをリンクして、文脈に応じてどちらが必要かをチェックする方法を見つけるか?

実際の問題というより、頭を使う問題ですが、面白い問題だと思いました。

解決方法は?

この場合、Gitがどのように振る舞うかを正確に知るために、私はある実験を行いました。バージョン2.7.9~rc0+next.20151210(Debian版)を使用した場合です。基本的には以下のdiffを適用してgitを再構築することで、ハッシュサイズを160bitから4bitに減らしただけです。

--- git-2.7.0~rc0+next.20151210.orig/block-sha1/sha1.c
+++ git-2.7.0~rc0+next.20151210/block-sha1/sha1.c
@@ -246,6 +246,8 @@ void blk_SHA1_Final(unsigned char hashou
    blk_SHA1_Update(ctx, padlen, 8);

    /* Output hash */
-   for (i = 0; i < 5; i++)
-       put_be32(hashout + i * 4, ctx->H[i]);
+   for (i = 0; i < 1; i++)
+       put_be32(hashout + i * 4, (ctx->H[i] & 0xf000000));
+   for (i = 1; i < 5; i++)
+       put_be32(hashout + i * 4, 0);
 }

そして、何度かコミットをして、次のようなことに気づきました。

  1. 同じハッシュのBLOBがすでに存在する場合、警告はまったく表示されません。すべて問題ないように見えますが、プッシュしたり、誰かがクローンしたり、リバートしたりすると、最新版が失われます(上記の説明と一致します)。
  2. ツリー オブジェクトがすでに存在し、同じハッシュを持つブロブを作成する場合。プッシュするか、誰かがあなたのリポジトリをクローンするまで、すべてが正常に見えるでしょう。そうすると、リポジトリが破損していることがわかります。
  3. コミットオブジェクトがすでに存在し、同じハッシュのブロブを作成した場合:#2 と同じ - 破損している
  4. Blobがすでに存在し、同じハッシュを持つコミットオブジェクトを作成した場合、"ref"を更新する際に失敗します。
  5. Blobがすでに存在し、同じハッシュを持つツリーオブジェクトを作成した場合。コミット作成時に失敗します。
  6. ツリーオブジェクトがすでに存在し、同じハッシュを持つコミットオブジェクトを作成した場合、"ref"を更新する際に失敗します。
  7. もし、あるツリーオブジェクトがすでに存在し、同じハッシュを持つツリーオブジェクトを作れば、すべてがうまくいくように見えます。しかし、コミットすると、リポジトリのすべてが間違ったツリーを参照することになります。
  8. コミットオブジェクトがすでに存在し、同じハッシュを持つコミットオブジェクトを作成した場合、すべてがうまくいくように見えるでしょう。しかし、コミットすると、コミットは決して作成されず、HEADポインタは古いコミットに移動します。
  9. コミットオブジェクトが既に存在し、同じハッシュを持つツリーオブジェクトを作成した場合、コミット作成時に失敗します。

2については、"git push"を実行すると、通常このようなエラーが表示されるでしょう。

error: object 0400000000000000000000000000000000000000 is a tree, not a blob
fatal: bad blob object
error: failed to push some refs to origin

または

error: unable to read sha1 file of file.txt (0400000000000000000000000000000000000000)

ファイルを削除してから "git checkout file.txt" を実行した場合。

4と#6については、通常、次のようなエラーが発生します。

error: Trying to write non-commit object
f000000000000000000000000000000000000000 to branch refs/heads/master
fatal: cannot update HEAD ref

git commit"を実行したとき。この場合、通常はもう一度 "git commit" と入力すれば、新しいハッシュが作成されます (タイムスタンプが変更されるため)。

5と#9については、通常、次のようなエラーが発生します。

fatal: 1000000000000000000000000000000000000000 is not a valid 'tree' object

git commit" を実行したとき。

もし誰かがあなたの壊れたリポジトリをクローンしようとすると、通常、次のようなものが表示されます。

git clone (one repo with collided blob,
d000000000000000000000000000000000000000 is commit,
f000000000000000000000000000000000000000 is tree)

Cloning into 'clonedversion'...
done.
error: unable to read sha1 file of s (d000000000000000000000000000000000000000)
error: unable to read sha1 file of tullebukk
(f000000000000000000000000000000000000000)
fatal: unable to checkout working tree
warning: Clone succeeded, but checkout failed.
You can inspect what was checked out with 'git status'
and retry the checkout with 'git checkout -f HEAD'

私が心配なのは、2つのケース(2,3)では警告なしにリポジトリが破損し、3つのケース(1,7,8)では、すべてが大丈夫そうに見えても、リポジトリの内容が期待とは異なるということです。クローンやプルをする人は、自分の持っているものと違う内容になってしまいます。ケース4,5,6,9は、エラーで停止するので大丈夫です。少なくともすべてのケースでエラーで失敗すればいいのでしょうけど。