Bagaimana cara menetapkan Git SHA1 ke file tanpa Git?

138

Seperti yang saya pahami ketika Git memberikan hash SHA1 ke sebuah file, SHA1 ini unik untuk file berdasarkan isinya.

Akibatnya jika file bergerak dari satu repositori ke yang lain SHA1 untuk file tetap sama karena isinya tidak berubah.

Bagaimana cara Git menghitung intisari SHA1? Apakah itu melakukannya pada konten file terkompresi penuh?

Saya ingin meniru menugaskan SHA1 di luar Git.

git-noob
sumber

Jawaban:

255

Ini adalah bagaimana Git menghitung SHA1 untuk file (atau, dalam istilah Git, "gumpalan"):

sha1("blob " + filesize + "\0" + data)

Jadi Anda dapat dengan mudah menghitungnya sendiri tanpa menginstal Git. Perhatikan bahwa "\ 0" adalah byte-NULL, bukan string dua karakter.

Misalnya, hash file kosong:

sha1("blob 0\0") = "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"

$ touch empty
$ git hash-object empty
e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

Contoh lain:

sha1("blob 7\0foobar\n") = "323fae03f4606ea9991df8befbb2fca795e648fa"

$ echo "foobar" > foo.txt
$ git hash-object foo.txt 
323fae03f4606ea9991df8befbb2fca795e648fa

Berikut ini adalah implementasi Python:

from hashlib import sha1
def githash(data):
    s = sha1()
    s.update("blob %u\0" % len(data))
    s.update(data)
    return s.hexdigest()
Ferdinand Beyer
sumber
Apakah jawaban ini dengan asumsi Python 2? Ketika saya mencoba ini di Python 3 saya mendapatkan TypeError: Unicode-objects must be encoded before hashingpengecualian di s.update()baris pertama .
Mark Booth
3
Dengan python 3 Anda harus menyandikan data: s.update(("blob %u\0" % filesize).encode('utf-8'))untuk menghindari TypeError.
Mark Booth
Pengkodean sebagai utf-8 akan berhasil, tetapi mungkin lebih baik untuk membuatnya dari string byte di tempat pertama (pengkodean utf-8 bekerja karena tidak ada karakter unicode yang bukan ASCII).
torek
Satu hal tambahan yang perlu disebutkan adalah bahwa git-objek hash juga tampaknya mengganti "\ r \ n" dengan "\ n" di dalam isi data. Mungkin menghapus "" sepenuhnya, saya tidak memeriksanya.
user420667
1
Saya meletakkan implementasi Python 2 + 3 (keduanya dalam satu) dari file dan generator hash pohon di sini: github.com/chris3torek/scripts/blob/master/githash.py (pembuat pohon membaca direktori tree).
torek
17

Goodie kecil: dalam cangkang

echo -en "blob ${#CONTENTS}\0$CONTENTS" | sha1sum
rajutan
sumber
1
Saya membandingkan echo -en "blob ${#CONTENTS}\0$CONTENTS" | sha1sumdengan output git hash-object path-to-filedan mereka menghasilkan hasil yang berbeda. Namun, echo -e ...menghasilkan hasil yang benar, kecuali ada trailing - ( tidakgit hash-object menghasilkan karakter trailing). Apakah ini sesuatu yang harus saya khawatirkan?
FrustratedWithFormsDesigner
2
@FrustratedWithFormsDesigner: Trailing -digunakan oleh sha1sumjika ia menghitung hash dari stdin dan bukan dari file. Tidak ada yang perlu dikhawatirkan. Namun aneh tentang -n, yang seharusnya menekan baris baru yang biasanya ditambahkan oleh gema. Apakah file Anda kebetulan memiliki baris terakhir yang kosong, yang Anda lupa tambahkan dalam CONTENTSvariabel Anda ?
knittl
Ya kamu benar Dan saya berpikir bahwa output sha1sum seharusnya hanya hash, tetapi tidak sulit untuk menghapusnya dengan sed atau sesuatu.
FrustratedWithFormsDesigner
@FrustratedWithFormsDesigner: Anda akan mendapatkan output yang sama jika Anda menggunakan cat file | sha1sumalih-alih sha1sum file(lebih banyak proses dan perpipaan)
knittl
8

Anda dapat membuat fungsi bash shell untuk menghitungnya dengan mudah jika Anda tidak menginstal git.

git_id () { printf 'blob %s\0' "$(ls -l "$1" | awk '{print $5;}')" | cat - "$1" | sha1sum | awk '{print $1}'; }
CB Bailey
sumber
1
Sedikit lebih pendek: (stat --printf="blob %s\0" "$1"; cat "$1") | sha1sum -b | cut -d" " -f1.
sschuberth
4

Lihatlah halaman manual untuk git-hash-object . Anda dapat menggunakannya untuk menghitung hash git dari file tertentu. Saya pikir git memberi makan lebih dari sekedar isi file ke dalam algoritma hash, tapi saya tidak tahu pasti, dan jika itu memberi makan dalam data tambahan, saya tidak tahu apa itu.

Dale Hagglund
sumber
2
/// Calculates the SHA1 for a given string
let calcSHA1 (text:string) =
    text 
      |> System.Text.Encoding.ASCII.GetBytes
      |> (new System.Security.Cryptography.SHA1CryptoServiceProvider()).ComputeHash
      |> Array.fold (fun acc e -> 
           let t = System.Convert.ToString(e, 16)
           if t.Length = 1 then acc + "0" + t else acc + t) 
           ""
/// Calculates the SHA1 like git
let calcGitSHA1 (text:string) =
    let s = text.Replace("\r\n","\n")
    sprintf "blob %d%c%s" (s.Length) (char 0) s
      |> calcSHA1

Ini adalah solusi dalam F #.

forki23
sumber
Saya masih memiliki masalah dengan umlauts: calcGitSHA1 ("ü"). ShouldBeEqualTo ("0f0f3e3b1ff2bc6722afc3e3812e6b782683896f") Tetapi fungsi saya memberikan 0d758c9c7bc06c1b300f05d08d08 Adakah ide bagaimana git hash-object menangani umlauts?
forki23
itu harus menangani gumpalan sebagai bytestream, itu berarti ü mungkin memiliki panjang 2 (unicode), properti Panjang F♯ akan mengembalikan panjang 1 (karena hanya satu karakter yang terlihat)
knittl
Tetapi System.Text.Encoding.ASCII.GetBytes ("ü") mengembalikan array byte dengan 1 elemen.
forki23
Menggunakan UTF8 dan 2 sebagai panjang string menghasilkan array byte: [98; 108; 111; 98; 32; 50; 0; 195; 188] dan karenanya SHA1 dari 99fe40df261f7d4afd1391fe2739b2c7466fe968. Yang juga bukan git SHA1.
forki23
1
Anda tidak boleh menerapkan intisari ke string karakter. Sebagai gantinya, Anda harus menerapkannya ke string byte (byte array) yang dapat Anda peroleh dengan mengonversi string karakter ke byte menggunakan pengodean eksplisit.
dolmen
2

Implementasi Python3 lengkap:

import os
from hashlib import sha1

def hashfile(filepath):
    filesize_bytes = os.path.getsize(filepath)

    s = sha1()
    s.update(b"blob %u\0" % filesize_bytes)

    with open(filepath, 'rb') as f:
        s.update(f.read())

    return s.hexdigest() 
Tomer
sumber
2
Yang Anda inginkan adalah pengkodean ASCII. UTF8 hanya berfungsi di sini karena kompatibel dengan ASCII dan "blob x \ 0" hanya berisi karakter dengan kode <= 127.
Ferdinand Beyer
1

Dalam Perl:

#!/usr/bin/env perl
use Digest::SHA1;

my $content = do { local $/ = undef; <> };
print Digest::SHA1->new->add('blob '.length($content)."\0".$content)->hexdigest(), "\n";

Sebagai perintah shell:

perl -MDigest::SHA1 -E '$/=undef;$_=<>;say Digest::SHA1->new->add("blob ".length()."\0".$_)->hexdigest' < file
dolmen
sumber
1

Dan di Perl (lihat juga Git :: PurePerl di http://search.cpan.org/dist/Git-PurePerl/ )

use strict;
use warnings;
use Digest::SHA1;

my @input = &lt;&gt;;

my $content = join("", @input);

my $git_blob = 'blob' . ' ' . length($content) . "\0" . $content;

my $sha1 = Digest::SHA1->new();

$sha1->add($git_blob);

print $sha1->hexdigest();
Alec si Geek
sumber
1

Menggunakan Ruby, Anda dapat melakukan sesuatu seperti ini:

require 'digest/sha1'

def git_hash(file)
  data = File.read(file)
  size = data.bytesize.to_s
  Digest::SHA1.hexdigest('blob ' + size + "\0" + data)
end
Leif
sumber
1

Script Bash kecil yang harus menghasilkan output identik dengan git hash-object:

#!/bin/sh
( 
    echo -en 'blob '"$(stat -c%s "$1")"'\0';
    cat "$1" 
) | sha1sum | cut -d\  -f 1
Fordi
sumber
0

Dalam JavaScript

const crypto = require('crypto')
const bytes = require('utf8-bytes')

function sha1(data) {
    const shasum = crypto.createHash('sha1')
    shasum.update(data)
    return shasum.digest('hex')
}

function shaGit(data) {
    const total_bytes = bytes(data).length
    return sha1(`blob ${total_bytes}\0${data}`)
}
EnZo
sumber
-4

Sangat menarik untuk dicatat bahwa jelas Git menambahkan karakter baris baru ke akhir data sebelum hash. File yang berisi tidak lebih dari "Hello World!" mendapat hash gumpalan 980a0d5 ..., yang sama seperti ini:

$ php -r 'echo sha1("blob 13" . chr(0) . "Hello World!\n") , PHP_EOL;'
Dorongan
sumber
4
Baris baru itu ditambahkan oleh editor teks Anda, bukan oleh git hash-object. Perhatikan bahwa melakukan echo "Hello World!" | git hash-object --stdinmemberi 980a0d5..., saat menggunakan echo -nmemberi hash c57eff5...sebagai gantinya.
bdesham