In che modo git calcola gli hash dei file?


124

Gli hash SHA1 memorizzati negli oggetti albero (come restituiti da git ls-tree) non corrispondono agli hash SHA1 del contenuto del file (come restituiti da sha1sum)

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

In che modo git calcola gli hash dei file? Comprime il contenuto prima di calcolare l'hash?



1
Per maggiori dettagli, vedere anche progit.org/book/ch9-2.html
netvope

5
Il collegamento di netvope sembra essere morto ora. Penso che questa sia la nuova posizione: git-scm.com/book/en/Git-Internals-Git-Objects che è §9.2 da git-scm.com/book
Rabarbaro

Risposte:


122

Git antepone all'oggetto "blob", seguito dalla lunghezza (come intero leggibile dall'uomo), seguito da un carattere NUL

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

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


2
Vale anche la pena ricordare che sostituisce "\ r \ n" con "\ n", ma lascia i "\ r" isolati.
user420667

8
^ correzione al commento sopra: a volte git fa la sostituzione sopra, a seconda delle proprie impostazioni eol / autocrlf.
user420667

5
Puoi anche confrontarlo con l'output di echo 'Hello, World!' | git hash-object --stdin. Opzionalmente puoi specificare --no-filtersdi assicurarti che non avvenga alcuna conversione crlf, o specificare --path=somethi.ngdi lasciare che git usi il filtro specificato tramite gitattributes(anche @ user420667). E -wper inviare effettivamente il BLOB a .git/objects(se ci si trova in un repository Git).
Tobias Kienzler

Esprimendo l'equivalenza, per avere un senso: echo -e 'blob 16\0Hello, \r\nWorld!' | shasum == echo -e 'Hello, \r\nWorld!' | git hash-object --stdin --no-filters e sarà anche equivalente con \ne 15.
Peter Krauss

1
echoaggiunge una nuova riga all'output, che viene anche passato a git. Ecco perché i suoi 14 caratteri. Per utilizzare l'eco senza una nuova riga, scriviecho -n 'Hello, World!'
Bouke Versteegh

36

Sto solo espandendo la risposta di @Leif Gruenwoldte specificando cosa c'è nel riferimento fornito da@Leif Gruenwoldt

Fallo da solo..

  • Passaggio 1. Crea un documento di testo vuoto (il nome non ha importanza) nel tuo repository
  • Passaggio 2. Metti in scena e salva il documento
  • Passaggio 3. Identificare l'hash del BLOB eseguendolo git ls-tree HEAD
  • Passaggio 4. Trova l'hash del blob da utilizzare e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
  • Passaggio 5. Tira fuori la tua sorpresa e leggi di seguito

In che modo GIT calcola gli hash di commit

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

Il testo blob⎵è un prefisso costante ed \0è anche costante ed è il NULLcarattere. Il<size_of_file> e<contents_of_file> variano a seconda del file.

Vedere: Qual è il formato di file di un oggetto commit git?

E questo è tutto gente!

Ma aspetta! , hai notato che <filename>non è un parametro utilizzato per il calcolo dell'hash? Due file potrebbero potenzialmente avere lo stesso hash se il loro contenuto è lo stesso indipendentemente dalla data e dall'ora in cui sono stati creati e dal loro nome. Questo è uno dei motivi per cui Git gestisce gli spostamenti e rinomina meglio di altri sistemi di controllo della versione.

Fai da te (est)

  • Passaggio 6. Creare un altro file vuoto con un altro filenamenella stessa directory
  • Passaggio 7. Confronta gli hash di entrambi i file.

Nota:

Il collegamento non menziona come treeviene eseguito l'hashing dell'oggetto. Non sono sicuro dell'algoritmo e dei parametri tuttavia dalla mia osservazione probabilmente calcola un hash basato su tutti i blobse trees(probabilmente i loro hash) che contiene


SHA1("blob" + <size_of_file>- c'è un carattere di spazio aggiuntivo tra blob e size? La dimensione è decimale? È il prefisso zero?
osgx

1
@osgx C'è. Il riferimento e il mio test lo confermano. Ho corretto la risposta. La dimensione sembra essere il numero di byte come numero intero senza prefisso.
Samuel Harmer

13

git hash-object

Questo è un modo rapido per verificare il tuo metodo di prova:

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

Produzione:

f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f
f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f  -

dov'è sha1sumin GNU Coreutils.

Quindi si tratta di comprendere il formato di ciascun tipo di oggetto. Abbiamo già coperto il banale blob, eccone gli altri:


Come accennato in una risposta precedente, la lunghezza dovrebbe piuttosto essere calcolata come $(printf "\0$s" | wc -c). Nota il carattere vuoto aggiunto. Cioè, se la stringa è 'abc' con il carattere vuoto aggiunto davanti, la lunghezza restituirà 4, non 3. Quindi i risultati con sha1sum corrispondono a git hash-object.
Michael Ekoka

Hai ragione, corrispondono. Sembra che ci sia un po 'di pernicioso effetto collaterale dall'uso di printf piuttosto che di echo -e qui. Quando applichi git hash-object a un file contenente la stringa 'abc' ottieni 8baef1b ... f903 che è ciò che ottieni quando usi echo -e invece di printf. A condizione che echo -e aggiunga una nuova riga alla fine di una stringa sembra che per abbinare il comportamento con printf tu possa fare lo stesso (es. S = "$ s \ n").
Michael Ekoka

3

Sulla base della risposta di Leif Gruenwoldt , ecco una funzione di shell sostitutiva di 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/ .*$//'
}

Test:

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

3

Ne avevo bisogno per alcuni unit test in Python 3, quindi ho pensato di lasciarlo qui.

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()

Mi attengo alle \nterminazioni di riga ovunque, ma in alcune circostanze Git potrebbe anche cambiare le terminazioni di riga prima di calcolare questo hash, quindi potresti aver bisogno di un .replace('\r\n', '\n')anche lì.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.