[解決済み] Git は Blob の SHA-1 衝突をどのように処理するのでしょうか?
質問
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);
}
そして、何度かコミットをして、次のようなことに気づきました。
- 同じハッシュのBLOBがすでに存在する場合、警告はまったく表示されません。すべて問題ないように見えますが、プッシュしたり、誰かがクローンしたり、リバートしたりすると、最新版が失われます(上記の説明と一致します)。
- ツリー オブジェクトがすでに存在し、同じハッシュを持つブロブを作成する場合。プッシュするか、誰かがあなたのリポジトリをクローンするまで、すべてが正常に見えるでしょう。そうすると、リポジトリが破損していることがわかります。
- コミットオブジェクトがすでに存在し、同じハッシュのブロブを作成した場合:#2 と同じ - 破損している
- Blobがすでに存在し、同じハッシュを持つコミットオブジェクトを作成した場合、"ref"を更新する際に失敗します。
- Blobがすでに存在し、同じハッシュを持つツリーオブジェクトを作成した場合。コミット作成時に失敗します。
- ツリーオブジェクトがすでに存在し、同じハッシュを持つコミットオブジェクトを作成した場合、"ref"を更新する際に失敗します。
- もし、あるツリーオブジェクトがすでに存在し、同じハッシュを持つツリーオブジェクトを作れば、すべてがうまくいくように見えます。しかし、コミットすると、リポジトリのすべてが間違ったツリーを参照することになります。
- コミットオブジェクトがすでに存在し、同じハッシュを持つコミットオブジェクトを作成した場合、すべてがうまくいくように見えるでしょう。しかし、コミットすると、コミットは決して作成されず、HEADポインタは古いコミットに移動します。
- コミットオブジェクトが既に存在し、同じハッシュを持つツリーオブジェクトを作成した場合、コミット作成時に失敗します。
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は、エラーで停止するので大丈夫です。少なくともすべてのケースでエラーで失敗すればいいのでしょうけど。
関連
-
[解決済み] Git で直近のローカルコミットを取り消すには?
-
[解決済み] Gitブランチをローカルやリモートで削除するには?
-
[解決済み] コミット前に 'git add' を取り消すにはどうすればよいですか?
-
[解決済み] リモートのGitブランチをチェックアウトするには?
-
[解決済み] Git リポジトリを以前のコミットに戻すにはどうすればよいですか?
-
[解決済み] 現在のGit作業ツリーからローカル(未追跡)ファイルを削除する方法
-
[解決済み] Git が追跡したファイルを .gitignore に登録したまま「忘れる」ようにするにはどうしたらいいですか?
-
[解決済み] Git で、ステージされていない変更を破棄するにはどうしたらいいですか?
-
[解決済み】"git pull" でローカルファイルを強制的に上書きするには?
-
[解決済み】ローカルのGitブランチの名前を変更するには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】現在のブランチの先端がリモートブランチより遅れているため、更新が拒否されました。
-
[解決済み】Gitマージで「すでに最新」と報告されるが、違いはある
-
[解決済み】git-mergeの-dry-runオプションはありますか?
-
[解決済み】Git Extensions。Win32 エラー 487: cygwinのヒープ用に領域を確保できなかった、Win32エラー0
-
[解決済み】未マージファイルがあるため、Gitマージができない
-
git commits with an error: 更新が拒否されました。現在のブランチの先端が、このブランチより後ろにあるためです。
-
git revert 複数コミット
-
[解決済み] Git pull - マージする前に移動または削除してください。
-
[解決済み] なぜGitは暗号化ハッシュ関数を使うのですか?
-
[解決済み] git はどのようにファイルのハッシュを計算するのですか?