Bagaimana cara git menghitung hash file?

124

Hash SHA1 yang disimpan di objek pohon (seperti yang dikembalikan oleh git ls-tree) tidak cocok dengan hash SHA1 dari konten file (seperti yang dikembalikan oleh sha1sum)

$ git cat-file blob 4716ca912495c805b94a88ef6dc3fb4aff46bf3c | sha1sum
de20247992af0f949ae8df4fa9a37e4a03d7063e  -

Bagaimana cara git menghitung hash file? Apakah itu memampatkan konten sebelum menghitung hash?

netvope.dll
sumber
1
Untuk detail lebih lanjut, lihat juga progit.org/book/ch9-2.html
netvope
5
Link netvope sepertinya sudah mati sekarang. Saya rasa ini adalah lokasi baru: git-scm.com/book/en/Git-Internals-Git-Objects yang §9.2 dari git-scm.com/book
Rhubbarb

Jawaban:

122

Git mengawali objek dengan "blob", diikuti dengan panjangnya (sebagai bilangan bulat yang dapat dibaca manusia), diikuti dengan karakter NUL

$ echo -e 'blob 14\0Hello, World!' | shasum 8ab686eafeb1f44702738c8b0f24f2567c36da6d

Sumber: http://alblue.bandlem.com/2011/08/git-tip-of-week-objects.html

Leif Gruenwoldt
sumber
2
Juga perlu disebutkan bahwa ini menggantikan "\ r \ n" dengan "\ n", tetapi meninggalkan "\ r" yang terisolasi saja.
user420667
8
^ koreksi untuk komentar di atas: terkadang git melakukan penggantian di atas, tergantung pada pengaturan eol / autocrlf seseorang.
user420667
5
Anda juga dapat membandingkan ini dengan keluaran echo 'Hello, World!' | git hash-object --stdin. Secara opsional, Anda dapat menentukan --no-filtersuntuk memastikan tidak ada konversi crlf yang terjadi, atau menetapkan --path=somethi.nguntuk membiarkan git menggunakan filter yang ditentukan melalui gitattributes(juga @ user420667). Dan -wuntuk benar-benar mengirimkan blob ke .git/objects(jika Anda berada dalam git repo).
Tobias Kienzler
Mengekspresikan kesetaraan, untuk masuk akal: echo -e 'blob 16\0Hello, \r\nWorld!' | shasum == echo -e 'Hello, \r\nWorld!' | git hash-object --stdin --no-filters dan itu juga akan setara dengan \ndan 15.
Peter Krauss
1
echomenambahkan baris baru ke keluaran, yang juga diteruskan ke git. Itulah mengapa 14 karakternya. Untuk menggunakan gema tanpa baris baru, tulisecho -n 'Hello, World!'
Bouke Versteegh
36

Saya hanya memperluas jawaban dengan @Leif Gruenwoldtdan merinci apa yang ada dalam referensi yang disediakan oleh@Leif Gruenwoldt

Lakukan sendiri..

  • Langkah 1. Buat dokumen teks kosong (nama tidak masalah) di repositori Anda
  • Langkah 2. Tahap dan Komit dokumen
  • Langkah 3. Identifikasi hash blob dengan menjalankan git ls-tree HEAD
  • Langkah 4. Temukan hash blob menjadi e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
  • Langkah 5. Keluarkan keterkejutan Anda dan baca di bawah

Bagaimana GIT menghitung hash komitnya

    Commit Hash (SHA1) = SHA1("blob " + <size_of_file> + "\0" + <contents_of_file>)

Teks blob⎵adalah awalan konstan dan \0juga konstan dan merupakan NULLkarakter. The <size_of_file>dan<contents_of_file> bervariasi tergantung pada file.

Lihat: Apa format file dari objek git commit?

Dan itu semua!

Tapi tunggu!, apakah Anda memperhatikan bahwa <filename>bukan merupakan parameter yang digunakan untuk komputasi hash? Dua file berpotensi memiliki hash yang sama jika isinya sama pada tanggal dan waktu pembuatan dan namanya. Inilah salah satu alasan Git menangani pemindahan dan mengganti nama dengan lebih baik daripada sistem kontrol versi lain.

Lakukan Sendiri (Ext)

  • Langkah 6. Buat file kosong lain dengan yang berbeda filenamedi direktori yang sama
  • Langkah 7. Bandingkan hash dari kedua file Anda.

catatan:

Tautan tidak menyebutkan bagaimana treeobjek di-hash. Saya tidak yakin dengan algoritme dan parameternya namun dari pengamatan saya mungkin menghitung hash berdasarkan semua blobsdan trees(hash mereka mungkin) yang dikandungnya

Lordbalmon
sumber
SHA1("blob" + <size_of_file>- apakah ada karakter spasi tambahan antara gumpalan dan ukuran? Apakah ukuran desimal? Apakah ini diawali nol?
osgx
1
@osgx Ada. Referensi dan pengujian saya menegaskan demikian. Saya sudah mengoreksi jawabannya. Ukuran tampaknya adalah jumlah byte sebagai bilangan bulat tanpa awalan.
Samuel Harmer
13

git hash-object

Ini adalah cara cepat untuk memverifikasi metode pengujian Anda:

s='abc'
printf "$s" | git hash-object --stdin
printf "blob $(printf "$s" | wc -c)\0$s" | sha1sum

Keluaran:

f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f
f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f  -

di mana sha1sumdi GNU Coreutils.

Kemudian turun untuk memahami format setiap jenis objek. Kami telah membahas yang sepele blob, berikut ini yang lainnya:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
Seperti yang disebutkan dalam jawaban sebelumnya, panjangnya seharusnya dihitung sebagai $(printf "\0$s" | wc -c). Perhatikan karakter kosong yang ditambahkan. Artinya, jika string adalah 'abc' dengan karakter kosong yang ditambahkan di depan panjangnya akan menghasilkan 4, bukan 3. Kemudian hasil dengan sha1sum cocok dengan git hash-object.
Michael Ekoka
Anda benar, mereka memang cocok. Tampaknya ada sedikit efek samping yang merusak dari penggunaan printf daripada echo -e di sini. Ketika Anda menerapkan git hash-object ke file yang berisi string 'abc' Anda mendapatkan 8baef1b ... f903 yang Anda dapatkan saat menggunakan echo -e daripada printf. Asalkan echo -e menambahkan baris baru di akhir string, tampaknya untuk mencocokkan perilaku dengan printf Anda dapat melakukan hal yang sama (yaitu s = "$ s \ n").
Michael Ekoka
3

Berdasarkan jawaban Leif Gruenwoldt , berikut adalah fungsi shell pengganti git hash-object:

git-hash-object () { # substitute when the `git` command is not available
    local type=blob
    [ "$1" = "-t" ] && shift && type=$1 && shift
    # depending on eol/autocrlf settings, you may want to substitute CRLFs by LFs
    # by using `perl -pe 's/\r$//g'` instead of `cat` in the next 2 commands
    local size=$(cat $1 | wc -c | sed 's/ .*$//')
    ( echo -en "$type $size\0"; cat "$1" ) | sha1sum | sed 's/ .*$//'
}

Uji:

$ echo 'Hello, World!' > test.txt
$ git hash-object test.txt
8ab686eafeb1f44702738c8b0f24f2567c36da6d
$ git-hash-object test.txt
8ab686eafeb1f44702738c8b0f24f2567c36da6d
Lucas Cimon
sumber
3

Saya membutuhkan ini untuk beberapa pengujian unit di Python 3 jadi saya pikir saya akan meninggalkannya di sini.

def git_blob_hash(data):
    if isinstance(data, str):
        data = data.encode()
    data = b'blob ' + str(len(data)).encode() + b'\0' + data
    h = hashlib.sha1()
    h.update(data)
    return h.hexdigest()

Saya tetap menggunakan \nakhiran baris di mana-mana tetapi dalam beberapa situasi Git mungkin juga mengubah akhir baris Anda sebelum menghitung hash ini sehingga Anda mungkin perlu .replace('\r\n', '\n')di sana juga.

Samuel Harmer
sumber